Squiz Matrix  4.12.2
 All Data Structures Namespaces Functions Variables Pages
file_versioning.inc
1 <?php
18 define('FUDGE_FV_OK', 1);
19 define('FUDGE_FV_ERROR', 2);
20 define('FUDGE_FV_NOT_CHECKED_OUT', 4);
21 define('FUDGE_FV_MODIFIED', 8);
22 define('FUDGE_FV_NOT_MODIFIED', 16);
23 define('FUDGE_FV_CURRENT_VERSION', 32);
24 define('FUDGE_FV_OLD_VERSION', 64);
25 
26 
39 {
40 
45  var $_dir;
46 
47 
51  var $num_lock_attempts = 4;
52 
53 
60  function File_Versioning($dir)
61  {
62  // if errors have occured, we can't be guaranteed that the system is integral, die
63  if (file_exists($dir.'/.FFV/error.log')) {
64  trigger_localised_error('FVER0024', E_USER_ERROR, $dir);
65  }
66  $this->_dir = $dir;
67 
68  }//end constructor
69 
70 
79  function initRepository()
80  {
81  require_once SQ_FUDGE_PATH.'/general/file_system.inc';
82  require_once SQ_LIB_PATH.'/db_install/db_install.inc';
83 
84  $GLOBALS['SQ_SYSTEM']->changeDatabaseConnection('db2');
85  $GLOBALS['SQ_SYSTEM']->doTransaction('BEGIN');
86 
87  $dir = SQ_DATA_PATH.'/file_repository';
88  $this->_dir = $dir;
89  if (!create_directory($dir)) {
90  trigger_localised_error('FVER0022', E_USER_WARNING, $dir);
91  $GLOBALS['SQ_SYSTEM']->doTransaction('ROLLBACK');
92  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
93  return false;
94  }//end if
95 
96  $GLOBALS['SQ_SYSTEM']->doTransaction('COMMIT');
97  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
98  return true;
99 
100  }//end initRepository()
101 
102 
112  function _iso8601Ts($iso8601)
113  {
114  return iso8601_ts($iso8601);
115 
116  }//end _iso8601Ts()
117 
118 
128  function _tsIso8601($timestamp)
129  {
130  return ts_iso8601($timestamp);
131 
132  }//end _tsIso8601()
133 
134 
145  function _getFileInfoAtVersion($fileid, $version=null)
146  {
147  $db = MatrixDAL::getDb();
148 
149  $sql = 'SELECT f.fileid, f.path, f.filename,
150  h.version, h.from_date, h.to_date, h.file_size, h.md5, h.sha1, h.removal, h.extra_info
151  FROM sq_file_vers_file f INNER JOIN sq_file_vers_history h ON f.fileid = h.fileid
152  WHERE f.fileid = :fileid
153  AND '.((is_null($version)) ? 'to_date IS NULL' : 'version = :version');
154 
155  try {
156  $query = MatrixDAL::preparePdoQuery($sql);
157  MatrixDAL::bindValueToPdo($query, 'fileid', $fileid);
158  if (!is_null($version)) {
159  MatrixDAL::bindValueToPdo($query, 'version', $version);
160  }
161  $result = MatrixDAL::executePdoAssoc($query);
162  if (!empty($result)) {
163  $result = $result[0];
164  }
165  } catch (Exception $e) {
166  throw new Exception('Unable to get versioning information for file ID '.$fileid.' at version '.(is_null($version) ? '(current)' : $version).' due to database error: '.$e->getMessage());
167  }
168 
169  if (!empty($result)) {
170  $result['fileid'] = (int) $result['fileid'];
171  $result['version'] = (int) $result['version'];
172  $result['from_date'] = iso8601_ts($result['from_date']);
173  if (!empty($result['to_date'])) {
174  $result['to_date'] = iso8601_ts($result['to_date']);
175  }
176  $result['file_size'] = (int) $result['file_size'];
177  }
178 
179  return $result;
180 
181  }//end _getFileInfoAtVersion()
182 
183 
194  function _getFileInfoFromRealFile($real_file)
195  {
196  $ffv_dir = dirname($real_file).'/.FFV';
197  if (!is_dir($ffv_dir)) return FUDGE_FV_NOT_CHECKED_OUT;
198 
199  $ffv_file = $ffv_dir.'/'.basename($real_file);
200  if (!is_file($ffv_file)) {
201  return FUDGE_FV_NOT_CHECKED_OUT;
202  }
203 
204  $ffv = parse_ini_file($ffv_file);
205  if (!is_array($ffv)) {
206  trigger_localised_error('FVER0025', E_USER_WARNING);
207  return FUDGE_FV_ERROR;
208  }
209 
210  if ($this->_dir != $ffv['dir']) {
211  trigger_localised_error('FVER0008', E_USER_WARNING);
212  return FUDGE_FV_ERROR;
213  }
214 
215  $info = $this->_getFileInfoAtVersion($ffv['fileid'], (int) $ffv['version']);
216  if (empty($info)) {
217  trigger_localised_error('FVER0028', E_USER_WARNING, $ffv['version'], $ffv['fileid']);
218  return FUDGE_FV_ERROR;
219  }
220 
221  $rep_file = $this->_dir.'/'.$info['path'].'/'.$info['filename'];
222 
223  if (empty($info['removal'])) {
224  if (!$this->_validateFileVersion($rep_file, $info)) {
225  trigger_localised_error('FVER0027', E_USER_NOTICE, $info['path'], $info['filename'], $info['version']);
226  return FUDGE_FV_ERROR;
227  }
228  }
229 
230  return $info;
231 
232  }//end _getFileInfoFromRealFile()
233 
234 
247  function _getFileInfoFromPath($file_path, $version=null)
248  {
249  $db = MatrixDAL::getDb();
250  $sql = 'SELECT fileid
251  FROM sq_file_vers_file
252  WHERE path = :path
253  AND filename = :filename';
254 
255  try {
256  $query = MatrixDAL::preparePdoQuery($sql);
257  MatrixDAL::bindValueToPdo($query, 'path', dirname($file_path));
258  MatrixDAL::bindValueToPdo($query, 'filename', basename($file_path));
259  $fileid = MatrixDAL::executePdoOne($query);
260  } catch (Exception $e) {
261  throw new Exception('Unable to get file versioning information for file "'.hide_system_root($file_path).'" due to database error: '.$e->getMessage());
262  }
263 
264  if (empty($fileid)) return Array();
265  return $this->_getFileInfoAtVersion($fileid, $version);
266 
267  }//end _getFileInfoFromPath()
268 
269 
278  function _lockFile($fileid)
279  {
280  $GLOBALS['SQ_SYSTEM']->changeDatabaseConnection('db2');
281  $GLOBALS['SQ_SYSTEM']->doTransaction('BEGIN');
282  $db = MatrixDAL::getDb();
283 
284  $select = 'SELECT COUNT(*)
285  FROM sq_file_vers_lock
286  WHERE fileid = :fileid';
287  $insert = 'INSERT INTO sq_file_vers_lock (fileid) VALUES (:fileid)';
288 
289  try {
290  $select_query = MatrixDAL::preparePdoQuery($select);
291  MatrixDAL::bindValueToPdo($select_query, 'fileid', $fileid);
292  $insert_query = MatrixDAL::preparePdoQuery($insert);
293  MatrixDAL::bindValueToPdo($insert_query, 'fileid', $fileid);
294  } catch (Exception $e) {
295  throw new Exception('Unable to prepare file versioning locking queries for file ID "'.$fileid.'" due to database error: '.$e->getMessage());
296  }
297 
298  // we will attempt a maxmimum of 4 times
299 
300  for ($i = 0; $i < $this->num_lock_attempts; $i++) {
301 
302  try {
303  $result = MatrixDAL::executePdoOne($select_query);
304  } catch (Exception $e) {
305  throw new Exception('Unable to select file versioning locking information for file ID "'.$fileid.'" due to database error: '.$e->getMessage());
306  }
307 
308  if (empty($result)) {
309 
310  try {
311  MatrixDAL::execPdoQuery($insert_query);
312  // If it makes it this far, then the INSERT worked (ie.
313  // no duplicate key)
314  $GLOBALS['SQ_SYSTEM']->doTransaction('COMMIT');
315  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
316  return true;
317  } catch (Exception $e) {
318  // There was a problem - but don't throw an error, instead
319  // re-iterate ourselves
320  }
321  }
322 
323  // wait one second and try again (except for last iteraction)
324  if ($i < $this->num_lock_attempts - 1) sleep(1);
325 
326  }//end for
327 
328  $GLOBALS['SQ_SYSTEM']->doTransaction('ROLLBACK');
329  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
330 
331  return false;
332 
333  }//end _lockFile()
334 
335 
344  function _releaseFile($fileid)
345  {
346  $GLOBALS['SQ_SYSTEM']->changeDatabaseConnection('db2');
347  $GLOBALS['SQ_SYSTEM']->doTransaction('BEGIN');
348  $db = MatrixDAL::getDb();
349 
350  $sql = 'DELETE FROM sq_file_vers_lock
351  WHERE fileid = :fileid';
352  try {
353  $query = MatrixDAL::preparePdoQuery($sql);
354  MatrixDAL::bindValueToPdo($query, 'fileid', $fileid);
355  MatrixDAL::execPdoQuery($query);
356  } catch (Exception $e) {
357  throw new Exception('Unable to unlock file ID "'.$fileid.'" due to database error: '.$e->getMessage());
358  }
359 
360  $GLOBALS['SQ_SYSTEM']->doTransaction('COMMIT');
361  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
362 
363  return true;
364 
365  }//end _releaseFile()
366 
367 
378  function add($rep_path, $real_file, $extra_info='')
379  {
380  $db = MatrixDAL::getDb();
381  // make sure we can get to the file
382  if (!is_file($real_file) || !is_readable($real_file)) {
383  trigger_localised_error('FVER0003', E_USER_NOTICE, $real_file);
384  return false;
385  }
386 
387  $filename = basename($real_file);
388 
389 
390  // make sure there isn't already a .FFV entry for this file (or that it hasn't been removed)
391  $existing_ffv_info = $this->_getFileInfoFromRealFile($real_file);
392  if ($existing_ffv_info != FUDGE_FV_NOT_CHECKED_OUT && empty($existing_ffv_info['removal'])) {
393  trigger_localised_error('FVER0017', E_USER_NOTICE, $real_file);
394  return false;
395  }
396 
397 
398  $rep_path = preg_replace('/\/+$/', '', $rep_path);
399  $rep_file = $rep_path.'/'.basename($real_file);
400 
401  // Make sure that the name is OK
402  $bits = explode('/', $rep_file);
403  foreach ($bits as $bit) {
404  // convert to upper for case insensitiveness
405  if (strtoupper($bit) == '.FFV') {
406  trigger_localised_error('FVER0015', E_USER_NOTICE, $real_file, $rep_path);
407  return false;
408  }
409 
410  if (preg_match('/,FFV[0-9]+$/i', $bit)) {
411  trigger_localised_error('FVER0016', E_USER_NOTICE, $real_file, $rep_path);
412  return false;
413  }
414 
415  }//end foreach
416 
417  $current_info = $this->_getFileInfoFromPath($rep_file);
418  // if there is a current file and it hasn't been "removed", you can't add a new file
419  if (!empty($current_info) && empty($current_info['removal'])) {
420  trigger_localised_error('FVER0004', E_USER_WARNING, $rep_file);
421  return false;
422 
423  // if there is no current file, check that this new file's path isn't already is use
424  } else if (empty($current_info)) {
425  // Basically this query finds if there are any parents or children that are currently using this
426  // any parts of this new path as either a directory or file
427 
428  // so much for SQL standards...
429  $concat_1 = '(path || \'/\' || filename)';
430  $concat_2 = '(path || \'/\' || filename || \'%\')';
431  $sql = 'SELECT COUNT(*)
432  FROM sq_file_vers_file
433  WHERE '.$concat_1.' LIKE :path_wildcard
434  OR :path LIKE '.$concat_2;
435 
436  try {
437  $query = MatrixDAL::preparePdoQuery($sql);
438  MatrixDAL::bindValueToPdo($query, 'path_wildcard', $rep_path.'/'.basename($real_file).'%');
439  MatrixDAL::bindValueToPdo($query, 'path', $rep_path.'/'.basename($real_file));
440  $fileid = MatrixDAL::executePdoOne($query);
441  } catch (Exception $e) {
442  throw new Exception('Unable to get file versioning information for file "'.hide_system_root($file_path).'" due to database error: '.$e->getMessage());
443  }
444 
445  if (!empty($result)) {
446  trigger_localised_error('FVER0014', E_USER_NOTICE, $real_file, $rep_path);
447  return false;
448  }
449 
450  }//end if
451 
452  $GLOBALS['SQ_SYSTEM']->changeDatabaseConnection('db2');
453  $GLOBALS['SQ_SYSTEM']->doTransaction('BEGIN');
454  $db = MatrixDAL::getDb();
455 
456  // if there is no current file we need to insert the new DB record
457  if (empty($current_info)) {
458  $fileid = MatrixDAL::executeOne('core', 'seqNextVal', Array('seqName' => 'sq_file_vers_file_seq'));
459  $fileid = (int) $fileid;
460 
461  $sql = 'INSERT INTO sq_file_vers_file (fileid, path, filename)
462  VALUES (:fileid, :path, :filename)';
463 
464  try {
465  $query = MatrixDAL::preparePdoQuery($sql);
466  MatrixDAL::bindValueToPdo($query, 'fileid', $fileid);
467  MatrixDAL::bindValueToPdo($query, 'path', $rep_path);
468  MatrixDAL::bindValueToPdo($query, 'filename', $filename);
469  MatrixDAL::execPdoQuery($query);
470  } catch (Exception $e) {
471  throw new Exception('Unable to insert new file versioning information for file "'.$filename.'" due to database error: '.$e->getMessage());
472  }
473 
474  // otherwise use the old file's id
475  } else {
476  $fileid = (int) $current_info['fileid'];
477 
478  }//end if
479 
480  // get the lock now that we have inserted the details
481  if (!$this->_lockFile($fileid)) {
482  trigger_localised_error('FVER0018', E_USER_NOTICE);
483  $GLOBALS['SQ_SYSTEM']->doTransaction('ROLLBACK');
484  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
485  return false;
486  }//end if
487 
488  // update the repository file
489  $version = $this->_updateFile($fileid, $rep_path, $real_file, $extra_info);
490  if (empty($version)) {
491  $this->_releaseFile($fileid);
492  $GLOBALS['SQ_SYSTEM']->doTransaction('ROLLBACK');
493  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
494  return false;
495  }
496 
497  $this->_releaseFile($fileid);
498 
499  $GLOBALS['SQ_SYSTEM']->doTransaction('COMMIT');
500  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
501 
502  return true;
503 
504  }//end add()
505 
506 
519  function _updateFile($fileid, $rep_path, $real_file, $extra_info='')
520  {
521  $GLOBALS['SQ_SYSTEM']->changeDatabaseConnection('db2');
522  $GLOBALS['SQ_SYSTEM']->doTransaction('BEGIN');
523  try {
524  $db = MatrixDAL::getDb();
525 
526  $sql = 'SELECT COALESCE(MAX(version), 0) + 1
527  FROM sq_file_vers_history
528  WHERE fileid = :fileid';
529 
530  try {
531  $query = MatrixDAL::preparePdoQuery($sql);
532  MatrixDAL::bindValueToPdo($query, 'fileid', $fileid);
533  $version = MatrixDAL::executePdoOne($query);
534  } catch (Exception $e) {
535  throw new Exception('Unable to get version information for file ID '.$fileid.' due to database error: '.$e->getMessage());
536  }
537 
538  $now = time();
539  $date = ts_iso8601($now);
540  /*if (MatrixDAL::getDbType() == 'oci') {
541  $date = db_extras_todate(MatrixDAL::getDbType(), $date);
542  }*/
543 
544  $sql = 'UPDATE sq_file_vers_history
545  SET to_date = :to_date
546  WHERE fileid = :fileid
547  AND to_date IS NULL';
548 
549 
550  try {
551  if (MatrixDAL::getDbType() == 'oci') {
552  $sql = str_replace(':to_date', db_extras_todate(MatrixDAL::getDbType(), ':to_date', FALSE), $sql);
553  }
554  $query = MatrixDAL::preparePdoQuery($sql);
555  MatrixDAL::bindValueToPdo($query, 'fileid', $fileid);
556  MatrixDAL::bindValueToPdo($query, 'to_date', $date);
557  MatrixDAL::execPdoQuery($query);
558  } catch (Exception $e) {
559  throw new Exception('Unable to update version history for file ID '.$fileid.' due to database error: '.$e->getMessage());
560  }
561 
562  $version = (int) $version;
563  if (file_exists($real_file)) {
564  $file_size = filesize($real_file);
565  $md5 = md5_file($real_file);
566  $sha1 = sha1_file($real_file);
567  $removal = '0';
568  } else {
569  $file_size = 0;
570  $md5 = '';
571  $sha1 = '';
572  $removal = '1';
573  }
574 
575  $sql = 'INSERT INTO sq_file_vers_history
576  (fileid, version, from_date, to_date, file_size, md5, sha1, removal, extra_info)
577  VALUES
578  (:fileid, :version, :from_date, :to_date, :file_size, :md5, :sha1, :removal, :extra_info)';
579 
580  try {
581  if (MatrixDAL::getDbType() == 'oci') {
582  $sql = str_replace(':from_date', db_extras_todate(MatrixDAL::getDbType(), ':from_date', FALSE), $sql);
583  }
584  $query = MatrixDAL::preparePdoQuery($sql);
585  MatrixDAL::bindValueToPdo($query, 'fileid', $fileid);
586  MatrixDAL::bindValueToPdo($query, 'version', $version);
587  MatrixDAL::bindValueToPdo($query, 'from_date', $date);
588  MatrixDAL::bindValueToPdo($query, 'to_date', NULL);
589  MatrixDAL::bindValueToPdo($query, 'file_size', $file_size);
590  MatrixDAL::bindValueToPdo($query, 'md5', $md5);
591  MatrixDAL::bindValueToPdo($query, 'sha1', $sha1);
592  MatrixDAL::bindValueToPdo($query, 'removal', $removal);
593  MatrixDAL::bindValueToPdo($query, 'extra_info', $extra_info);
594  MatrixDAL::execPdoQuery($query);
595  } catch (Exception $e) {
596  throw new Exception('Unable to insert version history for file ID '.$fileid.' due to database error: '.$e->getMessage());
597  }
598 
599  // if we aren't removing, copy the file into the repository
600  if (file_exists($real_file)) {
601 
602  require_once SQ_FUDGE_PATH.'/general/file_system.inc';
603  $rep_dir = $this->_dir.'/'.$rep_path;
604  if (!is_dir($rep_dir) && !create_directory($rep_dir)) {
605  //trigger_localised_error('FVER0020', E_USER_NOTICE, $rep_dir);
606  throw new Exception('There is no repository folder for file #'.$fileid);
607  }//end if
608 
609  $rep_file = $rep_dir.'/'.basename($real_file).',ffv'.$version;
610  if (!copy($real_file, $rep_file)) {
611  throw new Exception('Can not copy file to repository for file #'.$fileid);
612  }
613 
614  }//endif
615 
616  $GLOBALS['SQ_SYSTEM']->doTransaction('COMMIT');
617  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
618  } catch (Exception $e) {
619  $GLOBALS['SQ_SYSTEM']->doTransaction('ROLLBACK');
620  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
621  $version = 0;
622  }
623 
624  return $version;
625 
626  }//end _updateFile()
627 
628 
640  function _createFFVFile($real_file, $fileid, $version)
641  {
642  require_once SQ_FUDGE_PATH.'/general/file_system.inc';
643 
644  $ffv_dir = dirname($real_file).'/.FFV';
645  if (is_dir($ffv_dir) || create_directory($ffv_dir)) {
646 
647  $ffv_lines = Array(
648  'dir="'.$this->_dir.'"',
649  'fileid="'.$fileid.'"',
650  'version="'.$version.'"',
651  );
652 
653  $ffv_file = $ffv_dir.'/'.basename($real_file);
654  // move any existing ffv file to a backup tmp file
655  if (!file_exists($ffv_file) || rename($ffv_file, $ffv_file.'.bup')) {
656  if (string_to_file(implode("\n", $ffv_lines)."\n", $ffv_file)) {
657  if (file_exists($ffv_file.'.bup')) {
658  unlink($ffv_file.'.bup');
659  }
660  return true;
661  }
662 
663  // put the file back
664  rename($ffv_file.'.bup', $ffv_file);
665  }//end if ffv_file exists
666  } else {
667  trigger_localised_error('FVER0020', E_USER_NOTICE, $ffv_dir);
668 
669  }//end if create ffv_dir
670 
671  return false;
672 
673  }//end _createFFVFile()
674 
675 
690  function _checkOutCheck($rep_file, $version=null, $date=null)
691  {
692  $db = MatrixDAL::getDb();
693 
694  $info = $this->_getFileInfoFromPath($rep_file);
695 
696  if (empty($info)) {
697  trigger_localised_error('FVER0007', E_USER_WARNING,$rep_file);
698  return false;
699  }
700 
701  if (!$this->_lockFile($info['fileid'])) {
702  trigger_localised_error('FVER0018', E_USER_NOTICE);
703  return false;
704  }
705 
706  // get the version to checkout if there isn't one set
707  if (is_null($version)) {
708  // if no date is specified then, we need to get the latest and greatest
709  if (is_null($date)) {
710  // NOTE: this will not return a version if the file has been removed
711  // this is DELIBERATE as it forces people to specify a version number or
712  // date if they want an expired version
713  $sql = 'SELECT version, removal, md5, sha1
714  FROM sq_file_vers_history
715  WHERE fileid = :fileid
716  AND to_date IS NULL';
717  $nice_date = ts_iso8601(time());
718  } else {
719  $nice_date = ts_iso8601($date);
720  $date = ts_iso8601($date);
721 
722  require_once SQ_FUDGE_PATH.'/db_extras/db_extras.inc';
723  $sql = 'SELECT version, removal, md5, sha1
724  FROM sq_file_vers_history
725  WHERE fileid = :fileid
726  AND from_date <= :from_date_1
727  AND (to_date IS NULL OR to_date > :from_date_2)
728  ORDER BY version DESC';
729  }//end if
730 
731 
732  try {
733  if (MatrixDAL::getDbType() == 'oci') {
734  $sql = str_replace(':from_date_1', db_extras_todate(MatrixDAL::getDbType(), ':from_date_1', FALSE), $sql);
735  $sql = str_replace(':from_date_2', db_extras_todate(MatrixDAL::getDbType(), ':from_date_2', FALSE), $sql);
736  }
737 
738  $query = MatrixDAL::preparePdoQuery($sql);
739  MatrixDAL::bindValueToPdo($query, 'fileid', $info['fileid']);
740  if (!is_null($date)) {
741  MatrixDAL::bindValueToPdo($query, 'from_date_1', $date);
742  MatrixDAL::bindValueToPdo($query, 'from_date_2', $date);
743  }
744 
745  $result = MatrixDAL::executePdoAll($query);
746  if (!empty($result)) {
747  $ver_info = $result[0];
748  } else {
749  $ver_info = Array();
750  }
751  unset($result);
752  } catch (Exception $e) {
753  throw new Exception('Unable to get versioning information for file ID '.$fileid.' at version '.(is_null($version) ? '(current)' : $version).' due to database error: '.$e->getMessage());
754  }
755 
756  if (empty($ver_info)) {
757  trigger_localised_error('FVER0006', E_USER_NOTICE, $rep_file, $nice_date);
758  $this->_releaseFile($info['fileid']);
759  return false;
760  }
761 
762  // else make sure the set version is valid
763  } else {
764  $sql = 'SELECT version, removal, md5, sha1
765  FROM sq_file_vers_history
766  WHERE fileid = :fileid
767  AND version = :version';
768 
769  try {
770  $query = MatrixDAL::preparePdoQuery($sql);
771  MatrixDAL::bindValueToPdo($query, 'fileid', $info['fileid']);
772  MatrixDAL::bindValueToPdo($query, 'version', $version);
773 
774  $result = MatrixDAL::executePdoAll($query);
775  if (!empty($result)) {
776  $ver_info = $result[0];
777  } else {
778  $ver_info = Array();
779  }
780  unset($result);
781  } catch (Exception $e) {
782  throw new Exception('Unable to get versioning information for file ID '.$fileid.' at version '.(is_null($version) ? '(current)' : $version).' due to database error: '.$e->getMessage());
783  }
784 
785  if (empty($ver_info)) {
786  trigger_localised_error('FVER0005', E_USER_NOTICE, $rep_file);
787  $this->_releaseFile($info['fileid']);
788  return false;
789  }
790 
791  }//end if
792 
793 
794  $info['version'] = $ver_info['version'];
795  $info['removal'] = $ver_info['removal'];
796  $info['md5'] = $ver_info['md5'];
797  $info['sha1'] = $ver_info['sha1'];
798  $info['source_file'] = $this->_dir.'/'.$rep_file.',ffv'.$ver_info['version'];
799 
800  $this->_releaseFile($info['fileid']);
801 
802  if ($this->_validateFileVersion($rep_file, $info)) {
803  return $info;
804  } else {
805  return false;
806  }
807 
808 
809  }//end _checkOutCheck()
810 
811 
828  function _validateFileVersion($rep_file, $info)
829  {
830  // work out if its a relative or absolute path, and whether or not
831  // the prefix has already been added
832  $dir_prefix = $this->_dir.'/';
833  if (substr($rep_file, 0, 1) != '/' && substr($rep_file, 0, strlen($dir_prefix)) != $dir_prefix) {
834  $rep_file = $dir_prefix.$rep_file;
835  }
836 
837  $existing_version = $info['version'];
838  $exists = file_exists($rep_file.',ffv'.$existing_version);
839 
840  // our file is real, so lets do some hash checks
841  if ($exists) {
842  if ($info['md5'] && $info['sha1'] && trim($info['md5']) !== '' && trim($info['sha1']) !== '') {
843  $current = $rep_file.',ffv'.$existing_version;
844 
845  // verify its md5 and sha1 values, throw a warning to the user if something is amiss
846  if (md5_file($current) != $info['md5'] || sha1_file($current) != $info['sha1']) {
847  $exists = false;
848  }
849  }
850  }
851 
852  // file either had no integrity to check, or passed, eitherway its a success
853  if ($exists) return true;
854 
855  $destination = $rep_file.',ffv'.$info['version'];
856 
857  // attempt recovery from private
858  if (copy(SQ_DATA_PATH.'/private/'.$info['path'].'/'.$info['filename'], $destination)) {
859  if ($this->_updateFileVersion($destination, $info['fileid'], $info['version'])) {
860  trigger_localised_error('FVER0029', E_USER_WARNING, $info['version'], translate('data').' '.translate('private'));
861  return true;
862  }
863  }
864 
865  // attempt recovery from public
866  if (@copy(SQ_DATA_PATH.'/public/'.$info['path'].'/'.$info['filename'], $destination)) {
867  if ($this->_updateFileVersion($destination, $info['fileid'], $info['version'])) {
868  trigger_localised_error('FVER0029', E_USER_WARNING, $info['version'], translate('data').' '.translate('public'));
869  return true;
870  }
871  }
872 
873  // if the file is missing or damaged, loop through previous versions
874  // until we find a version thats valid (passes its md5 and sha1 checks)
875  while (!$exists && $existing_version > 0) {
876  $existing_version--;
877  $current = $rep_file.',ffv'.$existing_version;
878 
879  //assert file at version exists
880  $exists = file_exists($current);
881 
882  //file exists, ensure integrity
883  if ($exists) {
884  $ver_info = $this->_getFileInfoAtVersion($info['fileid'], $existing_version);
885 
886  // check md5 and sha1 values if we have them
887  if (isset($ver_info['md5']) && isset($ver_info['sha1'])) {
888  $exists = md5_file($current) == $ver_info['md5'] && sha1_file($current) == $ver_info['sha1'];
889  }
890  }
891  }
892 
893  // attempt recovery from a previous version, if we found a valid one
894  if ($exists && @copy($current, $destination)) {
895  if ($this->_updateFileVersion($destination, $info['fileid'], $info['version'])) {
896  trigger_localised_error('FVER0029', E_USER_WARNING, $info['version'], 'ffv'.$existing_version);
897  return true;
898  }
899  } else {
900  // we could do nothing to save it, manual user recovery time
901  trigger_localised_error('FVER0030', E_USER_WARNING);
902  return false;
903  }
904 
905 
906  }//end _validateFileVersion()
907 
908 
919  function _updateFileVersion($real_file, $fileid, $version)
920  {
921  // regenerate integrity info for this version
922  $filesize = filesize($real_file);
923  $md5 = md5_file($real_file);
924  $sha1 = sha1_file($real_file);
925 
926  $GLOBALS['SQ_SYSTEM']->changeDatabaseConnection('db2');
927  $GLOBALS['SQ_SYSTEM']->doTransaction('BEGIN');
928  $db = MatrixDAL::getDb();
929  try {
930  $bind_vars = Array (
931  'md5' => $md5,
932  'sha1' => $sha1,
933  'file_size' => $filesize,
934  'fileid' => $fileid,
935  'version' => $version,
936  );
937  $result = MatrixDAL::executeQuery('core', 'updateFileVersHistory', $bind_vars);
938  $GLOBALS['SQ_SYSTEM']->doTransaction('COMMIT');
939  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
940  return TRUE;
941  } catch (Exception $e) {
942  throw new Exception('Unable to update the filesize and hash information for fileid: '.$fileid.' with version: '.$version.' due to database error: '.$e->getMessage());
943  $GLOBALS['SQ_SYSTEM']->doTransaction('ROLLBACK');
944  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
945  return FALSE;
946  }
947  }//end _updateFileVersion()
948 
949 
961  function checkOut($rep_file, $dest_dir, $version=null, $date=null)
962  {
963  // make sure we can save the file
964  if (!is_dir($dest_dir) || !is_writable($dest_dir)) {
965  trigger_localised_error('FVER0001', E_USER_NOTICE, $dest_dir);
966  return false;
967  }
968 
969  $info = $this->_checkOutCheck($rep_file, $version, $date);
970  if (empty($info) || $info == FUDGE_FV_NOT_CHECKED_OUT) {
971  return false;
972  }
973 
974  $dest_file = $dest_dir.'/'.basename($rep_file);
975 
976  // create the FFV file entry
977  if (!$this->_createFFVFile($dest_file, $info['fileid'], $info['version'])) {
978  return false;
979  }//end if
980 
981  // if this version is a removal then we are need to remove any existing files
982  if ($info['removal']) {
983  if (file_exists($dest_file) && !unlink($dest_file)) {
984  // remove the FFV file
985  unlink($dest_dir.'/.FFV/'.basename($rep_file));
986  return false;
987  }//end if
988 
989  // else copy the source to the destination
990  } else {
991  if (!copy($info['source_file'], $dest_file)) {
992  // remove the FFV file
993  unlink($dest_dir.'/.FFV/'.basename($rep_file));
994  return false;
995  }//end if
996 
997  }//end if
998 
999  return true;
1000 
1001  }//end checkOut()
1002 
1003 
1015  function output($rep_file, $version=null, $date=null)
1016  {
1017  $info = $this->_checkOutCheck($rep_file, $version, $date);
1018  if (empty($info)) return false;
1019 
1020  if ($info['removal']) {
1021  trigger_localised_error('FVER0013', E_USER_NOTICE, $rep_file);
1022  return false;
1023  }
1024 
1025  readfile($info['source_file']);
1026  @ob_flush();
1027  return true;
1028 
1029  }//end output()
1030 
1031 
1040  function upToDate($real_file)
1041  {
1042  $info = $this->_getFileInfoFromRealFile($real_file);
1043  if (!is_array($info)) {
1044  if ($info == FUDGE_FV_ERROR) {
1045  trigger_localised_error('FVER0002', E_USER_WARNING, __FUNCTION__);
1046  }
1047  return $info;
1048  }
1049 
1050  if (!$this->_lockFile($info['fileid'])) {
1051  trigger_localised_error('FVER0018', E_USER_NOTICE);
1052  return FUDGE_FV_ERROR;
1053  }
1054 
1055  $curr_ver_info = $this->_getFileInfoAtVersion($info['fileid']);
1056  if (empty($curr_ver_info)) {
1057  trigger_localised_error('FVER0026', E_USER_NOTICE, $real_file);
1058  return FUDGE_FV_ERROR;
1059  }
1060 
1061  $file_ver_info = $this->_getFileInfoAtVersion($info['fileid'], $info['version']);
1062  if (empty($file_ver_info)) {
1063  trigger_localised_error('FVER0009', E_USER_NOTICE, $info['version']);
1064  return FUDGE_FV_ERROR;
1065  }
1066 
1067  $ret_val = ($curr_ver_info['version'] > $file_ver_info['version']) ? FUDGE_FV_OLD_VERSION : FUDGE_FV_CURRENT_VERSION;
1068 
1069  // check to see if the file has been modified
1070  $ret_val |= ($this->_fileModified($file_ver_info, $real_file)) ? FUDGE_FV_MODIFIED : FUDGE_FV_NOT_MODIFIED;
1071  $this->_releaseFile($info['fileid']);
1072  return $ret_val;
1073 
1074  }//end upToDate()
1075 
1076 
1086  function _fileModified($ver_info, $real_file)
1087  {
1088  return (
1089  $ver_info['file_size'] != filesize($real_file) ||
1090  $ver_info['md5'] != md5_file($real_file) ||
1091  $ver_info['sha1'] != sha1_file($real_file)
1092  );
1093 
1094  }//end _fileModified()
1095 
1096 
1106  function commit($real_file, $extra_info='')
1107  {
1108  $info = $this->_getFileInfoFromRealFile($real_file);
1109  if (!is_array($info)) {
1110  if ($info == FUDGE_FV_ERROR) {
1111  trigger_localised_error('FVER0002', E_USER_WARNING, __FUNCTION__);
1112  }
1113  return $info;
1114  }
1115 
1116  $GLOBALS['SQ_SYSTEM']->changeDatabaseConnection('db2');
1117  $GLOBALS['SQ_SYSTEM']->doTransaction('BEGIN');
1118 
1119  if (!$this->_lockFile($info['fileid'])) {
1120  trigger_localised_error('FVER0018', E_USER_NOTICE);
1121  $GLOBALS['SQ_SYSTEM']->doTransaction('ROLLBACK');
1122  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
1123  return FUDGE_FV_ERROR;
1124  }
1125 
1126  // get the lastest current info about this file
1127  $curr_ver_info = $this->_getFileInfoAtVersion($info['fileid']);
1128  if (empty($curr_ver_info)) {
1129  trigger_localised_error('FVER0011', E_USER_NOTICE, $info['path'], $info['filename']);
1130  $this->_releaseFile($info['fileid']);
1131  $GLOBALS['SQ_SYSTEM']->doTransaction('ROLLBACK');
1132  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
1133  return FUDGE_FV_ERROR;
1134  }
1135 
1136  // get the info about the version that this file is current checked out as
1137  $file_ver_info = $this->_getFileInfoAtVersion($info['fileid'], $info['version']);
1138  if (empty($file_ver_info)) {
1139  trigger_localised_error('FVER0010', E_USER_NOTICE, $info['version'], $info['path'], $info['filename']);
1140  $this->_releaseFile($info['fileid']);
1141  $GLOBALS['SQ_SYSTEM']->doTransaction('ROLLBACK');
1142  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
1143  return FUDGE_FV_ERROR;
1144  }
1145 
1146  // if the version that this file is checked out as is not the current version then
1147  // they are not allowed to commit this file
1148  if ($curr_ver_info['version'] != $file_ver_info['version']) {
1149  trigger_localised_error('FVER0019', E_USER_NOTICE, $info['path'], $info['filename'], $file_ver_info['version'], $curr_ver_info['version']);
1150  $this->_releaseFile($info['fileid']);
1151  $GLOBALS['SQ_SYSTEM']->doTransaction('ROLLBACK');
1152  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
1153  return FUDGE_FV_ERROR;
1154  }
1155 
1156  if (!$this->_fileModified($curr_ver_info, $real_file)) {
1157  $this->_releaseFile($info['fileid']);
1158  $GLOBALS['SQ_SYSTEM']->doTransaction('ROLLBACK');
1159  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
1160  return FUDGE_FV_NOT_MODIFIED;
1161  }
1162 
1163  // update the repository file
1164  $version = $this->_updateFile($info['fileid'], $info['path'], $real_file, $extra_info);
1165  if (empty($version)) {
1166  $this->_releaseFile($info['fileid']);
1167  $GLOBALS['SQ_SYSTEM']->doTransaction('ROLLBACK');
1168  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
1169  return FUDGE_FV_ERROR;
1170  }
1171 
1172  // create the FFV file entry
1173  if (!$this->_createFFVFile($real_file, $info['fileid'], $version)) {
1174  $this->_releaseFile($info['fileid']);
1175  $GLOBALS['SQ_SYSTEM']->doTransaction('ROLLBACK');
1176  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
1177  return FUDGE_FV_ERROR;
1178  }//end if
1179 
1180  $this->_releaseFile($info['fileid']);
1181 
1182  $GLOBALS['SQ_SYSTEM']->doTransaction('COMMIT');
1183  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
1184 
1185  return FUDGE_FV_OK;
1186 
1187  }//end commit()
1188 
1189 
1199  function remove($real_file, $extra_info='')
1200  {
1201  $info = $this->_getFileInfoFromRealFile($real_file);
1202  if (!is_array($info)) {
1203  if ($info == FUDGE_FV_ERROR) {
1204  trigger_localised_error('FVER0002', E_USER_WARNING, __FUNCTION__);
1205  }
1206  return $info;
1207  }
1208 
1209  $GLOBALS['SQ_SYSTEM']->changeDatabaseConnection('db2');
1210  $GLOBALS['SQ_SYSTEM']->doTransaction('BEGIN');
1211 
1212  if (!$this->_lockFile($info['fileid'])) {
1213  trigger_localised_error('FVER0018', E_USER_NOTICE);
1214  $GLOBALS['SQ_SYSTEM']->doTransaction('ROLLBACK');
1215  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
1216  return FUDGE_FV_ERROR;
1217  }
1218 
1219  // get the lastest current info about this file
1220  $curr_ver_info = $this->_getFileInfoAtVersion($info['fileid']);
1221  if (empty($curr_ver_info)) {
1222  trigger_localised_error('FVER0012', E_USER_NOTICE, $info['path'], $info['filename']);
1223  $this->_releaseFile($info['fileid']);
1224  $GLOBALS['SQ_SYSTEM']->doTransaction('ROLLBACK');
1225  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
1226  return FUDGE_FV_ERROR;
1227  }
1228 
1229  // get the info about the version that this file is current checked out as
1230  $file_ver_info = $this->_getFileInfoAtVersion($info['fileid'], $info['version']);
1231  if (empty($file_ver_info)) {
1232  trigger_localised_error('FVER0010', E_USER_NOTICE, $info['version'], $info['path'], $info['filename']);
1233  $this->_releaseFile($info['fileid']);
1234  $GLOBALS['SQ_SYSTEM']->doTransaction('ROLLBACK');
1235  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
1236  return FUDGE_FV_ERROR;
1237  }
1238 
1239  // if the version that this file is checked out as is not the current version then
1240  // they are not allowed to remove this file
1241  if ($curr_ver_info['version'] != $file_ver_info['version']) {
1242  trigger_localised_error('FVER0023', E_USER_NOTICE, $info['path'], $info['filename'], $curr_ver_info['version']);
1243  $this->_releaseFile($info['fileid']);
1244  $GLOBALS['SQ_SYSTEM']->doTransaction('ROLLBACK');
1245  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
1246  return FUDGE_FV_ERROR;
1247  }
1248 
1249  // remove the existing checked out file
1250  if ($this->clearOut($real_file) == FUDGE_FV_ERROR) {
1251  $this->_releaseFile($info['fileid']);
1252  $GLOBALS['SQ_SYSTEM']->doTransaction('ROLLBACK');
1253  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
1254  return FUDGE_FV_ERROR;
1255  }
1256 
1257  clearstatcache(); // just in case :)
1258 
1259  // update the repository file
1260  $version = $this->_updateFile($info['fileid'], $info['path'], $real_file, $extra_info);
1261  if (empty($version)) {
1262  $this->_releaseFile($info['fileid']);
1263  $GLOBALS['SQ_SYSTEM']->doTransaction('ROLLBACK');
1264  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
1265  return FUDGE_FV_ERROR;
1266  }
1267 
1268  // create the FFV file entry
1269  if (!$this->_createFFVFile($real_file, $info['fileid'], $version)) {
1270  $this->_releaseFile($info['fileid']);
1271  $GLOBALS['SQ_SYSTEM']->doTransaction('ROLLBACK');
1272  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
1273  return FUDGE_FV_ERROR;
1274  }//end if
1275 
1276  $this->_releaseFile($info['fileid']);
1277 
1278  $GLOBALS['SQ_SYSTEM']->doTransaction('COMMIT');
1279  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
1280 
1281  return FUDGE_FV_OK;
1282 
1283  }//end remove()
1284 
1285 
1294  function clearOut($real_file)
1295  {
1296  $info = $this->_getFileInfoFromRealFile($real_file);
1297  if (!is_array($info)) {
1298  if ($info == FUDGE_FV_ERROR) {
1299  trigger_localised_error('FVER0002', E_USER_WARNING, __FUNCTION__);
1300  }
1301  return $info;
1302  }
1303 
1304  // remove the FFV and real files
1305  if (!unlink(dirname($real_file).'/.FFV/'.basename($real_file))) {
1306  return FUDGE_FV_ERROR;
1307  }
1308 
1309  $file_removed = unlink($real_file);
1310  clearstatcache(); // just in case :)
1311  return ($file_removed) ? FUDGE_FV_OK : FUDGE_FV_ERROR;
1312 
1313  }//end clearOut()
1314 
1315 
1325  function changeFvTypeCode($real_file, $new_type_code)
1326  {
1327  $GLOBALS['SQ_SYSTEM']->changeDatabaseConnection('db2');
1328  $GLOBALS['SQ_SYSTEM']->doTransaction('BEGIN');
1329 
1330  require_once SQ_FUDGE_PATH.'/general/file_system.inc';
1331 
1332  $info = $this->_getFileInfoFromRealFile($real_file);
1333 
1334  if (!is_array($info)) {
1335  if ($info == FUDGE_FV_ERROR) {
1336  trigger_error('Error occured getting file information, unable to run '.__FUNCTION__.'()', E_USER_WARNING);
1337  }
1338  $GLOBALS['SQ_SYSTEM']->doTransaction('ROLLBACK');
1339  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
1340 
1341  return $info;
1342  }
1343 
1344  $old_path = $info['path'];
1345  $new_path = preg_replace('%/[^/]*/%', '/'.$new_type_code.'/', $old_path);
1346 
1347  $db = MatrixDAL::getDb();
1348  try {
1349  $bind_vars = Array (
1350  'new_path' => $new_path,
1351  'old_path' => $old_path,
1352  );
1353  $result = MatrixDAL::executeQuery('core', 'updateReposFilePath', $bind_vars);
1354  } catch (Exception $e) {
1355  throw new Exception('Unable to update pathname to: "'.$new_path.'" for previous path: "'.$old_path.'" due to database error: '.$e->getMessage());
1356  }
1357 
1358  $new_dir = $this->_dir.'/'.$new_path;
1359 
1360  if (!is_dir($new_dir)) create_directory($new_dir);
1361 
1362  $GLOBALS['SQ_SYSTEM']->doTransaction('COMMIT');
1363  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
1364 
1365  return rename($this->_dir.'/'.$old_path, $this->_dir.'/'.$new_path);
1366 
1367  }//end changeFvTypeCode()
1368 
1369 
1370 }//end class
1371 
1372 ?>