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

Last change on this file since 416 was 415, checked in by anonymous, 11 years ago

Disabled MX record check for email validation. Updated PEdit to work better with AcceptPathInfo? enabled.

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