1: <?php
2: /**
3: * XOOPS Kernel Class
4: *
5: * You may not change or alter any portion of this comment or credits
6: * of supporting developers from this source code or any supporting source code
7: * which is considered copyrighted (c) material of the original comment or credit authors.
8: * This program is distributed in the hope that it will be useful,
9: * but WITHOUT ANY WARRANTY; without even the implied warranty of
10: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
11: *
12: * @copyright (c) 2000-2016 XOOPS Project (www.xoops.org)
13: * @license GNU GPL 2 (https://www.gnu.org/licenses/gpl-2.0.html)
14: * @package kernel
15: * @since 2.0.0
16: * @author Kazumi Ono (AKA onokazu) http://www.myweb.ne.jp/, http://jp.xoops.org/
17: */
18: defined('XOOPS_ROOT_PATH') || exit('Restricted access');
19:
20: require_once __DIR__ . '/user.php';
21: require_once __DIR__ . '/group.php';
22:
23: /**
24: * XOOPS member handler class.
25: * This class provides simple interface (a facade class) for handling groups/users/
26: * membership data.
27: *
28: *
29: * @author Kazumi Ono <onokazu@xoops.org>
30: * @copyright (c) 2000-2016 XOOPS Project (www.xoops.org)
31: * @package kernel
32: */
33: class XoopsMemberHandler
34: {
35: /**
36: * holds reference to group handler(DAO) class
37: * @access private
38: */
39: protected $groupHandler;
40:
41: /**
42: * holds reference to user handler(DAO) class
43: */
44: protected $userHandler;
45:
46: /**
47: * holds reference to membership handler(DAO) class
48: */
49: protected $membershipHandler;
50:
51: /**
52: * holds temporary user objects
53: */
54: protected $membersWorkingList = array();
55:
56: /**
57: * constructor
58: * @param XoopsDatabase|null| $db
59: */
60: public function __construct(XoopsDatabase $db)
61: {
62: $this->groupHandler = new XoopsGroupHandler($db);
63: $this->userHandler = new XoopsUserHandler($db);
64: $this->membershipHandler = new XoopsMembershipHandler($db);
65: }
66:
67: /**
68: * create a new group
69: *
70: * @return XoopsGroup XoopsGroup reference to the new group
71: */
72: public function &createGroup()
73: {
74: $inst = $this->groupHandler->create();
75:
76: return $inst;
77: }
78:
79: /**
80: * create a new user
81: *
82: * @return XoopsUser reference to the new user
83: */
84: public function createUser()
85: {
86: $inst = $this->userHandler->create();
87:
88: return $inst;
89: }
90:
91: /**
92: * retrieve a group
93: *
94: * @param int $id ID for the group
95: * @return XoopsGroup|false XoopsGroup reference to the group
96: */
97: public function getGroup($id)
98: {
99: return $this->groupHandler->get($id);
100: }
101:
102: /**
103: * retrieve a user
104: *
105: * @param int $id ID for the user
106: * @return XoopsUser reference to the user
107: */
108: public function getUser($id)
109: {
110: if (!isset($this->membersWorkingList[$id])) {
111: $this->membersWorkingList[$id] = $this->userHandler->get($id);
112: }
113:
114: return $this->membersWorkingList[$id];
115: }
116:
117: /**
118: * delete a group
119: *
120: * @param XoopsGroup $group reference to the group to delete
121: * @return bool FALSE if failed
122: */
123: public function deleteGroup(XoopsGroup $group)
124: {
125: $s1 = $this->membershipHandler->deleteAll(new Criteria('groupid', $group->getVar('groupid')));
126: $s2 = $this->groupHandler->delete($group);
127:
128: return ($s1 && $s2);// ? true : false;
129: }
130:
131: /**
132: * delete a user
133: *
134: * @param XoopsUser $user reference to the user to delete
135: * @return bool FALSE if failed
136: */
137: public function deleteUser(XoopsUser $user)
138: {
139: $s1 = $this->membershipHandler->deleteAll(new Criteria('uid', $user->getVar('uid')));
140: $s2 = $this->userHandler->delete($user);
141:
142: return ($s1 && $s2);// ? true : false;
143: }
144:
145: /**
146: * insert a group into the database
147: *
148: * @param XoopsGroup $group reference to the group to insert
149: * @return bool TRUE if already in database and unchanged
150: * FALSE on failure
151: */
152: public function insertGroup(XoopsGroup $group)
153: {
154: return $this->groupHandler->insert($group);
155: }
156:
157: /**
158: * insert a user into the database
159: *
160: * @param XoopsUser $user reference to the user to insert
161: * @param bool $force
162: *
163: * @return bool TRUE if already in database and unchanged
164: * FALSE on failure
165: */
166: public function insertUser(XoopsUser $user, $force = false)
167: {
168: return $this->userHandler->insert($user, $force);
169: }
170:
171: /**
172: * retrieve groups from the database
173: *
174: * @param CriteriaElement $criteria {@link CriteriaElement}
175: * @param bool $id_as_key use the group's ID as key for the array?
176: * @return array array of {@link XoopsGroup} objects
177: */
178: public function getGroups(CriteriaElement $criteria = null, $id_as_key = false)
179: {
180: return $this->groupHandler->getObjects($criteria, $id_as_key);
181: }
182:
183: /**
184: * retrieve users from the database
185: *
186: * @param CriteriaElement $criteria {@link CriteriaElement}
187: * @param bool $id_as_key use the group's ID as key for the array?
188: * @return array array of {@link XoopsUser} objects
189: */
190: public function getUsers(CriteriaElement $criteria = null, $id_as_key = false)
191: {
192: return $this->userHandler->getObjects($criteria, $id_as_key);
193: }
194:
195: /**
196: * get a list of groupnames and their IDs
197: *
198: * @param CriteriaElement $criteria {@link CriteriaElement} object
199: * @return array associative array of group-IDs and names
200: */
201: public function getGroupList(CriteriaElement $criteria = null)
202: {
203: $groups = $this->groupHandler->getObjects($criteria, true);
204: $ret = array();
205: foreach (array_keys($groups) as $i) {
206: $ret[$i] = $groups[$i]->getVar('name');
207: }
208:
209: return $ret;
210: }
211:
212: /**
213: * get a list of usernames and their IDs
214: *
215: * @param CriteriaElement $criteria {@link CriteriaElement} object
216: * @return array associative array of user-IDs and names
217: */
218: public function getUserList(CriteriaElement $criteria = null)
219: {
220: $users =& $this->userHandler->getObjects($criteria, true);
221: $ret = array();
222: foreach (array_keys($users) as $i) {
223: $ret[$i] = $users[$i]->getVar('uname');
224: }
225:
226: return $ret;
227: }
228:
229: /**
230: * add a user to a group
231: *
232: * @param int $group_id ID of the group
233: * @param int $user_id ID of the user
234: * @return XoopsMembership XoopsMembership
235: */
236: public function addUserToGroup($group_id, $user_id)
237: {
238: $mship = $this->membershipHandler->create();
239: $mship->setVar('groupid', $group_id);
240: $mship->setVar('uid', $user_id);
241:
242: return $this->membershipHandler->insert($mship);
243: }
244:
245: /**
246: * remove a list of users from a group
247: *
248: * @param int $group_id ID of the group
249: * @param array $user_ids array of user-IDs
250: * @return bool success?
251: */
252: public function removeUsersFromGroup($group_id, $user_ids = array())
253: {
254: $criteria = new CriteriaCompo();
255: $criteria->add(new Criteria('groupid', $group_id));
256: $criteria2 = new CriteriaCompo();
257: foreach ($user_ids as $uid) {
258: $criteria2->add(new Criteria('uid', $uid), 'OR');
259: }
260: $criteria->add($criteria2);
261:
262: return $this->membershipHandler->deleteAll($criteria);
263: }
264:
265: /**
266: * get a list of users belonging to a group
267: *
268: * @param int $group_id ID of the group
269: * @param bool $asobject return the users as objects?
270: * @param int $limit number of users to return
271: * @param int $start index of the first user to return
272: * @return array Array of {@link XoopsUser} objects (if $asobject is TRUE)
273: * or of associative arrays matching the record structure in the database.
274: */
275: public function getUsersByGroup($group_id, $asobject = false, $limit = 0, $start = 0)
276: {
277: $user_ids = $this->membershipHandler->getUsersByGroup($group_id, $limit, $start);
278: if (!$asobject) {
279: return $user_ids;
280: } else {
281: $ret = array();
282: foreach ($user_ids as $u_id) {
283: $user = $this->getUser($u_id);
284: if (is_object($user)) {
285: $ret[] = &$user;
286: }
287: unset($user);
288: }
289:
290: return $ret;
291: }
292: }
293:
294: /**
295: * get a list of groups that a user is member of
296: *
297: * @param int $user_id ID of the user
298: * @param bool $asobject return groups as {@link XoopsGroup} objects or arrays?
299: * @return array array of objects or arrays
300: */
301: public function getGroupsByUser($user_id, $asobject = false)
302: {
303: $group_ids = $this->membershipHandler->getGroupsByUser($user_id);
304: if (!$asobject) {
305: return $group_ids;
306: } else {
307: $ret = array();
308: foreach ($group_ids as $g_id) {
309: $ret[] = $this->getGroup($g_id);
310: }
311:
312: return $ret;
313: }
314: }
315:
316: /**
317: * log in a user
318: *
319: * @param string $uname username as entered in the login form
320: * @param string $pwd password entered in the login form
321: *
322: * @return XoopsUser|false logged in XoopsUser, FALSE if failed to log in
323: */
324: public function loginUser($uname, $pwd)
325: {
326: $db = XoopsDatabaseFactory::getDatabaseConnection();
327: $uname = $db->escape($uname);
328: $pwd = $db->escape($pwd);
329: $criteria = new Criteria('uname', $uname);
330: $user =& $this->userHandler->getObjects($criteria, false);
331: if (!$user || count($user) != 1) {
332: return false;
333: }
334:
335: $hash = $user[0]->pass();
336: $type = substr($user[0]->pass(), 0, 1);
337: // see if we have a crypt like signature, old md5 hash is just hex digits
338: if ($type==='$') {
339: if (!password_verify($pwd, $hash)) {
340: return false;
341: }
342: // check if hash uses the best algorithm (i.e. after a PHP upgrade)
343: $rehash = password_needs_rehash($hash, PASSWORD_DEFAULT);
344: } else {
345: if ($hash!=md5($pwd)) {
346: return false;
347: }
348: $rehash = true; // automatically update old style
349: }
350: // hash used an old algorithm, so make it stronger
351: if ($rehash) {
352: if ($this->getColumnCharacterLength('users', 'pass') < 255) {
353: error_log('Upgrade required on users table!');
354: } else {
355: $user[0]->setVar('pass', password_hash($pwd, PASSWORD_DEFAULT));
356: $this->userHandler->insert($user[0]);
357: }
358: }
359: return $user[0];
360: }
361:
362: /**
363: * Get maximum character length for a table column
364: *
365: * @param string $table database table
366: * @param string $column table column
367: *
368: * @return int|null max length or null on error
369: */
370: public function getColumnCharacterLength($table, $column)
371: {
372: /** @var XoopsMySQLDatabase $db */
373: $db = XoopsDatabaseFactory::getDatabaseConnection();
374:
375: $dbname = constant('XOOPS_DB_NAME');
376: $table = $db->prefix($table);
377:
378: $sql = sprintf(
379: 'SELECT `CHARACTER_MAXIMUM_LENGTH` FROM `information_schema`.`COLUMNS` '
380: . "WHERE TABLE_SCHEMA = '%s'AND TABLE_NAME = '%s' AND COLUMN_NAME = '%s'",
381: $db->escape($dbname),
382: $db->escape($table),
383: $db->escape($column)
384: );
385:
386: /** @var mysqli_result $result */
387: $result = $db->query($sql);
388: if ($db->isResultSet($result)) {
389: $row = $db->fetchRow($result);
390: if ($row) {
391: $columnLength = $row[0];
392: return (int) $columnLength;
393: }
394: }
395: return null;
396: }
397:
398: /**
399: * count users matching certain conditions
400: *
401: * @param CriteriaElement $criteria {@link CriteriaElement} object
402: * @return int
403: */
404: public function getUserCount(CriteriaElement $criteria = null)
405: {
406: return $this->userHandler->getCount($criteria);
407: }
408:
409: /**
410: * count users belonging to a group
411: *
412: * @param int $group_id ID of the group
413: * @return int
414: */
415: public function getUserCountByGroup($group_id)
416: {
417: return $this->membershipHandler->getCount(new Criteria('groupid', $group_id));
418: }
419:
420: /**
421: * updates a single field in a users record
422: *
423: * @param XoopsUser $user reference to the {@link XoopsUser} object
424: * @param string $fieldName name of the field to update
425: * @param string $fieldValue updated value for the field
426: * @return bool TRUE if success or unchanged, FALSE on failure
427: */
428: public function updateUserByField(XoopsUser $user, $fieldName, $fieldValue)
429: {
430: $user->setVar($fieldName, $fieldValue);
431:
432: return $this->insertUser($user);
433: }
434:
435: /**
436: * updates a single field in a users record
437: *
438: * @param string $fieldName name of the field to update
439: * @param string $fieldValue updated value for the field
440: * @param CriteriaElement $criteria {@link CriteriaElement} object
441: * @return bool TRUE if success or unchanged, FALSE on failure
442: */
443: public function updateUsersByField($fieldName, $fieldValue, CriteriaElement $criteria = null)
444: {
445: return $this->userHandler->updateAll($fieldName, $fieldValue, $criteria);
446: }
447:
448: /**
449: * activate a user
450: *
451: * @param XoopsUser $user reference to the {@link XoopsUser} object
452: * @return mixed successful? false on failure
453: */
454: public function activateUser(XoopsUser $user)
455: {
456: if ($user->getVar('level') != 0) {
457: return true;
458: }
459: $user->setVar('level', 1);
460: $actkey = substr(md5(uniqid(mt_rand(), 1)), 0, 8);
461: $user->setVar('actkey', $actkey);
462:
463: return $this->userHandler->insert($user, true);
464: }
465:
466: /**
467: * Get a list of users belonging to certain groups and matching criteria
468: * Temporary solution
469: *
470: * @param array $groups IDs of groups
471: * @param CriteriaElement $criteria {@link CriteriaElement} object
472: * @param bool $asobject return the users as objects?
473: * @param bool $id_as_key use the UID as key for the array if $asobject is TRUE
474: * @return array Array of {@link XoopsUser} objects (if $asobject is TRUE)
475: * or of associative arrays matching the record structure in the database.
476: */
477: public function getUsersByGroupLink($groups, CriteriaElement $criteria = null, $asobject = false, $id_as_key = false)
478: {
479: $ret = array();
480: $criteriaCompo = new CriteriaCompo();
481: $select = $asobject ? 'u.*' : 'u.uid';
482: $sql = "SELECT {$select} FROM " . $this->userHandler->db->prefix('users') . " u WHERE ";
483: if (!empty($groups)) {
484: $group_in = '(' . implode(', ', $groups) . ')';
485: $sql .= " EXISTS (SELECT * FROM " . $this->membershipHandler->db->prefix('groups_users_link')
486: . " m " . "WHERE m.groupid IN {$group_in} and m.uid = u.uid) AND ";
487: }
488:
489: $limit = $start = 0;
490: if (isset($criteria) && is_subclass_of($criteria, 'CriteriaElement')) {
491: $criteriaCompo->add($criteria, 'AND');
492: $sql_criteria = $criteriaCompo->render();
493: if ($criteria->getSort() != '') {
494: $sql_criteria .= ' ORDER BY ' . $criteria->getSort() . ' ' . $criteria->getOrder();
495: }
496: $limit = $criteria->getLimit();
497: $start = $criteria->getStart();
498: } else {
499: $sql_criteria = $criteriaCompo->render();
500: }
501:
502: if ($sql_criteria) {
503: $sql .= $sql_criteria;
504: }
505:
506: $result = $this->userHandler->db->query($sql, $limit, $start);
507: if (!$this->userHandler->db->isResultSet($result)) {
508: return $ret;
509: }
510: /** @var array $myrow */
511: while (false !== ($myrow = $this->userHandler->db->fetchArray($result))) {
512: if ($asobject) {
513: $user = new XoopsUser();
514: $user->assignVars($myrow);
515: if (!$id_as_key) {
516: $ret[] =& $user;
517: } else {
518: $ret[$myrow['uid']] =& $user;
519: }
520: unset($user);
521: } else {
522: $ret[] = $myrow['uid'];
523: }
524: }
525:
526: return $ret;
527: }
528:
529: /**
530: * Get count of users belonging to certain groups and matching criteria
531: * Temporary solution
532: *
533: * @param array $groups IDs of groups
534: * @param CriteriaElement $criteria
535: * @return int count of users
536: */
537: public function getUserCountByGroupLink(array $groups, CriteriaElement $criteria = null)
538: {
539: $ret = 0;
540: $criteriaCompo = new CriteriaCompo();
541: $sql = "SELECT COUNT(*) FROM " . $this->userHandler->db->prefix('users') . " u WHERE ";
542: if (!empty($groups)) {
543: $group_in = is_array($groups) ? '(' . implode(', ', $groups) . ')' : (array) $groups;
544: $sql .= " EXISTS (SELECT * FROM " . $this->membershipHandler->db->prefix('groups_users_link')
545: . " m " . "WHERE m.groupid IN {$group_in} and m.uid = u.uid) ";
546: }
547:
548: if (isset($criteria) && is_subclass_of($criteria, 'CriteriaElement')) {
549: $criteriaCompo->add($criteria, 'AND');
550: }
551: $sql_criteria = $criteriaCompo->render();
552:
553: if ($sql_criteria) {
554: $sql .= ' AND ' . $sql_criteria;
555: }
556: $result = $this->userHandler->db->query($sql);
557: if (!$this->userHandler->db->isResultSet($result)) {
558: return $ret;
559: }
560: list($ret) = $this->userHandler->db->fetchRow($result);
561:
562: return (int)$ret;
563: }
564: }
565: