1<?php
2/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
3
4/**
5 * A framework for authentication and authorization in PHP applications
6 *
7 * LiveUser_Admin is meant to be used with the LiveUser package.
8 * It is composed of all the classes necessary to administrate
9 * data used by LiveUser.
10 *
11 * You'll be able to add/edit/delete/get things like:
12 * * Rights
13 * * Users
14 * * Groups
15 * * Areas
16 * * Applications
17 * * Subgroups
18 * * ImpliedRights
19 *
20 * And all other entities within LiveUser.
21 *
22 * At the moment we support the following storage containers:
23 * * DB
24 * * MDB
25 * * MDB2
26 *
27 * But it takes no time to write up your own storage container,
28 * so if you like to use native mysql functions straight, then it's possible
29 * to do so in under a hour!
30 *
31 * PHP version 4 and 5
32 *
33 * LICENSE: This library is free software; you can redistribute it and/or
34 * modify it under the terms of the GNU Lesser General Public
35 * License as published by the Free Software Foundation; either
36 * version 2.1 of the License, or (at your option) any later version.
37 *
38 * This library is distributed in the hope that it will be useful,
39 * but WITHOUT ANY WARRANTY; without even the implied warranty of
40 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
41 * Lesser General Public License for more details.
42 *
43 * You should have received a copy of the GNU Lesser General Public
44 * License along with this library; if not, write to the Free Software
45 * Foundation, Inc., 59 Temple Place, Suite 330, Boston,
46 * MA  02111-1307  USA
47 *
48 *
49 * @category authentication
50 * @package LiveUser_Admin
51 * @author  Markus Wolff <wolff@21st.de>
52 * @author  Helgi �ormar �orbj�rnsson <dufuz@php.net>
53 * @author  Lukas Smith <smith@pooteeweet.org>
54 * @author  Arnaud Limbourg <arnaud@php.net>
55 * @author  Christian Dickmann <dickmann@php.net>
56 * @author  Matt Scifo <mscifo@php.net>
57 * @author  Bjoern Kraus <krausbn@php.net>
58 * @copyright 2002-2006 Markus Wolff
59 * @license http://www.gnu.org/licenses/lgpl.txt
60 * @version CVS: $Id: Simple.php 211320 2006-04-13 13:41:47Z lsmith $
61 * @link http://pear.php.net/LiveUser_Admin
62 */
63
64require_once 'LiveUser/Perm/Simple.php';
65
66/**
67 * Simple permission administration class that features support for
68 * creating, updating, removing and assigning:
69 * - users
70 * - rights
71 * - areas (categorize rights)
72 * - applications (categorize areas)
73 * - translations (for rights, areas, applications and groups)
74 *
75 * This class provides a set of functions for implementing a user
76 * permission management system on live websites. All authorisation
77 * backends/containers must be extensions of this base class.
78 *
79 * @category authentication
80 * @package LiveUser_Admin
81 * @author  Markus Wolff <wolff@21st.de>
82 * @author  Bjoern Kraus <krausbn@php.net>
83 * @author  Helgi �ormar �orbj�rnsson <dufuz@php.net>
84 * @copyright 2002-2006 Markus Wolff
85 * @license http://www.gnu.org/licenses/lgpl.txt
86 * @version Release: @package_version@
87 * @link http://pear.php.net/LiveUser_Admin
88 */
89class LiveUser_Admin_Perm_Simple
90{
91    /**
92     * Error stack
93     *
94     * @var object PEAR_ErrorStack
95     * @access public
96     */
97    var $stack = null;
98
99    /**
100     * Storage Container
101     *
102     * @var object
103     * @access private
104     */
105    var $_storage = null;
106
107    /**
108     * Key (method names), with array lists of selectable tables for the given method
109     *
110     * @var array
111     * @access public
112     */
113    var $selectable_tables = array(
114        'getUsers' => array('perm_users', 'userrights', 'rights'),
115        'getRights' => array('rights', 'userrights', 'areas', 'applications', 'translations'),
116        'getAreas' => array('areas', 'applications', 'translations'),
117        'getApplications' => array('applications', 'translations'),
118        'getTranslations' => array('translations'),
119    );
120
121    /**
122     * Key (field name), with method names as values to determine what method
123     * should be called to get data when the 'with' option is used in a get*() method
124     *
125     * @var array
126     * @access public
127     */
128    var $withFieldMethodMap = array(
129        'perm_user_id' => 'getUsers',
130        'right_id' => 'getRights',
131        'area_id' => 'getAreas',
132        'application_id' => 'getApplications',
133    );
134
135    /**
136     * Constructor
137     *
138     * @return void
139     *
140     * @access protected
141     */
142    function LiveUser_Admin_Perm_Simple()
143    {
144        // Create the error stack, retrieve the errors using LiveUser_Admin->getErrors().
145        $this->stack = &PEAR_ErrorStack::singleton('LiveUser_Admin');
146    }
147
148    /**
149     * Initialize the storage container
150     *
151     * @param  array   array containing the configuration.
152     * @return bool true on success or false on failure
153     *
154     * @access  public
155     */
156    function init(&$conf)
157    {
158        // Sanity check, is there a storage container defined in the configuration.
159        if (!array_key_exists('storage', $conf)) {
160            $this->stack->push(LIVEUSER_ADMIN_ERROR, 'exception',
161                array('msg' => 'Missing storage configuration array'));
162            return false;
163        }
164
165        // Set the config to class vars.
166        if (is_array($conf)) {
167            $keys = array_keys($conf);
168            foreach ($keys as $key) {
169                if (isset($this->$key)) {
170                    $this->$key =& $conf[$key];
171                }
172            }
173        }
174
175        // Create the storage class, if and error occures, add it to the stack and return false.
176        $this->_storage =& LiveUser::storageFactory($conf['storage'], 'LiveUser_Admin_Perm_');
177        if ($this->_storage === false) {
178            end($conf['storage']);
179            $key = key($conf['storage']);
180            $this->stack->push(LIVEUSER_ERROR, 'exception',
181                array('msg' => 'Could not instanciate perm storage container: '.$key));
182            return false;
183        }
184
185        return true;
186    }
187
188    /**
189     * Add a user
190     *
191     * @param array containing atleast the key-value-pairs of all required
192     *              columns in the perm_users table
193     * @return int|bool false on error, true (or new id) on success
194     *
195     * @access public
196     */
197    function addUser($data)
198    {
199        // Sanity check. If not present, set the perm_type to the default value.
200        if (!array_key_exists('perm_type', $data)) {
201            $data['perm_type'] = LIVEUSER_USER_TYPE_ID;
202        }
203
204        $result = $this->_storage->insert('perm_users', $data);
205        // todo: notify observer
206        return $result;
207    }
208
209    /**
210     * Update users
211     *
212     * @param array containing the key value pairs of columns to update
213     * @param array key values pairs (value may be a string or an array)
214     *                      This will construct the WHERE clause of your update
215     *                      Be careful, if you leave this blank no WHERE clause
216     *                      will be used and all users will be affected by the update
217     * @return int|bool false on error, the affected rows on success
218     *
219     * @access public
220     */
221    function updateUser($data, $filters)
222    {
223        $result = $this->_storage->update('perm_users', $data, $filters);
224        // todo: notify observer
225        return $result;
226    }
227
228    /**
229     * Remove users and all their relevant relations
230     *
231     * @param array key values pairs (value may be a string or an array)
232     *                      This will construct the WHERE clause of your update
233     *                      Be careful, if you leave this blank no WHERE clause
234     *                      will be used and all users will be affected by the removed
235     * @return int|bool false on error, the affected rows on success
236     *
237     * @access public
238     */
239    function removeUser($filters)
240    {
241        // Prepare the filters. Based on the provided filters a new array will be
242        // created with the corresponding perm_user_id's. If the filters are empty,
243        // cause an error or just have no result 0 or false will be returned
244        $filters = $this->_makeRemoveFilter($filters, 'perm_user_id', 'getUsers');
245        if (!$filters) {
246            return $filters;
247        }
248
249        // Revoke all the rights this user might have (clean up the database).
250        $result = $this->revokeUserRight($filters);
251        if ($result === false) {
252            return false;
253        }
254
255        $result = $this->_storage->delete('perm_users', $filters);
256        // todo: notify observer
257        return $result;
258    }
259
260    /**
261     * Add a right
262     *
263     * @param array containing atleast the key-value-pairs of all required
264     *              columns in the rights table
265     * @return int|bool false on error, true (or new id) on success
266     *
267     * @access public
268     */
269    function addRight($data)
270    {
271        $result = $this->_storage->insert('rights', $data);
272        // todo: notify observer
273        return $result;
274    }
275
276    /**
277     * Update rights
278     *
279     * @param array containing the key value pairs of columns to update
280     * @param array key values pairs (value may be a string or an array)
281     *                      This will construct the WHERE clause of your update
282     *                      Be careful, if you leave this blank no WHERE clause
283     *                      will be used and all rights will be affected by the update
284     * @return int|bool false on error, the affected rows on success
285     *
286     * @access public
287     */
288    function updateRight($data, $filters)
289    {
290        $result = $this->_storage->update('rights', $data, $filters);
291        // todo: notify observer
292        return $result;
293    }
294
295    /**
296     * Remove rights and all their relevant relations
297     *
298     * @param array key values pairs (value may be a string or an array)
299     *                      This will construct the WHERE clause of your update
300     *                      Be careful, if you leave this blank no WHERE clause
301     *                      will be used and all rights will be affected by the remove
302     * @return int|bool false on error, the affected rows on success
303     *
304     * @access public
305     */
306    function removeRight($filters)
307    {
308        // Prepare the filters. Based on the provided filters a new array will be
309        // created with the corresponding right_id's. If the filters are empty,
310        // cause an error or just have no result 0 or false will be returned
311        $filters = $this->_makeRemoveFilter($filters, 'right_id', 'getRights');
312        if (!$filters) {
313            return $filters;
314        }
315
316        // Revoke this right from any user it might have been assigned to (clean up database)
317        $result = $this->revokeUserRight($filters);
318        if ($result === false) {
319            return false;
320        }
321
322        $result = $this->_storage->delete('rights', $filters);
323        // todo: notify observer
324        return $result;
325    }
326
327    /**
328     * Add an area
329     *
330     * @param array containing atleast the key-value-pairs of all required
331     *              columns in the areas table
332     * @return int|bool false on error, true (or new id) on success
333     *
334     * @access public
335     */
336    function addArea($data)
337    {
338        $result = $this->_storage->insert('areas', $data);
339        // todo: notify observer
340        return $result;
341    }
342
343    /**
344     * Update areas
345     *
346     * @param array    associative array in the form of $fieldname => $data
347     * @param array associative array in the form of $fieldname => $data
348     *                       This will construct the WHERE clause of your update
349     *                       Be careful, if you leave this blank no WHERE clause
350     *                       will be used and all areas will be affected by the update
351     * @return int|bool false on error, the affected rows on success
352     *
353     * @access public
354     */
355    function updateArea($data, $filters)
356    {
357        $result = $this->_storage->update('areas', $data, $filters);
358        // todo: notify observer
359        return $result;
360    }
361
362    /**
363     * Remove areas and all their relevant relations
364     *
365     * @param array key values pairs (value may be a string or an array)
366     *                      This will construct the WHERE clause of your update
367     *                      Be careful, if you leave this blank no WHERE clause
368     *                      will be used and all areas will be affected by the remove
369     * @return int|bool false on error, the affected rows on success
370     *
371     * @access public
372     */
373    function removeArea($filters)
374    {
375        // Prepare the filters. Based on the provided filters a new array will be
376        // created with the corresponding area_id's. If the filters are empty,
377        // cause an error or just have no result 0 or false will be returned
378        $filters = $this->_makeRemoveFilter($filters, 'area_id', 'getAreas');
379        if (!$filters) {
380            return $filters;
381        }
382
383        // Remove all the rights that are part of this area.
384        $result = $this->removeRight($filters);
385        if ($result === false) {
386            return false;
387        }
388
389        $result = $this->_storage->delete('areas', $filters);
390        // todo: notify observer
391        return $result;
392    }
393
394    /**
395     * Add an application
396     *
397     * @param array containing atleast the key-value-pairs of all required
398     *              columns in the applications table
399     * @return int|bool false on error, true (or new id) on success
400     *
401     * @access public
402     */
403    function addApplication($data)
404    {
405        $result = $this->_storage->insert('applications', $data);
406        // todo: notify observer
407        return $result;
408    }
409
410    /**
411     * Update applications
412     *
413     * @param array containing the key value pairs of columns to update
414     * @param array key values pairs (value may be a string or an array)
415     *                      This will construct the WHERE clause of your update
416     *                      Be careful, if you leave this blank no WHERE clause
417     *                      will be used and all applictions will be affected by the update
418     * @return int|bool false on error, the affected rows on success
419     *
420     * @access public
421     */
422    function updateApplication($data, $filters)
423    {
424        $result = $this->_storage->update('applications', $data, $filters);
425        // todo: notify observer
426        return $result;
427    }
428
429    /**
430     * Remove applications and all their relevant relations
431     *
432     * @param array key values pairs (value may be a string or an array)
433     *                      This will construct the WHERE clause of your update
434     *                      Be careful, if you leave this blank no WHERE clause
435     *                      will be used and all applications will be affected by the remove
436     * @return int|bool false on error, the affected rows on success
437     *
438     * @access public
439     */
440    function removeApplication($filters)
441    {
442        // Prepare the filters. Based on the provided filters a new array will be
443        // created with the corresponding application_id's. If the filters are empty,
444        // cause an error or just have no result 0 or false will be returned
445        $filters = $this->_makeRemoveFilter($filters, 'application_id', 'getApplications');
446        if (!$filters) {
447            return $filters;
448        }
449
450        // Remove all the area's that are part of this application
451        $result = $this->removeArea($filters);
452        if ($result === false) {
453            return false;
454        }
455
456        $result = $this->_storage->delete('applications', $filters);
457        // todo: notify observer
458        return $result;
459    }
460
461    /**
462     * Grant user a right
463     *
464     * <code>
465     * // grant user id 13 the right NEWS_CHANGE
466     * $data = array(
467     *      'right_id'     => NEWS_CHANGE,
468     *      'perm_user_id' => 13
469     * );
470     * $lua->perm->grantUserRight($data);
471     * </code>
472     *
473     * @param array containing the perm_user_id and right_id and optionally a right_level
474     * @return
475     *
476     * @access public
477     */
478    function grantUserRight($data)
479    {
480        // Sanity check. Set the right_level to it's default value if it's not set.
481        if (!array_key_exists('right_level', $data)) {
482            $data['right_level'] = LIVEUSER_MAX_LEVEL;
483        }
484
485        // check if already exists
486        $filters = array(
487            'perm_user_id' => $data['perm_user_id'],
488            'right_id'     => $data['right_id'],
489        );
490
491        $count = $this->_storage->selectCount('userrights', 'right_id', $filters);
492
493        // The user already has this right, adding an error to the stack and return false.
494        if ($count > 0) {
495            $this->stack->push(
496                LIVEUSER_ADMIN_ERROR, 'exception',
497                array('msg' => 'This user with perm id '.$data['perm_user_id'].
498                    ' has already been granted the right id '.$data['right_id'])
499            );
500            return false;
501        }
502
503        $result = $this->_storage->insert('userrights', $data);
504        // todo: notify observer
505        return $result;
506    }
507
508    /**
509     * Update right(s) for the given user(s)
510     *
511     * @param array containing the key value pairs of columns to update
512     * @param array key values pairs (value may be a string or an array)
513     *                      This will construct the WHERE clause of your update
514     *                      Be careful, if you leave this blank no WHERE clause
515     *                      will be used and all users will be affected by the update
516     * @return int|bool false on error, the affected rows on success
517     *
518     * @access public
519     */
520    function updateUserRight($data, $filters)
521    {
522        $result = $this->_storage->update('userrights', $data, $filters);
523        // todo: notify observer
524        return $result;
525    }
526
527    /**
528     * Revoke (remove) right(s) from the user(s)
529     *
530     * @param array key values pairs (value may be a string or an array)
531     *                      This will construct the WHERE clause of your update
532     *                      Be careful, if you leave this blank no WHERE clause
533     *                      will be used and all users will be affected by the remove
534     * @return int|bool false on error, the affected rows on success
535     *
536     * @access public
537     */
538    function revokeUserRight($filters)
539    {
540        $result = $this->_storage->delete('userrights', $filters);
541        // todo: notify observer
542        return $result;
543    }
544
545    /**
546     * Add a translation
547     *
548     * @param array containing atleast the key-value-pairs of all required
549     *              columns in the users table
550     * @return int|bool false on error, true (or new id) on success
551     *
552     * @access public
553     */
554    function addTranslation($data)
555    {
556        $result = $this->_storage->insert('translations', $data);
557        // todo: notify observer
558        return $result;
559    }
560
561    /**
562     * Update translations
563     *
564     * @param array containing the key value pairs of columns to update
565     * @param array key values pairs (value may be a string or an array)
566     *                      This will construct the WHERE clause of your update
567     *                      Be careful, if you leave this blank no WHERE clause
568     *                      will be used and all translations will be affected by the update
569     * @return int|bool false on error, the affected rows on success
570     *
571     * @access public
572     */
573    function updateTranslation($data, $filters)
574    {
575        $result = $this->_storage->update('translations', $data, $filters);
576        // todo: notify observer
577        return $result;
578    }
579
580    /**
581     * Remove translations and all their relevant relations
582     *
583     * @param array key values pairs (value may be a string or an array)
584     *                      This will construct the WHERE clause of your update
585     *                      Be careful, if you leave this blank no WHERE clause
586     *                      will be used and all translations will be affected by the remove
587     * @return int|bool false on error, the affected rows on success
588     *
589     * @access public
590     */
591    function removeTranslation($filters)
592    {
593        // Prepare the filters. Based on the provided filters a new array will be
594        // created with the corresponding translation_id's. If the filters are empty,
595        // cause an error or just have no result 0 or false will be returned
596        $filters = $this->_makeRemoveFilter($filters, 'translation_id', 'getTranslations');
597        if (!$filters) {
598            return $filters;
599        }
600
601        $result = $this->_storage->delete('translations', $filters);
602        // todo: notify observer
603        return $result;
604    }
605
606    /**
607     * Makes the filters used by the remove functions and also
608     * checks if there is actually something that needs removing.
609     *
610     * @param array key values pairs (value may be a string or an array)
611     *                      This will construct the WHERE clause of your update
612     *                      Be careful, if you leave this blank no WHERE clause
613     *                      will be used and all users will be affected by the update
614     * @param string name of the column for which we require a filter to be set
615     * @param string name of the method that should be used to determine the filter
616     * @return int|array|bool 0, an array containing the filter for the key
617     *                                  or false on error
618     *
619     * @access private
620     */
621    function _makeRemoveFilter($filters, $key, $method)
622    {
623        // Do not allow people to delete the entire contents of a given table
624        if (empty($filters) || !is_array($filters)) {
625            return 0;
626        }
627
628        // todo: if all filters apply to the given table only then we can probably skip running the select ..
629
630        // Rewrite filter to only include the provided key, since we cannot
631        // rely on joins in delete for all backends
632        if (!isset($filters[$key]) || count($filters) > 1) {
633            // Prepare the params for fetching the column provided. It should
634            // return an array with only the keys.
635            $params = array(
636                'fields' => array($key),
637                'filters' => $filters,
638                'select' => 'col',
639            );
640            $result = $this->$method($params);
641            if ($result === false) {
642                return false;
643            }
644
645            if (empty($result)) {
646                return 0;
647            }
648
649            // Rebuild the filters array.
650            $filters = array($key => $result);
651        }
652        return $filters;
653    }
654
655    /**
656     * This function finds the list of selectable tables either from the params
657     * or from the selectable_tables property using the method parameter
658     *
659     * @param string name of the method
660     * @param array containing the parameters passed to a get*() method
661     * @return array contains the selectable tables
662     *
663     * @access private
664     */
665    function _findSelectableTables($method, $params = array())
666    {
667        $selectable_tables = array();
668        // Check if the provided params might already have the selectable tables.
669        // If so, return them, else fetch them through this->selectable_tables.
670        if (array_key_exists('selectable_tables', $params)) {
671            $selectable_tables = $params['selectable_tables'];
672        } elseif (array_key_exists($method, $this->selectable_tables)) {
673            $selectable_tables = $this->selectable_tables[$method];
674        }
675        return $selectable_tables;
676    }
677
678    /**
679     * This function holds up most of the heat for all the get* functions.
680     *
681     * @param array containing key-value pairs for:
682     *                 'fields'  - ordered array containing the fields to fetch
683     *                             if empty all fields from the user table are fetched
684     *                 'filters' - key values pairs (value may be a string or an array)
685     *                 'orders'  - key value pairs (values 'ASC' or 'DESC')
686     *                 'rekey'   - if set to true, returned array will have the
687     *                             first column as its first dimension
688     *                 'group'   - if set to true and $rekey is set to true, then
689     *                             all values with the same first column will be
690     *                             wrapped in an array
691     *                 'limit'   - number of rows to select
692     *                 'offset'  - first row to select
693     *                 'select'  - determines what query method to use:
694     *                             'one' -> queryOne, 'row' -> queryRow,
695     *                             'col' -> queryCol, 'all' ->queryAll (default)
696     * @param string name of the table from which to start looking
697     *               for join points
698     * @param array list of tables that may be joined to
699     * @return bool|array false on failure or array with selected data
700     *
701     * @access private
702     */
703    function _makeGet($params, $root_table, $selectable_tables)
704    {
705        // Ensure that default params are set
706        $params = LiveUser_Admin_Storage::setSelectDefaultParams($params);
707
708        $data = $this->_storage->select($params['select'], $params['fields'],
709            $params['filters'], $params['orders'], $params['rekey'], $params['group'],
710            $params['limit'], $params['offset'], $root_table, $selectable_tables);
711
712        // If 'with' is set and the result data is not empty
713        if (!empty($params['with']) && !empty($data)) {
714            if ($params['select'] != 'all') {
715                $this->stack->push(
716                    LIVEUSER_ADMIN_ERROR, 'exception',
717                    array('msg' => 'Using "with" requires "select" to be set to "all"')
718                );
719                return false;
720            }
721            // Check if all with keys were fetched
722            $missing = array_diff(array_keys($params['with']), array_keys(reset($data)));
723            if (!empty($missing)) {
724                $this->stack->push(
725                    LIVEUSER_ADMIN_ERROR, 'exception',
726                    array('msg' => 'The following "with" elements are not included in the result: '.implode(', ', $missing))
727                );
728                return false;
729            }
730            foreach ($data as $key => $row) {
731                foreach ($params['with'] as $field => $with_params) {
732                    $with_params['filters'][$field] = $row[$field];
733                    $method = $this->withFieldMethodMap[$field];
734                    // remove "_id" from the field name (group_id => group)
735                    $data_key = preg_replace('/(.+)_id/', '\\1s', $field);
736                    $data[$key][$data_key] = $this->$method($with_params);
737                }
738            }
739        }
740
741        return $data;
742    }
743
744    /**
745     * Fetches users
746     *
747     * @param array containing key-value pairs for:
748     *                 'fields'  - ordered array containing the fields to fetch
749     *                             if empty all fields from the user table are fetched
750     *                 'filters' - key values pairs (value may be a string or an array)
751     *                 'orders'  - key value pairs (values 'ASC' or 'DESC')
752     *                 'rekey'   - if set to true, returned array will have the
753     *                             first column as its first dimension
754     *                 'group'   - if set to true and $rekey is set to true, then
755     *                             all values with the same first column will be
756     *                             wrapped in an array
757     *                 'limit'   - number of rows to select
758     *                 'offset'  - first row to select
759     *                 'select'  - determines what query method to use:
760     *                             'one' -> queryOne, 'row' -> queryRow,
761     *                             'col' -> queryCol, 'all' ->queryAll (default)
762     *                 'selectable_tables' - array list of tables that may be
763     *                             joined to in this query, the first element is
764     *                             the root table from which the joins are done
765     * @return bool|array false on failure or array with selected data
766     *
767     * @access public
768     */
769    function getUsers($params = array())
770    {
771        $selectable_tables = $this->_findSelectableTables('getUsers', $params);
772        $root_table = reset($selectable_tables);
773
774        return $this->_makeGet($params, $root_table, $selectable_tables);
775    }
776
777    /**
778     * Fetches rights
779     *
780     * @param array containing key-value pairs for:
781     *                 'fields'  - ordered array containing the fields to fetch
782     *                             if empty all fields from the user table are fetched
783     *                 'filters' - key values pairs (value may be a string or an array)
784     *                 'orders'  - key value pairs (values 'ASC' or 'DESC')
785     *                 'rekey'   - if set to true, returned array will have the
786     *                             first column as its first dimension
787     *                 'group'   - if set to true and $rekey is set to true, then
788     *                             all values with the same first column will be
789     *                             wrapped in an array
790     *                 'limit'   - number of rows to select
791     *                 'offset'  - first row to select
792     *                 'select'  - determines what query method to use:
793     *                             'one' -> queryOne, 'row' -> queryRow,
794     *                             'col' -> queryCol, 'all' ->queryAll (default)
795     *                 'selectable_tables' - array list of tables that may be
796     *                             joined to in this query, the first element is
797     *                             the root table from which the joins are done
798     * @return bool|array false on failure or array with selected data
799     *
800     * @access public
801     */
802    function getRights($params = array())
803    {
804        $selectable_tables = $this->_findSelectableTables('getRights' , $params);
805        $root_table = reset($selectable_tables);
806
807        return $this->_makeGet($params, $root_table, $selectable_tables);
808    }
809
810    /**
811     * Fetches areas
812     *
813     * @param array containing key-value pairs for:
814     *                 'fields'  - ordered array containing the fields to fetch
815     *                             if empty all fields from the user table are fetched
816     *                 'filters' - key values pairs (value may be a string or an array)
817     *                 'orders'  - key value pairs (values 'ASC' or 'DESC')
818     *                 'rekey'   - if set to true, returned array will have the
819     *                             first column as its first dimension
820     *                 'group'   - if set to true and $rekey is set to true, then
821     *                             all values with the same first column will be
822     *                             wrapped in an array
823     *                 'limit'   - number of rows to select
824     *                 'offset'  - first row to select
825     *                 'select'  - determines what query method to use:
826     *                             'one' -> queryOne, 'row' -> queryRow,
827     *                             'col' -> queryCol, 'all' ->queryAll (default)
828     *                 'selectable_tables' - array list of tables that may be
829     *                             joined to in this query, the first element is
830     *                             the root table from which the joins are done
831     * @return bool|array false on failure or array with selected data
832     *
833     * @access public
834     */
835    function getAreas($params = array())
836    {
837        $selectable_tables = $this->_findSelectableTables('getAreas' , $params);
838        $root_table = reset($selectable_tables);
839
840        return $this->_makeGet($params, $root_table, $selectable_tables);
841    }
842
843    /**
844     * Fetches applications
845     *
846     * @param array containing key-value pairs for:
847     *                 'fields'  - ordered array containing the fields to fetch
848     *                             if empty all fields from the user table are fetched
849     *                 'filters' - key values pairs (value may be a string or an array)
850     *                 'orders'  - key value pairs (values 'ASC' or 'DESC')
851     *                 'rekey'   - if set to true, returned array will have the
852     *                             first column as its first dimension
853     *                 'group'   - if set to true and $rekey is set to true, then
854     *                             all values with the same first column will be
855     *                             wrapped in an array
856     *                 'limit'   - number of rows to select
857     *                 'offset'  - first row to select
858     *                 'select'  - determines what query method to use:
859     *                             'one' -> queryOne, 'row' -> queryRow,
860     *                             'col' -> queryCol, 'all' ->queryAll (default)
861     *                 'selectable_tables' - array list of tables that may be
862     *                             joined to in this query, the first element is
863     *                             the root table from which the joins are done
864     * @return bool|array false on failure or array with selected data
865     *
866     * @access public
867     */
868    function getApplications($params = array())
869    {
870        $selectable_tables = $this->_findSelectableTables('getApplications' , $params);
871        $root_table = reset($selectable_tables);
872
873        return $this->_makeGet($params, $root_table, $selectable_tables);
874    }
875
876    /**
877     * Fetches translations
878     *
879     * @param array containing key-value pairs for:
880     *                 'fields'  - ordered array containing the fields to fetch
881     *                             if empty all fields from the user table are fetched
882     *                 'filters' - key values pairs (value may be a string or an array)
883     *                 'orders'  - key value pairs (values 'ASC' or 'DESC')
884     *                 'rekey'   - if set to true, returned array will have the
885     *                             first column as its first dimension
886     *                 'group'   - if set to true and $rekey is set to true, then
887     *                             all values with the same first column will be
888     *                             wrapped in an array
889     *                 'limit'   - number of rows to select
890     *                 'offset'  - first row to select
891     *                 'select'  - determines what query method to use:
892     *                             'one' -> queryOne, 'row' -> queryRow,
893     *                             'col' -> queryCol, 'all' ->queryAll (default)
894     *                 'selectable_tables' - array list of tables that may be
895     *                             joined to in this query, the first element is
896     *                             the root table from which the joins are done
897     * @return bool|array false on failure or array with selected data
898     *
899     * @access public
900     */
901    function getTranslations($params = array())
902    {
903        $selectable_tables = $this->_findSelectableTables('getTranslations' , $params);
904        $root_table = reset($selectable_tables);
905
906        return $this->_makeGet($params, $root_table, $selectable_tables);
907    }
908
909    /**
910     * Generate the constants to a file or define them directly.
911     *
912     * $type can be either 'constant' or 'array'. Constant will result in
913     * defining constants while array results in defining an array.
914     *
915     * $options can contain
916     * 'prefix'      => prefix for the generated (qualified) names
917     * 'area'        => specific area id to grab rights from
918     * 'application' => specific application id to grab rights from
919     * 'filters'     => specific set of filters to use (overwrites area/application)
920     * 'by_group'    => if joins should be done using the 'userrights' (false default)
921     *                  or through the 'grouprights' and 'groupusers' tables (true)
922     * 'inherited'   => filter array to fetch all rights from (sub)group membership
923     * 'implied'     => filter array for fetching implied rights
924     * 'naming'      => LIVEUSER_SECTION_RIGHT for PREFIX_RIGHTNAME  <- DEFAULT
925     *                  LIVEUSER_SECTION_AREA for PREFIX_AREANAME_RIGHTNAME
926     *                  LIVEUSER_SECTION_APPLICATION for PREFIX_APPLICATIONNAME_AREANAME_RIGHTNAME
927     * 'filename'    => if $mode is 'file' you must give the full path for the
928     *                  output file
929     * 'varname'     => if $mode is 'file' and $type is 'array' you must give
930     *                  the name of the variable to define
931     *
932     * If no prefix is given it will not be used to generate the constants/arrays
933     *
934     * $mode can either be 'file' or 'direct' and will determine of the
935     * constants/arrays will be written to a file, or returned/defined.
936     * returned as an array when $type is set to 'array' and defined when $type
937     * is set to 'constant'
938     *
939     * @param  string  type of output ('constant' or 'array')
940     * @param  array   options for constants generation
941     * @param  string  output mode desired ('file' or 'direct')
942     * @return bool|array depending on the type an array with the data or
943     *                       a boolean denoting success or failure
944     *
945     * @access public
946     */
947    function outputRightsConstants($type, $options = array(), $mode = null)
948    {
949        $params = array();
950
951        // Prepare the fields to fetch.
952        $params['fields'] = array('right_id', 'right_define_name');
953
954        $naming = LIVEUSER_SECTION_RIGHT;
955        if (array_key_exists('naming', $options)) {
956            $naming = $options['naming'];
957            switch ($naming) {
958            case LIVEUSER_SECTION_AREA:
959                $params['fields'][] = 'area_define_name';
960                break;
961            case LIVEUSER_SECTION_APPLICATION:
962                $params['fields'][] = 'application_define_name';
963                $params['fields'][] = 'area_define_name';
964                break;
965            }
966        }
967
968        // Prepare the filters.
969        if (array_key_exists('by_group', $options)) {
970            $params['by_group'] = $options['by_group'];
971        }
972
973        if (array_key_exists('inherited', $options)) {
974            $params['inherited'] = $options['inherited'];
975        }
976
977        if (array_key_exists('implied', $options)) {
978            $params['implied'] = $options['implied'];
979        }
980
981        if (array_key_exists('filters', $options)) {
982            $params['filters'] = $options['filters'];
983        } else {
984            if (array_key_exists('area', $options)) {
985                $params['filters']['area_id'] = $options['area'];
986            }
987
988            if (array_key_exists('application', $options)) {
989                $params['filters']['application_id'] = $options['application'];
990            }
991        }
992
993        $prefix = '';
994        if (array_key_exists('prefix', $options)) {
995            $prefix = $options['prefix'] . '_';
996        }
997
998        $rekey = false;
999        if ($type == 'array' && array_key_exists('rekey', $options)) {
1000            $rekey = $options['rekey'];
1001        }
1002
1003        $rights = $this->getRights($params);
1004
1005        if ($rights === false) {
1006            return false;
1007        }
1008
1009        $generate = array();
1010
1011        // Prepare an array containing all the rights to be defined. The stucture of
1012        // this array is dependent on the value of naming and if the rekey is set.
1013        switch ($naming) {
1014        case LIVEUSER_SECTION_APPLICATION:
1015            if ($rekey) {
1016                foreach ($rights as $r) {
1017                    $app_name = $prefix . $r['application_define_name'];
1018                    $area_name = $r['area_define_name'];
1019                    $generate[$app_name][$area_name][$r['right_define_name']] = $r['right_id'];
1020                }
1021            } else {
1022                foreach ($rights as $r) {
1023                    $key = $prefix . $r['application_define_name'] . '_'
1024                        . $r['area_define_name'] . '_' . $r['right_define_name'];
1025                    $generate[$key] = $r['right_id'];
1026                }
1027            }
1028            break;
1029        case LIVEUSER_SECTION_AREA:
1030            if ($rekey) {
1031                foreach ($rights as $r) {
1032                    $area_name = $prefix . $r['area_define_name'];
1033                    $generate[$area_name][$r['right_define_name']] = $r['right_id'];
1034                }
1035            } else {
1036                foreach ($rights as $r) {
1037                    $key = $prefix . $r['area_define_name'] . '_' . $r['right_define_name'];
1038                    $generate[$key] = $r['right_id'];
1039                }
1040            }
1041            break;
1042        case LIVEUSER_SECTION_RIGHT:
1043        default:
1044            foreach ($rights as $r) {
1045                $generate[$prefix . $r['right_define_name']] = $r['right_id'];
1046            }
1047            break;
1048        }
1049
1050        if ($type == 'array' && $mode != 'file') {
1051            return $generate;
1052        }
1053
1054        // Define the rights, either as an array or defines.
1055        // Add an error to the stack if the provided variable name is not valid.
1056        if ($type == 'array') {
1057           if (!array_key_exists('varname', $options)
1058                || !preg_match('/^[a-zA-Z_0-9]+$/', $options['varname'])
1059            ) {
1060                $this->stack->push(
1061                    LIVEUSER_ADMIN_ERROR_FILTER, 'exception',
1062                    array('msg' => 'varname is not a valid variable name in PHP: '.$options['varname'])
1063                );
1064                return false;
1065            }
1066            $strDef = sprintf("\$%s = %s;\n", $options['varname'], var_export($generate, true));
1067        } else {
1068            if ($mode == 'file') {
1069                $strDef = '';
1070            }
1071            foreach ($generate as $v => $k) {
1072                if (!preg_match('/^[a-zA-Z_0-9]+$/', $v)) {
1073                    $this->stack->push(
1074                        LIVEUSER_ADMIN_ERROR_FILTER, 'exception',
1075                        array('msg' => 'definename is not a valid define name in PHP: '.$v)
1076                    );
1077                    return false;
1078                }
1079                $v = strtoupper($v);
1080                if ($mode == 'file') {
1081                    $strDef .= sprintf("define('%s', %s);\n", $v, $k);
1082                } elseif (!defined($v)) {
1083                    define($v, $k);
1084                }
1085            }
1086        }
1087
1088        if ($mode != 'file') {
1089            return true;
1090        }
1091
1092        // The results should be written to a file.
1093        // If the filename doesn't exist or the file cannot be opened, add an error to the stack.
1094        if (!array_key_exists('filename', $options) || !$options['filename']) {
1095            $this->stack->push(
1096                LIVEUSER_ADMIN_ERROR_FILTER, 'exception',
1097                array('msg' => 'no filename is set for output mode file')
1098            );
1099            return false;
1100        }
1101
1102        $fp = @fopen($options['filename'], 'wb');
1103
1104        if (!$fp) {
1105            $this->stack->push(
1106                LIVEUSER_ADMIN_ERROR_FILTER, 'exception',
1107                array('msg' => 'file could not be opened: '.$options['filename'])
1108            );
1109            return false;
1110        }
1111
1112        @fputs($fp, "<?php\n".$strDef.'?>');
1113        @fclose($fp);
1114
1115        return true;
1116    }
1117
1118    /**
1119     * properly disconnect from resources
1120     *
1121     * @access  public
1122     */
1123    function disconnect()
1124    {
1125        $this->_storage->disconnect();
1126    }
1127}
1128?>
1129