source: trunk/lib/PEdit.inc.php @ 350

Last change on this file since 350 was 334, checked in by quinn, 16 years ago

Fixed lots of misplings. I'm so embarrassed! ;P

File size: 26.5 KB
RevLine 
[1]1<?php
2/**
[136]3 * PEdit.inc.php
4 * code by strangecode :: www.strangecode.com :: this document contains copyrighted information
5 *
6 * PEdit provides a mechanism to store text in php variables
[42]7 * which will be printed to the client browser under normal
[1]8 * circumstances, but an authenticated user can 'edit' the document--
[334]9 * data stored in vars will be shown in html form elements to be edited
[92]10 * and saved. Posted data is stored in XML format in a specified data dir.
11 * A copy of the previous version is saved with the unix
[1]12 * timestamp as part of the filename. This allows reverting to previous versions.
13 *
14 * To use, include this file, initialize variables,
15 * and call printing/editing functions where you want data and forms to
[136]16 * show up.
17 *
18 * @author  Quinn Comendant <quinn@strangecode.com>
19 * @concept Beau Smith <beau@beausmith.com>
20 * @version 2.0
21 *
22 * Example of use:
[92]23 
24 // Initialize PEdit object.
25 require_once 'codebase/lib/PEdit.inc.php';
26 $pedit = new PEdit(array(
27     'data_dir' => COMMON_BASE . '/html/_pedit_data',
28     'authorized' => true,
29 ));
30 
31 // Setup content data types.
32 $pedit->set('title');
33 $pedit->set('content', array('type' => 'textarea'));
34 
35 // After setting all parameters and data, load the data.
36 $pedit->start();
37 
38 // Print content.
39 echo $pedit->get('title');
40 echo $pedit->get('content');
41 
42 // Print additional PEdit functionality.
43 $pedit->formBegin();
44 $pedit->printAllForms();
45 $pedit->printVersions();
46 $pedit->formEnd();
47
[1]48 */
[92]49class PEdit {
[1]50
[92]51    // PEdit object parameters.
52    var $_params = array(
53        'data_dir' => '',
54        'character_set' => 'utf-8',
55        'versions_min_qty' => 20,
56        'versions_min_days' => 10,
57    );
[42]58
[92]59    var $_data = array(); // Array to store loaded data.
60    var $_data_file = ''; // Full file path to the pedit data file.
61    var $_authorized = false; // User is authenticated to see extended functions.
62    var $_data_loaded = false;
63    var $op = '';
[42]64
65    /**
[92]66     * Constructs a new PEdit object. Initializes what file is being operated with
67     * (PHP_SELF) and what that operation is. The two
[1]68     * operations that actually modify data (save, restore) are treated differently
[92]69     * than view operations (versions, view, default). They die redirect so you see
[1]70     * the page you just modified.
[42]71     *
72     * @access public
73     * @param optional array $params  A hash containing connection parameters.
74     */
[92]75    function PEdit($params)
[1]76    {
[92]77        $this->setParam($params);
78       
79        if ($this->getParam('authorized') === true) {
[1]80            $this->_authorized = true;
81        }
[92]82       
83        // Setup PEAR XML libraries.
[300]84        require_once 'XML/Serializer.php';
85        $this->xml_serializer =& new XML_Serializer(array(
86            XML_SERIALIZER_OPTION_INDENT => '',
87            XML_SERIALIZER_OPTION_LINEBREAKS => '',
88            XML_SERIALIZER_OPTION_RETURN_RESULT => true,
89            XML_SERIALIZER_OPTION_TYPEHINTS => true,
90        ));
[92]91        require_once 'XML/Unserializer.php';
92        $this->xml_unserializer =& new XML_Unserializer(array(
93            XML_UNSERIALIZER_OPTION_COMPLEXTYPE => 'array',
94        ));
95    }
96   
97    /**
98     * Set (or overwrite existing) parameters by passing an array of new parameters.
99     *
100     * @access public
101     * @param  array    $params     Array of parameters (key => val pairs).
102     */
103    function setParam($params)
104    {
[136]105        $app =& App::getInstance();
106
[92]107        if (isset($params) && is_array($params)) {
108            // Merge new parameters with old overriding only those passed.
109            $this->_params = array_merge($this->_params, $params);
110        } else {
[136]111            $app->logMsg(sprintf('Parameters are not an array: %s', $params), LOG_WARNING, __FILE__, __LINE__);
[92]112        }
113    }
[42]114
[92]115    /**
116     * Return the value of a parameter, if it exists.
117     *
118     * @access public
119     * @param string $param        Which parameter to return.
120     * @return mixed               Configured parameter value.
121     */
122    function getParam($param)
123    {
[136]124        $app =& App::getInstance();
125   
[92]126        if (isset($this->_params[$param])) {
127            return $this->_params[$param];
128        } else {
[146]129            $app->logMsg(sprintf('Parameter is not set: %s', $param), LOG_DEBUG, __FILE__, __LINE__);
[92]130            return null;
[1]131        }
[92]132    }
133   
134    /*
135    * Load the pedit data and run automatic functions.
136    *
137    * @access   public
138    * @author   Quinn Comendant <quinn@strangecode.com>
139    * @since    12 Apr 2006 12:43:47
140    */
141    function start($initialize_data_file=false)
142    {
[136]143        $app =& App::getInstance();
144
[92]145        if (!is_dir($this->getParam('data_dir'))) {
146            trigger_error(sprintf('PEdit data directory not found: %s', $this->getParam('data_dir')), E_USER_WARNING);
147        }
148       
149        // The location of the data file. (i.e.: "COMMON_DIR/html/_pedit_data/news/index.xml")
150        $this->_data_file = sprintf('%s%s.xml', $this->getParam('data_dir'), $_SERVER['PHP_SELF']);
[42]151
[92]152        // op is used throughout the script to determine state.
[1]153        $this->op = getFormData('op');
[42]154
[92]155        // Automatic functions based on state.
[1]156        switch ($this->op) {
157        case 'Save' :
158            if ($this->_writeData()) {
[136]159                $app->dieURL($_SERVER['PHP_SELF']);
[1]160            }
161            break;
[332]162
[1]163        case 'Restore' :
[92]164            if ($this->_restoreVersion(getFormData('version'))) {
[136]165                $app->dieURL($_SERVER['PHP_SELF']);
[1]166            }
167            break;
[332]168
[92]169        case 'View' :
170            $this->_data_file = sprintf('%s%s__%s.xml', $this->getParam('data_dir'), $_SERVER['PHP_SELF'], getFormData('version'));
[136]171            $app->raiseMsg(sprintf(_("This is <em><strong>only a preview</strong></em> of version %s."), getFormData('version')), MSG_NOTICE, __FILE__, __LINE__);
[92]172            break;
[1]173        }
[92]174       
175        // Load data.
176        $this->_loadDataFile();
177
178        if ($initialize_data_file === true) {
179            $this->_createVersion();
180            $this->_initializeDataFile();
181        }
[1]182    }
[42]183
[1]184    /**
185     * Stores a variable in the pedit data array with the content name, and type of form.
[42]186     *
187     * @access public
188     *
[92]189     * @param string    $content         The variable containing the text to store.
190     * @param array     $options         Additional options to store with this data.
[1]191     */
[92]192    function set($name, $options=array())
[1]193    {
[136]194        $app =& App::getInstance();
195
[92]196        $name = preg_replace('/\s/', '_', $name);
197        if (!isset($this->_data[$name])) {
198            $this->_data[$name] = array_merge(array('content' => ''), $options);
199        } else {
[136]200            $app->logMsg(sprintf('Duplicate set data: %s', $name), LOG_NOTICE, __FILE__, __LINE__);
[1]201        }
202    }
203
[42]204    /**
[92]205     * Returns the contents of a data variable. The variable must first be 'set'.
[1]206     *
[42]207     * @access public
[92]208     * @param string $name   The name of the variable to return.
209     * @return string        The trimmed content of the named data.
[42]210     */
[92]211    function get($name)
[1]212    {
[92]213        $name = preg_replace('/\s/', '_', $name);
[1]214        if ($this->op != 'Edit' && $this->op != 'Versions' && isset($this->_data[$name]['content'])) {
[92]215            return $this->_data[$name]['content'];
216        } else {
217            return '';
[1]218        }
219    }
220
[42]221    /**
[92]222     * Prints the beginning <form> HTML tag, as well as hidden input forms.
[42]223     *
[92]224     * @return bool  False if unauthorized or current page is a version.
[42]225     */
[92]226    function formBegin()
[1]227    {
[136]228        $app =& App::getInstance();
229
[93]230        if (!$this->_authorized || empty($this->_data)) {
[92]231            return false;
232        }
[101]233        ?>       
[185]234        <form action="<?php echo oTxt($_SERVER['PHP_SELF']); ?>" method="post" id="sc-pedit-form">
235        <input type="hidden" name="filename" value="<?php echo oTxt($_SERVER['PHP_SELF']); ?>" />
[101]236        <input type="hidden" name="file_hash" value="<?php echo $this->_fileHash(); ?>" />
237        <?php
[136]238        $app->printHiddenSession();
[92]239        switch ($this->op) {
240        case 'Edit' :
[101]241            ?>
242            <div class="sc-pedit-buttons">
[247]243                <input type="submit" name="op" value="<?php echo _("Save"); ?>" title="<?php echo preg_replace('/^(\w)/i', '($1)', _("Save"))?>" accesskey="<?php echo mb_substr(_("Save"), 0, 1) ?>" />
244                <input type="submit" name="op" value="<?php echo _("Cancel"); ?>" title="<?php echo preg_replace('/^(\w)/i', '($1)', _("Cancel"))?>" accesskey="<?php echo mb_substr(_("Cancel"), 0, 1) ?>" />
[101]245            </div>
246            <?php
[92]247            break;
[332]248
[92]249        case 'View' :
[101]250            ?>
251            <input type="hidden" name="version" value="<?php echo getFormData('version'); ?>" />
252            <?php
[92]253            break;
[1]254        }
255    }
256
[42]257    /**
[92]258     * Loops through the PEdit data array and prints all the HTML forms corresponding
259     * to all pedit variables, in the order in which they were 'set'.
[1]260     *
[42]261     * @access public
262     */
[92]263    function printAllForms()
[1]264    {
[92]265        if ($this->_authorized && $this->op == 'Edit' && is_array($this->_data) && $this->_data_loaded) {
266            foreach ($this->_data as $name=>$d) {
267                $this->printForm($name);
[1]268            }
269        }
270    }
271
[42]272    /**
273     * Prints the HTML forms corresponding to pedit variables. Each variable
[1]274     * must first be 'set'.
[42]275     *
276     * @access public
277     * @param string $name      The name of the variable.
[92]278     * @param string $type      Type of form to print. Currently only 'text' and 'textarea' supported.
[42]279     */
[92]280    function printForm($name, $type='text')
[1]281    {
[98]282        if ($this->_authorized && $this->op == 'Edit' && $this->_data_loaded) {       
[101]283            ?>
284            <div class="sc-pedit-item">
285            <?php
[92]286            $type = (isset($this->_data[$name]['type'])) ? $this->_data[$name]['type'] : $type;
[1]287            // Print edit form.
[92]288            switch ($type) {
289            case 'text' :
290            default :
[101]291                ?>
[332]292                <label>
293                <?php echo ucfirst(str_replace('_', ' ', $name)); ?>
[121]294                <input type="text" name="_pedit_data[<?php echo $name; ?>]" id="sc-pedit-field-<?php echo $name; ?>" value="<?php echo oTxt($this->_data[$name]['content']); ?>" class="sc-full" />
[332]295                </label>
[101]296                <?php
[1]297                break;
[332]298
[1]299            case 'textarea' :
[101]300                ?>
[332]301                <label>
302                <?php echo ucfirst(str_replace('_', ' ', $name)); ?>
[124]303                <textarea name="_pedit_data[<?php echo $name; ?>]" id="sc-pedit-field-<?php echo $name; ?>" rows="" cols="" class="sc-full sc-tall"><?php echo oTxt($this->_data[$name]['content']); ?></textarea>
[332]304                </label>
[101]305                <?php
[1]306                break;
307            }
[101]308            ?>
309            </div>
310            <?php
[1]311        }
312    }
[42]313
314    /**
[334]315     * Prints the ending </form> HTML tag, as well as buttons used during
[42]316     * different operations.
317     *
318     * @return bool  False if unauthorized or current page is a version.
319     */
[1]320    function formEnd()
321    {
[93]322        if (!$this->_authorized || empty($this->_data)) {
[1]323            // Don't show form elements for versioned documents.
324            return false;
325        }
326        switch ($this->op) {
327        case 'Edit' :
[101]328            ?>
329            <div class="sc-pedit-buttons">
[247]330                <input type="submit" name="op" value="<?php echo _("Save"); ?>" title="<?php echo preg_replace('/^(\w)/i', '($1)', _("Save"))?>" accesskey="<?php echo mb_substr(_("Save"), 0, 1) ?>" />
331                <input type="submit" name="op" value="<?php echo _("Cancel"); ?>" title="<?php echo preg_replace('/^(\w)/i', '($1)', _("Cancel"))?>" accesskey="<?php echo mb_substr(_("Cancel"), 0, 1) ?>" />
[101]332            </div>
333            </form>
334            <?php
[1]335            break;
[332]336
[1]337        case 'Versions' :
[101]338            ?>
339            <div class="sc-pedit-buttons">
[247]340                <input type="submit" name="op" value="<?php echo _("Cancel"); ?>" title="<?php echo preg_replace('/^(\w)/i', '($1)', _("Cancel"))?>" accesskey="<?php echo mb_substr(_("Cancel"), 0, 1) ?>" />
[101]341            </div>
342            </form>
343            <?php
[1]344            break;
[332]345
[92]346        case 'View' :
[101]347            ?>
348            <div class="sc-pedit-buttons">
[247]349                <input type="submit" name="op" value="<?php echo _("Restore"); ?>" title="<?php echo preg_replace('/^(\w)/i', '($1)', _("Restore"))?>" accesskey="<?php echo mb_substr(_("Restore"), 0, 1) ?>" />
350                <input type="submit" name="op" value="<?php echo _("Versions"); ?>" title="<?php echo preg_replace('/^(\w)/i', '($1)', _("Versions"))?>" accesskey="<?php echo mb_substr(_("Versions"), 0, 1) ?>" />
351                <input type="submit" name="op" value="<?php echo _("Cancel"); ?>" title="<?php echo preg_replace('/^(\w)/i', '($1)', _("Cancel"))?>" accesskey="<?php echo mb_substr(_("Cancel"), 0, 1) ?>" />
[101]352            </div>
353            </form>
354            <?php
[92]355            break;
[332]356
[1]357        default :
[101]358            ?>
359            <div class="sc-pedit-buttons">
[247]360                <input type="submit" name="op" value="<?php echo _("Edit"); ?>" title="<?php echo preg_replace('/^(\w)/i', '($1)', _("Edit"))?>" accesskey="<?php echo mb_substr(_("Edit"), 0, 1) ?>" />
361                <input type="submit" name="op" value="<?php echo _("Versions"); ?>" title="<?php echo preg_replace('/^(\w)/i', '($1)', _("Versions"))?>" accesskey="<?php echo mb_substr(_("Versions"), 0, 1) ?>" />
[101]362            </div>
363            </form>
364            <?php
[1]365        }
366    }
[42]367
368    /**
[92]369     * Prints an HTML list of versions of current file, with the filesize
370     * and links to view and restore the file.
371     *
372     * @access public
373     */
374    function printVersions()
375    {
[136]376        $app =& App::getInstance();
377
[92]378        if ($this->_authorized && $this->op == 'Versions') {
379            // Print versions and commands to view/restore.
380            $version_files = $this->_getVersions();
[101]381            ?><h1><?php printf(_("%s saved versions of %s"), sizeof($version_files), basename($_SERVER['PHP_SELF'])); ?></h1><?php
[92]382            if (is_array($version_files) && !empty($version_files)) {
[101]383                ?>
384                <table id="sc-pedit-versions-table">
385                <tr>
386                    <th><?php echo _("Date"); ?></th>
387                    <th><?php echo _("Time"); ?></th>
388                    <th><?php echo _("File size"); ?></th>
389                    <th><?php echo _("Version options"); ?></th>
390                </tr>
391                <?php
[92]392                foreach ($version_files as $v) {
[101]393                    ?>
394                    <tr>
[136]395                        <td><?php echo date($app->getParam('date_format'), $v['unixtime']); ?></td>
396                        <td><?php echo date($app->getParam('time_format'), $v['unixtime']); ?></td>
[101]397                        <td><?php echo humanFileSize($v['filesize']); ?></td>
[136]398                        <td class="sc-nowrap"><a href="<?php echo $app->oHREF($_SERVER['PHP_SELF'] . '?op=View&version=' . $v['unixtime'] . '&file_hash=' . $this->_fileHash()); ?>"><?php echo _("View"); ?></a> <?php echo _("or"); ?> <a href="<?php echo $app->oHREF($_SERVER['PHP_SELF'] . '?op=Restore&version=' . $v['unixtime'] . '&file_hash=' . $this->_fileHash()); ?>"><?php echo _("Restore"); ?></a></td>
[101]399                    </tr>
400                    <?php
[92]401                }
[101]402                ?>
403                </table>
[121]404                <div class="sc-help"><?php printf(_("When there are more than %s versions, those over %s days old are deleted."), $this->getParam('versions_min_qty'), $this->getParam('versions_min_days')); ?></div>
[101]405                <?php
[92]406            }
407        }
408    }
409
410    /*
[334]411    * Returns a secret hash for the current file.
[92]412    *
413    * @access   public
414    * @author   Quinn Comendant <quinn@strangecode.com>
415    * @since    12 Apr 2006 10:52:35
416    */
417    function _fileHash()
418    {
[136]419        $app =& App::getInstance();
420
421        return md5($app->getParam('signing_key') . $_SERVER['PHP_SELF']);
[92]422    }
423
424    /*
425    * Load the XML data file into $this->_data.
426    *
427    * @access   public
428    * @return   bool    false on error
429    * @author   Quinn Comendant <quinn@strangecode.com>
430    * @since    11 Apr 2006 20:36:26
431    */
432    function _loadDataFile()
433    {
[136]434        $app =& App::getInstance();
435
[92]436        if (!file_exists($this->_data_file)) {
437            if (!$this->_initializeDataFile()) {
[136]438                $app->logMsg(sprintf('Initializing content file failed: %s', $this->_data_file), LOG_WARNING, __FILE__, __LINE__);
[92]439                return false;
440            }
441        }
442        $xml_file_contents = file_get_contents($this->_data_file);
443        $status = $this->xml_unserializer->unserialize($xml_file_contents, false);   
444        if (PEAR::isError($status)) {
[136]445            $app->logMsg(sprintf('XML_Unserialize error: %s', $status->getMessage()), LOG_WARNING, __FILE__, __LINE__);
[92]446            return false;
447        }
448        $xml_file_data = $this->xml_unserializer->getUnserializedData();
449
450        // Only load data specified with set(), even though there may be more in the xml file.
451        foreach ($this->_data as $name => $initial_data) {
452            if (isset($xml_file_data[$name])) {
453                $this->_data[$name] = array_merge($initial_data, $xml_file_data[$name]);
454            } else {
455                $this->_data[$name] = $initial_data;
456            }
457        }
458
459        $this->_data_loaded = true;
460        return true;
461    }
462   
463    /*
464    * Start a new data file.
465    *
466    * @access   public
467    * @return   The success value of both xml_serializer->serialize() and _filePutContents()
468    * @author   Quinn Comendant <quinn@strangecode.com>
469    * @since    11 Apr 2006 20:53:42
470    */
471    function _initializeDataFile()
472    {
[136]473        $app =& App::getInstance();
474
475        $app->logMsg(sprintf('Initializing data file: %s', $this->_data_file), LOG_INFO, __FILE__, __LINE__);
[92]476        $xml_file_contents = $this->xml_serializer->serialize($this->_data);
477        return $this->_filePutContents($this->_data_file, $xml_file_contents);
478    }
479
480    /**
[1]481     * Saves the POSTed data by overwriting the pedit variables in the
[42]482     * current file.
483     *
484     * @access private
485     * @return bool  False if unauthorized or on failure. True on success.
486     */
[1]487    function _writeData()
488    {
[136]489        $app =& App::getInstance();
490
[1]491        if (!$this->_authorized) {
492            return false;
493        }
[92]494        if ($this->_fileHash() != getFormData('file_hash')) {
[1]495            // Posted data is NOT for this file!
[136]496            $app->logMsg(sprintf('File_hash does not match current file.', null), LOG_WARNING, __FILE__, __LINE__);
[1]497            return false;
498        }
[42]499
[92]500        // Scrub incoming data. Escape tags?
501        $new_data = getFormData('_pedit_data');
[1]502
[92]503        if (is_array($new_data) && !empty($new_data)) {
[1]504            // Make certain a version is created.
[92]505            $this->_deleteOldVersions();
506            if (!$this->_createVersion()) {
[136]507                $app->logMsg(sprintf('Failed creating new version of file.', null), LOG_NOTICE, __FILE__, __LINE__);
[1]508                return false;
509            }
[92]510           
511            // Collect posted data that is already specified in _data (by set()).
512            foreach ($new_data as $name => $content) {
513                if (isset($this->_data[$name])) {
514                    $this->_data[$name]['content'] = $content;
515                }
516            }
517           
518            if (is_array($this->_data) && !empty($this->_data)) {
519                $xml_file_contents = $this->xml_serializer->serialize($this->_data);
520                return $this->_filePutContents($this->_data_file, $xml_file_contents);
521            }
522        }
523    }
524   
525    /*
526    * Writes content to the specified file.
527    *
528    * @access   public
529    * @param    string  $filename   Path to file.
530    * @param    string  $content    Data to write into file.
531    * @return   bool                Success or failure.
532    * @author   Quinn Comendant <quinn@strangecode.com>
533    * @since    11 Apr 2006 22:48:30
534    */
535    function _filePutContents($filename, $content)
536    {
[136]537        $app =& App::getInstance();
538
[92]539        // Ensure requested filename is within the pedit data dir.
[247]540        if (mb_strpos($filename, $this->getParam('data_dir')) === false) {
[136]541            $app->logMsg(sprintf('Failed writing file outside pedit _data_dir: %s', $filename), LOG_ERR, __FILE__, __LINE__);
[92]542            return false;
543        }
[42]544
[92]545        // Recursively create directories.
546        $subdirs = preg_split('!/!', str_replace($this->getParam('data_dir'), '', dirname($filename)), -1, PREG_SPLIT_NO_EMPTY);
547        // Start with the pedit _data_dir base.
548        $curr_path = $this->getParam('data_dir');
549        while (!empty($subdirs)) {
550            $curr_path .= '/' . array_shift($subdirs);
551            if (!is_dir($curr_path)) {
552                if (!mkdir($curr_path)) {
[136]553                    $app->logMsg(sprintf('Failed mkdir: %s', $curr_path), LOG_ERR, __FILE__, __LINE__);
[1]554                    return false;
555                }
[92]556            }
557        }
558
559        // Open file for writing and truncate to zero length.
560        if ($fp = fopen($filename, 'w')) {
561            if (flock($fp, LOCK_EX)) {
[247]562                fwrite($fp, $content, mb_strlen($content));
[92]563                flock($fp, LOCK_UN);
[1]564            } else {
[136]565                $app->logMsg(sprintf('Could not lock file for writing: %s', $filename), LOG_ERR, __FILE__, __LINE__);
[1]566                return false;
567            }
[92]568            fclose($fp);
569            // Success!
[136]570            $app->logMsg(sprintf('Wrote to file: %s', $filename), LOG_DEBUG, __FILE__, __LINE__);
[92]571            return true;
572        } else {
[136]573            $app->logMsg(sprintf('Could not open file for writing: %s', $filename), LOG_ERR, __FILE__, __LINE__);
[92]574            return false;
[1]575        }
576    }
[42]577
578    /**
579     * Makes a copy of the current file with the unix timestamp appended to the
[92]580     * filename.
[42]581     *
582     * @access private
[92]583     * @return bool  False on failure. True on success.
[42]584     */
[92]585    function _createVersion()
[1]586    {
[136]587        $app =& App::getInstance();
588
[1]589        if (!$this->_authorized) {
590            return false;
591        }
[92]592        if ($this->_fileHash() != getFormData('file_hash')) {
[1]593            // Posted data is NOT for this file!
[136]594            $app->logMsg(sprintf('File_hash does not match current file.', null), LOG_ERR, __FILE__, __LINE__);
[1]595            return false;
596        }
[42]597
[92]598        // Ensure current data file exists.
599        if (!file_exists($this->_data_file)) {
[136]600            $app->logMsg(sprintf('Data file does not yet exist: %s', $this->_data_file), LOG_NOTICE, __FILE__, __LINE__);
[92]601            return false;
[1]602        }
603
604        // Do the actual copy. File naming scheme must be consistent!
[92]605        // filename.php.xml becomes filename.php__1124124128.xml
606        $version_file = sprintf('%s__%s.xml', preg_replace('/\.xml$/', '', $this->_data_file), time());
607        if (!copy($this->_data_file, $version_file)) {
[136]608            $app->logMsg(sprintf('Failed copying new version: %s -> %s', $this->_data_file, $version_file), LOG_ERR, __FILE__, __LINE__);
[1]609            return false;
610        }
[42]611
[1]612        return true;
613    }
[92]614   
615    /*
616    * Delete all versions older than versions_min_days if there are more than versions_min_qty or 100.
617    *
618    * @access   public
619    * @return bool  False on failure. True on success.
620    * @author   Quinn Comendant <quinn@strangecode.com>
621    * @since    12 Apr 2006 11:08:11
622    */
623    function _deleteOldVersions()
624    {
[136]625        $app =& App::getInstance();
626
[92]627        $version_files = $this->_getVersions();
628        if (is_array($version_files) && sizeof($version_files) > $this->getParam('versions_min_qty')) {
629            // Pop oldest ones off bottom of array.
630            $oldest = array_pop($version_files);
631            // Loop while minimum X qty && minimum X days worth but never more than 100 qty.
632            while ((sizeof($version_files) > $this->getParam('versions_min_qty')
633            && $oldest['unixtime'] < mktime(date('H'), date('i'), date('s'), date('m'), date('d') - $this->getParam('versions_min_days'), date('Y')))
634            || sizeof($version_files) > 100) {
635                $del_file = dirname($this->_data_file) . '/' . $oldest['filename'];
636                if (!unlink($del_file)) {
[136]637                    $app->logMsg(sprintf('Failed deleting version: %s', $del_file), LOG_ERR, __FILE__, __LINE__);
[92]638                }
639                $oldest = array_pop($version_files);
640            }
641        }
642    }
[42]643
644    /**
645     * Returns an array of all archived versions of the current file,
[1]646     * sorted with newest versions at the top of the array.
[42]647     *
[1]648     * @access private
[42]649     * @return array  Array of versions.
650     */
[1]651    function _getVersions()
652    {
[92]653        $version_files = array();
654        $dir_handle = opendir(dirname($this->_data_file));
655        $curr_file_preg_pattern = sprintf('/^%s__(\d+).xml$/', preg_quote(basename($_SERVER['PHP_SELF'])));
656        while ($dir_handle && ($version_file = readdir($dir_handle)) !== false) {
657            if (!preg_match('/^\./', $version_file) && !is_dir($version_file) && preg_match($curr_file_preg_pattern, $version_file, $time)) {
658                $version_files[] = array(
659                    'filename' => $version_file,
[42]660                    'unixtime' => $time[1],
[92]661                    'filesize' => filesize(dirname($this->_data_file) . '/' . $version_file)
[1]662                );
663            }
664        }
665
[92]666        if (is_array($version_files) && !empty($version_files)) {
667            array_multisort($version_files, SORT_DESC);
668            return $version_files;
[1]669        } else {
670            return array();
671        }
672    }
[42]673
674    /**
[1]675     * Makes a version backup of the current file, then copies the specified
[42]676     * archived version over the current file.
677     *
678     * @access private
[92]679     * @param string $version    Unix timestamp of archived version to restore.
680     * @return bool  False on failure. True on success.
[42]681     */
[92]682    function _restoreVersion($version)
[1]683    {
[136]684        $app =& App::getInstance();
685
[1]686        if (!$this->_authorized) {
687            return false;
688        }
[92]689       
690        // The file to restore.
691        $version_file = sprintf('%s__%s.xml', preg_replace('/\.xml$/', '', $this->_data_file), $version);
692       
693        // Ensure specified version exists.
694        if (!file_exists($version_file)) {
[334]695            $app->logMsg(sprintf('Cannot restore non-existent file: %s', $version_file), LOG_NOTICE, __FILE__, __LINE__);
[92]696            return false;
697        }
[42]698
[92]699        // Make certain a version is created.
700        if (!$this->_createVersion()) {
[136]701            $app->logMsg(sprintf('Failed creating new version of file.', null), LOG_ERR, __FILE__, __LINE__);
[92]702            return false;
703        }
[42]704
[92]705        // Do the actual copy.
706        if (!copy($version_file, $this->_data_file)) {
[136]707            $app->logMsg(sprintf('Failed copying old version: %s -> %s', $version_file, $this->_data_file), LOG_ERR, __FILE__, __LINE__);
[1]708            return false;
709        }
[92]710
711        // Success!
[136]712        $app->raiseMsg(sprintf(_("Page has been restored to version %s."), $version), MSG_SUCCESS, __FILE__, __LINE__);
[92]713        return true;
[1]714    }
[42]715
[1]716} // End class.
717
718?>
Note: See TracBrowser for help on using the repository browser.