Changeset 92


Ignore:
Timestamp:
Apr 13, 2006 12:00:40 AM (18 years ago)
Author:
scdev
Message:

Q - Complete rebuild of PEdit

Location:
trunk/lib
Files:
4 edited

Legend:

Unmodified
Added
Removed
  • trunk/lib/CSS.inc.php

    r91 r92  
    1414    var $_css_files = array('default' => array());
    1515
    16     // Cache style sheets?
     16    // CSS object parameters.
    1717    var $_params = array(
    1818        'cache_css' => true,
  • trunk/lib/Hierarchy.inc.php

    r91 r92  
    677677     * @return array  Details of from the node table of all nodes below the
    678678     *                specified node: (type, id, title, indent level, selected status)
    679      *
    680      * FIX ME: $include_curr doesn't work because the current node will break with an infinite loop.
    681679     */
    682680    function &getNodeList($preselected=null, $child_type=null, $child_id=null, $type_constraint=null, $include_curr=false, $order='', $_indent=0, $_return_flag=true)
     
    709707            for ($i=0; $i<$num_children; $i++) {
    710708
    711                 $row = $my_children[$i];
    712709                // Preventing circular references.
    713                 if ($row['child_type'] == $child_type && $row['child_id'] == $child_id) {
    714                     App::logMsg(sprintf(_("Circular reference detected: %s has itself as a parent."), $this->toStringID($row['child_type'], $row['child_id'])), LOG_ERR, __FILE__, __LINE__);
     710                if ($my_children[$i]['child_type'] == $child_type && $my_children[$i]['child_id'] == $child_id && !($_return_flag && $include_curr)) {
     711                    App::logMsg(sprintf(_("Circular reference detected: %s has itself as a parent."), $this->toStringID($my_children[$i]['child_type'], $my_children[$i]['child_id'])), LOG_ERR, __FILE__, __LINE__);
    715712                    continue;
    716713                }
    717                 $row['indent'] = $_indent;
    718                 if (in_array($this->toStringID($row['child_type'], $row['child_id']), $preselected)) {
    719                     $row['selected'] = true;
     714
     715                $my_children[$i]['indent'] = $_indent;
     716                if (in_array($this->toStringID($my_children[$i]['child_type'], $my_children[$i]['child_id']), $preselected)) {
     717                    $my_children[$i]['selected'] = true;
    720718                }
    721                 $output[] = $row;
    722                 unset($row);
    723 
    724                 // This is so we test if each node is a string only once. We store the result in the is_a_leaf array statically.
     719                $output[] = $my_children[$i];
     720
     721                // Test if each node is a string only once. Store the result in the is_a_leaf array statically.
    725722                if (!isset($is_a_leaf[$this->toStringID($my_children[$i]['child_type'], $my_children[$i]['child_id'])])) {
    726723                    $is_a_leaf[$this->toStringID($my_children[$i]['child_type'], $my_children[$i]['child_id'])] = $this->isLeaf($my_children[$i]['child_type'], $my_children[$i]['child_id']);
  • trunk/lib/PEdit.inc.php

    r91 r92  
    55 * circumstances, but an authenticated user can 'edit' the document--
    66 * data stored in vars will be shown in html form elements to be editied
    7  * and saved. On save, a mass search and replace is complated to inssert the new
    8  * data into the old file. A copy of the previous version is saved with the unix
     7 * and saved. Posted data is stored in XML format in a specified data dir.
     8 * A copy of the previous version is saved with the unix
    99 * timestamp as part of the filename. This allows reverting to previous versions.
    1010 *
     
    1212 * and call printing/editing functions where you want data and forms to
    1313 * show up. Below is an example of use:
    14  *
    15  *  // Initialize.
    16  require_once 'codebase/lib/PEdit.inc.php';
    17  *  $p = new PEdit($auth->hasClearance('pedit'));
    18  *
    19  *  $title = <<<P_E_D_I_T_title
    20  *  Using Burritos to Improve Student Learnin'
    21  *  P_E_D_I_T_title;
    22  *  $p->set($title, 'title', 'textbox');
    23  *
    24  *  // Begin content. Include a header or something right here.
    25  *  $p->printContent('title');
    26  *
    27  *  // Prints beginning form tags and special hidden forms. (Only happens if page is NOT a archived version.)
    28  *  $p->formBegin();
    29  *
    30  *  // Print editing form elements. (Only happens if op == Edit.)
    31  *  $p->printForm('title');
    32  *
    33  *  // Print versions list. (Only happens if op == Versions.)
    34  *  $p->printVersions();
    35  *
    36  *  // Prints ending form tags and command buttons.(Only happens if page is NOT a archived version.)
    37  *  $p->formEnd();
    38  *
     14 
     15 // Initialize PEdit object.
     16 require_once 'codebase/lib/PEdit.inc.php';
     17 $pedit = new PEdit(array(
     18     'data_dir' => COMMON_BASE . '/html/_pedit_data',
     19     'authorized' => true,
     20 ));
     21 
     22 // Setup content data types.
     23 $pedit->set('title');
     24 $pedit->set('content', array('type' => 'textarea'));
     25 
     26 // After setting all parameters and data, load the data.
     27 $pedit->start();
     28 
     29 // Print content.
     30 echo $pedit->get('title');
     31 echo $pedit->get('content');
     32 
     33 // Print additional PEdit functionality.
     34 $pedit->formBegin();
     35 $pedit->printAllForms();
     36 $pedit->printVersions();
     37 $pedit->formEnd();
     38
    3939 * @author  Quinn Comendant <quinn@strangecode.com>
    4040 * @concept Beau Smith <beau@beausmith.com>
    41  * @version 1.1
     41 * @version 2.0
    4242 */
    43 class PEdit
    44 {
    45     var $_data = array();       // Array to store editable data.
    46     var $_filename = '';        // Full file path to current file.
    47     var $_authorized = false;   // User is authenticated to see extended functions.
    48     var $versions_min_qty = 20; // Keep at least this many versions of each file.
    49     var $versions_min_days = 10;// Keep ALL versions within this many days, even if MORE than versions_min_qty.
    50 
    51     // Tags that are not stripped from the POSTed data.
    52     var $allowed_tags = '<p><h1><h2><h3><h4><h5><h6><div><br><hr><a><img><i><em><b><strong><small><blockquote><ul><ol><li><dl><dt><dd><map><area><table><tr><td>';
    53 
    54 
    55     /**
    56      * Constructs a new PEdit object. Initializes what file is being operated on
    57      * (SCRIPT_FILENAME) and what that operation is. The two
     43class PEdit {
     44
     45    // PEdit object parameters.
     46    var $_params = array(
     47        'data_dir' => '',
     48        'character_set' => 'utf-8',
     49        'versions_min_qty' => 20,
     50        'versions_min_days' => 10,
     51    );
     52
     53    var $_data = array(); // Array to store loaded data.
     54    var $_data_file = ''; // Full file path to the pedit data file.
     55    var $_authorized = false; // User is authenticated to see extended functions.
     56    var $_data_loaded = false;
     57    var $op = '';
     58
     59    /**
     60     * Constructs a new PEdit object. Initializes what file is being operated with
     61     * (PHP_SELF) and what that operation is. The two
    5862     * operations that actually modify data (save, restore) are treated differently
    59      * than view operations (versions, '' - default). They die redirect so you see
     63     * than view operations (versions, view, default). They die redirect so you see
    6064     * the page you just modified.
    6165     *
    6266     * @access public
    63      *
    6467     * @param optional array $params  A hash containing connection parameters.
    6568     */
    66     function PEdit($authorized=false)
    67     {
    68         if ($authorized === true) {
     69    function PEdit($params)
     70    {
     71        $this->setParam($params);
     72       
     73        if ($this->getParam('authorized') === true) {
    6974            $this->_authorized = true;
    7075        }
    71 
    72         $this->_filename = $_SERVER['SCRIPT_FILENAME'];
    73         if (empty($this->_filename)) {
    74             App::logMsg(sprintf('PEdit error: server variable SCRIPT_FILENAME must be defined.', null), LOG_WARNING, __FILE__, __LINE__);
    75             die;
    76         }
    77 
     76       
     77        // Setup PEAR XML libraries.
     78        require_once 'XML/Unserializer.php';
     79        $this->xml_unserializer =& new XML_Unserializer(array(
     80            XML_UNSERIALIZER_OPTION_COMPLEXTYPE => 'array',
     81        ));
     82        require_once 'XML/Serializer.php';
     83        $this->xml_serializer =& new XML_Serializer(array(
     84            XML_SERIALIZER_OPTION_INDENT        => '    ',
     85            XML_SERIALIZER_OPTION_RETURN_RESULT => true
     86        ));
     87    }
     88   
     89    /**
     90     * Set (or overwrite existing) parameters by passing an array of new parameters.
     91     *
     92     * @access public
     93     * @param  array    $params     Array of parameters (key => val pairs).
     94     */
     95    function setParam($params)
     96    {
     97        if (isset($params) && is_array($params)) {
     98            // Merge new parameters with old overriding only those passed.
     99            $this->_params = array_merge($this->_params, $params);
     100        } else {
     101            App::logMsg(sprintf('Parameters are not an array: %s', $params), LOG_WARNING, __FILE__, __LINE__);
     102        }
     103    }
     104
     105    /**
     106     * Return the value of a parameter, if it exists.
     107     *
     108     * @access public
     109     * @param string $param        Which parameter to return.
     110     * @return mixed               Configured parameter value.
     111     */
     112    function getParam($param)
     113    {
     114        if (isset($this->_params[$param])) {
     115            return $this->_params[$param];
     116        } else {
     117            App::logMsg(sprintf('Parameter is not set: %s', $param), LOG_NOTICE, __FILE__, __LINE__);
     118            return null;
     119        }
     120    }
     121   
     122    /*
     123    * Load the pedit data and run automatic functions.
     124    *
     125    * @access   public
     126    * @author   Quinn Comendant <quinn@strangecode.com>
     127    * @since    12 Apr 2006 12:43:47
     128    */
     129    function start($initialize_data_file=false)
     130    {
     131        if (!is_dir($this->getParam('data_dir'))) {
     132            trigger_error(sprintf('PEdit data directory not found: %s', $this->getParam('data_dir')), E_USER_WARNING);
     133        }
     134       
     135        // The location of the data file. (i.e.: "COMMON_DIR/html/_pedit_data/news/index.xml")
     136        $this->_data_file = sprintf('%s%s.xml', $this->getParam('data_dir'), $_SERVER['PHP_SELF']);
     137
     138        // op is used throughout the script to determine state.
    78139        $this->op = getFormData('op');
    79140
     141        // Automatic functions based on state.
    80142        switch ($this->op) {
    81143        case 'Save' :
     
    85147            break;
    86148        case 'Restore' :
    87             if ($this->_restoreVersion(getFormData('with_file'))) {
     149            if ($this->_restoreVersion(getFormData('version'))) {
    88150                App::dieURL($_SERVER['PHP_SELF']);
    89151            }
    90152            break;
     153        case 'View' :
     154            $this->_data_file = sprintf('%s%s__%s.xml', $this->getParam('data_dir'), $_SERVER['PHP_SELF'], getFormData('version'));
     155            App::raiseMsg(sprintf(_("This is <em><strong>only a preview</strong></em> of version %s."), getFormData('version')), MSG_NOTICE, __FILE__, __LINE__);
     156            break;
     157        }
     158       
     159        // Load data.
     160        $this->_loadDataFile();
     161
     162        if ($initialize_data_file === true) {
     163            $this->_createVersion();
     164            $this->_initializeDataFile();
    91165        }
    92166    }
     
    97171     * @access public
    98172     *
    99      * @param string $content         The variable containing the text to store.
    100      * @param string $name            The name of the variable.
    101      * @param string $type            The type of form element to use.
    102      * @param optional int $form_size The size of the form element.
    103      */
    104     function set($content, $name, $type, $form_size=null)
    105     {
    106         $this->_data[$name] = array(
    107             'type' => $type,
    108             'content' => $content,
    109             'form_size' => $form_size
    110         );
    111     }
    112 
    113     /**
    114      * Stores a checkbox variable in the pedit data array with the content name, and type of form.
    115      *
    116      * @access public
    117      *
    118      * @param string $content            The variable containing the text to store.
    119      * @param string $name               The name of the variable.
    120      * @param string $corresponding_text The text that corresponds to this checkbox.
    121      */
    122     function setCheckbox($content, $name, $corresponding_text)
    123     {
    124         if (isset($content) && isset($name) && isset($corresponding_text)) {
    125             $this->_data[$name] = array(
    126                 'type' => 'checkbox',
    127                 'content' => $content,
    128                 'corresponding_text' => $corresponding_text
    129             );
    130         }
    131     }
    132 
    133     /**
    134      * Tests if we are should display page contents.
    135      *
    136      * @access public
    137      *
    138      * @return bool        true if we are displaying page normally, false if editing page, or viewing versions.
    139      */
    140     function displayMode()
    141     {
     173     * @param string    $content         The variable containing the text to store.
     174     * @param array     $options         Additional options to store with this data.
     175     */
     176    function set($name, $options=array())
     177    {
     178        $name = preg_replace('/\s/', '_', $name);
     179        if (!isset($this->_data[$name])) {
     180            $this->_data[$name] = array_merge(array('content' => ''), $options);
     181        } else {
     182            App::logMsg(sprintf('Duplicate set data: %s', $name), LOG_NOTICE, __FILE__, __LINE__);
     183        }
     184    }
     185
     186    /**
     187     * Returns the contents of a data variable. The variable must first be 'set'.
     188     *
     189     * @access public
     190     * @param string $name   The name of the variable to return.
     191     * @return string        The trimmed content of the named data.
     192     */
     193    function get($name)
     194    {
     195        $name = preg_replace('/\s/', '_', $name);
    142196        if ($this->op != 'Edit' && $this->op != 'Versions' && isset($this->_data[$name]['content'])) {
    143             return true;
    144         }
    145     }
    146 
    147 
    148     /**
    149      * Prints an HTML list of versions of current file, with the filesize
    150      * and links to view and restore the file.
    151      *
    152      * @access public
    153      */
    154     function printVersions()
    155     {
    156         if ($this->_authorized && $this->op == 'Versions') {
    157             // Print versions and commands to view/restore.
    158             $versions = $this->_getVersions();
    159             ?><h1><?php printf(_("%s saved versions of %s"), sizeof($versions), basename($this->_filename)); ?></h1><?php
    160             if (is_array($versions) && !empty($versions)) {
    161                 ?><table border="0" cellspacing="0" cellpadding="4"><?php
    162                 foreach ($versions as $v) {
    163                     ?>
    164                     <tr>
    165                     <td nowrap="nowrap"><p><?php echo date('r', $v['unixtime']); ?></p></td>
    166                     <td nowrap="nowrap"><p>&nbsp;&nbsp;&nbsp;<?php printf(_("%s bytes"), $v['filesize']); ?></p></td>
    167                     <td nowrap="nowrap"><p>&nbsp;&nbsp;&nbsp;[<a href="<?php echo App::oHREF(dirname($_SERVER['PHP_SELF']) . (preg_match('!/$!', dirname($_SERVER['PHP_SELF'])) ? '' : '/') . $v['filename']); ?>" target="_blank"><?php echo _("view"); ?></a>]</p></td>
    168                     <td nowrap="nowrap"><p>&nbsp;&nbsp;&nbsp;[<a href="<?php echo App::oHREF($_SERVER['PHP_SELF'] . '?op=Restore&with_file=' . $v['filename'] . '&file_hash=' . md5('frog_guts' . $this->_filename)); ?>"><?php echo _("restore"); ?></a>]</p></td>
    169                     </tr>
    170                     <?php
    171                 }
    172                 ?></table><?php
    173             ?><div class="help"><?php printf(_("When there are more than %s versions, those over %s days old are deleted."), $this->versions_min_qty, $this->versions_min_days); ?></div><?php
    174             }
    175         }
    176     }
    177 
    178     /**
    179      * Returns the contents of a data variable. The variable must first be 'set'.
    180      *
    181      * @access public
    182      *
    183      * @param string $name   The name of the variable to return.
    184      *
    185      * @return string        The trimmed content of the named data.
    186      */
    187     function getContent($name, $preserve_html=true)
    188     {
    189         if ($this->op != 'Edit' && $this->op != 'Versions' && isset($this->_data[$name]['content'])) {
    190             // Print content.
    191             switch ($this->_data[$name]['type']) {
    192             case 'checkbox' :
    193                 return 'off' == $this->_data[$name]['content'] ? '' : oTxt($this->_data[$name]['corresponding_text'], $preserve_html);
    194             default :
    195                 return trim(oTxt($this->_data[$name]['content'], $preserve_html));
    196             }
    197         }
    198     }
    199 
    200     /**
    201      * Prints the contents of a data variable. The variable must first be 'set'.
    202      *
    203      * @access public
    204      *
    205      * @param string $name      The name of the variable to print.
    206      */
    207     function printContent($name, $preserve_html=true)
    208     {
    209         echo $this->getContent($name, $preserve_html);
    210     }
    211 
    212     /**
    213      * Prints the HTML forms corresponding to pedit variables. Each variable
    214      * must first be 'set'.
    215      *
    216      * @access public
    217      *
    218      * @param string $name      The name of the variable.
    219      */
    220     function printForm($name)
    221     {
    222         if ($this->_authorized && $this->op == 'Edit' && isset($this->_data[$name]['type'])) {
    223             // Print edit form.
    224             switch ($this->_data[$name]['type']) {
    225             case 'textbox' :
    226                 $rows = is_numeric($this->_data[$name]['form_size']) ? $this->_data[$name]['form_size'] : 50;
    227                 echo '<input class="monospaced" style="width: 100%;" type="text" name="data[' . $name . ']" value="' . oTxt($this->_data[$name]['content']) . '" size="' . $rows . '" /><br />';
    228                 break;
    229             case 'textarea' :
    230                 $rows = is_numeric($this->_data[$name]['form_size']) ? $this->_data[$name]['form_size'] : 20;
    231                 echo '<textarea class="monospaced" style="width: 100%;" rows="' . $rows . '" cols="60" name="data[' . $name . ']">' . oTxt($this->_data[$name]['content']) . '</textarea><br />';
    232                 break;
    233             case 'checkbox' :
    234                 $checked = ('off' == $this->_data[$name]['content']) ? '' : 'checked="checked" ';
    235                 // Note hidden form below. If the checkbox is not checked, the hidden variable will send "off" as the variable,
    236                 // otherwise if the form is not posted for that checkbox the update will not occur.
    237                 ?>
    238                 <table border="0" cellspacing="0" cellpadding="2"><tr>
    239                 <td><input type="hidden" name="data[<?php echo $name; ?>]" value="off" /><input type="checkbox" name="data[<?php echo $name; ?>]" <?php echo $checked; ?>/></td>
    240                 <td><?php echo oTxt($this->_data[$name]['corresponding_text']); ?></td>
    241                 </tr></table>
    242                 <?php
    243                 break;
    244             }
    245         }
    246     }
    247 
    248     /**
    249      * Loops through the PEdit data array and prints all the HTML forms corresponding
    250      * to all pedit variables, in the order in which they were 'set'.
    251      *
    252      * @access public
    253      */
    254     function printAllForms()
    255     {
    256         if ($this->_authorized && $this->op == 'Edit' && is_array($this->_data) && !empty($this->_data)) {
    257             foreach ($this->_data as $name=>$d) {
    258                 $this->printForm($name);
    259             }
     197            return $this->_data[$name]['content'];
     198        } else {
     199            return '';
    260200        }
    261201    }
     
    268208    function formBegin()
    269209    {
    270         if (!$this->_authorized || preg_match('/\.php__/', $this->_filename)) {
    271             // Don't show form elements for versioned documents.
     210        if (!$this->_authorized) {
    272211            return false;
    273212        }
    274213        ?>
    275         <form action="<?php echo $_SERVER['PHP_SELF']; ?>" method="post">
    276         <input type="hidden" name="filename" value="<?php echo $this->_filename; ?>" />
    277         <input type="hidden" name="file_hash" value="<?php echo md5('frog_guts' . $this->_filename); ?>" />
     214        <style type="text/css" media="screen">
     215        /* <![CDATA[ */
     216            #sc-pedit-form input[type="text"], textarea {
     217                width: 100%;
     218            }
     219            #sc-pedit-form textarea {
     220                height: 30em;
     221            }
     222        /* ]]> */
     223        </style>
     224       
     225        <form action="<?php echo $_SERVER['PHP_SELF']; ?>" method="post" id="sc-pedit-form">
     226        <input type="hidden" name="filename" value="<?php echo $_SERVER['PHP_SELF']; ?>" />
     227        <input type="hidden" name="file_hash" value="<?php echo $this->_fileHash(); ?>" />
    278228        <?php
    279229        App::printHiddenSession();
     
    281231        case 'Edit' :
    282232            ?>
    283             <div class="pedit_buttons">
    284             <input type="submit" name="op" value="<?php echo _("Save"); ?>" />
    285             <input type="submit" name="op" value="<?php echo _("Cancel"); ?>" />
     233            <div class="sc-pedit-buttons">
     234                <input type="submit" name="op" value="<?php echo _("Save"); ?>" />
     235                <input type="submit" name="op" value="<?php echo _("Cancel"); ?>" />
    286236            </div>
    287237            <?php
    288238            break;
     239        case 'View' :
     240            ?>
     241            <input type="hidden" name="version" value="<?php echo getFormData('version'); ?>" />
     242            <?php
     243            break;
     244        }
     245    }
     246
     247    /**
     248     * Loops through the PEdit data array and prints all the HTML forms corresponding
     249     * to all pedit variables, in the order in which they were 'set'.
     250     *
     251     * @access public
     252     */
     253    function printAllForms()
     254    {
     255        if ($this->_authorized && $this->op == 'Edit' && is_array($this->_data) && $this->_data_loaded) {
     256            foreach ($this->_data as $name=>$d) {
     257                $this->printForm($name);
     258            }
     259        }
     260    }
     261
     262    /**
     263     * Prints the HTML forms corresponding to pedit variables. Each variable
     264     * must first be 'set'.
     265     *
     266     * @access public
     267     * @param string $name      The name of the variable.
     268     * @param string $type      Type of form to print. Currently only 'text' and 'textarea' supported.
     269     */
     270    function printForm($name, $type='text')
     271    {
     272        if ($this->_authorized && $this->op == 'Edit' && $this->_data_loaded) {
     273            $type = (isset($this->_data[$name]['type'])) ? $this->_data[$name]['type'] : $type;
     274            // Print edit form.
     275            switch ($type) {
     276            case 'text' :
     277            default :
     278                ?><br /><label><?php echo ucfirst(str_replace('_', ' ', $name)); ?> <input type="text" name="_pedit_data[<?php echo $name; ?>]" id="sc-pedit-<?php echo $name; ?>" value="<?php echo oTxt($this->_data[$name]['content']); ?>" /></label><?php
     279                break;
     280            case 'textarea' :
     281                ?><br /><label><?php echo ucfirst(str_replace('_', ' ', $name)); ?> <textarea name="_pedit_data[<?php echo $name; ?>]" id="sc-pedit-<?php echo $name; ?>"><?php echo oTxt($this->_data[$name]['content']); ?></textarea></label><?php
     282                break;
     283            }
    289284        }
    290285    }
     
    298293    function formEnd()
    299294    {
    300         if (!$this->_authorized || preg_match('/\.php__/', $this->_filename)) {
     295        if (!$this->_authorized || preg_match('/\.php__/', $_SERVER['PHP_SELF'])) {
    301296            // Don't show form elements for versioned documents.
    302297            return false;
     
    305300        case 'Edit' :
    306301            ?>
    307             <div class="pedit_buttons">
    308             <input type="submit" name="op" value="<?php echo _("Save"); ?>" />
    309             <input type="submit" name="op" value="<?php echo _("Cancel"); ?>" />
     302            <div class="sc-pedit-buttons">
     303                <input type="submit" name="op" value="<?php echo _("Save"); ?>" />
     304                <input type="submit" name="op" value="<?php echo _("Cancel"); ?>" />
    310305            </div>
    311306            </form>
     
    314309        case 'Versions' :
    315310            ?>
    316             <div class="pedit_buttons">
    317             <input type="submit" name="op" value="<?php echo _("Cancel"); ?>" />
     311            <div class="sc-pedit-buttons">
     312                <input type="submit" name="op" value="<?php echo _("Cancel"); ?>" />
    318313            </div>
    319314            </form>
    320315            <?php
    321316            break;
     317        case 'View' :
     318            ?>
     319            <div class="sc-pedit-buttons">
     320                <input type="submit" name="op" value="<?php echo _("Restore"); ?>" />
     321                <input type="submit" name="op" value="<?php echo _("Cancel"); ?>" />
     322            </div>
     323            <?php
     324            break;
    322325        default :
    323326            ?>
    324             <div class="pedit_buttons">
    325             <input type="submit" name="op" value="<?php echo _("Edit"); ?>" />
    326             <input type="submit" name="op" value="<?php echo _("Versions"); ?>" />
     327            <div class="sc-pedit-buttons">
     328                <input type="submit" name="op" value="<?php echo _("Edit"); ?>" />
     329                <input type="submit" name="op" value="<?php echo _("Versions"); ?>" />
    327330            </div>
    328331            </form>
     
    332335
    333336    /**
     337     * Prints an HTML list of versions of current file, with the filesize
     338     * and links to view and restore the file.
     339     *
     340     * @access public
     341     */
     342    function printVersions()
     343    {
     344        if ($this->_authorized && $this->op == 'Versions') {
     345            // Print versions and commands to view/restore.
     346            $version_files = $this->_getVersions();
     347            ?><h1><?php printf(_("%s saved versions of %s"), sizeof($version_files), basename($_SERVER['PHP_SELF'])); ?></h1><?php
     348            if (is_array($version_files) && !empty($version_files)) {
     349                ?><table id="sc-pedit-versions"><?php
     350                foreach ($version_files as $v) {
     351                    ?>
     352                    <tr>
     353                        <td><?php echo date('r', $v['unixtime']); ?></td>
     354                        <td><?php printf(_("%s bytes"), $v['filesize']); ?></td>
     355                        <td><a href="<?php echo App::oHREF($_SERVER['PHP_SELF'] . '?op=View&version=' . $v['unixtime'] . '&file_hash=' . $this->_fileHash()); ?>"><?php echo _("View"); ?></a></td>
     356                        <td><a href="<?php echo App::oHREF($_SERVER['PHP_SELF'] . '?op=Restore&version=' . $v['unixtime'] . '&file_hash=' . $this->_fileHash()); ?>"><?php echo _("Restore"); ?></a></td>
     357                    </tr>
     358                    <?php
     359                }
     360                ?></table><?php
     361            ?><div class="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_qty')); ?></div><?php
     362            }
     363        }
     364    }
     365
     366    /*
     367    * Returns a secreat hash for the current file.
     368    *
     369    * @access   public
     370    * @author   Quinn Comendant <quinn@strangecode.com>
     371    * @since    12 Apr 2006 10:52:35
     372    */
     373    function _fileHash()
     374    {
     375        return md5(App::getParam('signing_key') . $_SERVER['PHP_SELF']);
     376    }
     377
     378    /*
     379    * Load the XML data file into $this->_data.
     380    *
     381    * @access   public
     382    * @return   bool    false on error
     383    * @author   Quinn Comendant <quinn@strangecode.com>
     384    * @since    11 Apr 2006 20:36:26
     385    */
     386    function _loadDataFile()
     387    {
     388        if (!file_exists($this->_data_file)) {
     389            if (!$this->_initializeDataFile()) {
     390                App::logMsg(sprintf('Initializing content file failed: %s', $this->_data_file), LOG_WARNING, __FILE__, __LINE__);
     391                return false;
     392            }
     393        }
     394        $xml_file_contents = file_get_contents($this->_data_file);
     395        $status = $this->xml_unserializer->unserialize($xml_file_contents, false);   
     396        if (PEAR::isError($status)) {
     397            App::logMsg(sprintf('XML_Unserialize error: %s', $status->getMessage()), LOG_WARNING, __FILE__, __LINE__);
     398            return false;
     399        }
     400        $xml_file_data = $this->xml_unserializer->getUnserializedData();
     401
     402        // Only load data specified with set(), even though there may be more in the xml file.
     403        foreach ($this->_data as $name => $initial_data) {
     404            if (isset($xml_file_data[$name])) {
     405                $this->_data[$name] = array_merge($initial_data, $xml_file_data[$name]);
     406            } else {
     407                $this->_data[$name] = $initial_data;
     408            }
     409        }
     410
     411        $this->_data_loaded = true;
     412        return true;
     413    }
     414   
     415    /*
     416    * Start a new data file.
     417    *
     418    * @access   public
     419    * @return   The success value of both xml_serializer->serialize() and _filePutContents()
     420    * @author   Quinn Comendant <quinn@strangecode.com>
     421    * @since    11 Apr 2006 20:53:42
     422    */
     423    function _initializeDataFile()
     424    {
     425        App::logMsg(sprintf('Initializing data file: %s', $this->_data_file), LOG_INFO, __FILE__, __LINE__);
     426        $xml_file_contents = $this->xml_serializer->serialize($this->_data);
     427        return $this->_filePutContents($this->_data_file, $xml_file_contents);
     428    }
     429
     430    /**
    334431     * Saves the POSTed data by overwriting the pedit variables in the
    335432     * current file.
    336433     *
    337434     * @access private
    338      *
    339435     * @return bool  False if unauthorized or on failure. True on success.
    340436     */
     
    344440            return false;
    345441        }
    346         if (md5('frog_guts' . $this->_filename) != getFormData('file_hash')) {
     442        if ($this->_fileHash() != getFormData('file_hash')) {
    347443            // Posted data is NOT for this file!
    348             trigger_error('PEdit error: file_hash does not match current file.', E_USER_WARNING);
    349             return false;
    350         }
    351         $whole_file = file_get_contents($this->_filename);
    352         $new_data = getFormData('data');
    353         $search = array();
    354         $replace = array();
     444            App::logMsg(sprintf('File_hash does not match current file.', null), LOG_WARNING, __FILE__, __LINE__);
     445            return false;
     446        }
     447
     448        // Scrub incoming data. Escape tags?
     449        $new_data = getFormData('_pedit_data');
     450
    355451        if (is_array($new_data) && !empty($new_data)) {
    356             foreach ($new_data as $name=>$d) {
    357                 if ('' != $d && !preg_match('/P_E_D_I_T_/', $d)) {
    358                     // If the new posted data is not empty, and the heredoc identifer is not in it.
    359                     $block_identifier = 'P_E_D_I_T_' . preg_quote($name);
    360                     if (substr_count($whole_file, $block_identifier) != 2) {
    361                         App::logMsg(sprintf('PEdit error: more than one %s heredoc identifier found.', $block_identifier), LOG_NOTICE, __FILE__, __LINE__);
    362                         return false;
    363                     }
    364                     // Strip extra linefeeds. For some reason POST data has double EOL chars when writing to a unix file.
    365                     $d = preg_replace("/[\n\r]{2,}/", "\n", $d);
    366                     $search[] = "/$block_identifier.*?\n$block_identifier;/s";
    367                     $replace[] = "$block_identifier\n" . strip_tags($d, $this->allowed_tags) . "\n$block_identifier;";
     452            // Make certain a version is created.
     453            $this->_deleteOldVersions();
     454            if (!$this->_createVersion()) {
     455                App::logMsg(sprintf('Failed creating new version of file.', null), LOG_NOTICE, __FILE__, __LINE__);
     456                return false;
     457            }
     458           
     459            // Collect posted data that is already specified in _data (by set()).
     460            foreach ($new_data as $name => $content) {
     461                if (isset($this->_data[$name])) {
     462                    $this->_data[$name]['content'] = $content;
    368463                }
    369464            }
    370 
    371             // Search and replace all blocks.
    372             $whole_file = preg_replace($search, $replace, $whole_file);
    373 
    374             // Probably unnecessary, testing if resulting file is empty or smaller than input.
    375             if (strlen($whole_file) < strlen(strip_tags(serialize($new_data), $this->allowed_tags))) {
    376                 App::logMsg(sprintf('PEdit error: saved file size (%s) is less than input data size (%s).', strlen($whole_file), strlen(strip_tags(serialize($new_data), $this->allowed_tags))), LOG_NOTICE, __FILE__, __LINE__);
    377                 return false;
    378             }
    379 
    380             // Make certain a version is created.
    381             if (! $this->_createVersion()) {
    382                 App::logMsg(sprintf('PEdit error: failed creating new version of file.', null), LOG_NOTICE, __FILE__, __LINE__);
    383                 return false;
    384             }
    385 
    386             // Open file for writing and truncate to zero length.
    387             if (is_writable($this->_filename) && $fp = fopen($this->_filename, 'w')) {
    388                 if (flock($fp, LOCK_EX)) {
    389                     fwrite($fp, $whole_file, strlen($whole_file));
    390                     flock($fp, LOCK_UN);
    391                 } else {
    392                     App::logMsg(sprintf('PEdit error: could not lock file for writing: %s', $this->_filename), LOG_NOTICE, __FILE__, __LINE__);
     465           
     466            if (is_array($this->_data) && !empty($this->_data)) {
     467                $xml_file_contents = $this->xml_serializer->serialize($this->_data);
     468                return $this->_filePutContents($this->_data_file, $xml_file_contents);
     469            }
     470        }
     471    }
     472   
     473    /*
     474    * Writes content to the specified file.
     475    *
     476    * @access   public
     477    * @param    string  $filename   Path to file.
     478    * @param    string  $content    Data to write into file.
     479    * @return   bool                Success or failure.
     480    * @author   Quinn Comendant <quinn@strangecode.com>
     481    * @since    11 Apr 2006 22:48:30
     482    */
     483    function _filePutContents($filename, $content)
     484    {
     485        // Ensure requested filename is within the pedit data dir.
     486        if (strpos($filename, $this->getParam('data_dir')) === false) {
     487            App::logMsg(sprintf('Failed writing file outside pedit _data_dir: %s', $filename), LOG_ERR, __FILE__, __LINE__);
     488            return false;
     489        }
     490
     491        // Recursively create directories.
     492        $subdirs = preg_split('!/!', str_replace($this->getParam('data_dir'), '', dirname($filename)), -1, PREG_SPLIT_NO_EMPTY);
     493        // Start with the pedit _data_dir base.
     494        $curr_path = $this->getParam('data_dir');
     495        while (!empty($subdirs)) {
     496            $curr_path .= '/' . array_shift($subdirs);
     497            if (!is_dir($curr_path)) {
     498                if (!mkdir($curr_path)) {
     499                    App::logMsg(sprintf('Failed mkdir: %s', $curr_path), LOG_ERR, __FILE__, __LINE__);
    393500                    return false;
    394501                }
    395                 fclose($fp);
    396                 // Success!
    397                 return true;
     502            }
     503        }
     504
     505        // Open file for writing and truncate to zero length.
     506        if ($fp = fopen($filename, 'w')) {
     507            if (flock($fp, LOCK_EX)) {
     508                fwrite($fp, $content, strlen($content));
     509                flock($fp, LOCK_UN);
    398510            } else {
    399                 App::logMsg(sprintf('PEdit error: could not open file for writing: %s', $this->_filename), LOG_NOTICE, __FILE__, __LINE__);
     511                App::logMsg(sprintf('Could not lock file for writing: %s', $filename), LOG_ERR, __FILE__, __LINE__);
    400512                return false;
    401513            }
     514            fclose($fp);
     515            // Success!
     516            App::logMsg(sprintf('Wrote to file: %s', $filename), LOG_DEBUG, __FILE__, __LINE__);
     517            return true;
     518        } else {
     519            App::logMsg(sprintf('Could not open file for writing: %s', $filename), LOG_ERR, __FILE__, __LINE__);
     520            return false;
    402521        }
    403522    }
     
    405524    /**
    406525     * Makes a copy of the current file with the unix timestamp appended to the
    407      * filename. Deletes old versions based on threshold of age and qty.
     526     * filename.
    408527     *
    409528     * @access private
    410      *
    411      * @param optional boolean $do_cleanup    Set to false to turn off the
    412      *                                        cleanup routine.
    413      *
    414      * @return bool  False if unauthorized or on failure. True on success.
    415      */
    416     function _createVersion($do_cleanup=true)
     529     * @return bool  False on failure. True on success.
     530     */
     531    function _createVersion()
    417532    {
    418533        if (!$this->_authorized) {
    419534            return false;
    420535        }
    421         if (md5('frog_guts' . $this->_filename) != getFormData('file_hash')) {
     536        if ($this->_fileHash() != getFormData('file_hash')) {
    422537            // Posted data is NOT for this file!
    423             trigger_error('PEdit error: file_hash does not match current file.', E_USER_WARNING);
    424             return false;
    425         }
    426         $versions = $this->_getVersions();
    427 
    428         // Clean up old versions.
    429         if (is_array($versions) && sizeof($versions) > $this->versions_min_qty && $do_cleanup) {
     538            App::logMsg(sprintf('File_hash does not match current file.', null), LOG_ERR, __FILE__, __LINE__);
     539            return false;
     540        }
     541
     542        // Ensure current data file exists.
     543        if (!file_exists($this->_data_file)) {
     544            App::logMsg(sprintf('Data file does not yet exist: %s', $this->_data_file), LOG_NOTICE, __FILE__, __LINE__);
     545            return false;
     546        }
     547
     548        // Do the actual copy. File naming scheme must be consistent!
     549        // filename.php.xml becomes filename.php__1124124128.xml
     550        $version_file = sprintf('%s__%s.xml', preg_replace('/\.xml$/', '', $this->_data_file), time());
     551        if (!copy($this->_data_file, $version_file)) {
     552            App::logMsg(sprintf('Failed copying new version: %s -> %s', $this->_data_file, $version_file), LOG_ERR, __FILE__, __LINE__);
     553            return false;
     554        }
     555
     556        return true;
     557    }
     558   
     559    /*
     560    * Delete all versions older than versions_min_days if there are more than versions_min_qty or 100.
     561    *
     562    * @access   public
     563    * @return bool  False on failure. True on success.
     564    * @author   Quinn Comendant <quinn@strangecode.com>
     565    * @since    12 Apr 2006 11:08:11
     566    */
     567    function _deleteOldVersions()
     568    {
     569        $version_files = $this->_getVersions();
     570        if (is_array($version_files) && sizeof($version_files) > $this->getParam('versions_min_qty')) {
    430571            // Pop oldest ones off bottom of array.
    431             $oldest = array_pop($versions);
     572            $oldest = array_pop($version_files);
    432573            // Loop while minimum X qty && minimum X days worth but never more than 100 qty.
    433             while ((sizeof($versions) > $this->versions_min_qty
    434             && $oldest['unixtime'] < mktime(date('H'),date('i'),date('s'),date('m'),date('d')-$this->versions_min_days,date('Y')))
    435             || sizeof($versions) > 100) {
    436                 unlink(dirname($this->_filename) . '/' . $oldest['filename']);
    437                 $oldest = array_pop($versions);
    438             }
    439         }
    440 
    441         // Do the actual copy. File naming scheme must be consistent!
    442         if (!copy($this->_filename, $this->_filename . '__' . time() . '.php')) {
    443             trigger_error('PEdit error: failed copying new version. Check file and directory permissions.', E_USER_WARNING);
    444             return false;
    445         }
    446 
    447         return true;
     574            while ((sizeof($version_files) > $this->getParam('versions_min_qty')
     575            && $oldest['unixtime'] < mktime(date('H'), date('i'), date('s'), date('m'), date('d') - $this->getParam('versions_min_days'), date('Y')))
     576            || sizeof($version_files) > 100) {
     577                $del_file = dirname($this->_data_file) . '/' . $oldest['filename'];
     578                if (!unlink($del_file)) {
     579                    App::logMsg(sprintf('Failed deleting version: %s', $del_file), LOG_ERR, __FILE__, __LINE__);
     580                }
     581                $oldest = array_pop($version_files);
     582            }
     583        }
    448584    }
    449585
     
    453589     *
    454590     * @access private
    455      *
    456591     * @return array  Array of versions.
    457592     */
    458593    function _getVersions()
    459594    {
    460         $versions = array();
    461         $dir_handle = opendir(dirname($this->_filename));
    462         while ($dir_handle && ($version = readdir($dir_handle)) !== false) {
    463             if (!preg_match('/^\./', $version) && !is_dir($version) && preg_match('/^' . preg_quote(basename($this->_filename)) . '__.*/', $version)) {
    464                 preg_match('/.+__(\d+)\.php/', $version, $time);
    465                 $versions[] = array(
    466                     'filename' => $version,
     595        $version_files = array();
     596        $dir_handle = opendir(dirname($this->_data_file));
     597        $curr_file_preg_pattern = sprintf('/^%s__(\d+).xml$/', preg_quote(basename($_SERVER['PHP_SELF'])));
     598        while ($dir_handle && ($version_file = readdir($dir_handle)) !== false) {
     599            if (!preg_match('/^\./', $version_file) && !is_dir($version_file) && preg_match($curr_file_preg_pattern, $version_file, $time)) {
     600                $version_files[] = array(
     601                    'filename' => $version_file,
    467602                    'unixtime' => $time[1],
    468                     'filesize' => filesize(dirname($this->_filename) . '/' . $version)
     603                    'filesize' => filesize(dirname($this->_data_file) . '/' . $version_file)
    469604                );
    470605            }
    471606        }
    472607
    473         if (is_array($versions) && !empty($versions)) {
    474             array_multisort($versions, SORT_DESC);
    475             return $versions;
     608        if (is_array($version_files) && !empty($version_files)) {
     609            array_multisort($version_files, SORT_DESC);
     610            return $version_files;
    476611        } else {
    477612            return array();
     
    484619     *
    485620     * @access private
    486      *
    487      * @param string $with_file    Filename of archived version to restore.
    488      *
    489      * @return bool  False if unauthorized. True on success.
    490      */
    491     function _restoreVersion($with_file)
     621     * @param string $version    Unix timestamp of archived version to restore.
     622     * @return bool  False on failure. True on success.
     623     */
     624    function _restoreVersion($version)
    492625    {
    493626        if (!$this->_authorized) {
    494627            return false;
    495628        }
    496 
    497         if (is_writable($this->_filename)) {
    498             // Make certain a version is created.
    499             if (! $this->_createVersion(false)) {
    500                 App::logMsg(sprintf('PEdit error: failed creating new version of file.', null), LOG_NOTICE, __FILE__, __LINE__);
    501                 return false;
    502             }
    503 
    504             // Do the actual copy.
    505             if (!copy(dirname($this->_filename) . '/' . $with_file, $this->_filename)) {
    506                 App::logMsg(sprintf('PEdit error: failed copying old version: %s', $with_file), LOG_NOTICE, __FILE__, __LINE__);
    507                 return false;
    508             }
    509 
    510             // Success!
    511             return true;
    512         } else {
    513             App::logMsg(sprintf('PEdit error: could not open file for writing: %s', $this->_filename), LOG_NOTICE, __FILE__, __LINE__);
    514             return false;
    515         }
     629       
     630        // The file to restore.
     631        $version_file = sprintf('%s__%s.xml', preg_replace('/\.xml$/', '', $this->_data_file), $version);
     632       
     633        // Ensure specified version exists.
     634        if (!file_exists($version_file)) {
     635            App::logMsg(sprintf('Cannot restore non-existant file: %s', $version_file), LOG_NOTICE, __FILE__, __LINE__);
     636            return false;
     637        }
     638
     639        // Make certain a version is created.
     640        if (!$this->_createVersion()) {
     641            App::logMsg(sprintf('Failed creating new version of file.', null), LOG_ERR, __FILE__, __LINE__);
     642            return false;
     643        }
     644
     645        // Do the actual copy.
     646        if (!copy($version_file, $this->_data_file)) {
     647            App::logMsg(sprintf('Failed copying old version: %s -> %s', $version_file, $this->_data_file), LOG_ERR, __FILE__, __LINE__);
     648            return false;
     649        }
     650
     651        // Success!
     652        App::raiseMsg(sprintf(_("Page has been restored to version %s."), $version), MSG_SUCCESS, __FILE__, __LINE__);
     653        return true;
    516654    }
    517655
  • trunk/lib/ScriptTimer.inc.php

    r91 r92  
    11<?php
    22/**
    3  * ScriptTimer.inc.php
     3 * ScriptTimer.inc.php 
    44 * Code by Strangecode :: www.strangecode.com :: This document contains copyrighted information
    55 */
    6 
     6 
    77class ScriptTimer {
    88
    9     var $time_format = '%.10f';
    10     var $_timing_start_times;
    11     var $_timing_stop_times;
    12 
    13     function start($name='default')
    14     {
     9    var $time_format = '%.10s';
     10    var $_timing_start_times = array();
     11    var $_timing_stop_times = array();
     12    var $_timing_cumulative_times = array();
     13   
     14    function start($name='default')
     15    {
    1516        $this->_timing_start_times[$name] = explode(' ', microtime());
    16     }
    17 
    18     function stop($name='default')
    19     {
     17    }
     18   
     19    function stop($name='default')
     20    {
    2021        $this->_timing_stop_times[$name] = explode(' ', microtime());
    21     }
    22 
    23     function getTime($name='default')
    24     {
     22        $this->_timing_cumulative_times[$name] += $this->getTime($name);
     23    }
     24   
     25    function getTime($name='default')
     26    {
    2527        if (!isset($this->_timing_start_times[$name])) {
    2628            return 0;
    2729        }
    28 
    2930        if (!isset($this->_timing_stop_times[$name])) {
    3031            $stop_time = explode(' ', microtime());
     
    3233            $stop_time = $this->_timing_stop_times[$name];
    3334        }
    34 
    3535        // Do the big numbers first so the small ones aren't lost.
    3636        $current = $stop_time[1] - $this->_timing_start_times[$name][1];
    3737        $current += $stop_time[0] - $this->_timing_start_times[$name][0];
     38        return $current;
     39    }
     40   
     41    function printAll($sort_by_time=false)
     42    {
     43        $names = array_map('strlen', array_keys($this->_timing_start_times));
     44        sort($names);
     45        $name_len = end($names);
     46       
     47        if ($sort_by_time) {
     48           arsort($this->_timing_cumulative_times, SORT_NUMERIC);
     49        }
     50       
     51        $this->_timing_cumulative_times["TOTAL"] = array_sum($this->_timing_cumulative_times);
    3852
    39         return sprintf($this->time_format, $current);
    40     }
     53        echo '<pre>';
     54        foreach ($this->_timing_cumulative_times as $name => $time) {
     55            printf("\n%-{$name_len}s $this->time_format", $name, $time);
     56        }
     57        echo '</pre>';
     58    }
    4159}
    4260
Note: See TracChangeset for help on using the changeset viewer.