source: trunk/lib/RecordLock.inc.php @ 1

Last change on this file since 1 was 1, checked in by scdev, 19 years ago

Initial import.

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