2 if(!defined('sugarEntry') || !sugarEntry) die('Not A Valid Entry Point');
3 /*********************************************************************************
4 * SugarCRM Community Edition is a customer relationship management program developed by
5 * SugarCRM, Inc. Copyright (C) 2004-2011 SugarCRM Inc.
7 * This program is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU Affero General Public License version 3 as published by the
9 * Free Software Foundation with the addition of the following permission added
10 * to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
11 * IN WHICH THE COPYRIGHT IS OWNED BY SUGARCRM, SUGARCRM DISCLAIMS THE WARRANTY
12 * OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
14 * This program is distributed in the hope that it will be useful, but WITHOUT
15 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
16 * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
19 * You should have received a copy of the GNU Affero General Public License along with
20 * this program; if not, see http://www.gnu.org/licenses or write to the Free
21 * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
24 * You can contact SugarCRM, Inc. headquarters at 10050 North Wolfe Road,
25 * SW2-130, Cupertino, CA 95014, USA. or at email address contact@sugarcrm.com.
27 * The interactive user interfaces in modified source and object code versions
28 * of this program must display Appropriate Legal Notices, as required under
29 * Section 5 of the GNU Affero General Public License version 3.
31 * In accordance with Section 7(b) of the GNU Affero General Public License version 3,
32 * these Appropriate Legal Notices must retain the display of the "Powered by
33 * SugarCRM" logo. If the display of the logo is not reasonably feasible for
34 * technical reasons, the Appropriate Legal Notices must display the words
35 * "Powered by SugarCRM".
36 ********************************************************************************/
39 require_once 'modules/ModuleBuilder/parsers/constants.php' ;
40 require_once 'modules/ModuleBuilder/parsers/views/HistoryInterface.php' ;
42 class History implements HistoryInterface
45 private $_dirname ; // base directory for the history files
46 private $_basename ; // base name for a history file, for example, listviewdef.php
47 private $_list ; // the history - a list of history files
49 private $_previewFilename ; // the location of a file for preview
53 * @param string $previewFilename The filename which the caller expects for a preview file
55 public function __construct ( $previewFilename )
57 $GLOBALS [ 'log' ]->debug ( get_class ( $this ) . "->__construct( {$previewFilename} )" ) ;
58 $this->_previewFilename = $previewFilename ;
59 $this->_list = array ( ) ;
61 $this->_basename = basename ( $this->_previewFilename ) ;
62 $this->_dirname = dirname ( $this->_previewFilename );
63 $this->_historyLimit = isset ( $GLOBALS [ 'sugar_config' ] [ 'studio_max_history' ] ) ? $GLOBALS [ 'sugar_config' ] [ 'studio_max_history' ] : 50 ;
65 // create the history directory if it does not already exist
66 if (!is_dir($this->_dirname)) {
67 mkdir_recursive($this->_dirname);
71 // Reconstruct the history from the saved files
72 foreach (glob($this->getFileByTimestamp('*')) as $filename)
74 if(preg_match('/(\d+)$/', $filename, $match)) {
75 $this->_list [] = $match[1];
79 // now sort the files, oldest first
80 if (count ( $this->_list ) > 0)
82 sort ( $this->_list ) ;
88 * Get the most recent item in the history
89 * @return timestamp of the first item
91 public function getCount ()
93 return count ( $this->_list ) ;
97 * Get the most recent item in the history
98 * @return timestamp of the first item
100 public function getFirst ()
102 return end ( $this->_list ) ;
106 * Get the oldest item in the history (the default layout)
107 * @return timestamp of the last item
109 public function getLast ()
111 return reset ( $this->_list ) ;
115 * Get the next oldest item in the history
116 * @return timestamp of the next item
118 public function getNext ()
120 return prev ( $this->_list ) ;
124 * Get the nth item in the history (where the zeroeth record is the most recent)
125 * @return timestamp of the nth item
127 public function getNth ( $index )
129 $value = end ( $this->_list ) ;
131 while ( $i < $index )
133 $value = prev ( $this->_list ) ;
140 * Add an item to the history
141 * @return String A GMT Unix timestamp for this newly added item
143 public function append ($path)
145 // make sure we don't have a duplicate filename - highly unusual as two people should not be using Studio/MB concurrently, but when testing quite possible to do two appends within one second...
146 // because so unlikely in normal use we handle this the naive way by waiting a second so our naming scheme doesn't get overelaborated
149 $now = TimeDate::getInstance()->getNow();
151 for($retries = 0; !file_exists($new_file) && $retries < 5; $retries ++)
153 $now->modify("+1 second");
154 $time = $now->__get('ts');
155 $new_file = $this->getFileByTimestamp( $time );
157 // now we have a unique filename, copy the file into the history
158 copy ( $path, $new_file ) ;
159 $this->_list [ ] = $time ;
161 // finally, trim the number of files we're holding in the history to that specified in the configuration
162 // truncate the oldest files, keeping only the most recent $GLOBALS['sugar_config']['studio_max_history'] files (zero=keep them all)
163 $to_delete = $this->getCount() - $this->_historyLimit;
164 if ($this->_historyLimit != 0 && $to_delete)
166 // most recent files are at the end of the list, so we strip out the first count-max_history records
167 // can't just use array_shift because it renumbers numeric keys (our timestamp keys) to start from zero...
168 for ( $i = 0 ; $i < $to_delete ; $i ++ )
170 $timestamp = array_shift( $this->_list ) ;
171 if (! unlink ( $this->getFileByTimestamp( $timestamp ) ))
173 $GLOBALS [ 'log' ]->warn ( "History.php: unable to remove history file {$timestamp} from directory {$this->_dirname} - permissions problem?" ) ; }
177 // finally, remove any history preview file that might be lurking around - as soon as we append a new record it supercedes any old preview, so that must be removed (bug 20130)
178 if (file_exists($this->_previewFilename))
180 $GLOBALS [ 'log' ]->debug( get_class($this)."->append(): removing old history file at {$this->_previewFilename}");
181 unlink ( $this->_previewFilename);
188 * Restore the historical layout identified by timestamp
189 * @param Unix timestamp $timestamp GMT Timestamp of the layout to recover
190 * @return GMT Timestamp if successful, null if failure (if the file could not be copied for some reason)
192 public function restoreByTimestamp ($timestamp)
194 $filename = $this->getFileByTimestamp( $timestamp );
195 $GLOBALS [ 'log' ]->debug ( get_class ( $this ) . ": restoring from $filename to {$this->_previewFilename}" ) ;
197 if (file_exists ( $filename ))
199 copy ( $filename, $this->_previewFilename ) ;
206 * Undo the restore - revert back to the layout before the restore
208 public function undoRestore ()
210 if (file_exists ( $this->_previewFilename ))
212 unlink ( $this->_previewFilename ) ;
217 * Returns full path to history file by timestamp. This function returns file path even if file doesn't exist
221 public function getFileByTimestamp($timestamp)
223 return $this->_dirname . DIRECTORY_SEPARATOR . $this->_basename . '_' . $timestamp ;