Mereged updates from DokuWiki 38
[sudaraka-org:dokuwiki-mods.git] / inc / auth / pgsql.class.php
1 <?php
2 /**
3  * PgSQL authentication backend
4  *
5  * This class inherits much functionality from the MySQL class
6  * and just reimplements the Postgres specific parts.
7  *
8  * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
9  * @author     Andreas Gohr <andi@splitbrain.org>
10  * @author     Chris Smith <chris@jalakai.co.uk>
11  * @author     Matthias Grimm <matthias.grimmm@sourceforge.net>
12  */
13
14 require_once(DOKU_INC.'inc/auth/mysql.class.php');
15
16 class auth_pgsql extends auth_mysql {
17
18     /**
19      * Constructor
20      *
21      * checks if the pgsql interface is available, otherwise it will
22      * set the variable $success of the basis class to false
23      *
24      * @author Matthias Grimm <matthiasgrimm@users.sourceforge.net>
25      * @author Andreas Gohr <andi@splitbrain.org>
26      */
27     function __construct() {
28         global $conf;
29         $this->cnf = $conf['auth']['pgsql'];
30         if(!$this->cnf['port']){
31             $this->cnf['port'] = 5432;
32         }
33
34         if (method_exists($this, 'auth_basic')){
35             parent::auth_basic();
36         }
37
38         if(!function_exists('pg_connect')) {
39             if ($this->cnf['debug'])
40                 msg("PgSQL err: PHP Postgres extension not found.",-1);
41             $this->success = false;
42             return;
43         }
44
45         $this->defaultgroup = $conf['defaultgroup'];
46
47         // set capabilities based upon config strings set
48         if (empty($this->cnf['user']) ||
49             empty($this->cnf['password']) || empty($this->cnf['database'])){
50             if ($this->cnf['debug']){
51                 msg("PgSQL err: insufficient configuration.",-1,__LINE__,__FILE__);
52             }
53             $this->success = false;
54             return;
55         }
56
57         $this->cando['addUser']     = $this->_chkcnf(array(
58                                         'getUserInfo',
59                                         'getGroups',
60                                         'addUser',
61                                         'getUserID',
62                                         'getGroupID',
63                                         'addGroup',
64                                         'addUserGroup'));
65         $this->cando['delUser']      = $this->_chkcnf(array(
66                                         'getUserID',
67                                         'delUser',
68                                         'delUserRefs'));
69         $this->cando['modLogin']     = $this->_chkcnf(array(
70                                         'getUserID',
71                                         'updateUser',
72                                         'UpdateTarget'));
73         $this->cando['modPass']      = $this->cando['modLogin'];
74         $this->cando['modName']      = $this->cando['modLogin'];
75         $this->cando['modMail']      = $this->cando['modLogin'];
76         $this->cando['modGroups']    = $this->_chkcnf(array(
77                                         'getUserID',
78                                         'getGroups',
79                                         'getGroupID',
80                                         'addGroup',
81                                         'addUserGroup',
82                                         'delGroup',
83                                         'getGroupID',
84                                         'delUserGroup'));
85         /* getGroups is not yet supported
86            $this->cando['getGroups']    = $this->_chkcnf(array('getGroups',
87            'getGroupID')); */
88         $this->cando['getUsers']     = $this->_chkcnf(array(
89                                         'getUsers',
90                                         'getUserInfo',
91                                         'getGroups'));
92         $this->cando['getUserCount'] = $this->_chkcnf(array('getUsers'));
93     }
94
95     /**
96      * Check if the given config strings are set
97      *
98      * @author  Matthias Grimm <matthiasgrimm@users.sourceforge.net>
99      * @return  bool
100      */
101     function _chkcnf($keys, $wop=false){
102         foreach ($keys as $key){
103             if (empty($this->cnf[$key])) return false;
104         }
105         return true;
106     }
107
108     // @inherit function checkPass($user,$pass)
109     // @inherit function getUserData($user)
110     // @inherit function createUser($user,$pwd,$name,$mail,$grps=null)
111     // @inherit function modifyUser($user, $changes)
112     // @inherit function deleteUsers($users)
113
114
115     /**
116      * [public function]
117      *
118      * Counts users which meet certain $filter criteria.
119      *
120      * @param  array  $filter  filter criteria in item/pattern pairs
121      * @return count of found users.
122      *
123      * @author  Matthias Grimm <matthiasgrimm@users.sourceforge.net>
124      */
125     function getUserCount($filter=array()) {
126         $rc = 0;
127
128         if($this->_openDB()) {
129             $sql = $this->_createSQLFilter($this->cnf['getUsers'], $filter);
130
131             // no equivalent of SQL_CALC_FOUND_ROWS in pgsql?
132             if (($result = $this->_queryDB($sql))){
133                 $rc = count($result);
134             }
135             $this->_closeDB();
136         }
137         return $rc;
138     }
139
140     /**
141      * Bulk retrieval of user data. [public function]
142      *
143      * @param   first     index of first user to be returned
144      * @param   limit     max number of users to be returned
145      * @param   filter    array of field/pattern pairs
146      * @return  array of userinfo (refer getUserData for internal userinfo details)
147      *
148      * @author  Matthias Grimm <matthiasgrimm@users.sourceforge.net>
149      */
150     function retrieveUsers($first=0,$limit=10,$filter=array()) {
151         $out   = array();
152
153         if($this->_openDB()) {
154             $this->_lockTables("READ");
155             $sql  = $this->_createSQLFilter($this->cnf['getUsers'], $filter);
156             $sql .= " ".$this->cnf['SortOrder']." LIMIT $limit OFFSET $first";
157             $result = $this->_queryDB($sql);
158
159             foreach ($result as $user)
160                 if (($info = $this->_getUserInfo($user['user'])))
161                     $out[$user['user']] = $info;
162
163             $this->_unlockTables();
164             $this->_closeDB();
165         }
166         return $out;
167     }
168
169     // @inherit function joinGroup($user, $group)
170     // @inherit function leaveGroup($user, $group) {
171
172     /**
173      * Adds a user to a group.
174      *
175      * If $force is set to '1' non existing groups would be created.
176      *
177      * The database connection must already be established. Otherwise
178      * this function does nothing and returns 'false'.
179      *
180      * @param   $user    user to add to a group
181      * @param   $group   name of the group
182      * @param   $force   '1' create missing groups
183      * @return  bool     'true' on success, 'false' on error
184      *
185      * @author Matthias Grimm <matthiasgrimm@users.sourceforge.net>
186      * @author Andreas Gohr   <andi@splitbrain.org>
187      */
188     function _addUserToGroup($user, $group, $force=0) {
189         $newgroup = 0;
190
191         if (($this->dbcon) && ($user)) {
192             $gid = $this->_getGroupID($group);
193             if (!$gid) {
194                 if ($force) {  // create missing groups
195                     $sql = str_replace('%{group}',addslashes($group),$this->cnf['addGroup']);
196                     $this->_modifyDB($sql);
197                     //group should now exists try again to fetch it
198                     $gid = $this->_getGroupID($group);
199                     $newgroup = 1;  // group newly created
200                 }
201             }
202             if (!$gid) return false; // group didn't exist and can't be created
203
204             $sql = $this->cnf['addUserGroup'];
205             if(strpos($sql,'%{uid}') !== false){
206                 $uid = $this->_getUserID($user);
207                 $sql = str_replace('%{uid}', addslashes($uid), $sql);
208             }
209             $sql = str_replace('%{user}', addslashes($user),$sql);
210             $sql = str_replace('%{gid}',  addslashes($gid),$sql);
211             $sql = str_replace('%{group}',addslashes($group),$sql);
212             if ($this->_modifyDB($sql) !== false) return true;
213
214             if ($newgroup) { // remove previously created group on error
215                 $sql = str_replace('%{gid}',  addslashes($gid),$this->cnf['delGroup']);
216                 $sql = str_replace('%{group}',addslashes($group),$sql);
217                 $this->_modifyDB($sql);
218             }
219         }
220         return false;
221     }
222
223     // @inherit function _delUserFromGroup($user $group)
224     // @inherit function _getGroups($user)
225     // @inherit function _getUserID($user)
226
227     /**
228      * Adds a new User to the database.
229      *
230      * The database connection must already be established
231      * for this function to work. Otherwise it will return
232      * 'false'.
233      *
234      * @param  $user  login of the user
235      * @param  $pwd   encrypted password
236      * @param  $name  full name of the user
237      * @param  $mail  email address
238      * @param  $grps  array of groups the user should become member of
239      * @return bool
240      *
241      * @author  Andreas Gohr <andi@splitbrain.org>
242      * @author  Chris Smith <chris@jalakai.co.uk>
243      * @author  Matthias Grimm <matthiasgrimm@users.sourceforge.net>
244      */
245     function _addUser($user,$pwd,$name,$mail,$grps){
246         if($this->dbcon && is_array($grps)) {
247             $sql = str_replace('%{user}', addslashes($user),$this->cnf['addUser']);
248             $sql = str_replace('%{pass}', addslashes($pwd),$sql);
249             $sql = str_replace('%{name}', addslashes($name),$sql);
250             $sql = str_replace('%{email}',addslashes($mail),$sql);
251             if($this->_modifyDB($sql)){
252                 $uid = $this->_getUserID($user);
253             }else{
254                 return false;
255             }
256
257             if ($uid) {
258                 foreach($grps as $group) {
259                     $gid = $this->_addUserToGroup($user, $group, 1);
260                     if ($gid === false) break;
261                 }
262
263                 if ($gid) return true;
264                 else {
265                     /* remove the new user and all group relations if a group can't
266                      * be assigned. Newly created groups will remain in the database
267                      * and won't be removed. This might create orphaned groups but
268                      * is not a big issue so we ignore this problem here.
269                      */
270                     $this->_delUser($user);
271                     if ($this->cnf['debug'])
272                         msg("PgSQL err: Adding user '$user' to group '$group' failed.",-1,__LINE__,__FILE__);
273                 }
274             }
275         }
276         return false;
277     }
278
279     // @inherit function _delUser($user)
280     // @inherit function _getUserInfo($user)
281     // @inherit function _updateUserInfo($changes, $uid)
282     // @inherit function _getGroupID($group)
283
284     /**
285      * Opens a connection to a database and saves the handle for further
286      * usage in the object. The successful call to this functions is
287      * essential for most functions in this object.
288      *
289      * @return bool
290      *
291      * @author Matthias Grimm <matthiasgrimm@users.sourceforge.net>
292      */
293     function _openDB() {
294         if (!$this->dbcon) {
295             $dsn  = $this->cnf['server'] ? 'host='.$this->cnf['server'] : '';
296             $dsn .= ' port='.$this->cnf['port'];
297             $dsn .= ' dbname='.$this->cnf['database'];
298             $dsn .= ' user='.$this->cnf['user'];
299             $dsn .= ' password='.$this->cnf['password'];
300
301             $con = @pg_connect($dsn);
302             if ($con) {
303                 $this->dbcon = $con;
304                 return true;   // connection and database successfully opened
305             } else if ($this->cnf['debug']){
306                 msg ("PgSQL err: Connection to {$this->cnf['user']}@{$this->cnf['server']} not possible.",
307                         -1,__LINE__,__FILE__);
308             }
309             return false;  // connection failed
310         }
311         return true;  // connection already open
312     }
313
314     /**
315      * Closes a database connection.
316      *
317      * @author Matthias Grimm <matthiasgrimm@users.sourceforge.net>
318      */
319     function _closeDB() {
320         if ($this->dbcon) {
321             pg_close ($this->dbcon);
322             $this->dbcon = 0;
323         }
324     }
325
326     /**
327      * Sends a SQL query to the database and transforms the result into
328      * an associative array.
329      *
330      * This function is only able to handle queries that returns a
331      * table such as SELECT.
332      *
333      * @param $query  SQL string that contains the query
334      * @return array with the result table
335      *
336      * @author Matthias Grimm <matthiasgrimm@users.sourceforge.net>
337      */
338     function _queryDB($query) {
339         if ($this->dbcon) {
340             $result = @pg_query($this->dbcon,$query);
341             if ($result) {
342                 while (($t = pg_fetch_assoc($result)) !== false)
343                     $resultarray[]=$t;
344                 pg_free_result ($result);
345                 return $resultarray;
346             }elseif ($this->cnf['debug'])
347             msg('PgSQL err: '.pg_last_error($this->dbcon),-1,__LINE__,__FILE__);
348         }
349         return false;
350     }
351
352     /**
353      * Executes an update or insert query. This differs from the
354      * MySQL one because it does NOT return the last insertID
355      *
356      * @author Andreas Gohr
357      */
358     function _modifyDB($query) {
359         if ($this->dbcon) {
360             $result = @pg_query($this->dbcon,$query);
361             if ($result) {
362                 pg_free_result ($result);
363                 return true;
364             }
365             if ($this->cnf['debug']){
366                 msg('PgSQL err: '.pg_last_error($this->dbcon),-1,__LINE__,__FILE__);
367             }
368         }
369         return false;
370     }
371
372     /**
373      * Start a transaction
374      *
375      * @param $mode  could be 'READ' or 'WRITE'
376      * @author Matthias Grimm <matthiasgrimm@users.sourceforge.net>
377      */
378     function _lockTables($mode) {
379         if ($this->dbcon) {
380             $this->_modifyDB('BEGIN');
381             return true;
382         }
383         return false;
384     }
385
386     /**
387      * Commit a transaction
388      *
389      * @author Matthias Grimm <matthiasgrimm@users.sourceforge.net>
390      */
391     function _unlockTables() {
392         if ($this->dbcon) {
393             $this->_modifyDB('COMMIT');
394             return true;
395         }
396         return false;
397     }
398
399     // @inherit function _createSQLFilter($sql, $filter)
400
401
402     /**
403      * Escape a string for insertion into the database
404      *
405      * @author Andreas Gohr <andi@splitbrain.org>
406      * @param  string  $string The string to escape
407      * @param  boolean $like   Escape wildcard chars as well?
408      */
409     function _escape($string,$like=false){
410         $string = pg_escape_string($string);
411         if($like){
412             $string = addcslashes($string,'%_');
413         }
414         return $string;
415     }
416
417 }
418
419 //Setup VIM: ex: et ts=2 :