source: branches/eli_branch/lib/Lock.inc.php @ 439

Last change on this file since 439 was 439, checked in by anonymous, 11 years ago

added public and private keywords to all properties and methods, changed old classname constructor function to construct, removed more ?> closing tags

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