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

Last change on this file since 354 was 334, checked in by quinn, 16 years ago

Fixed lots of misplings. I'm so embarrassed! ;P

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