source: tags/2.1.5/lib/Lock.inc.php

Last change on this file was 377, checked in by quinn, 14 years ago

Releasing trunk as stable version 2.1.5

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