PageRenderTime 27ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 0ms

/core/members.class.php

http://snowcms.googlecode.com/
PHP | 1195 lines | 682 code | 131 blank | 382 comment | 128 complexity | c66a9356dfb9d1640cca98a8b43f4da2 MD5 | raw file
Possible License(s): CC-BY-SA-3.0
  1. <?php
  2. ////////////////////////////////////////////////////////////////////////////
  3. // SnowCMS v2.0 //
  4. // By the SnowCMS Team //
  5. // www.snowcms.com //
  6. // Released under the Microsoft Reciprocal License //
  7. // www.opensource.org/licenses/ms-rl.html //
  8. ////////////////////////////////////////////////////////////////////////////
  9. // //
  10. // SnowCMS originally pawned by soren121 started in early 2008 //
  11. // //
  12. ////////////////////////////////////////////////////////////////////////////
  13. // //
  14. // SnowCMS v2.0 began in November 2009 //
  15. // //
  16. ////////////////////////////////////////////////////////////////////////////
  17. // File version: SnowCMS 2.0 //
  18. ////////////////////////////////////////////////////////////////////////////
  19. if(!defined('INSNOW'))
  20. {
  21. die('Nice try...');
  22. }
  23. /*
  24. Class: Members
  25. This is a class which allows the system and plugins to load various
  26. information about either an individual or multiple accounts. Anything
  27. relating to the obtaining or modification of member information should be
  28. done via the Members class as the current site may not be using SnowCMS's
  29. built in member database (You know, like someone is using a bridge ;))
  30. */
  31. class Members
  32. {
  33. // Variable: loaded
  34. // An array containing members which have already been loaded once (they
  35. // don't need to be twice ;))
  36. private $loaded;
  37. /*
  38. Constructor: __construct
  39. Parameters:
  40. none
  41. */
  42. public function __construct()
  43. {
  44. $this->loaded = array();
  45. }
  46. /*
  47. Method: load
  48. This method loads the specified members into an array, which can be
  49. retrieved via <Members::get>
  50. Parameters:
  51. mixed $members - Either an array or single interger containing the
  52. member ID(s) of which members you want to load the
  53. information of.
  54. Returns:
  55. bool - Returns true if the information was successfully loaded, false
  56. on failure.
  57. */
  58. public function load($members)
  59. {
  60. // Not an array? Easy fix!
  61. if(!is_array($members))
  62. {
  63. $members = array((int)$members);
  64. }
  65. else
  66. {
  67. if(count($members) > 0)
  68. {
  69. foreach($members as $key => $member_id)
  70. {
  71. if((int)$member_id > 0)
  72. {
  73. $members[$key] = (int)$member_id;
  74. }
  75. else
  76. {
  77. unset($members[$key]);
  78. }
  79. }
  80. $members = array_unique($members);
  81. }
  82. }
  83. // Alright, so do you want to do this yourself? ;) If you do do this
  84. // yourself, set the handled parameter to a bool, otherwise, if it is
  85. // null, this method will just do it itself!!! :P
  86. $handled = null;
  87. api()->run_hooks('load_members', array(&$handled, &$members));
  88. if($handled === null && count($members) > 0)
  89. {
  90. // Make sure this member isn't already loaded, otherwise it is a waste
  91. // of resources.
  92. foreach($members as $member_id)
  93. {
  94. if(isset($this->loaded[$member_id]))
  95. {
  96. unset($members[$member_id]);
  97. }
  98. }
  99. if(count($members) > 0)
  100. {
  101. $result = db()->query('
  102. SELECT
  103. *
  104. FROM {db->prefix}members
  105. WHERE member_id IN({int_array:members})
  106. LIMIT {int:member_count}',
  107. array(
  108. 'members' => $members,
  109. 'member_count' => count($members),
  110. ), 'load_members_query');
  111. if($result->num_rows() > 0)
  112. {
  113. while($row = $result->fetch_assoc())
  114. {
  115. $member = array(
  116. 'id' => $row['member_id'],
  117. 'name' => $row['display_name'],
  118. 'username' => $row['member_name'],
  119. 'password' => $row['member_pass'],
  120. 'email' => $row['member_email'],
  121. 'groups' => explode(',', $row['member_groups']),
  122. 'member_groups' => explode(',', $row['member_groups']),
  123. 'last_active' => $row['member_last_active'],
  124. 'registered' => $row['member_registered'],
  125. 'ip' => $row['member_ip'],
  126. 'is_activated' => !empty($row['member_activated']),
  127. 'acode' => $row['member_acode'],
  128. 'data' => array(),
  129. );
  130. // Got something to add?
  131. api()->run_hooks('members_load_array', array(&$member, $row));
  132. $this->loaded[$row['member_id']] = $member;
  133. }
  134. $result = db()->query('
  135. SELECT
  136. member_id, variable, value
  137. FROM {db->prefix}member_data
  138. WHERE member_id IN({int_array:members})',
  139. array(
  140. 'members' => $members,
  141. ), 'load_members_data_query');
  142. if($result->num_rows() > 0)
  143. {
  144. while($row = $result->fetch_assoc())
  145. {
  146. $this->loaded[$row['member_id']]['data'][$row['variable']] = $row['value'];
  147. }
  148. }
  149. }
  150. }
  151. $handled = true;
  152. }
  153. elseif($handled === null)
  154. {
  155. $handled = false;
  156. }
  157. return !empty($handled);
  158. }
  159. /*
  160. Method: get
  161. After the member(s) information has been loaded via <Members::get> the
  162. data can be retrieved through this method.
  163. Parameters:
  164. mixed $members - Either an array of integers or an integer containing
  165. the member you want to get the loaded information of.
  166. Returns:
  167. array - If you set the members parameter as an array, you will
  168. retrieve an array containing nested arrays, the index of the
  169. subarrays being the members id. If a single integer is
  170. supplied, then a single array will be returned. However, if
  171. the member id supplied has not yet been loaded, the value will
  172. be false.
  173. */
  174. public function get($members)
  175. {
  176. // If they aren't requesting multiple members we will handle the return
  177. // differently -- after all, we will simply do a foreach and call on
  178. // this method with a single ID to load up multiple members information.
  179. if(!is_array($members))
  180. {
  181. $members = (int)$members;
  182. $member_data = -1;
  183. // You want to do the loading? Set the member_data variable to something other
  184. // than -1, otherwise, this method will get the data itself. Set member_data to
  185. // null if that member has not been loaded.
  186. api()->run_hooks('members_get', array(&$member_data, &$members));
  187. if($member_data == -1 && isset($this->loaded[$members]))
  188. {
  189. $member_data = $this->loaded[$members];
  190. }
  191. elseif($member_data == -1)
  192. {
  193. $member_data = null;
  194. }
  195. // Wanna touch it?
  196. api()->run_hooks('post_members_get', array(&$member_data, &$members));
  197. return $member_data === null ? false : $member_data;
  198. }
  199. else
  200. {
  201. // Load all those members ;)
  202. $member_data = array();
  203. foreach($members as $member_id)
  204. {
  205. $member_data[$member_id] = $this->get($member_id);
  206. }
  207. // Simple, no?
  208. return $member_data;
  209. }
  210. }
  211. /*
  212. Method: add
  213. Creates a member with the supplied information.
  214. Parameters:
  215. string $member_name - The login name of the member you want to create.
  216. string $member_pass - The unhashed password the member uses to login
  217. with.
  218. string $member_email - The email of the member.
  219. array $options - An optional array containing extra information (such
  220. as their hash, display name, ip, data (member_data
  221. table), if none of these are supplied, the system
  222. automatically creates this info.)
  223. Returns:
  224. int - Returns an integer if the member was successfully created (which
  225. is the new new members ID) or true on failure.
  226. */
  227. public function add($member_name, $member_pass, $member_email, $options = array())
  228. {
  229. global $func;
  230. // Allows a plugin to handle the creation of members themselves ;)
  231. // Set the handled parameter to an integer or false, otherwise the
  232. // system will handle the creation of the member.
  233. $handled = null;
  234. api()->run_hooks('members_add', array(&$handled, $member_name, $member_pass, $member_email, $options));
  235. if($handled === null)
  236. {
  237. $member_name = trim($member_name);
  238. // Now make sure that the member name and email are allowed, we don't
  239. // want them to be in use already, as that would be pretty bad :P
  240. if(!$this->name_allowed($member_name) || !$this->email_allowed($member_email) || !$this->password_allowed($member_name, $member_pass))
  241. {
  242. return false;
  243. }
  244. // Have you set a display name? Gotta check that!
  245. if(!empty($options['display_name']) && !$this->name_allowed($options['display_name']))
  246. {
  247. return false;
  248. }
  249. elseif(empty($options['display_name']))
  250. {
  251. // We will just make your login name your display name too...
  252. $options['display_name'] = $member_name;
  253. }
  254. // No member groups assigned? Member it is! (If the member is not an
  255. // administrator, they must have at least the member group assigned to
  256. // them)
  257. if(isset($options['member_groups']) && is_array($options['member_groups']) && !in_array('administrator', $options['member_groups']) && !in_array('member', $options['member_groups']))
  258. {
  259. return false;
  260. }
  261. elseif(!isset($options['member_groups']) || !is_array($options['member_groups']))
  262. {
  263. $options['member_groups'] = array('member');
  264. }
  265. // Registration time can be manually set, must be greater than 0
  266. // though :P
  267. if(isset($options['member_registered']) && $options['member_registered'] <= 0)
  268. {
  269. return false;
  270. }
  271. elseif(!isset($options['member_registered']))
  272. {
  273. $options['member_registered'] = time_utc();
  274. }
  275. // An IP?
  276. if(empty($options['member_ip']))
  277. {
  278. $options['member_ip'] = member()->ip();
  279. }
  280. // Is the member activated?
  281. $options['member_activated'] = !empty($options['member_activated']) ? 1 : 0;
  282. // If the member is not activated, then we will generate an activation
  283. // code...
  284. $options['member_acode'] = empty($options['member_activated']) && empty($options['member_acode']) ? sha1($this->rand_str(mt_rand(30, 40))) : (!empty($options['member_acode']) ? $options['member_acode'] : '');
  285. // Alright! Now insert that member!!!
  286. $result = db()->insert('insert', '{db->prefix}members',
  287. array(
  288. 'member_name' => 'string', 'member_pass' => 'string', 'display_name' => 'string',
  289. 'member_email' => 'string', 'member_groups' => 'string', 'member_registered' => 'int',
  290. 'member_ip' => 'string', 'member_activated' => 'int', 'member_acode' => 'string',
  291. ),
  292. array(
  293. htmlchars($member_name), sha1($func['strtolower'](htmlchars($member_name)). $member_pass), htmlchars($options['display_name']),
  294. htmlchars($member_email), implode(',', $options['member_groups']), $options['member_registered'],
  295. $options['member_ip'], $options['member_activated'], $options['member_acode'],
  296. ), array(), 'members_add_query');
  297. $handled = $result->success() ? $result->insert_id() : false;
  298. // Maybe there is some default data that needs insertion?
  299. if(!empty($handled))
  300. {
  301. $data = array();
  302. // If you want to add data, do:
  303. // $data[] = array(varible, value)
  304. api()->run_hooks('members_add_default_data', array(&$data));
  305. // Anything?
  306. if(count($data) > 0)
  307. {
  308. foreach($data as $key => $value)
  309. {
  310. $data[$key] = array($handled, $value[0], $value[1]);
  311. }
  312. db()->insert('replace', '{db->prefix}member_data',
  313. array(
  314. 'member_id' => 'int', 'variable' => 'string-255', 'value' => 'string',
  315. ),
  316. $data, array('member_id'), 'members_add_default_data_query');
  317. }
  318. }
  319. }
  320. return (string)$handled == (string)(int)$handled ? (int)$handled : false;
  321. }
  322. /*
  323. Method: name_allowed
  324. Checks to see if the specified member name is allowed, or already taken.
  325. Parameters:
  326. string $member_name - The member name to check.
  327. int $member_id - If there is a certain member which you want to
  328. exclude from your search, give it here ;) Default is
  329. 0, which is to search all members.
  330. Returns:
  331. bool - Returns true if the name is allowed, false if not.
  332. */
  333. public function name_allowed($member_name, $member_id = 0)
  334. {
  335. global $func;
  336. if(empty($member_id))
  337. {
  338. $member_id = 0;
  339. }
  340. // You know what to do, set it to a bool if you handle it ;)
  341. $handled = null;
  342. api()->run_hooks('members_name_allowed', array(&$handled, &$member_name, &$member_id));
  343. if($handled === null)
  344. {
  345. // Make sure the name isn't too long, or too short!
  346. if($func['strlen']($member_name) < settings()->get('members_min_name_length', 'int', 1) || $func['strlen']($member_name) > settings()->get('members_max_name_length', 'int', 80))
  347. {
  348. return false;
  349. }
  350. // Lower it!!! (And htmlspecialchars it as well :P)
  351. $member_name = $func['strtolower'](htmlchars($member_name));
  352. // First check to see if it is a reserved name...
  353. $disallowed_names = explode("\n", $func['strtolower'](settings()->get('disallowed_names', 'string')));
  354. if(count($disallowed_names))
  355. {
  356. foreach($disallowed_names as $disallowed_name)
  357. {
  358. $disallowed_name = trim($disallowed_name);
  359. // Any wildcards?
  360. if($func['strpos']($disallowed_name, '*') !== false)
  361. {
  362. if(preg_match('~^'. str_replace('*', '(?:.*?)?', $disallowed_name). '$~i', $member_name))
  363. {
  364. return false;
  365. }
  366. }
  367. elseif($member_name == $disallowed_name)
  368. {
  369. return false;
  370. }
  371. }
  372. }
  373. // Now search the database...
  374. $result = db()->query('
  375. SELECT
  376. member_id
  377. FROM {db->prefix}members
  378. WHERE '. (db()->case_sensitive ? '(LOWER(member_name) = {string:member_name} OR LOWER(display_name) = {string:member_name})' : '(member_name = {string:member_name} OR display_name = {string:member_name})'). ' AND member_id != {int:member_id}
  379. LIMIT 1',
  380. array(
  381. 'member_name' => $member_name,
  382. 'member_id' => $member_id,
  383. ), 'members_name_allowed_query');
  384. // We find any matches?
  385. if($result->num_rows() > 0)
  386. {
  387. return false;
  388. }
  389. }
  390. // Are we still going? Then return the handled value, unless it wasn't
  391. // modified, in which case, that means we handled it and we didn't find
  392. // the name!
  393. return $handled === null ? true : !empty($handled);
  394. }
  395. /*
  396. Method: email_allowed
  397. Checks to see if the specified email is allowed (either the whole
  398. address itself, or the domain) and not already taken.
  399. Parameters:
  400. string $member_email - The email to check.
  401. int $member_id - If there is a certain member which you want to
  402. exclude from your search, give it here ;) Default is
  403. 0, which is to search all members.
  404. Returns:
  405. bool - Returns true if the email address is allowed, false if not.
  406. */
  407. public function email_allowed($member_email, $member_id = 0)
  408. {
  409. global $func;
  410. // You know what to do, set it to a bool if you handle it ;)
  411. $handled = null;
  412. api()->run_hooks('members_email_allowed', array(&$handled, $member_email));
  413. if($handled === null)
  414. {
  415. $member_email = $func['strtolower'](htmlchars($member_email));
  416. // Check the email checker function.
  417. if(!is_email($member_email))
  418. {
  419. return false;
  420. }
  421. // Now check disallowed emails...
  422. $disallowed_emails = explode("\n", $func['strtolower'](settings()->get('disallowed_emails', 'string')));
  423. if(count($disallowed_emails) > 0)
  424. {
  425. foreach($disallowed_emails as $disallowed_email)
  426. {
  427. $disallowed_email = trim($disallowed_email);
  428. // Any wildcards?
  429. if($func['strpos']($disallowed_email, '*') !== false)
  430. {
  431. if(preg_match('~^'. str_replace('*', '(?:.*?)?', $disallowed_email). '$~i', $member_email))
  432. {
  433. return false;
  434. }
  435. }
  436. elseif($member_email == $disallowed_email)
  437. {
  438. return false;
  439. }
  440. }
  441. }
  442. // Or maybe, just maybe, it is already in use...
  443. $result = db()->query('
  444. SELECT
  445. member_id
  446. FROM {db->prefix}members
  447. WHERE '. (db()->case_sensitive ? 'LOWER(member_email) = {string:member_email}' : 'member_email = {string:member_email}'). ' AND member_id != {int:member_id}
  448. LIMIT 1',
  449. array(
  450. 'member_email' => $member_email,
  451. 'member_id' => $member_id,
  452. ), 'members_email_allowed_query');
  453. if($result->num_rows() > 0)
  454. {
  455. return false;
  456. }
  457. }
  458. return $handled === null ? true : !empty($handled);
  459. }
  460. /*
  461. Method: password_allowed
  462. Checks to see if the supplied password is allowed.
  463. Parameters:
  464. string $member_name - The login name of the member you are checking
  465. the password of.
  466. string $member_pass - The password to check.
  467. Returns:
  468. bool - Returns true if the password is allowed, false if not.
  469. Note:
  470. The supplied password parameter should NOT be hashed, it should
  471. be the password in its original (unhashed) form.
  472. */
  473. public function password_allowed($member_name, $member_pass)
  474. {
  475. global $func;
  476. $handled = null;
  477. api()->run_hooks('members_password_allowed', array(&$handled, $member_pass));
  478. if($handled === null)
  479. {
  480. // Just a low setting? So the password must have at least 3
  481. // characters.
  482. if(settings()->get('password_security', 'int') == 1)
  483. {
  484. $handled = $func['strlen']($member_pass) >= 4;
  485. }
  486. // Medium requires the password be at least 6 characters and cannot
  487. // contain the users name.
  488. elseif(settings()->get('password_security', 'int') == 2)
  489. {
  490. $handled = $func['strlen']($member_pass) >= 6 && $func['stripos']($member_pass, $member_name) === false;
  491. }
  492. // High requires that the password be at least 8 characters, cannot
  493. // contain the users name and it also must be alphanumeric.
  494. else
  495. {
  496. $handled = $func['strlen']($member_pass) >= 8 && $func['stripos']($member_pass, $member_name) === false && preg_match('~[0-9]+~', $member_pass);
  497. }
  498. }
  499. return !empty($handled);
  500. }
  501. /*
  502. Method: authenticate
  503. This method takes a login name and a password, and checks to see if the
  504. supplied credentials would actually allow the user to login... This
  505. could be use for various things, oh, say, someone commenting under a
  506. username, but they don't actually want to login to post under their
  507. account (you know, like they are in a hurry/in a public place), hint
  508. hint ;) or actually verifying the login of a member...
  509. Parameters:
  510. string $member_name - The name of the member to check the credentials
  511. of.
  512. string $member_pass - The password to attempt to login with.
  513. string $pass_hash - This can be either false, which means that the
  514. supplied password is not hashed at all (in which
  515. case, this method hashes it), it can also be true,
  516. which means that the password was hashed in this
  517. format: SHA1(LOWER(members name) + members
  518. password) or a string that was used to salt the
  519. supplied hashed password. That means that the
  520. members password hashed in this format:
  521. SHA1(SHA1(LOWER(members name) + members password)
  522. + password hash).
  523. Returns:
  524. bool - Returns true if the credentials were correct, false on failure.
  525. */
  526. public function authenticate($member_name, $member_pass, $pass_hash = false)
  527. {
  528. global $func;
  529. // You should get this idea by now :P
  530. $authenticated = null;
  531. api()->run_hooks('members_authenticate', array(&$authenticated, $member_name, $member_pass, $pass_hash));
  532. if($authenticated === null)
  533. {
  534. $member_name = htmlchars(trim($member_name));
  535. // Password not hashed..? That's fine, I'll do it myself, then. :P
  536. if(empty($pass_hash))
  537. {
  538. $member_pass = sha1($func['strtolower'](htmlchars($member_name)). $member_pass);
  539. $pass_hash = true;
  540. }
  541. // Alright, let's query that database!
  542. $result = db()->query('
  543. SELECT
  544. member_pass
  545. FROM {db->prefix}members
  546. WHERE member_name = {string:member_name}
  547. LIMIT 1',
  548. array(
  549. 'member_name' => $member_name,
  550. ), 'members_authenticate_query');
  551. // Did we get anything?
  552. if($result->num_rows() > 0)
  553. {
  554. // Sure, we may have gotten a result, but that doesn't mean their
  555. // password is right :P
  556. $row = $result->fetch_assoc();
  557. // So let's check
  558. if($member_pass == $row['member_pass'] || (!empty($pass_hash) && $pass_hash !== true && $member_pass == sha1($row['member_pass']. $pass_hash)))
  559. {
  560. return true;
  561. }
  562. else
  563. {
  564. // Maybe you would like to check..?
  565. $authenticated = false;
  566. api()->run_hooks('members_authenticate_other', array(&$authenticated, $member_name, $member_pass, $pass_hash, $row));
  567. return !empty($authenticated);
  568. }
  569. }
  570. else
  571. {
  572. // We got nothing!
  573. return false;
  574. }
  575. }
  576. return !empty($authenticated);
  577. }
  578. /*
  579. Method: rand_str
  580. Generates a random as long as the supplied length.
  581. Parameters:
  582. int $length - The length of the random string you want to create.
  583. If no length is supplied, a random length between
  584. 1 and 100 is used.
  585. Returns:
  586. string - Returns the randomly (pseudo-random, of course, because we
  587. all know, computers can't really make true random stuff ;))
  588. generated string.
  589. */
  590. public function rand_str($length = 0)
  591. {
  592. // If for some very strange, unknown reason you want to do a random
  593. // string, be my guest!
  594. $handled = null;
  595. $str = '';
  596. api()->run_hooks('members_rand_str', array(&$handled, &$length, &$str));
  597. if($handled === null)
  598. {
  599. if(empty($length) || $length < 1)
  600. {
  601. $length = mt_rand(1, 100);
  602. }
  603. $chars = array(
  604. 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
  605. 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
  606. '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '~', '!', '@', '#', '$', '%', '^', '*', '-', '_', '+', '=', '?',
  607. );
  608. $str = '';
  609. for($i = 0; $i < $length; $i++)
  610. {
  611. $str .= $chars[array_rand($chars)];
  612. }
  613. return $str;
  614. }
  615. else
  616. {
  617. return $str;
  618. }
  619. }
  620. /*
  621. Method: update
  622. Updates the information of a supplied member ID.
  623. Parameters:
  624. int $member_id - The member ID to update.
  625. array $options - An array containing the information you want to
  626. update.
  627. Returns:
  628. bool - Returns true if the member was successfully updated, false if
  629. not (such as if the member does not exist).
  630. Note:
  631. For the $options parameter, these are acceptable indices to use:
  632. member_name - Their login name, however, if this is changed, their
  633. current password MUST be supplied, otherwise, the
  634. update will fail (password must not be hashed yet),
  635. their member name must be supplied if their password
  636. is changed as well.
  637. member_pass - The users new password.
  638. display_name - The members display name.
  639. member_email - The members email address.
  640. member_groups - An array containing the members groups.
  641. member_ip - The IP of the member.
  642. member_activated - The current status of the member. 0 means
  643. unactivated, 1 means activated and 11 means that
  644. the member changed their email and that the
  645. administrator has set the option to require the
  646. member to verify their new email address before
  647. it is changed.
  648. member_acode - An activation code for activating or reactivating
  649. their account.
  650. data - An array formatted as so: variable => value, simple, no? If
  651. you want to delete a data variable for the specified member,
  652. set the value to false.
  653. admin_override - Set this to true if it is the administrator
  654. modifying the account, in which case a password
  655. request is set up and an email sent asking the
  656. user to set a new password.
  657. */
  658. public function update($member_id, $options)
  659. {
  660. global $func;
  661. // No options? No go!
  662. if(count($options) == 0)
  663. {
  664. return false;
  665. }
  666. $handled = null;
  667. api()->run_hooks('members_update', array(&$handled, &$member_id, &$options));
  668. if($handled === null)
  669. {
  670. // Make sure it is an integer.
  671. if((string)$member_id != (string)(int)$member_id)
  672. {
  673. return false;
  674. }
  675. // Can't update a profile that doesn't exist, can we?
  676. $result = db()->query('
  677. SELECT
  678. member_id
  679. FROM {db->prefix}members
  680. WHERE member_id = {int:member_id}
  681. LIMIT 1',
  682. array(
  683. 'member_id' => $member_id,
  684. ), 'members_update_profile_exists');
  685. if($result->num_rows() == 0)
  686. {
  687. return false;
  688. }
  689. elseif(isset($options['data']))
  690. {
  691. $member_data = $options['data'];
  692. unset($options['data']);
  693. }
  694. $allowed_columns = array(
  695. 'member_name' => 'string-80',
  696. 'member_pass' => 'string-40',
  697. 'display_name' => 'string-255',
  698. 'member_email' => 'string-255',
  699. 'member_groups' => 'string-255',
  700. 'member_registered' => 'int',
  701. 'member_ip' => 'string-150',
  702. 'member_activated' => 'int',
  703. 'member_acode' => 'string-40',
  704. );
  705. api()->run_hooks('members_update_allowed_columns', array(&$allowed_columns));
  706. $data = array();
  707. foreach($allowed_columns as $column => $type)
  708. {
  709. // Only add the data if the column exists...
  710. if(isset($options[$column]))
  711. {
  712. $data[$column] = $options[$column];
  713. }
  714. }
  715. // Let's let you check the data (just incase ;)) first...
  716. api()->run_hooks('members_update_check_data', array(&$handled, &$data));
  717. // If a hook didn't change handled, we can do our stuff :P
  718. if($handled !== false)
  719. {
  720. if(isset($data['member_groups']) && !is_array($data['member_groups']))
  721. {
  722. $data['member_groups'] = array($data['member_groups']);
  723. }
  724. // Make sure their name and password are OK, if supplied!
  725. if(isset($data['member_name']) && !$this->name_allowed($data['member_name'], $member_id))
  726. {
  727. // Must be taken!
  728. return false;
  729. }
  730. elseif(isset($data['member_pass']) && !$this->password_allowed(isset($data['member_name']) ? $data['member_name'] : '', $data['member_pass']))
  731. {
  732. return false;
  733. }
  734. elseif(isset($data['member_email']) && !$this->email_allowed($data['member_email'], $member_id))
  735. {
  736. return false;
  737. }
  738. elseif(isset($data['display_name']) && !$this->name_allowed($data['display_name'], $member_id))
  739. {
  740. return false;
  741. }
  742. elseif((isset($data['member_groups']) && !is_array($data['member_groups'])) || (isset($data['member_groups']) && (!in_array('member', $data['member_groups']) && !in_array('administrator', $data['member_groups']))))
  743. {
  744. return false;
  745. }
  746. // Remove any blank groups.
  747. if(isset($data['member_groups']) && count($data['member_groups']) > 0)
  748. {
  749. $member_groups = array();
  750. foreach($data['member_groups'] as $group_id)
  751. {
  752. if(strlen(trim($group_id)) > 0)
  753. {
  754. $member_groups[] = trim($group_id);
  755. }
  756. }
  757. // There, all done :-)
  758. $data['member_groups'] = $member_groups;
  759. }
  760. // Now we need to hash the password, maybe! They're user name must
  761. // be supplied, because we salt their password with their user name.
  762. if(!empty($options['member_name']) && !empty($options['member_pass']))
  763. {
  764. $data['member_pass'] = sha1($func['strtolower']($options['member_name']). $options['member_pass']);
  765. }
  766. // If they didn't supply a user name but they did supply a password
  767. // then we can't continue... Sorry!
  768. elseif(empty($options['admin_override']) && ((!empty($options['member_name']) && empty($options['member_pass'])) || (empty($options['member_name']) && !empty($options['member_pass']))))
  769. {
  770. return false;
  771. }
  772. // However, if admin_override has been invoked then we will simply
  773. // ignore the fact that their user name was not supplied. In order
  774. // to get passed that hurdle we must then start the password reset
  775. // process on the account.
  776. elseif(!empty($options['admin_override']) && !empty($options['member_name']))
  777. {
  778. // Yup, invoke that password reset... Once we make sure the update
  779. // actually takes effect.
  780. $invoke_pwreset = true;
  781. }
  782. // Can't be a bool, the database wants an integer! Easy fix, though!
  783. if(in_array('member_activated', array_keys($data)) && is_bool($data['member_activated']))
  784. {
  785. $data['member_activated'] = !empty($data['member_activated']) ? 1 : 0;
  786. }
  787. // Our data array could be empty, simply because they are updating
  788. // member_data table information!
  789. if(!empty($data))
  790. {
  791. $db_vars = array(
  792. 'member_id' => $member_id,
  793. );
  794. $values = array();
  795. foreach($data as $column => $value)
  796. {
  797. $values[] = $column. ' = {'. $allowed_columns[$column]. ':'. $column. '_value}';
  798. $db_vars[$column. '_value'] = $column == 'member_groups' ? implode(',', $value) : $value;
  799. }
  800. // Now update that data!
  801. $result = db()->query('
  802. UPDATE {db->prefix}members
  803. SET '. implode(', ', $values). '
  804. WHERE member_id = {int:member_id}
  805. LIMIT 1',
  806. $db_vars, 'members_update_query');
  807. $handled = $result->success();
  808. // We may need to invoke the password reset process on this
  809. // account.
  810. if($handled && !empty($invoke_pwreset))
  811. {
  812. require_once(coredir. '/forgotpw.php');
  813. forgotpw_invoke($member_id, true);
  814. }
  815. }
  816. // Changing member data? But only if the other options were
  817. // successfully updated or if no other options were updated.
  818. if(!empty($member_data) && count($member_data) > 0 && ($handled === null || $handled))
  819. {
  820. $data = array();
  821. $delete = array();
  822. foreach($member_data as $variable => $value)
  823. {
  824. // If the value isn't false we will update it.
  825. if($value !== false)
  826. {
  827. // Be sure to typecast the value to a string.
  828. $data[] = array($member_id, $variable, typecast()->to('string', $value));
  829. }
  830. else
  831. {
  832. // Otherwise, we delete it.
  833. $delete[] = $variable;
  834. }
  835. }
  836. if(count($data) > 0)
  837. {
  838. $result = db()->insert('replace', '{db->prefix}member_data',
  839. array(
  840. 'member_id' => 'int', 'variable' => 'string-255', 'value' => 'string',
  841. ),
  842. $data,
  843. array('member_id'), 'members_update_data_query');
  844. $handled = $result->success();
  845. }
  846. if(count($delete) > 0)
  847. {
  848. $result = db()->query('
  849. DELETE FROM {db->prefix}member_data
  850. WHERE member_id = {int:member_id} AND variable IN({string_array:variables})',
  851. array(
  852. 'member_id' => $member_id,
  853. 'variables' => $delete,
  854. ), 'members_update_delete_data_query');
  855. $handled = $result->success();
  856. }
  857. }
  858. // This member will need to be reloaded ;)
  859. unset($this->loaded[$member_id]);
  860. api()->run_hooks('members_update_force_refresh', array($member_id));
  861. }
  862. }
  863. return !empty($handled);
  864. }
  865. /*
  866. Method: delete
  867. Deletes the specified members.
  868. Parameters:
  869. mixed $members - This can either be an integer, or an array of integers.
  870. Returns:
  871. bool - Returns TRUE if the specified members were deleted, FALSE if not.
  872. Note:
  873. Be sure before executing this command that you verify their session id!
  874. Check out <Members.verify>
  875. */
  876. public function delete($members)
  877. {
  878. $handled = null;
  879. api()->run_hooks('members_delete', array(&$handled, $members));
  880. if($handled === null)
  881. {
  882. // Not an array? We will fix that!!!
  883. if(!is_array($members))
  884. {
  885. $members = array($members);
  886. }
  887. // Yeah, we deleted nothing successfully! Ha!
  888. if(count($members) == 0)
  889. {
  890. return true;
  891. }
  892. // Now let's just make sure they are all plausible ids...
  893. foreach($members as $key => $member_id)
  894. {
  895. $member_id = (int)$member_id;
  896. if($member_id < 1)
  897. {
  898. unset($members[$key]);
  899. }
  900. }
  901. $members = array_unique($members);
  902. // Now delete those members!
  903. $result = db()->query('
  904. DELETE FROM {db->prefix}members
  905. WHERE member_id IN({int_array:members})
  906. LIMIT {int:member_count}',
  907. array(
  908. 'members' => $members,
  909. 'member_count' => count($members),
  910. ), 'members_delete_query');
  911. // Was it a success? We still have some more to do!
  912. if($result->success())
  913. {
  914. // Now delete their data in the member_data table.
  915. $result = db()->query('
  916. DELETE FROM {db->prefix}member_data
  917. WHERE member_id IN({int_array:members})',
  918. array(
  919. 'members' => $members,
  920. ), 'members_delete_query_data');
  921. }
  922. $handled = $result->success();
  923. api()->run_hooks('post_members_delete', array($members));
  924. }
  925. return !empty($handled);
  926. }
  927. /*
  928. Method: name_to_id
  929. Converts a username or email address to a member ID.
  930. Parameters:
  931. mixed $name - The username or email address to translate into a member
  932. ID, this can also be an array of usernames/emails as
  933. well.
  934. Returns:
  935. mixed - Returns an integer if one username or email address is
  936. supplied, an associative rray containing the IDs
  937. (LOWER(name/email) => ID). The value of the name/email will be
  938. false (for arrays or single lookups) if the name/email was not
  939. found.
  940. Note:
  941. Please note that this looks up usernames and email addresses, not
  942. display names!
  943. */
  944. public function name_to_id($name)
  945. {
  946. global $func;
  947. // You might want to do this if you have your own member setup ;)
  948. $handled = null;
  949. api()->run_hooks('member_name_to_id', array(&$handled, &$name));
  950. if($handled === null)
  951. {
  952. // Is it a bird, a plane, an array?!
  953. if(!is_array($name))
  954. {
  955. // It's not an array, yet ;)
  956. $name = array($name);
  957. }
  958. // Nothing? Bad!
  959. if(count($name) == 0)
  960. {
  961. return false;
  962. }
  963. // Lowercase all the names.
  964. foreach($name as $key => $value)
  965. {
  966. $name[$key] = $func['strtolower']($value);
  967. }
  968. // Simple in reality...
  969. $result = db()->query('
  970. SELECT
  971. LOWER(member_name) AS name, LOWER(member_email) AS email, member_id AS id
  972. FROM {db->prefix}members
  973. WHERE ('. (db()->case_sensitive ? 'LOWER(member_name)' : 'member_name'). ' IN({string_array:names})) OR ('. (db()->case_sensitive ? 'LOWER(member_email)' : 'member_email'). ' IN({string_array:names}))',
  974. array(
  975. 'names' => $name,
  976. ), 'member_name_to_id_query');
  977. // Now it gets different... We may just return the ID itself, no array.
  978. if(count($name) == 1)
  979. {
  980. if($result->num_rows() == 0)
  981. {
  982. return false;
  983. }
  984. list(,, $member_id) = $result->fetch_row();
  985. return $member_id;
  986. }
  987. else
  988. {
  989. // Flip!!! :-)
  990. $names = array_flip($name);
  991. // For now, we will assume none were found.
  992. foreach($names as $key => $name)
  993. {
  994. $names[$name] = false;
  995. }
  996. while($row = $result->fetch_assoc())
  997. {
  998. // They could have supplied a name and email address which are
  999. // from the same member, so:
  1000. if(isset($names[$row['email']]))
  1001. {
  1002. $names[$row['email']] = $row['id'];
  1003. }
  1004. if(isset($names[$row['name']]))
  1005. {
  1006. $names[$row['name']] = $row['id'];
  1007. }
  1008. }
  1009. return $names;
  1010. }
  1011. }
  1012. return $handled;
  1013. }
  1014. }
  1015. ?>