source: branches/2.0singleton/lib/RecordLock.inc.php @ 132

Last change on this file since 132 was 130, checked in by scdev, 18 years ago

finished updating DB:: to $db->

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