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

Last change on this file since 415 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
Line 
1<?php
2/**
3 * The Strangecode Codebase - a general application development framework for PHP
4 * For details visit the project site: <http://trac.strangecode.com/codebase/>
5 * Copyright 2001-2012 Strangecode, LLC
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/**
24 * PEdit.inc.php
25 *
26 * PEdit provides a mechanism to store text in php variables
27 * which will be printed to the client browser under normal
28 * circumstances, but an authenticated user can 'edit' the document--
29 * data stored in vars will be shown in html form elements to be edited
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
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
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:
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
68 */
69class PEdit {
70
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    );
78
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 = '';
84
85    /**
86     * Constructs a new PEdit object. Initializes what file is being operated with
87     * (PHP_SELF) and what that operation is. The two
88     * operations that actually modify data (save, restore) are treated differently
89     * than view operations (versions, view, default). They die redirect so you see
90     * the page you just modified.
91     *
92     * @access public
93     * @param optional array $params  A hash containing connection parameters.
94     */
95    function PEdit($params)
96    {
97        $this->setParam($params);
98       
99        if ($this->getParam('authorized') === true) {
100            $this->_authorized = true;
101        }
102       
103        // Setup PEAR XML libraries.
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        ));
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    {
125        $app =& App::getInstance();
126
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 {
131            $app->logMsg(sprintf('Parameters are not an array: %s', $params), LOG_WARNING, __FILE__, __LINE__);
132        }
133    }
134
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    {
144        $app =& App::getInstance();
145   
146        if (isset($this->_params[$param])) {
147            return $this->_params[$param];
148        } else {
149            $app->logMsg(sprintf('Parameter is not set: %s', $param), LOG_DEBUG, __FILE__, __LINE__);
150            return null;
151        }
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    {
163        $app =& App::getInstance();
164
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['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        }
178
179        // op is used throughout the script to determine state.
180        $this->op = getFormData('op');
181
182        // Automatic functions based on state.
183        switch ($this->op) {
184        case 'Save' :
185            if ($this->_writeData()) {
186                $app->dieURL($_SERVER['PHP_SELF']);
187            }
188            break;
189
190        case 'Restore' :
191            if ($this->_restoreVersion(getFormData('version'))) {
192                $app->dieURL($_SERVER['PHP_SELF']);
193            }
194            break;
195
196        case 'View' :
197            $this->_data_file = sprintf('%s%s__%s.xml', $this->getParam('data_dir'), $_SERVER['PHP_SELF'], getFormData('version'));
198            $app->raiseMsg(sprintf(_("This is <em><strong>only a preview</strong></em> of version %s."), getFormData('version')), MSG_NOTICE, __FILE__, __LINE__);
199            break;
200        }
201       
202        // Load data.
203        $this->_loadDataFile();
204
205        if ($initialize_data_file === true) {
206            $this->_createVersion();
207            $this->_initializeDataFile();
208        }
209    }
210
211    /**
212     * Stores a variable in the pedit data array with the content name, and type of form.
213     *
214     * @access public
215     *
216     * @param string    $content         The variable containing the text to store.
217     * @param array     $options         Additional options to store with this data.
218     */
219    function set($name, $options=array())
220    {
221        $app =& App::getInstance();
222
223        $name = preg_replace('/\s/', '_', $name);
224        if (!isset($this->_data[$name])) {
225            $this->_data[$name] = array_merge(array('content' => ''), $options);
226        } else {
227            $app->logMsg(sprintf('Duplicate set data: %s', $name), LOG_NOTICE, __FILE__, __LINE__);
228        }
229    }
230
231    /**
232     * Returns the contents of a data variable. The variable must first be 'set'.
233     *
234     * @access public
235     * @param string $name   The name of the variable to return.
236     * @return string        The trimmed content of the named data.
237     */
238    function get($name)
239    {
240        $name = preg_replace('/\s/', '_', $name);
241        if ($this->op != 'Edit' && $this->op != 'Versions' && isset($this->_data[$name]['content'])) {
242            return $this->_data[$name]['content'];
243        } else {
244            return '';
245        }
246    }
247
248    /**
249     * Prints the beginning <form> HTML tag, as well as hidden input forms.
250     *
251     * @return bool  False if unauthorized or current page is a version.
252     */
253    function formBegin()
254    {
255        $app =& App::getInstance();
256
257        if (!$this->_authorized || empty($this->_data)) {
258            return false;
259        }
260        ?>       
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']); ?>" />
263        <input type="hidden" name="file_hash" value="<?php echo $this->_fileHash(); ?>" />
264        <?php
265        $app->printHiddenSession();
266        switch ($this->op) {
267        case 'Edit' :
268            ?>
269            <div class="sc-pedit-buttons">
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) ?>" />
272            </div>
273            <?php
274            break;
275
276        case 'View' :
277            ?>
278            <input type="hidden" name="version" value="<?php echo getFormData('version'); ?>" />
279            <?php
280            break;
281        }
282    }
283
284    /**
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'.
287     *
288     * @access public
289     */
290    function printAllForms()
291    {
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);
295            }
296        }
297    }
298
299    /**
300     * Prints the HTML forms corresponding to pedit variables. Each variable
301     * must first be 'set'.
302     *
303     * @access public
304     * @param string $name      The name of the variable.
305     * @param string $type      Type of form to print. Currently only 'text' and 'textarea' supported.
306     */
307    function printForm($name, $type='text')
308    {
309        if ($this->_authorized && $this->op == 'Edit' && $this->_data_loaded) {       
310            ?>
311            <div class="sc-pedit-item">
312            <?php
313            $type = (isset($this->_data[$name]['type'])) ? $this->_data[$name]['type'] : $type;
314            // Print edit form.
315            switch ($type) {
316            case 'text' :
317            default :
318                ?>
319                <label>
320                <?php echo ucfirst(str_replace('_', ' ', $name)); ?>
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" />
322                </label>
323                <?php
324                break;
325
326            case 'textarea' :
327                ?>
328                <label>
329                <?php echo ucfirst(str_replace('_', ' ', $name)); ?>
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>
331                </label>
332                <?php
333                break;
334            }
335            ?>
336            </div>
337            <?php
338        }
339    }
340
341    /**
342     * Prints the ending </form> HTML tag, as well as buttons used during
343     * different operations.
344     *
345     * @return bool  False if unauthorized or current page is a version.
346     */
347    function formEnd()
348    {
349        if (!$this->_authorized || empty($this->_data)) {
350            // Don't show form elements for versioned documents.
351            return false;
352        }
353        switch ($this->op) {
354        case 'Edit' :
355            ?>
356            <div class="sc-pedit-buttons">
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) ?>" />
359            </div>
360            </form>
361            <?php
362            break;
363
364        case 'Versions' :
365            ?>
366            <div class="sc-pedit-buttons">
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) ?>" />
368            </div>
369            </form>
370            <?php
371            break;
372
373        case 'View' :
374            ?>
375            <div class="sc-pedit-buttons">
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) ?>" />
379            </div>
380            </form>
381            <?php
382            break;
383
384        default :
385            ?>
386            <div class="sc-pedit-buttons">
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) ?>" />
389            </div>
390            </form>
391            <?php
392        }
393    }
394
395    /**
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    {
403        $app =& App::getInstance();
404
405        if ($this->_authorized && $this->op == 'Versions') {
406            // Print versions and commands to view/restore.
407            $version_files = $this->_getVersions();
408            ?><h1><?php printf(_("%s saved versions of %s"), sizeof($version_files), basename($_SERVER['PHP_SELF'])); ?></h1><?php
409            if (is_array($version_files) && !empty($version_files)) {
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
419                foreach ($version_files as $v) {
420                    ?>
421                    <tr>
422                        <td><?php echo date($app->getParam('date_format'), $v['unixtime']); ?></td>
423                        <td><?php echo date($app->getParam('time_format'), $v['unixtime']); ?></td>
424                        <td><?php echo humanFileSize($v['filesize']); ?></td>
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>
426                    </tr>
427                    <?php
428                }
429                ?>
430                </table>
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>
432                <?php
433            }
434        }
435    }
436
437    /*
438    * Returns a secret hash for the current file.
439    *
440    * @access   public
441    * @author   Quinn Comendant <quinn@strangecode.com>
442    * @since    12 Apr 2006 10:52:35
443    */
444    function _fileHash()
445    {
446        $app =& App::getInstance();
447
448        return md5($app->getParam('signing_key') . $_SERVER['PHP_SELF']);
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    {
461        $app =& App::getInstance();
462
463        if (!file_exists($this->_data_file)) {
464            if (!$this->_initializeDataFile()) {
465                $app->logMsg(sprintf('Initializing content file failed: %s', $this->_data_file), LOG_WARNING, __FILE__, __LINE__);
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)) {
472            $app->logMsg(sprintf('XML_Unserialize error: %s', $status->getMessage()), LOG_WARNING, __FILE__, __LINE__);
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    {
500        $app =& App::getInstance();
501
502        $app->logMsg(sprintf('Initializing data file: %s', $this->_data_file), LOG_INFO, __FILE__, __LINE__);
503        $xml_file_contents = $this->xml_serializer->serialize($this->_data);
504        return $this->_filePutContents($this->_data_file, $xml_file_contents);
505    }
506
507    /**
508     * Saves the POSTed data by overwriting the pedit variables in the
509     * current file.
510     *
511     * @access private
512     * @return bool  False if unauthorized or on failure. True on success.
513     */
514    function _writeData()
515    {
516        $app =& App::getInstance();
517
518        if (!$this->_authorized) {
519            return false;
520        }
521        if ($this->_fileHash() != getFormData('file_hash')) {
522            // Posted data is NOT for this file!
523            $app->logMsg(sprintf('File_hash does not match current file.', null), LOG_WARNING, __FILE__, __LINE__);
524            return false;
525        }
526
527        // Scrub incoming data. Escape tags?
528        $new_data = getFormData('_pedit_data');
529
530        if (is_array($new_data) && !empty($new_data)) {
531            // Make certain a version is created.
532            $this->_deleteOldVersions();
533            if (!$this->_createVersion()) {
534                $app->logMsg(sprintf('Failed creating new version of file.', null), LOG_NOTICE, __FILE__, __LINE__);
535                return false;
536            }
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    {
564        $app =& App::getInstance();
565
566        // Ensure requested filename is within the pedit data dir.
567        if (mb_strpos($filename, $this->getParam('data_dir')) === false) {
568            $app->logMsg(sprintf('Failed writing file outside pedit data_dir: %s', $filename), LOG_ERR, __FILE__, __LINE__);
569            return false;
570        }
571
572        // Recursively create directories.
573        $subdirs = preg_split('!/!', str_replace($this->getParam('data_dir'), '', dirname($filename)), -1, PREG_SPLIT_NO_EMPTY);
574        // Start with the pedit data_dir base.
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)) {
580                    $app->logMsg(sprintf('Failed mkdir: %s', $curr_path), LOG_ERR, __FILE__, __LINE__);
581                    return false;
582                }
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)) {
589                fwrite($fp, $content, mb_strlen($content));
590                flock($fp, LOCK_UN);
591            } else {
592                $app->logMsg(sprintf('Could not lock file for writing: %s', $filename), LOG_ERR, __FILE__, __LINE__);
593                return false;
594            }
595            fclose($fp);
596            // Success!
597            $app->logMsg(sprintf('Wrote to file: %s', $filename), LOG_DEBUG, __FILE__, __LINE__);
598            return true;
599        } else {
600            $app->logMsg(sprintf('Could not open file for writing: %s', $filename), LOG_ERR, __FILE__, __LINE__);
601            return false;
602        }
603    }
604
605    /**
606     * Makes a copy of the current file with the unix timestamp appended to the
607     * filename.
608     *
609     * @access private
610     * @return bool  False on failure. True on success.
611     */
612    function _createVersion()
613    {
614        $app =& App::getInstance();
615
616        if (!$this->_authorized) {
617            return false;
618        }
619        if ($this->_fileHash() != getFormData('file_hash')) {
620            // Posted data is NOT for this file!
621            $app->logMsg(sprintf('File_hash does not match current file.', null), LOG_ERR, __FILE__, __LINE__);
622            return false;
623        }
624
625        // Ensure current data file exists.
626        if (!file_exists($this->_data_file)) {
627            $app->logMsg(sprintf('Data file does not yet exist: %s', $this->_data_file), LOG_NOTICE, __FILE__, __LINE__);
628            return false;
629        }
630
631        // Do the actual copy. File naming scheme must be consistent!
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)) {
635            $app->logMsg(sprintf('Failed copying new version: %s -> %s', $this->_data_file, $version_file), LOG_ERR, __FILE__, __LINE__);
636            return false;
637        }
638
639        return true;
640    }
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    {
652        $app =& App::getInstance();
653
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)) {
664                    $app->logMsg(sprintf('Failed deleting version: %s', $del_file), LOG_ERR, __FILE__, __LINE__);
665                }
666                $oldest = array_pop($version_files);
667            }
668        }
669    }
670
671    /**
672     * Returns an array of all archived versions of the current file,
673     * sorted with newest versions at the top of the array.
674     *
675     * @access private
676     * @return array  Array of versions.
677     */
678    function _getVersions()
679    {
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,
687                    'unixtime' => $time[1],
688                    'filesize' => filesize(dirname($this->_data_file) . '/' . $version_file)
689                );
690            }
691        }
692
693        if (is_array($version_files) && !empty($version_files)) {
694            array_multisort($version_files, SORT_DESC);
695            return $version_files;
696        } else {
697            return array();
698        }
699    }
700
701    /**
702     * Makes a version backup of the current file, then copies the specified
703     * archived version over the current file.
704     *
705     * @access private
706     * @param string $version    Unix timestamp of archived version to restore.
707     * @return bool  False on failure. True on success.
708     */
709    function _restoreVersion($version)
710    {
711        $app =& App::getInstance();
712
713        if (!$this->_authorized) {
714            return false;
715        }
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)) {
722            $app->logMsg(sprintf('Cannot restore non-existent file: %s', $version_file), LOG_NOTICE, __FILE__, __LINE__);
723            return false;
724        }
725
726        // Make certain a version is created.
727        if (!$this->_createVersion()) {
728            $app->logMsg(sprintf('Failed creating new version of file.', null), LOG_ERR, __FILE__, __LINE__);
729            return false;
730        }
731
732        // Do the actual copy.
733        if (!copy($version_file, $this->_data_file)) {
734            $app->logMsg(sprintf('Failed copying old version: %s -> %s', $version_file, $this->_data_file), LOG_ERR, __FILE__, __LINE__);
735            return false;
736        }
737
738        // Success!
739        $app->raiseMsg(sprintf(_("Page has been restored to version %s."), $version), MSG_SUCCESS, __FILE__, __LINE__);
740        return true;
741    }
742
743} // End class.
744
745?>
Note: See TracBrowser for help on using the repository browser.