remove old readme
[atutor.git] / docs / mods / _standard / patcher / classes / Patch.class.php
1 <?php
2 /************************************************************************/
3 /* ATutor                                                               */
4 /************************************************************************/
5 /* Copyright (c) 2002-2010                                              */
6 /* Inclusive Design Institute                                           */
7 /* http://atutor.ca                                                     */
8 /*                                                                      */
9 /* This program is free software. You can redistribute it and/or        */
10 /* modify it under the terms of the GNU General Public License          */
11 /* as published by the Free Software Foundation.                        */
12 /************************************************************************/
13 // $Id$
14
15 /**
16 * Patch
17 * Class for patch installation
18 * @access       public
19 * @author       Cindy Qi Li
20 * @package      Patch
21 */
22
23 define('AT_INCLUDE_PATH', '../../../include/');
24
25 require_once(AT_INCLUDE_PATH. "../mods/_standard/patcher/include/common.inc.php");
26
27 class Patch {
28
29         // all private
30         var $patch_array = array();           // the patch data
31         var $patch_summary_array = array();   // patch summary information 
32         var $patch_id;                        // current patches.patches_id
33         var $patch_file_id;                   // current patches_files.patches_files_id
34         
35         var $need_access_to_folders = array();// folders that need to have write permission
36         var $need_access_to_files = array();  // files that need to have write permission
37         var $backup_files = array();          // backup files
38         var $patch_files = array();           // patch files
39
40         var $errors = array();                // error messages
41         var $baseURL;                         // patch folder at update.atutor.ca
42         var $backup_suffix;                   // suffix appended for backup files
43         var $patch_suffix;                    // suffix appended for patch files copied from update.atutor.ca
44         var $skipFilesModified = false;       // if set to true, report error for files that have been modified by user
45         var $module_content_dir;              // content folder used to create patch.sql
46         var $svn_server_connected;            // flag indicating if can connect to svn server, if not, consider all files manipulated by patch as modified
47
48         // constant, URL of user's ATutor release version in SVN 
49         var $svn_tag_folder = 'http://atutorsvn.atrc.utoronto.ca/repos/atutor/tags/';
50         var $sql_file = 'patch.sql';
51         var $relative_to_atutor_root = '../../../';   // relative path from mods/_standard/patcher to root
52         
53         var $error_title_printed = false;      // a flag only used in constructor when error happens 
54                                                // it's to indicate if the title of the error has been printed into $this->errors
55
56         /**
57         * Constructor: Initialize object members
58         * @access  public
59         * @param   $patch_array The name of the file to find charset definition
60         *          $patch_summary_array
61         *          $skipFilesModified
62         * @author  Cindy Qi Li
63         */
64         function Patch($patch_array, $patch_summary_array, $skipFilesModified, $patch_folder) 
65         {
66                 // add relative path to move to ATutor root folder
67                 for ($i = 0; $i < count($patch_array[files]); $i++)
68                 {
69                         // prevent the access to the areas that are outside of ATutor
70                         $first_char = substr($patch_array[files][$i]['location'], 0, 1);
71                         if ($first_char == '.' || $first_char == '/'){
72                                 if (!$this->error_title_printed) {
73                                         $this->errors[] = _AT('path_not_allowed');
74                                         $this->error_title_printed = true;
75                                 }
76                                 $this->errors[0] .= '<strong>'. $patch_array[files][$i]['location'] . "</strong><br />";
77                         }
78                         $patch_array[files][$i]['location'] = $this->relative_to_atutor_root . $patch_array[files][$i]['location'];
79                 }
80                         
81                 $this->patch_array = $patch_array; 
82                 $this->patch_summary_array = $patch_summary_array;
83
84                 $this->baseURL = $patch_folder;
85                 $this ->backup_suffix = $patch_array['atutor_patch_id'] . ".old";
86                 $this ->patch_suffix = $patch_array['atutor_patch_id'];
87                 $this->skipFilesModified = $skipFilesModified;
88                 
89                 $this->module_content_dir = AT_CONTENT_DIR . "patcher";
90
91                 session_start();
92                 
93                 if (!is_array($_SESSION['remove_permission'])) $_SESSION['remove_permission']=array();
94                 
95         }
96
97         /**
98         * Main process to apply patch.
99         * @access  public
100         * @return  true  if patch is successfully applied
101         *          false if failed
102         * @author  Cindy Qi Li
103         */
104         function applyPatch() 
105         {
106                 global $msg;
107                 
108                 // Checks on
109                 // 1. if there's error from class constructor 
110                 // 2. if svn server is up. If not, consider all files manipulated by patch as modified
111                 // 3. if the local file is customized by user
112                 // 4. if script has write priviledge on local file/folder
113                 // 5. if dependent patches have been installed
114                 
115                 if (count($this->errors) > 0) {
116                         print_errors($this->errors, $notes);
117                 
118                         unset($this->errors);
119                         return false;
120                 }
121                 
122                 if (!$this->pingDomain($this->svn_tag_folder)) 
123                 {
124                         $msg->addInfo('CANNOT_CONNECT_SVN_SERVER');
125                         $msg->printInfos();
126                         $this->svn_server_connected = false;
127                 }
128                 else
129                         $this->svn_server_connected = true;
130                 
131                 if (!$this->checkDependentPatches()) return false;
132
133                 if (!$this->checkAppliedVersion()) return false;
134
135                 if (!$this->skipFilesModified && $this->hasFilesModified()) return false;
136                 
137                 if (!$this->checkPriviledge()) return false;
138                 // End of check
139
140                 if (strlen(trim($this->patch_array['sql'])) > 0) $this->runSQL();
141
142                 // Start applying patch
143                 $this->createPatchesRecord($this->patch_summary_array);
144
145                 // if no file action defined, update database and return true
146                 if (!is_array($this->patch_array[files])) 
147                 {
148                         $updateInfo = array("status"=>"Installed");
149                         updatePatchesRecord($this->patch_id, $updateInfo);
150         
151                         return true;
152                 }
153                 
154                 foreach ($this->patch_array[files] as $row_num => $patch_file)
155                 {
156                         $this->createPatchesFilesRecord($this->patch_array['files'][$row_num]);
157
158                         if ($patch_file['action'] == 'alter')
159                         {
160                                 $this->alterFile($row_num);
161                         }
162                         else if ($patch_file['action'] == 'add')
163                         {
164                                 $this->addFile($row_num);
165                         }
166                         else if ($patch_file['action'] == 'delete')
167                         {
168                                 $this->deleteFile($row_num);
169                         }
170                         else if ($patch_file['action'] == 'overwrite')
171                         {
172                                 $this->overwriteFile($row_num);
173                         }
174                 }
175                 
176                 // if only has backup files info, patch is considered successfully installed
177                 // if has permission to remove, considered partly installed
178                 $updateInfo = array();
179
180                 if (count($this->backup_files) > 0)
181                 {
182                         foreach($this->backup_files as $backup_file)
183                                 $backup_files .= $backup_file. '|';
184                 
185                         $updateInfo = array("backup_files"=>mysql_real_escape_string($backup_files));
186                 }
187         
188                 if (count($this->patch_files) > 0)
189                 {
190                         foreach($this->patch_files as $patch_file)
191                                 $patch_files .= $patch_file. '|';
192                 
193                         $updateInfo = array_merge($updateInfo, array("patch_files"=>mysql_real_escape_string($patch_files)));
194                 }
195         
196                 if (is_array($_SESSION['remove_permission']) && count($_SESSION['remove_permission']))
197                 {
198                         foreach($_SESSION['remove_permission'] as $remove_permission_file)
199                                 $remove_permission_files .= $remove_permission_file. '|';
200
201                         $updateInfo = array_merge($updateInfo, array("remove_permission_files"=>mysql_real_escape_string($remove_permission_files), "status"=>"Partly Installed"));
202                 }
203                 else
204                 {
205                         $updateInfo = array_merge($updateInfo, array("status"=>"Installed"));
206                 } 
207
208                 updatePatchesRecord($this->patch_id, $updateInfo);
209                 
210                 unset($_SESSION['remove_permission']);
211
212                 return true;
213         }
214
215         /**
216         * return patch array
217         * @access  public
218         * @return  patch array
219         * @author  Cindy Qi Li
220         */
221         function getPatchArray() 
222         {
223                 return $this->patch_array;
224         }
225         
226         /**
227         * return patch id processed by this object
228         * @access  public
229         * @return  patch id
230         * @author  Cindy Qi Li
231         */
232         function getPatchID() 
233         {
234                 return $this->patch_id;
235         }
236         
237         /**
238         * Check if script has write permission to the files and folders that need to be written
239         * if no permission, warn user to give permission
240         * @access  private
241         * @return  true  if there are files or folders that script has no permission
242         *          false if permissions are in place
243         * @author  Cindy Qi Li
244         */
245         function checkPriviledge()
246         {
247                 global $id, $who;
248                 
249                 // no file action is defined, return true;
250                 if (!is_array($this->patch_array[files])) return true;
251                 
252                 foreach ($this->patch_array[files] as $row_num => $patch_file)
253                 {
254                         $real_location = realpath($patch_file['location']);
255                         
256                         if ($real_location <> '' && !is_writable($patch_file['location']) && !in_array($real_location, $this->need_access_to_folders))
257                         { // folder exists. check if has write permission
258                                 $this->need_access_to_folders[] = $real_location;
259
260                                 if (!in_array($real_location, $_SESSION['remove_permission']))
261                                         $_SESSION['remove_permission'][] = $real_location;
262                         } else if ($real_location == '' && $patch_file['action'] != 'delete') {
263                                 // The folder does not exist. Create it before proceed
264                                 
265                                 // find the real path for the folder to be created
266                                 $full_folder_path = realpath($this->relative_to_atutor_root).
267                                                     substr($patch_file['location'], strlen($this->relative_to_atutor_root)-1);
268                                 // remove ending '/'
269                                 mkdir($full_folder_path, 0755, true);
270                         }
271
272                         if ($patch_file['action'] == 'alter' || $patch_file['action'] == 'delete' || $patch_file['action'] == 'overwrite')
273                         {
274                                 $file = $patch_file['location'] . "/" . $patch_file['name'];
275
276                                 $real_file = realpath($file);
277                                 if (file_exists($file) && !is_writable($file) && !in_array($real_file, $this->need_access_to_files))
278                                 {
279                                         $this->need_access_to_files[] = $real_file;
280
281                                         if (!in_array($real_file, $_SESSION['remove_permission']) && $patch_file['action'] <> 'delete')
282                                                 $_SESSION['remove_permission'][] = $real_file;
283                                 }
284                         }
285                 }
286                 
287                 if (count($this->need_access_to_folders) > 0 || count($this->need_access_to_files) > 0)
288                 {
289                         $this->errors[] = _AT('grant_write_permission');
290                         
291                         foreach($this->need_access_to_folders as $folder)
292                         {
293                                 $this->errors[0] .= '<strong>'. $folder . "</strong><br />";
294                         }
295
296                         foreach($this->need_access_to_files as $file)
297                         {
298                                 $this->errors[0] .= '<strong>'. $file . "</strong><br />";
299                         }
300
301                         $notes = '<form action="'. $_SERVER['PHP_SELF'].'?id='.$id.'&who='. $who .'" method="post" name="skip_files_modified">
302                   <div class="row buttons">
303                                 <input type="submit" name="yes" value="'._AT('continue').'" accesskey="y" />
304                                 <input type="submit" name="no" value="'. _AT('cancel'). '" />
305                                 <input type="hidden" name="install" value="' . $_POST['install'] . '" />
306                                 <input type="hidden" name="install_upload" value="' . $_POST['install_upload'] . '" />
307                                 <input type="hidden" name="ignore_version" value="' . $_POST['ignore_version'] . '" />
308                         </div>
309                         </form>';
310                         
311                         print_errors($this->errors, $notes);
312                 
313                         unset($this->errors);
314                         return false;
315                 }
316                 
317                 return true;
318         }
319         
320         /**
321         * Check if ATutor version is same as "applied version" defined in the patch.
322         * @access  private
323         * @return  true  if versions match
324         *          false if versions don't match
325         * @author  Cindy Qi Li
326         */
327         function checkAppliedVersion()
328         {
329                 global $msg;
330                 
331                 if ($this->patch_summary_array["applied_version"] <> VERSION)
332                 {
333                         $this->errors[] = _AT("version_not_match", $this->patch_summary_array["applied_version"]);
334                                 
335                         $notes = '
336                           <form action="'. $_SERVER['PHP_SELF'].'?id='.$_POST['id'].'&who='. $_POST['who'] .'" method="post" name="skip_files_modified">
337                           <div class="row buttons">
338                                         <input type="submit" name="ignore_version" value="'._AT('yes').'" accesskey="y" />
339                                         <input type="submit" name="not_ignore_version" value="'. _AT('no'). '" />
340                                         <input type="hidden" name="install" value="' . $_POST['install'] . '" />
341                                         <input type="hidden" name="install_upload" value="' . $_POST['install_upload'] . '" />
342                                 </div>
343                                 </form>';
344
345                         print_errors($this->errors, $notes);
346                 
347                         unset($this->errors);
348                         
349                         return false;
350                 }
351
352                 return true;
353         }
354
355         /**
356         * Check if all the dependent patches have been installed.
357         * @access  private
358         * @return  true  if all the dependent patches have been installed
359         *          false if any dependent patch has not been installed.
360         * @author  Cindy Qi Li
361         */
362         function checkDependentPatches()
363         {
364                 global $msg;
365                 
366                 $dependent_patches_installed = true;
367                 
368                 // if no dependent patch defined, return true
369                 if (!is_array($this->patch_summary_array["dependent_patches"])) return true;
370                 
371                 foreach($this->patch_summary_array["dependent_patches"] as $num => $dependent_patch)
372                 {
373                         if (!is_patch_installed($dependent_patch))
374                         {
375                                 $dependent_patches_installed = false;
376                                 $dependent_patches .= $dependent_patch. ", ";
377                         }
378                 }
379                 
380                 if (!$dependent_patches_installed)
381                 {
382                         $errors = array('PATCH_DEPENDENCY', substr($dependent_patches, 0, -2));
383                         $msg->addError($errors);
384                         return false;
385                 }
386                 
387                 return true;
388         }
389         
390         /**
391         * Loop thru all the patch files that will be overwitten or altered, 
392         * to find out if they are modified by user. If it's modified, warn user.
393         * @access  private
394         * @return  true  if there are files being modified
395         *          false if no file is modified
396         * @author  Cindy Qi Li
397         */
398         function hasFilesModified()
399         {
400                 $overwrite_modified_files = false;
401                 $alter_modified_files = false;
402                 $has_not_exist_files = false;
403                 
404                 // no file action is defined, return nothing is modified (false)
405                 if (!is_array($this->patch_array[files])) return false;
406                 
407                 foreach ($this->patch_array[files] as $row_num => $patch_file)
408                 {
409                         if ($patch_file["action"]=='alter' || $patch_file["action"]=='overwrite')
410                         {
411                                 if (!file_exists($patch_file['location'] . $patch_file['name']))
412                                 {
413                                         $not_exist_files .= $patch_file['location'] . $patch_file['name'] . '<br />';
414                                         $has_not_exist_files = true;
415                                 }
416                                 else if ($this->isFileModified($patch_file['location'], $patch_file['name']))
417                                 {
418                                         if ($patch_file['action']=='overwrite')
419                                         {
420                                                 $overwrite_files .= realpath($patch_file['location'] . $patch_file['name']) . '<br />';
421                                                 $overwrite_modified_files = true;
422                                         }
423                                         if ($patch_file['action']=='alter')
424                                         {
425                                                 $alter_files .= realpath($patch_file['location'] . $patch_file['name']) . '<br />';
426                                                 $alter_modified_files = true;
427                                         }
428                                 }
429                         }
430                 }
431
432                 if ($has_not_exist_files) $this->errors[] = _AT('patch_local_file_not_exist'). $not_exist_files;
433                 if ($overwrite_modified_files)    $this->errors[] = _AT('patcher_overwrite_modified_files') . $overwrite_files;
434                 if ($alter_modified_files)    $this->errors[] = _AT('patcher_alter_modified_files') . $alter_files;
435                 if (count($this->errors) > 0)
436                 {
437                         if ($has_not_exist_files)
438                                 $notes = '';
439                         else
440                                 $notes = '
441                           <form action="'. $_SERVER['PHP_SELF'].'?id='.$_POST['id'].'&who='. $_POST['who'] .'" method="post" name="skip_files_modified">
442                           <div class="row buttons">
443                                         <input type="submit" name="yes" value="'._AT('yes').'" accesskey="y" />
444                                         <input type="submit" name="no" value="'. _AT('no'). '" />
445                                         <input type="hidden" name="install" value="' . $_POST['install'] . '" />
446                                         <input type="hidden" name="install_upload" value="' . $_POST['install_upload'] . '" />
447                                         <input type="hidden" name="ignore_version" value="' . $_POST['ignore_version'] . '" />
448                                 </div>
449                                 </form>';
450
451                         print_errors($this->errors, $notes);
452                 
453                         unset($this->errors);
454                         return true;
455                 }
456                 
457                 return false;
458         }
459
460         /**
461         * Compare user's local file with SVN backup for user's ATutor version,
462         * if different, check table at_patches_files to see if user's local file
463         * was altered by previous patch installation. If it is, return false 
464         * (not modified), otherwise, return true (modified).
465         * @access  private
466         * @param   $folder  folder of the file to be compared
467         *          $file    name of the file to be compared
468         * @return  true     if the file is modified
469         *          false    if the file is not modified
470         * @author  Cindy Qi Li
471         */
472         function isFileModified($folder, $file)
473         {
474                 global $db;
475
476                 if (!$this->svn_server_connected) return true;
477                 
478                 $svn_file = $this->svn_tag_folder . 'atutor_' . str_replace('.', '_', VERSION) .
479                             str_replace(substr($this->relative_to_atutor_root, 0, -1), '' , $folder) .$file;
480                 $local_file = $folder.$file;
481
482                 // if svn script does not exist, consider the script is modified
483                 if (!file_get_contents($svn_file)) return true;
484
485                 // check if the local file has been modified by user. if it is, don't overwrite
486                 if ($this->compareFiles($svn_file, $local_file) <> 0)
487                 {
488                         // check if the file was changed by previous installed patches
489                         $sql = "SELECT count(*) num_of_updates FROM " . TABLE_PREFIX. "patches patches, " . TABLE_PREFIX."patches_files patches_files " .
490                                "WHERE patches.applied_version = '" . VERSION . "' ".
491                                "  AND patches.status = 'Installed' " .
492                                "  AND patches.patches_id = patches_files.patches_id " .
493                                "  AND patches_files.name = '" . $file . "'";
494                         
495                         $result = mysql_query($sql, $db) or die(mysql_error());
496                         $row = mysql_fetch_assoc($result);
497                         
498                         if ($row["num_of_updates"] == 0) return true;
499                 }
500                 return false;
501         }
502
503         /**
504         * Run SQL defined in patch.xml
505         * @access  private
506         * @author  Cindy Qi Li
507         */
508         function runSQL()
509         {
510                 // run sql
511                 // As sqlutility.class.php reads sql from a file, write sql to module content folder
512                 $patch_sql_file = $this->module_content_dir . '/' . $this->sql_file;
513
514                 $fp = fopen($patch_sql_file, 'w');
515                 fwrite($fp, trim($this->patch_array['sql']));
516                 fclose($fp);
517
518                 require(AT_INCLUDE_PATH . 'classes/sqlutility.class.php');
519                 $sqlUtility = new SqlUtility();
520         
521                 $sqlUtility->queryFromFile($patch_sql_file, TABLE_PREFIX);
522                 
523                 @unlink($patch_sql_file);
524                 
525                 return true;
526         }
527                 
528         /**
529         * Copy file from update.atutor.ca to user's computer
530         * @access  private
531         * @param   $row_num     row number of patch record to be processed
532         * @author  Cindy Qi Li
533         */
534         function addFile($row_num)
535         {
536                 $this->copyFile($this->baseURL . preg_replace('/.php$/', '.new', $this->patch_array['files'][$row_num]['name']), $this->patch_array['files'][$row_num]['location'].$this->patch_array['files'][$row_num]['name']);
537                 
538                 return true;
539         }
540         
541         /**
542         * Delete file, backup before deletion
543         * @access  private
544         * @param   $row_num     row number of patch record to be processed
545         * @author  Cindy Qi Li
546         */
547         function deleteFile($row_num)
548         {
549                 $local_file = $this->patch_array['files'][$row_num]['location'].$this->patch_array['files'][$row_num]['name'];
550                 $backup_file = $local_file . "." . $this->backup_suffix;
551                 
552                 if (file_exists($local_file))
553                 {
554                         // move file to backup
555                         $this->copyFile($local_file, $backup_file);
556                         $this->backup_files[] = realpath($backup_file);
557                         @unlink($local_file);
558                 }
559                 
560                 return true;
561                 
562         }
563         
564         /**
565         * Alter file based on <action_detail>
566         * If user's local file is modified and user agrees to proceed with applying patch,
567         * alter user's local file.
568         * @access  private
569         * @param   $row_num     row number of patch record to be processed
570         * @author  Cindy Qi Li
571         */
572         function alterFile($row_num)
573         {
574                 $local_file = $this->patch_array['files'][$row_num]['location'].$this->patch_array['files'][$row_num]['name'];
575                 
576                 // backup user's file
577                 $backup_file = $local_file . "." . $this->backup_suffix;
578                 
579                 // Checking existence of $backup_file is to fix the bug when there are multiple alter/delete actions 
580                 // on the same file, the following backups overwrite the first backup which results in the loss of the
581                 // original code.
582                 if (!file_exists($backup_file))
583                 {
584                         $this->copyFile($local_file, $backup_file);
585                         $this->backup_files[] = realpath($backup_file);
586                 }
587                 
588                 $local_file_content = file_get_contents($local_file);
589
590                 // Modify user's file
591                 foreach ($this->patch_array['files'][$row_num]['action_detail'] as $garbage => $alter_file_action)
592                 {
593                         if ($alter_file_action['type'] == 'delete')
594                                 $modified_local_file_content = $this->strReplace($alter_file_action['code_from'], '', $local_file_content);
595
596                         if ($alter_file_action['type'] == 'replace')
597                                 $modified_local_file_content = $this->strReplace($alter_file_action['code_from'], $alter_file_action['code_to'], $local_file_content);
598                                 
599                         // when code_from is not found, add in warning
600                         if ($modified_local_file_content == $local_file_content)  
601                         {
602                                 for ($i = 0; $i < count($this->backup_files); $i++)
603                                         if ($this->backup_files[$i] == realpath($backup_file))
604                                                 $this->backup_files[$i] .= ' '._AT("chunks_not_found");
605                         }
606                         else
607                                 $local_file_content = $modified_local_file_content;
608
609                         $this->createPatchesFilesActionsRecord($alter_file_action);
610                 }
611
612                 $fp = fopen($local_file, 'w');
613                 fwrite($fp, $local_file_content);
614                 fclose($fp);
615
616                 return true;
617         }
618         
619         /**
620         * Fetch file from update.atutor.ca and overwrite user's local file if the local file is not modified
621         * If user's local file is modified and user agrees to proceed with applying patch,
622         * copy the new file to user's local for them to merge manually.
623         * @access  private
624         * @param   $row_num     row number of patch record to be processed
625         * @author  Cindy Qi Li
626         */
627         function overwriteFile($row_num)
628         {
629                 $local_file = $this->patch_array['files'][$row_num]['location'].$this->patch_array['files'][$row_num]['name'];
630                 $patch_file = $this->baseURL . preg_replace('/.php$/', '.new', $this->patch_array['files'][$row_num]['name']);
631                 
632                 // if local file is modified and user agrees to proceed with applying patch,
633                 // copy the new file to user's local for them to merge manually
634                 if ($this->skipFilesModified && $this->isFileModified($this->patch_array['files'][$row_num]['location'], $this->patch_array['files'][$row_num]['name']))
635                 {
636                         $local_patch_file = $local_file . "." . $this->patch_suffix;
637
638                         $this->copyFile($patch_file, $local_patch_file);
639                         
640                         $this->patch_files[] = realpath($local_patch_file);
641                 }
642                 else
643                 {
644                         $backup_file = $local_file . "." . $this->backup_suffix;
645                         
646                         // backup user's file
647                         $this->copyFile($local_file, $backup_file);
648                         $this->backup_files[] = realpath($backup_file);
649                         
650                         // overwrite user's file
651                         $this->copyFile($patch_file, $local_file);
652                 }
653                 
654                 return true;
655         }
656         
657         /**
658         * Copy file $src to $dest. $src can be a local file or a remote file
659         * @access  private
660         * @param   $src location of the source file
661         *          $dest        location of the destination file
662         * @author  Cindy Qi Li
663         */
664         function copyFile($src, $dest)
665         {
666                 $content = file_get_contents($src);
667                 $fp = fopen($dest, 'w');
668                 fwrite($fp, $content);
669                 fclose($fp);
670                 
671                 return true;
672         }
673         
674         /**
675         * Compare files $src against $dest
676         * @access  private
677         * @param   $src location of the source file
678         *          $dest        location of the destination file
679         * @return  Returns < 0 if $src is less than $dest ; > 0 if $src is greater than $dest, and 0 if they are equal.
680         * @author  Cindy Qi Li
681         */
682         function compareFiles($src, $dest)
683         {
684                 // use preg_replace to delete 
685                 // 1. the line starting with // $Id:
686                 // 2. the line starting with $lm = '$LastChangedDate, ending with ;
687                 // These lines are created by SVN. It could be different in different copies of the same file.
688                 $pattern = '/\/\/ \$Id.*\$|\$lm = \'\$LastChangedDate.*;/';
689                 
690                 $src_content = preg_replace($pattern, '', file_get_contents($src));
691                 $dest_content = preg_replace($pattern, '', file_get_contents($dest));
692
693                 return strcasecmp($src_content, $dest_content);
694         }
695         
696         /**
697         * Replace single/multiple lines of string. 
698         * This function handles different new line character at windows/unix platform
699         * @access  private
700         * @param   $search      String to replace from
701         *          $replace     String to replace to
702         *          $subject Subject to be handled  
703         * @return  return replaced string, if nothing is replaced, return original subject
704         * @author  Cindy Qi Li
705         */
706         function strReplace($search, $replace, $subject)
707         {
708                 // Note: DO NOT change the order of the array elements. 
709                 // "\n\r", "\r\n" must come before "\n", "\r" in the array, 
710                 // otherwise, the new line replace underneath would wrongly replace "\n\r" to "\r\r" or "\n\n"
711                 $new_line_array = array("\n\r", "\r\n", "\r", "\n");
712                 
713                 foreach ($new_line_array as $new_line)
714                 {
715                         if (preg_match('/'.preg_quote($new_line).'/', $search) > 0)   $search_new_lines[] = $new_line;
716                         if (preg_match('/'.preg_quote($new_line).'/', $replace) > 0)   $replace_new_lines[] = $new_line;
717                         if (preg_match('/'.preg_quote($new_line).'/', $subject) > 0)   $subject_new_lines[] = $new_line;
718                 }
719
720                 // replace new line chars in $search, $replace, $subject to the last new line in $subject
721                 if (is_array($subject_new_lines)) $new_line_replace_to = array_pop($subject_new_lines);
722
723                 if ($new_line_replace_to <> '')
724                 {
725                         if (count($search_new_lines) > 0)
726                                 foreach ($search_new_lines as $new_line)
727                                         if ($new_line <> $new_line_replace_to)
728                                                 $search = preg_replace('/'.preg_quote($new_line).'/', $new_line_replace_to, $search);
729                         
730                         if (count($replace_new_lines) > 0)
731                                 foreach ($replace_new_lines as $new_line)
732                                         if ($new_line <> $new_line_replace_to)
733                                                 $replace = preg_replace('/'.preg_quote($new_line).'/', $new_line_replace_to, $replace);
734                         
735                         if (count($subject_new_lines) > 0)
736                                 foreach ($subject_new_lines as $new_line)
737                                         $subject = preg_replace('/'.preg_quote($new_line).'/', $new_line_replace_to, $subject);
738                 }
739                 
740                 return preg_replace('/'. preg_quote($search, '/') .'/', $replace, $subject);
741         }
742         
743         /**
744         * Check if the server is down
745         * @access  private
746         * @param   $domain      Server Domain
747         * @return  return false if server is down, otherwise, return true
748         * @author  Cindy Qi Li
749         */
750         function pingDomain($domain)
751         {
752     $file = @fopen ($domain, 'r');
753
754     if (!$file) 
755         return false;
756
757     return true;
758         }
759
760         /**
761         * Insert record into table patches
762         * @access  private
763         * @param   $patch_summary_array Patch summary information
764         * @author  Cindy Qi Li
765         */
766         function createPatchesRecord($patch_summary_array)
767         {
768                 global $db;
769                 
770                 $sql = "INSERT INTO " . TABLE_PREFIX. "patches " .
771                                          "(atutor_patch_id, 
772                                            applied_version,
773                                            patch_folder,
774                                            description,
775                                            available_to,
776                                            sql_statement,
777                                            status,
778                                            remove_permission_files,
779                                            backup_files,
780                                            patch_files,
781                                            author,
782                                            installed_date)
783                                           VALUES
784                                           ('".$patch_summary_array["atutor_patch_id"]."',
785                                            '".$patch_summary_array["applied_version"]."',
786                                            '".mysql_real_escape_string($patch_summary_array["patch_folder"])."',
787                                            '".mysql_real_escape_string($patch_summary_array["description"])."',
788                                            '".$patch_summary_array["available_to"]."',
789                                            '".mysql_real_escape_string($patch_summary_array["sql"])."',
790                                            '".$patch_summary_array["status"]."',
791                                            '',
792                                            '',
793                                            '',
794                                            '".mysql_real_escape_string($patch_summary_array["author"])."',
795                                            now()
796                                            )";
797
798                 $result = mysql_query($sql, $db) or die(mysql_error());
799                 
800                 $this->patch_id = mysql_insert_id();
801                 
802                 return true;
803         }
804
805         /**
806         * Insert record into table patches_files
807         * @access  private
808         * @param   $patch_files_array   Patch information
809         * @author  Cindy Qi Li
810         */
811         function createPatchesFilesRecord($patch_files_array)
812         {
813                 global $db;
814                 
815                 $sql = "INSERT INTO " . TABLE_PREFIX. "patches_files " .
816                                          "(patches_id, 
817                                            action,
818                                            name,
819                                            location)
820                                           VALUES
821                                           (".$this->patch_id.",
822                                            '".$patch_files_array['action']."',
823                                            '".mysql_real_escape_string($patch_files_array['name'])."',
824                                            '".mysql_real_escape_string($patch_files_array['location'])."'
825                                            )";
826
827                 $result = mysql_query($sql, $db) or die(mysql_error());
828                 
829                 $this->patch_file_id = mysql_insert_id();
830
831                 return true;
832         }
833
834         /**
835         * Insert record into table patches_files_actions
836         * @access  private
837         * @param   $patch_files_actions_array   alter file actions and contents
838         * @author  Cindy Qi Li
839         */
840         function createPatchesFilesActionsRecord($patch_files_actions_array)
841         {
842                 global $db;
843                 
844                 $sql = "INSERT INTO " . TABLE_PREFIX. "patches_files_actions " .
845                                          "(patches_files_id, 
846                                            action,
847                                            code_from,
848                                            code_to)
849                                           VALUES
850                                           (".$this->patch_file_id.",
851                                            '".$patch_files_actions_array['type']."',
852                                            '".mysql_real_escape_string($patch_files_actions_array['code_from'])."',
853                                            '".mysql_real_escape_string($patch_files_actions_array['code_to'])."'
854                                            )";
855
856                 $result = mysql_query($sql, $db) or die(mysql_error());
857                 
858                 return true;
859         }
860 }
861
862 ?>