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

Last change on this file since 185 was 185, checked in by scdev, 18 years ago

Q - added oTxt() around all printed PHP_SELFs to avoid XSS attack. See: http://blog.phpdoc.info/archives/13-XSS-Woes.html

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