source: tags/2.1.5/lib/PEdit.inc.php @ 774

Last change on this file since 774 was 377, checked in by quinn, 14 years ago

Releasing trunk as stable version 2.1.5

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