source: trunk/lib/Lock.inc.php @ 399

Last change on this file since 399 was 396, checked in by anonymous, 12 years ago

Updated copyright date; comments elaboration; spelling fixes.

File size: 14.9 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/**
[138]24 * Lock.inc.php
[1]25 *
[138]26 * The Lock class provides a system for locking abstract DB rows.
[136]27 *
[1]28 * @author  Quinn Comendant <quinn@strangecode.com>
[136]29 * @version 2.1
[1]30 */
[138]31class Lock {
[1]32
33    // Configuration of this object.
34    var $_params = array(
35        'timeout' => 600,
36        'auto_timeout' => 1800,
37        'error_url' => '/lock.php',
38        'db_table' => 'lock_tbl',
[146]39
40        // Automatically create table and verify columns. Better set to false after site launch.
[396]41        // This value is overwritten by the $app->getParam('db_create_tables') setting if it is available.
[146]42        'create_table' => true,
[1]43    );
44
45    // Store lock data from DB.
46    var $data = array();
47
48    // Auth_SQL object from which to access a current user_id.
49    var $_auth;
50
51    /**
52     * This method enforces the singleton pattern for this class.
53     *
[138]54     * @return  object  Reference to the global Lock object.
[1]55     * @access  public
56     * @static
57     */
[380]58    static function &getInstance($auth_object)
[1]59    {
[136]60        static $instance = null;
[42]61
[136]62        if ($instance === null) {
[138]63            $instance = new Lock($auth_object);
[1]64        }
65
[136]66        return $instance;
[1]67    }
68
69    /**
70     * Constructor. Pass an Auth object on which to perform user lookups.
71     *
[136]72     * @param mixed  $auth_object  An Auth_SQL or Auth_FILE object.
[1]73     */
[138]74    function Lock($auth_object)
[1]75    {
[136]76        $app =& App::getInstance();
77
[149]78        if (!method_exists($auth_object, 'get') || !method_exists($auth_object, 'getUsername')) {
[136]79            trigger_error('Constructor not provided a valid Auth_* object.', E_USER_ERROR);
[22]80        }
81
[1]82        $this->_auth = $auth_object;
[42]83
[1]84        // Get create tables config from global context.
[136]85        if (!is_null($app->getParam('db_create_tables'))) {
86            $this->setParam(array('create_table' => $app->getParam('db_create_tables')));
[1]87        }
88    }
89
90    /**
91     * Setup the database table for this class.
92     *
93     * @access  public
94     * @author  Quinn Comendant <quinn@strangecode.com>
95     * @since   26 Aug 2005 17:09:36
96     */
97    function initDB($recreate_db=false)
98    {
[136]99        $app =& App::getInstance();
100        $db =& DB::getInstance();
101
[1]102        static $_db_tested = false;
[42]103
[1]104        if ($recreate_db || !$_db_tested && $this->getParam('create_table')) {
105            if ($recreate_db) {
[136]106                $db->query("DROP TABLE IF EXISTS " . $this->getParam('db_table'));
[201]107                $app->logMsg(sprintf('Dropping and recreating table %s.', $this->getParam('db_table')), LOG_INFO, __FILE__, __LINE__);
[1]108            }
[146]109            $db->query("CREATE TABLE IF NOT EXISTS " . $db->escapeString($this->getParam('db_table')) . " (
[1]110                lock_id int NOT NULL auto_increment,
111                record_table varchar(255) NOT NULL default '',
112                record_key varchar(255) NOT NULL default '',
113                record_val varchar(255) NOT NULL default '',
114                title varchar(255) NOT NULL default '',
115                set_by_admin_id smallint(11) NOT NULL default '0',
116                lock_datetime datetime NOT NULL default '0000-00-00 00:00:00',
117                PRIMARY KEY (lock_id),
118                KEY record_table (record_table),
119                KEY record_key (record_key),
120                KEY record_val (record_val)
121            )");
[42]122
[136]123            if (!$db->columnExists($this->getParam('db_table'), array(
[42]124                'lock_id',
125                'record_table',
126                'record_key',
127                'record_val',
128                'title',
129                'set_by_admin_id',
130                'lock_datetime',
[1]131            ), false, false)) {
[136]132                $app->logMsg(sprintf('Database table %s has invalid columns. Please update this table manually.', $this->getParam('db_table')), LOG_ALERT, __FILE__, __LINE__);
[1]133                trigger_error(sprintf('Database table %s has invalid columns. Please update this table manually.', $this->getParam('db_table')), E_USER_ERROR);
134            }
[42]135        }
[1]136        $_db_tested = true;
137    }
138
139    /**
140     * Set the params of this object.
141     *
142     * @param  array $params   Array of param keys and values to set.
143     */
144    function setParam($params=null)
145    {
146        if (isset($params) && is_array($params)) {
147            // Merge new parameters with old overriding only those passed.
148            $this->_params = array_merge($this->_params, $params);
149        }
150    }
151
152    /**
[136]153     * Return the value of a parameter, if it exists.
[1]154     *
[136]155     * @access public
156     * @param string $param        Which parameter to return.
157     * @return mixed               Configured parameter value.
[1]158     */
159    function getParam($param)
160    {
[136]161        $app =& App::getInstance();
162   
[1]163        if (isset($this->_params[$param])) {
164            return $this->_params[$param];
165        } else {
[146]166            $app->logMsg(sprintf('Parameter is not set: %s', $param), LOG_DEBUG, __FILE__, __LINE__);
[1]167            return null;
168        }
169    }
170
171    /**
172     * Select the lock to manipulate.
173     *
174     * @param mixed  $record_table_or_lock_id  The table containing the record to lock,
175     *                                         or a numeric lock_id.
176     * @param string $record_key  The key column for the record to lock.
177     * @param string $record_val  The value of the key column for the record to lock.
178     * @param string $title       A title to apply to the lock, for display purposes.
179     */
180    function select($record_table_or_lock_id, $record_key=null, $record_val=null)
181    {
[136]182        $app =& App::getInstance();
183        $db =& DB::getInstance();
184
[1]185        $this->initDB();
186
187        // Expire old locks.
188        $this->_auto_timeout();
[42]189
[1]190        if (is_numeric($record_table_or_lock_id) && !isset($record_key) && !isset($record_val)) {
191            // Get lock data by lock_id.
[136]192            $qid = $db->query("
[146]193                SELECT * FROM " . $db->escapeString($this->getParam('db_table')) . "
[136]194                WHERE lock_id = '" . $db->escapeString($record_table_or_lock_id) . "'
[1]195            ");
196        } else {
197            // Get lock data by record specs
[136]198            $qid = $db->query("
[146]199                SELECT * FROM " . $db->escapeString($this->getParam('db_table')) . "
[136]200                WHERE record_table = '" . $db->escapeString($record_table_or_lock_id) . "'
201                AND record_key = '" . $db->escapeString($record_key) . "'
202                AND record_val = '" . $db->escapeString($record_val) . "'
[1]203            ");
204        }
205        if ($this->data = mysql_fetch_assoc($qid)) {
[149]206            $app->logMsg(sprintf('Selecting %slocked record: %s %s %s', ($this->data['set_by_admin_id'] == $this->_auth->get('user_id') ? 'self-' : ''), $record_table_or_lock_id, $record_key, $record_val), LOG_DEBUG, __FILE__, __LINE__);
[22]207            /// FIX ME: What if admin set lock, but public user is current lock user?
[1]208            $this->data['editor'] = $this->_auth->getUsername($this->data['set_by_admin_id']);
209            return true;
210        } else {
[136]211            $app->logMsg(sprintf('No locked record: %s %s %s', $record_table_or_lock_id, $record_key, $record_val), LOG_DEBUG, __FILE__, __LINE__);
[1]212            return false;
213        }
214    }
215
216    /**
217     * Returns true if the record we instantiated with is locked.
218     *
219     * @return bool            True if locked.
220     */
221    function isLocked()
222    {
223        return isset($this->data['lock_id']);
224    }
225
226    /**
[42]227     * Returns the status of who set the lock. Use this to ignore locks set by
[1]228     * the current user.
229     *
230     * @return bool            True if current user set the lock.
231     */
232    function isMine()
233    {
[136]234        $db =& DB::getInstance();
235   
[1]236        $this->initDB();
[42]237
[1]238        if (isset($this->data['lock_id'])) {
[146]239            $qid = $db->query("SELECT * FROM " . $db->escapeString($this->getParam('db_table')) . " WHERE lock_id = '" . $db->escapeString($this->data['lock_id']) . "'");
[1]240            if ($lock = mysql_fetch_assoc($qid)) {
[149]241                return ($lock['set_by_admin_id'] == $this->_auth->get('user_id'));
[1]242            } else {
243                return false;
244            }
245        } else {
246            return false;
247        }
248    }
249
250    /**
251     * Create a new lock for the specified table/key/value.
252     *
253     * @param string $record_table  The table containing the record to lock.
254     * @param string $record_key  The key column for the record to lock.
255     * @param string $record_val  The value of the key column for the record to lock.
256     * @param string $title       A title to apply to the lock, for display purposes.
257     *
258     * @return int            The id for the lock (mysql last insert id).
259     */
260    function set($record_table, $record_key, $record_val, $title='')
[42]261    {
[136]262        $db =& DB::getInstance();
263   
[1]264        $this->initDB();
265
266        // Expire old locks.
267        $this->_auto_timeout();
[42]268
[1]269        // Remove previous locks if exist. Is this better than using a REPLACE INTO?
[136]270        $db->query("
[146]271            DELETE FROM " . $db->escapeString($this->getParam('db_table')) . "
[136]272            WHERE record_table = '" . $db->escapeString($record_table) . "'
273            AND record_key = '" . $db->escapeString($record_key) . "'
274            AND record_val = '" . $db->escapeString($record_val) . "'
[1]275        ");
[42]276
[1]277        // Set new lock.
[136]278        $db->query("
[146]279            INSERT INTO " . $db->escapeString($this->getParam('db_table')) . " (
[1]280                record_table,
281                record_key,
282                record_val,
283                title,
284                set_by_admin_id,
285                lock_datetime
286            ) VALUES (
[136]287                '" . $db->escapeString($record_table) . "',
288                '" . $db->escapeString($record_key) . "',
289                '" . $db->escapeString($record_val) . "',
290                '" . $db->escapeString($title) . "',
[149]291                '" . $db->escapeString($this->_auth->get('user_id')) . "',
[1]292                NOW()
293            )
294        ");
[136]295        $lock_id = mysql_insert_id($db->getDBH());
[42]296
[1]297        // Must register this locked record as the current.
298        $this->select($lock_id);
[42]299
[1]300        return $lock_id;
301    }
302
303    /**
304     * Unlock the currently selected record.
305     */
306    function remove()
307    {
[136]308        $app =& App::getInstance();
309        $db =& DB::getInstance();
310
[1]311        $this->initDB();
312
313        // Expire old locks.
314        $this->_auto_timeout();
[42]315
[1]316        // Delete a specific lock.
[136]317        $db->query("
[146]318            DELETE FROM " . $db->escapeString($this->getParam('db_table')) . "
[136]319            WHERE lock_id = '" . $db->escapeString($this->data['lock_id']) . "'
[1]320        ");
[42]321
[136]322        $app->logMsg(sprintf('Removing lock: %s', $this->data['lock_id']), LOG_DEBUG, __FILE__, __LINE__);
[1]323    }
324
325    /**
326     * Unlock all records, or all records for a specified user.
327     */
328    function removeAll($user_id=null)
[42]329    {
[136]330        $app =& App::getInstance();
331        $db =& DB::getInstance();
332
[1]333        $this->initDB();
334
335        // Expire old locks.
336        $this->_auto_timeout();
[42]337
[1]338        if (isset($user_id)) {
339            // Delete specific user's locks.
[146]340            $db->query("DELETE FROM " . $db->escapeString($this->getParam('db_table')) . " WHERE set_by_admin_id = '" . $db->escapeString($user_id) . "'");
[202]341            $app->logMsg(sprintf('Record locks owned by user %s have been deleted', $this->_auth->getUsername($user_id)), LOG_DEBUG, __FILE__, __LINE__);
[1]342        } else {
343            // Delete ALL locks.
[146]344            $db->query("DELETE FROM " . $db->escapeString($this->getParam('db_table')) . "");
[202]345            $app->logMsg(sprintf('All record locks deleted by user %s', $this->_auth->get('username')), LOG_DEBUG, __FILE__, __LINE__);
[1]346        }
347    }
348
349    /**
[334]350     * Deletes all locks that are older than auto_timeout.
[1]351     */
352    function _auto_timeout()
353    {
[136]354        $db =& DB::getInstance();
355   
[1]356        static $_timeout_run = false;
357
358        $this->initDB();
[42]359
[1]360        if (!$_timeout_run) {
361            // Delete all old locks.
[136]362            $db->query("
[146]363                DELETE FROM " . $db->escapeString($this->getParam('db_table')) . "
[1]364                WHERE DATE_ADD(lock_datetime, INTERVAL '" . $this->getParam('auto_timeout') . "' SECOND) < NOW()
365            ");
366            $_timeout_run = true;
367        }
368    }
369
370    /**
371     * Redirect to record lock error page.
372     */
373    function dieErrorPage()
374    {
[136]375        $app =& App::getInstance();
376
377        $app->dieURL(sprintf('%s?lock_id=%s&boomerang=%s', $this->getParam('error_url'), $this->data['lock_id'], urlencode(absoluteMe())));
[1]378    }
379
380    /**
381     * Print error page.
382     */
383    function printErrorHTML()
384    {
[136]385        $app =& App::getInstance();
386
[1]387        ?>
[185]388        <form method="post" action="<?php echo oTxt($_SERVER['PHP_SELF']); ?>">
[136]389        <?php $app->printHiddenSession() ?>
[1]390        <input type="hidden" name="lock_id" value="<?php echo $this->getID(); ?>" />
[42]391
[1]392        <p><?php
[141]393        printf(_("The record %s is currently being edited by %s (%d minutes elapsed). You cannot modify the record while it is locked by another user."),
[42]394            $this->getTitle(),
395            $this->getEditor(),
[1]396            date('i', $this->getSecondsElapsed() + 60)
397        );
398        ?></p>
[42]399
[235]400        <?php if ($this->getSecondsElapsed() >= $this->getParam('timeout')) { ?>
[1]401        <p><?php printf(_("You can forcibly unlock the record if you believe the editing session has expired. You might want to confirm with %s before doing this."), $this->getEditor()) ?></p>
[28]402        <input type="submit" name="unlock" value="<?php echo _("Unlock"); ?>" />
[1]403        <?php } ?>
[42]404
[28]405        <input type="submit" name="cancel" value="<?php echo _("Cancel"); ?>" />
[1]406        </form>
407        <?php
408    }
409
410    /**
411     * Return lock_id of locked record.
412     */
413    function getID()
414    {
415        return $this->data['lock_id'];
416    }
417
418    /**
419     * Return title of locked record.
420     */
421    function getTitle()
422    {
423        return $this->data['title'];
424    }
425
426    /**
427     * Return administrator username for locked record.
428     */
429    function getEditor()
430    {
431        return $this->data['editor'];
432    }
433
434    /**
435     * Return total seconds since the record was locked.
436     */
437    function getSecondsElapsed()
438    {
[235]439        if (isset($this->data['lock_datetime']) && strtotime($this->data['lock_datetime']) < time()) {
[1]440            return time() - strtotime($this->data['lock_datetime']);
441        } else {
442            return 0;
443        }
444    }
445
446
447} // End of class.
448?>
Note: See TracBrowser for help on using the repository browser.