added basic functions to GITManagedWebpage.class.php
[GITManagedWebpage.git] / GITManagedWebpage.class.php
1 <?php
2 /*************************** GITManagedWebpage **************************
3  * Copyright (C) 2013       Philipp Kreil (pk910)                       *
4  *                                                                      *
5  * This program is free software: you can redistribute it and/or modify *
6  * it under the terms of the GNU General Public License as published by *
7  * the Free Software Foundation, either version 3 of the License, or    *
8  * (at your option) any later version.                                  *
9  *                                                                      *
10  * This program is distributed in the hope that it will be useful,      *
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of       *
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *
13  * GNU General Public License for more details.                         *
14  *                                                                      *
15  * You should have received a copy of the GNU General Public License    *
16  * along with this program. If not, see <http://www.gnu.org/licenses/>. *
17  *                                                                      *
18  ************************************************************************
19  *
20  *  GITManagedWebpage.class.php
21  *
22  * Functions to manage a Website using GIT Version Control
23  *
24  */
25
26 class GITManagedWebpage {
27     const ERROR_CRITICAL = 1;
28     const SESSION_PREFIX = "GITManagedWebpage_";
29
30     private $giturl;
31     private $workdir, $localdir;
32     private $ready = false;
33     private $loopedcall = false;
34     private $config = null;
35     private $config_changed = true;
36     
37     public function __construct($giturl, $workdir = null, $localdir = null) {
38         if(session_status() != PHP_SESSION_ACTIVE) {
39             session_start();
40         }
41         
42         if(defined("GITMANAGED_EXECUTED")) {
43             $this->loopedcall = true;
44             return;
45         }
46         
47         if($workdir === null) {
48             $workdir = dirname(__FILE__);
49             if(substr($workdir, -1) != "/")
50                 $workdir .= "/";
51             $workdir .= ".gitmanaged/";
52         }
53         else if(substr($workdir, -1) != "/")
54             $workdir .= "/";
55         
56         if($localdir === null) {
57             $localdir = dirname(__FILE__);
58             if(substr($localdir, -1) != "/")
59                 $localdir .= "/";
60         }
61         else if(substr($localdir, -1) != "/")
62             $localdir .= "/";
63         
64         $this->giturl = $giturl;
65         $this->workdir = $workdir;
66         $this->localdir = $localdir;
67         
68         if(!file_exists($this->workdir) || !is_dir($this->workdir)) {
69             if(file_exists($this->workdir) && !is_dir($this->workdir)) {
70                 $this->error(self::ERROR_CRITICAL, "local workdir (".htmlspecialchars($this->workdir).") is not a directory.");
71                 return;
72             }
73             $this->setupWorkdir();
74         }
75         $this->ready = true;
76     }
77     
78     /* private function gitcmd(...)
79     * Execute a git command and return output
80     */
81     private function gitcmd() {
82         $args = func_get_args();
83         $argstr = "";
84         foreach($args as $arg) {
85             if(is_array($arg)) {
86                 foreach($arg as $subarg) {
87                     $argstr .= " ".escapeshellarg($subarg);
88                 }
89             } else
90                 $argstr .= " ".escapeshellarg($arg);
91         }
92         $gitcmd = 'git '.escapeshellarg('--git-dir='.$this->workdir.'repository/.git').$argstr;
93         $output = shell_exec($gitcmd);
94         return $output;
95     }
96     
97     /* private function setConfig($name, $value)
98     * store a option in the configuration
99     */
100     private function setConfig($name, $value) {
101         $this->config[strtolower($name)] = $value;
102         $this->config_changed = true;
103     }
104     
105     /* private function getConfig($name)
106     * get an option from the configuration
107     */
108     private function getConfig($name) {
109         if($this->config == null) {
110             if(!$this->ready)
111                 return null;
112             if(file_exists($this->workdir."config.txt")) {
113                 $config_txt = @file_get_contents($this->workdir."config.txt");
114                 $this->config = unserialize($config_txt);
115             } else {
116                 $this->config = array();
117                 return null;
118             }
119         }
120         if(array_key_exists(strtolower($name), $this->config))
121             return $this->config[strtolower($name)];
122         else
123             return null;
124     }
125     
126     private function saveConfig() {
127         if($this->config_changed && $this->ready) {
128             $fp = fopen($this->workdir."config.txt", "w");
129             fwrite($fp, serialize($this->config));
130             fclose($fp);
131         }
132     }
133     
134     /* private function setupWorkdir()
135     * Setup local GITManagedWebpage Work directory with git repository
136     */
137     private function setupWorkdir() {
138         // check requirements
139         $git_exec = shell_exec('which git');
140         if(!preg_match('#git#', $git_exec)) {
141             $this->error(self::ERROR_CRITICAL, "git not installed locally.");
142             return;
143         }
144         
145         mkdir($this->workdir);
146         mkdir($this->workdir.'repository');
147         shell_exec('git clone '.escapeshellarg($this->giturl).' '.escapeshellarg($this->workdir.'repository'));
148         $gitok = $this->gitcmd("status");
149         if(preg_match("#Not a git repository#", $gitok)) {
150             rmdir($this->workdir.'repository');
151             rmdir($this->workdir);
152             $this->error(self::ERROR_CRITICAL, "error cloning git repository.");
153             return;
154         }
155         $this->ready = true;
156         
157         $default_branch = str_replace(array("\r", "\n"), array("", ""), $this->gitcmd("rev-parse", "--abbrev-ref", "HEAD"));
158         $this->setConfig("defaultbranch", $default_branch);
159         $this->saveConfig();
160     }
161     
162     private function getActiveBranch() {
163         if($this->activeSession)
164             return $this->activeSession;
165         else if(isset($_SESSION[self::SESSION_PREFIX.'branch']))
166             return $_SESSION[self::SESSION_PREFIX.'branch'];
167         else
168             return $this->getConfig("defaultbranch");
169     }
170     
171     private function setActiveBranch($branch, $remember) {
172         $this->activeSession = $branch;
173         if($remember)
174             $_SESSION[self::SESSION_PREFIX.'branch'] = $branch;
175     }
176     
177     private function getLocalUntrackedFiles() {
178         $default_branch = $this->getConfig("defaultbranch");
179         $tracked_files = $this->gitcmd("ls-tree", $default_branch, "--full-name", "--name-only");
180         $local_files = shell_exec("find ".escapeshellarg($this->localdir));
181         $untracked_files = array();
182         
183         $tracked_files = explode("\n", str_replace(array("\r"), array(""), $tracked_files));
184         $local_files = explode("\n", str_replace(array("\r"), array(""), $local_files));
185         
186         foreach($local_files as $local_file) {
187             if(!$local_file)
188                 continue;
189             if($strip_local || (($strip_local = strlen($this->localdir)) && substr($local_file, 0, $strip_local) == $this->localdir)) {
190                 $local_file = substr($local_file, $strip_local);
191             }
192             $tracked = false;
193             foreach($tracked_files as $tracked_file) {
194                 if($tracked_file == $local_file) {
195                     $tracked = true;
196                     break;
197                 }
198             }
199             if(!$tracked) {
200                 $untracked_files[] = $local_file;
201             }
202         }
203         return $untracked_files;
204     }
205     
206     private function branchExists($branch) {
207         //check if branch exists
208         $gitret = $this->gitcmd("rev-list", "--max-count=1", $branch);
209         if(!preg_match("#([a-z0-9]{40})#", $gitret, $match))
210             return false;
211         else
212             return $match[1];
213     }
214     
215     private function localBranchPath($branch, $create = false) {
216         $default_branch = $this->getConfig("defaultbranch");
217         if($branch == $default_branch)
218             $dir = $this->localdir;
219         else
220             $dir = $this->workdir.'branch_'.$branch.'/';
221         if(file_exists($dir))
222             return $dir;
223         else if($create) {
224             mkdir($dir);
225             return $dir;
226         } else 
227             return false;
228     }
229     
230     private function updateBranch($branch, $path) {
231         if(substr($path, -1) != '/')
232             $path .= '/';
233         $current_branch = str_replace(array("\r", "\n"), array("", ""), $this->gitcmd("rev-parse", "--abbrev-ref", "HEAD"));
234         if($current_branch != $branch)
235             $this->gitcmd("checkout", $branch);
236         $this->gitcmd("pull");
237         $gitret = $this->gitcmd("rev-list", "--max-count=1", $branch);
238         preg_match("#([a-z0-9]{40})#", $gitret, $match);
239         $newest_version = $match[1];
240         
241         if(($current_version = $this->getConfig('version_'.$branch))) {
242             if($current_version == $newest_version)
243                 return;
244             else {
245                 //applying patch
246                 $difftxt = $this->gitcmd("diff", $current_version, $newest_version);
247                 echo $difftxt;
248             }
249         } else
250             $override_all = true;
251         if($override_all) {
252             $rsync_present = preg_match("#rsync#", `which rsync`);
253             if($rsync_present)
254                 shell_exec('rsync -avz --exclude ".git" '.escapeshellarg($this->workdir."repository/").' '.escapeshellarg($path));
255             else
256                 shell_exec('tar -c --exclude ".git" -C '.escapeshellarg($this->workdir."repository").' . | tar -x -C '.escapeshellarg($path));
257         }
258         $this->setConfig('version_'.$branch, $newest_version);
259         //$this->saveConfig();
260     }
261     
262     /* public function update()
263     * Pulls latest commit of active branch and overwrites files in branch folder
264     */
265     public function update() {
266         if($this->loopedcall)
267             return;
268         
269         $active_branch = $this->getActiveBranch();
270         
271         if(!$this->branchExists($active_branch))
272             return false;
273         $dir = $this->localBranchPath($active_branch, true);
274         $this->updateBranch($active_branch, $dir);
275     }
276     
277     public function setBranch($branch, $remember = false) {
278         if($this->loopedcall)
279             return;
280         
281         if(!$this->branchExists($branch))
282             return false;
283         $this->setActiveBranch($branch, $remember);
284         
285         if(!$this->localBranchPath($branch)) {
286             $dir = $this->localBranchPath($branch, true);
287             $this->updateBranch($branch, $dir);
288         }
289     }
290     
291     public function execute($file = null) {
292         if($this->loopedcall)
293             return;
294         define("GITMANAGED_EXECUTED", true);
295         
296         if(!$file)
297             $file = $_SERVER['PHP_SELF'];
298         if($file[0] == '/')
299             $file = substr($file, 1);
300         
301         $default_branch = $this->getConfig("defaultbranch");
302         $active_branch = $this->getActiveBranch();
303         if($active_branch != $default_branch) {
304             if(!($dir = $this->localBranchPath($branch))) {
305                 $dir = $this->localBranchPath($active_branch, true);
306                 $this->updateBranch($active_branch, $dir);
307             }
308             include_once($dir.$file);
309         } else {
310             include_once($this->localdir.$file);
311         }
312     }
313     
314 }
315
316 ?>