Squiz Matrix  4.12.2
 All Data Structures Namespaces Functions Variables Pages
system_integrity_recover_file_versions.php
1 <?php
26 error_reporting(E_ALL);
27 if ((php_sapi_name() != 'cli')) trigger_error("You can only run this script from the command line\n", E_USER_ERROR);
28 
29 $SYSTEM_ROOT = (isset($_SERVER['argv'][1])) ? $_SERVER['argv'][1] : '';
30 if (empty($SYSTEM_ROOT)) {
31  echo "ERROR: You need to supply the path to the System Root as the first argument\n";
32  print_usage();
33  exit();
34 }
35 
36 if (!is_dir($SYSTEM_ROOT) || !is_readable($SYSTEM_ROOT.'/core/include/init.inc')) {
37  echo "ERROR: Path provided doesn't point to a Matrix installation's System Root. Please provide correct path and try again.\n";
38  print_usage();
39  exit();
40 }
41 
42 $command = (isset($_SERVER['argv'][2])) ? $_SERVER['argv'][2] : 'test';
43 
44 $TREE_ID = (isset($_SERVER['argv'][3])) ? $_SERVER['argv'][3] : '1';
45 
46 echo "You want to $command the file versioning system. Your selected root node is #$TREE_ID\n\n";
47 
48 require_once $SYSTEM_ROOT.'/core/include/init.inc';
49 
50 $root_user = &$GLOBALS['SQ_SYSTEM']->am->getSystemAsset('root_user');
51 
52 if (!$GLOBALS['SQ_SYSTEM']->setCurrentUser($root_user)) {
53  echo "ERROR: Failed login in as root user\n";
54  exit();
55 }
56 
57 //get children of the tree root asset which are file and its descendant types
58 $assetids = $GLOBALS['SQ_SYSTEM']->am->getChildren($TREE_ID, 'file', FALSE);
59 
60 //if the tree root asset is file type, include it
61 if ($TREE_ID != '1') {
62  $asset = $GLOBALS['SQ_SYSTEM']->am->getAsset($TREE_ID);
63  if ($GLOBALS['SQ_SYSTEM']->am->isTypeDecendant($asset->type(), 'file') || $asset instanceof Image_Variety) {
64  $assetids[$asset->id] = Array(0 => Array('type_code' => $asset->type())); //match with the return of the getChildren() method above
65  }
66 }
67 
68 $fv = $GLOBALS['SQ_SYSTEM']->getFileVersioning();
69 $error_count = 0;
70 $error_fixed = 0;
71 
72 // add image varieties to check list
73 $imageids = $assetids;
74 foreach ($imageids as $assetid => $asset_info) {
75  $asset = $GLOBALS['SQ_SYSTEM']->am->getAsset($assetid);
76  if($asset instanceof Image) {
77  $varieties = $asset->attr('varieties');
78  if(empty($varieties)) continue;
79  foreach($varieties['data'] as $id => $details) {
80  $assetids[$asset->id.':'.$id] = $details;
81  }
82  }
83 }
84 
85 //Check the file version integrity of each file
86 foreach ($assetids as $assetid => $asset_info) {
87  $asset = $GLOBALS['SQ_SYSTEM']->am->getAsset($assetid);
88  if($asset instanceof Image_Variety) {
89  $file_name = $asset->attr('filename');
90  }
91  else {
92  $file_name = $asset->name;
93  }
94  $rep_file = $asset->data_path_suffix.'/'.$file_name;
95  $real_file = $asset->data_path.'/'.$file_name;
96 
97  //get the current version info of the file stored in database
98  $db_info = $fv->_getFileInfoFromPath($rep_file);
99 
100  //if there is no current version in database, set it to 0 and fix it later
101  if (empty($db_info)) {
102  $db_info = Array('version' => 0);
103  }
104 
105  //get the version info stored in the FFV file (in .FFV folder in data/private/{data_path} folder)
106  $fs_info = _getFileInfoFromRealFile($fv, $real_file);
107 
108  //if there is no version info in the file system, set it to 0 and fix it later
109  if (($fs_info == FUDGE_FV_NOT_CHECKED_OUT) || ($fs_info == FUDGE_FV_ERROR)) {
110  $fs_info = Array('version' => 0);
111  }
112 
113 
114  //if the 2 previous versions are different, there is something wrong with this file asset => report and fix it (if required)
115  if ($db_info['version'] != $fs_info['version']) {
116 
117  //report the problem
118  $file_id = isset($db_info['fileid'])? $db_info['fileid'] : $fs_info['fileid'];
119  echo "The versions of the file asset #{$asset->id} (fileid = $file_id, rep_path = $rep_file) are different. Database version: {$db_info['version']} - File system version {$fs_info['version']}\n";
120  $error_count++;
121  //want to fix the problem
122  if ($command == 'recover') {
123  $is_fixed = FALSE;
124  //db version is greater than file system version
125  if ($db_info['version'] > $fs_info['version']) {
126  //add version file if needed
127  $new_version = $db_info['version'];
128  $is_fixed = _updateVersionFile($fv, $asset->data_path_suffix, $real_file, $db_info, $new_version);
129  if ($is_fixed) {
130  //add new or update ffv file so that its version equals with the version in the database
131  $is_fixed = $fv->_createFFVFile($real_file, $db_info['fileid'], $new_version);
132  if (!$is_fixed) {
133  echo "ERROR: CAN NOT CREATE FFV CONFIGURATION FILE!\n";
134  }
135  } else {
136  echo "ERROR: CAN NOT COPY CURRENT FILE TO VERSION FILE!\n";
137  }
138  } else {
139  //db version is less than file system version
140  //if db version is 0, check to see if the file has been stored in sq_file_vers_file table
141  if ($db_info['version'] == 0) {
142  //insert file info to sq_file_vers_file table if one does not exist
143  _insertFileVersFileInfo($fv, $fs_info['fileid'], $rep_file);
144  }
145  //add version file if needed
146  $new_version = $fs_info['version'];
147  $is_fixed = _updateVersionFile($fv, $asset->data_path_suffix, $real_file, $fs_info, $new_version);
148  if ($is_fixed) {
149  if ($new_version != $fs_info['version']) {
150  //db version history record is aready updated, only need to update FFV configuration file
151  $is_fixed = $fv->_createFFVFile($real_file, $fs_info['fileid'], $new_version);
152  if (!$is_fixed) {
153  echo "ERROR: CAN NOT CREATE FFV CONFIGURATION FILE!\n";
154  }
155  } else {
156  //update db version history record
157  $is_fixed = _updateFileVersionHistory($fv, $fs_info['fileid'], $real_file, $new_version);
158  if (!$is_fixed) {
159  echo "ERROR: CAN NOT UPDATE VERSION HISTORY TABLE IN DB!\n";
160  }
161  }
162  } else {
163  echo "ERROR: CAN NOT COPY CURRENT FILE TO VERSION FILE!\n";
164  }
165  }
166  //if the error is fixed, count it
167  if ($is_fixed) {
168  //Fix #4532. Use this public function to access _checkFileState function which
169  //looks after the placing and removing of files in the public directory.
170  $asset->permissionsUpdated();
171  $error_fixed++;
172  }
173  }
174  }
175 }
176 
177 
178 if ($error_count > 0) {
179  echo "\nThere are $error_count errors detected in the file versioning system. $error_fixed are fixed!\n";
180 } else {
181  echo "\nThere are no errors detected.\n";
182 }
183 
184 
185 
197 function _getFileInfoFromRealFile($file_versioning, $real_file)
198 {
199  $ffv_dir = dirname($real_file).'/.FFV';
200 
201  if (!is_dir($ffv_dir)) return FUDGE_FV_NOT_CHECKED_OUT;
202 
203  $ffv_file = $ffv_dir.'/'.basename($real_file);
204  if (!is_file($ffv_file)) {
205  return FUDGE_FV_NOT_CHECKED_OUT;
206  }
207 
208  $ffv = parse_ini_file($ffv_file);
209  if (!is_array($ffv)) {
210  trigger_localised_error('FVER0025', E_USER_WARNING);
211  return FUDGE_FV_ERROR;
212  }
213 
214  if (realpath($file_versioning->_dir) != realpath($ffv['dir'])) {
215  trigger_localised_error('FVER0008', E_USER_WARNING);
216  return FUDGE_FV_ERROR;
217  }
218 
219  return $ffv;
220 
221 }//end _getFileInfoFromRealFile()
222 
223 
230 function _getNextVersion($fileid){
231  $sql = 'SELECT COALESCE(MAX(version), 0) + 1
232  FROM sq_file_vers_history
233  WHERE fileid = :fileid';
234 
235  try {
236  $query = MatrixDAL::preparePdoQuery($sql);
237  MatrixDAL::bindValueToPdo($query, 'fileid', $fileid);
238  $version = MatrixDAL::executePdoOne($query);
239  } catch (Exception $e) {
240  $version = 0;
241  }
242 
243  return $version;
244 
245 }
246 
247 
258 function _updateVersionFile($file_versioning, $rep_path, $real_file, $file_info, &$new_version) {
259  $fileid = $file_info['fileid'];
260  $version = $file_info['version'];
261  $new_version = $version;
262  if (file_exists($real_file)) {
263  $real_file_size = filesize($real_file);
264  $real_file_md5 = md5_file($real_file);
265  $real_file_sha1 = sha1_file($real_file);
266 
267  require_once SQ_FUDGE_PATH.'/general/file_system.inc';
268  $rep_dir = $file_versioning->_dir.'/'.$rep_path;
269  if (!is_dir($rep_dir) && !create_directory($rep_dir)) {
270  echo "ERROR: CAN NOT CREATE FOLDER: $rep_dir\n";
271  return FALSE;
272  }//end if
273 
274  $rep_file = $rep_dir.'/'.basename($real_file).',ffv'.$version;
275 
276  if (!file_exists($rep_file)) {
277  //this version does not exist, copy it
278  if (!copy($real_file, $rep_file)) return FALSE;
279  } else {
280  //if this version exists and is different from the real file, increase the version
281  if (($real_file_size != filesize($rep_file)) || ($real_file_md5 != md5_file($rep_file)) || ($real_file_sha1 != sha1_file($rep_file))) {
282  $next_version = _getNextVersion($fileid);
283  if ($next_version == 0) {
284  return FALSE;
285  }
286  //if next version is greater than current version, use _updateFile() method of File_Versioning class
287  if ($next_version > $version) {
288  $nv = $file_versioning->_updateFile($fileid, $rep_path, $real_file);
289 
290  if ($nv == 0) return FALSE;
291 
292  $new_version = $nv;
293  } else {
294  //if next version from database is not greater than version, copy version file and update database manually
295  $version++;
296  $rep_file = $rep_dir.'/'.basename($real_file).',ffv'.$version;
297  if (!copy($real_file, $rep_file)) return FALSE;
298  if (!_updateFileVersionHistory($file_versioning, $fileid, $real_file, $version)) return FALSE;
299  $new_version = $version;
300  $version--;
301  }
302  }
303  }
304 
305  //if new_version still equals version, check if the current file is the one stored in db
306  if (($new_version == $version) && isset($file_info['file_size'])) {
307  if (($real_file_size != $file_info['file_size']) || ($real_file_md5 != $file_info['md5']) || ($real_file_sha1 != $file_info['sha1'])) {
308  return $file_versioning->_updateFileVersion($real_file, $fileid, $version);
309  }
310  }
311  return TRUE;
312  }
313 
314  return FALSE;
315 
316 }//end _updateVersionFile()
317 
318 
330 function _updateFileVersionHistory($file_versioning, $fileid, $real_file, $version, $extra_info='')
331 {
332  $GLOBALS['SQ_SYSTEM']->changeDatabaseConnection('db2');
333  $GLOBALS['SQ_SYSTEM']->doTransaction('BEGIN');
334  try {
335  //If this version is already in version history table, but the to_date is set, report it.
336  //This case is not likely to happen, just to make sure there is no duplicate version is set
337  $db_version_info = $file_versioning->_getFileInfoAtVersion($fileid, $version);
338  if (!empty($db_version_info)) {
339  echo "ERROR: THIS FILE (FILEID = $fileid, VERSION = $version) IS NO LONGER USED SINCE {$db_version_info['to_date']}\n";
340  return FALSE;
341  }
342 
343  $now = time();
344  $date = ts_iso8601($now);
345  /*if (MatrixDAL::getDbType() == 'oci') {
346  $date = db_extras_todate(MatrixDAL::getDbType(), $date);
347  }*/
348 
349  $sql = 'UPDATE sq_file_vers_history
350  SET to_date = :to_date
351  WHERE fileid = :fileid
352  AND to_date IS NULL';
353 
354 
355  try {
356  if (MatrixDAL::getDbType() == 'oci') {
357  $sql = str_replace(':to_date', db_extras_todate(MatrixDAL::getDbType(), ':to_date', FALSE), $sql);
358  }
359  $query = MatrixDAL::preparePdoQuery($sql);
360  MatrixDAL::bindValueToPdo($query, 'fileid', $fileid);
361  MatrixDAL::bindValueToPdo($query, 'to_date', $date);
362  MatrixDAL::execPdoQuery($query);
363  } catch (Exception $e) {
364  throw new Exception('Unable to update version history for file ID '.$fileid.' due to database error: '.$e->getMessage());
365  }
366 
367  if (file_exists($real_file)) {
368  $file_size = filesize($real_file);
369  $md5 = md5_file($real_file);
370  $sha1 = sha1_file($real_file);
371  $removal = '0';
372  } else {
373  $file_size = 0;
374  $md5 = '';
375  $sha1 = '';
376  $removal = '1';
377  }
378 
379  $sql = 'INSERT INTO sq_file_vers_history
380  (fileid, version, from_date, to_date, file_size, md5, sha1, removal, extra_info)
381  VALUES
382  (:fileid, :version, :from_date, :to_date, :file_size, :md5, :sha1, :removal, :extra_info)';
383 
384  try {
385  if (MatrixDAL::getDbType() == 'oci') {
386  $sql = str_replace(':from_date', db_extras_todate(MatrixDAL::getDbType(), ':from_date', FALSE), $sql);
387  }
388  $query = MatrixDAL::preparePdoQuery($sql);
389  MatrixDAL::bindValueToPdo($query, 'fileid', $fileid);
390  MatrixDAL::bindValueToPdo($query, 'version', $version);
391  MatrixDAL::bindValueToPdo($query, 'from_date', $date);
392  MatrixDAL::bindValueToPdo($query, 'to_date', NULL);
393  MatrixDAL::bindValueToPdo($query, 'file_size', $file_size);
394  MatrixDAL::bindValueToPdo($query, 'md5', $md5);
395  MatrixDAL::bindValueToPdo($query, 'sha1', $sha1);
396  MatrixDAL::bindValueToPdo($query, 'removal', $removal);
397  MatrixDAL::bindValueToPdo($query, 'extra_info', $extra_info);
398  MatrixDAL::execPdoQuery($query);
399  } catch (Exception $e) {
400  throw new Exception('Unable to insert version history for file ID '.$fileid.' due to database error: '.$e->getMessage());
401  }
402 
403  $GLOBALS['SQ_SYSTEM']->doTransaction('COMMIT');
404  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
405  } catch (Exception $e) {
406  echo "ERROR: ".$e->getMessage()."\n";
407  $GLOBALS['SQ_SYSTEM']->doTransaction('ROLLBACK');
408  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
409  return FALSE;
410  }
411 
412  return TRUE;
413 
414 }//end _updateFileVersionHistory()
415 
416 
425 function _insertFileVersFileInfo($file_versioning, $fileid, $rep_file) {
426  $sql = 'SELECT COUNT(*)
427  FROM sq_file_vers_file
428  WHERE fileid = :fileid';
429  $fileid_count = 0;
430  try {
431  $query = MatrixDAL::preparePdoQuery($sql);
432  MatrixDAL::bindValueToPdo($query, 'fileid', $fileid);
433  $fileid_count = MatrixDAL::executePdoOne($query);
434  } catch (Exception $e) {
435  echo "ERROR: ".$e->getMessage()."\n";
436  return;
437  }
438 
439  //the record info already exists, return without doing anything
440  if ($fileid_count != 0) {
441  return;
442  }
443 
444  $GLOBALS['SQ_SYSTEM']->changeDatabaseConnection('db2');
445  $GLOBALS['SQ_SYSTEM']->doTransaction('BEGIN');
446 
447  $sql = 'INSERT INTO sq_file_vers_file (fileid, path, filename)
448  VALUES (:fileid, :path, :filename)';
449 
450  try {
451  $query = MatrixDAL::preparePdoQuery($sql);
452  MatrixDAL::bindValueToPdo($query, 'fileid', $fileid);
453  MatrixDAL::bindValueToPdo($query, 'path', dirname($rep_file));
454  MatrixDAL::bindValueToPdo($query, 'filename', basename($rep_file));
455  MatrixDAL::execPdoQuery($query);
456  $GLOBALS['SQ_SYSTEM']->doTransaction('COMMIT');
457  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
458  } catch (Exception $e) {
459  echo "ERROR: ".$e->getMessage()."\n";
460  $GLOBALS['SQ_SYSTEM']->doTransaction('ROLLBACK');
461  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
462  }
463 
464 }//end _insertFileVersFileInfo()
465 
466 
471 function print_usage() {
472  echo "\n\n------------------------------------------------------------------------------------------------\n\n";
473  echo "This script is used to test and recover file (and its decendants) assets.\n\n";
474  echo "Usage: php ".basename(__FILE__)." SYSTEM_ROOT [test|recover] [TREE_ID]\n\n";
475  echo "\tSYSTEM_ROOT: The root directory of Matrix system.\n";
476  echo "\tRunning direction: test (default) - show which assets have their file version integrity broken | recover - fix the broken integrity showed by test option\n";
477  echo "\tTREE_ID: The asset id of the root of the asset tree. If not specified, all file assets in the system will be used.\n";
478  echo "\nNOTES: YOU SHOULD BACKUP YOUR SYSTEM BEFORE USING THE recover (in lowercase) OPTION OF THIS SCRIPT\n\n";
479 
480 }//end print_usage()
481 
482 ?>