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

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

Tons of little updates and bugfixes. CSS updates to templates and core css files. File upload ability to module_maker. Remade Upload interface to use setParam/getParam.

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.