You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

rcube_user.php 26KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842
  1. <?php
  2. /**
  3. +-----------------------------------------------------------------------+
  4. | This file is part of the Roundcube Webmail client |
  5. | Copyright (C) 2005-2012, The Roundcube Dev Team |
  6. | |
  7. | Licensed under the GNU General Public License version 3 or |
  8. | any later version with exceptions for skins & plugins. |
  9. | See the README file for a full license statement. |
  10. | |
  11. | PURPOSE: |
  12. | This class represents a system user linked and provides access |
  13. | to the related database records. |
  14. +-----------------------------------------------------------------------+
  15. | Author: Thomas Bruederli <roundcube@gmail.com> |
  16. | Author: Aleksander Machniak <alec@alec.pl> |
  17. +-----------------------------------------------------------------------+
  18. */
  19. /**
  20. * Class representing a system user
  21. *
  22. * @package Framework
  23. * @subpackage Core
  24. */
  25. class rcube_user
  26. {
  27. public $ID;
  28. public $data;
  29. public $language;
  30. public $prefs;
  31. /**
  32. * Holds database connection.
  33. *
  34. * @var rcube_db
  35. */
  36. private $db;
  37. /**
  38. * Framework object.
  39. *
  40. * @var rcube
  41. */
  42. private $rc;
  43. /**
  44. * Internal identities cache
  45. *
  46. * @var array
  47. */
  48. private $identities = array();
  49. /**
  50. * Internal emails cache
  51. *
  52. * @var array
  53. */
  54. private $emails;
  55. const SEARCH_ADDRESSBOOK = 1;
  56. const SEARCH_MAIL = 2;
  57. /**
  58. * Object constructor
  59. *
  60. * @param int $id User id
  61. * @param array $sql_arr SQL result set
  62. */
  63. function __construct($id = null, $sql_arr = null)
  64. {
  65. $this->rc = rcube::get_instance();
  66. $this->db = $this->rc->get_dbh();
  67. if ($id && !$sql_arr) {
  68. $sql_result = $this->db->query(
  69. "SELECT * FROM " . $this->db->table_name('users', true)
  70. . " WHERE `user_id` = ?", $id);
  71. $sql_arr = $this->db->fetch_assoc($sql_result);
  72. }
  73. if (!empty($sql_arr)) {
  74. $this->ID = $sql_arr['user_id'];
  75. $this->data = $sql_arr;
  76. $this->language = $sql_arr['language'];
  77. }
  78. }
  79. /**
  80. * Build a user name string (as e-mail address)
  81. *
  82. * @param string $part Username part (empty or 'local' or 'domain', 'mail')
  83. * @return string Full user name or its part
  84. */
  85. function get_username($part = null)
  86. {
  87. if ($this->data['username']) {
  88. // return real name
  89. if (!$part) {
  90. return $this->data['username'];
  91. }
  92. list($local, $domain) = explode('@', $this->data['username']);
  93. // at least we should always have the local part
  94. if ($part == 'local') {
  95. return $local;
  96. }
  97. // if no domain was provided...
  98. if (empty($domain)) {
  99. $domain = $this->rc->config->mail_domain($this->data['mail_host']);
  100. }
  101. if ($part == 'domain') {
  102. return $domain;
  103. }
  104. if (!empty($domain))
  105. return $local . '@' . $domain;
  106. else
  107. return $local;
  108. }
  109. return false;
  110. }
  111. /**
  112. * Get the preferences saved for this user
  113. *
  114. * @return array Hash array with prefs
  115. */
  116. function get_prefs()
  117. {
  118. if (isset($this->prefs)) {
  119. return $this->prefs;
  120. }
  121. $this->prefs = array();
  122. if (!empty($this->language))
  123. $this->prefs['language'] = $this->language;
  124. if ($this->ID) {
  125. // Preferences from session (write-master is unavailable)
  126. if (!empty($_SESSION['preferences'])) {
  127. // Check last write attempt time, try to write again (every 5 minutes)
  128. if ($_SESSION['preferences_time'] < time() - 5 * 60) {
  129. $saved_prefs = unserialize($_SESSION['preferences']);
  130. $this->rc->session->remove('preferences');
  131. $this->rc->session->remove('preferences_time');
  132. $this->save_prefs($saved_prefs);
  133. }
  134. else {
  135. $this->data['preferences'] = $_SESSION['preferences'];
  136. }
  137. }
  138. if ($this->data['preferences']) {
  139. $this->prefs += (array)unserialize($this->data['preferences']);
  140. }
  141. }
  142. return $this->prefs;
  143. }
  144. /**
  145. * Write the given user prefs to the user's record
  146. *
  147. * @param array $a_user_prefs User prefs to save
  148. * @param bool $no_session Simplified language/preferences handling
  149. *
  150. * @return boolean True on success, False on failure
  151. */
  152. function save_prefs($a_user_prefs, $no_session = false)
  153. {
  154. if (!$this->ID)
  155. return false;
  156. $plugin = $this->rc->plugins->exec_hook('preferences_update', array(
  157. 'userid' => $this->ID, 'prefs' => $a_user_prefs, 'old' => (array)$this->get_prefs()));
  158. if (!empty($plugin['abort'])) {
  159. return;
  160. }
  161. $a_user_prefs = $plugin['prefs'];
  162. $old_prefs = $plugin['old'];
  163. $config = $this->rc->config;
  164. // merge (partial) prefs array with existing settings
  165. $this->prefs = $save_prefs = $a_user_prefs + $old_prefs;
  166. unset($save_prefs['language']);
  167. // don't save prefs with default values if they haven't been changed yet
  168. foreach ($a_user_prefs as $key => $value) {
  169. if ($value === null || (!isset($old_prefs[$key]) && ($value == $config->get($key)))) {
  170. unset($save_prefs[$key]);
  171. }
  172. }
  173. $save_prefs = serialize($save_prefs);
  174. if (!$no_session) {
  175. $this->language = $_SESSION['language'];
  176. }
  177. $this->db->query(
  178. "UPDATE ".$this->db->table_name('users', true).
  179. " SET `preferences` = ?, `language` = ?".
  180. " WHERE `user_id` = ?",
  181. $save_prefs,
  182. $this->language,
  183. $this->ID);
  184. // Update success
  185. if ($this->db->affected_rows() !== false) {
  186. $this->data['preferences'] = $save_prefs;
  187. if (!$no_session) {
  188. $config->set_user_prefs($this->prefs);
  189. if (isset($_SESSION['preferences'])) {
  190. $this->rc->session->remove('preferences');
  191. $this->rc->session->remove('preferences_time');
  192. }
  193. }
  194. return true;
  195. }
  196. // Update error, but we are using replication (we have read-only DB connection)
  197. // and we are storing session not in the SQL database
  198. // we can store preferences in session and try to write later (see get_prefs())
  199. else if (!$no_session && $this->db->is_replicated()
  200. && $config->get('session_storage', 'db') != 'db'
  201. ) {
  202. $_SESSION['preferences'] = $save_prefs;
  203. $_SESSION['preferences_time'] = time();
  204. $config->set_user_prefs($this->prefs);
  205. $this->data['preferences'] = $save_prefs;
  206. }
  207. return false;
  208. }
  209. /**
  210. * Generate a unique hash to identify this user whith
  211. */
  212. function get_hash()
  213. {
  214. $prefs = $this->get_prefs();
  215. // generate a random hash and store it in user prefs
  216. if (empty($prefs['client_hash'])) {
  217. $prefs['client_hash'] = md5($this->data['username'] . mt_rand() . $this->data['mail_host']);
  218. $this->save_prefs(array('client_hash' => $prefs['client_hash']));
  219. }
  220. return $prefs['client_hash'];
  221. }
  222. /**
  223. * Return a list of all user emails (from identities)
  224. *
  225. * @param bool Return only default identity
  226. *
  227. * @return array List of emails (identity_id, name, email)
  228. */
  229. function list_emails($default = false)
  230. {
  231. if ($this->emails === null) {
  232. $this->emails = array();
  233. $sql_result = $this->db->query(
  234. "SELECT `identity_id`, `name`, `email`"
  235. ." FROM " . $this->db->table_name('identities', true)
  236. ." WHERE `user_id` = ? AND `del` <> 1"
  237. ." ORDER BY `standard` DESC, `name` ASC, `email` ASC, `identity_id` ASC",
  238. $this->ID);
  239. while ($sql_arr = $this->db->fetch_assoc($sql_result)) {
  240. $this->emails[] = $sql_arr;
  241. }
  242. }
  243. return $default ? $this->emails[0] : $this->emails;
  244. }
  245. /**
  246. * Get default identity of this user
  247. *
  248. * @param int $id Identity ID. If empty, the default identity is returned
  249. * @return array Hash array with all cols of the identity record
  250. */
  251. function get_identity($id = null)
  252. {
  253. $id = (int)$id;
  254. // cache identities for better performance
  255. if (!array_key_exists($id, $this->identities)) {
  256. $result = $this->list_identities($id ? "AND `identity_id` = $id" : '');
  257. $this->identities[$id] = $result[0];
  258. }
  259. return $this->identities[$id];
  260. }
  261. /**
  262. * Return a list of all identities linked with this user
  263. *
  264. * @param string $sql_add Optional WHERE clauses
  265. * @param bool $formatted Format identity email and name
  266. *
  267. * @return array List of identities
  268. */
  269. function list_identities($sql_add = '', $formatted = false)
  270. {
  271. $result = array();
  272. $sql_result = $this->db->query(
  273. "SELECT * FROM ".$this->db->table_name('identities', true).
  274. " WHERE `del` <> 1 AND `user_id` = ?".
  275. ($sql_add ? " ".$sql_add : "").
  276. " ORDER BY `standard` DESC, `name` ASC, `email` ASC, `identity_id` ASC",
  277. $this->ID);
  278. while ($sql_arr = $this->db->fetch_assoc($sql_result)) {
  279. if ($formatted) {
  280. $ascii_email = format_email($sql_arr['email']);
  281. $utf8_email = format_email(rcube_utils::idn_to_utf8($ascii_email));
  282. $sql_arr['email_ascii'] = $ascii_email;
  283. $sql_arr['email'] = $utf8_email;
  284. $sql_arr['ident'] = format_email_recipient($ascii_email, $sql_arr['name']);
  285. }
  286. $result[] = $sql_arr;
  287. }
  288. return $result;
  289. }
  290. /**
  291. * Update a specific identity record
  292. *
  293. * @param int $iid Identity ID
  294. * @param array $data Hash array with col->value pairs to save
  295. * @return boolean True if saved successfully, false if nothing changed
  296. */
  297. function update_identity($iid, $data)
  298. {
  299. if (!$this->ID)
  300. return false;
  301. $query_cols = $query_params = array();
  302. foreach ((array)$data as $col => $value) {
  303. $query_cols[] = $this->db->quote_identifier($col) . ' = ?';
  304. $query_params[] = $value;
  305. }
  306. $query_params[] = $iid;
  307. $query_params[] = $this->ID;
  308. $sql = "UPDATE ".$this->db->table_name('identities', true).
  309. " SET `changed` = ".$this->db->now().", ".join(', ', $query_cols).
  310. " WHERE `identity_id` = ?".
  311. " AND `user_id` = ?".
  312. " AND `del` <> 1";
  313. call_user_func_array(array($this->db, 'query'),
  314. array_merge(array($sql), $query_params));
  315. // clear the cache
  316. $this->identities = array();
  317. $this->emails = null;
  318. return $this->db->affected_rows();
  319. }
  320. /**
  321. * Create a new identity record linked with this user
  322. *
  323. * @param array $data Hash array with col->value pairs to save
  324. * @return int The inserted identity ID or false on error
  325. */
  326. function insert_identity($data)
  327. {
  328. if (!$this->ID)
  329. return false;
  330. unset($data['user_id']);
  331. $insert_cols = $insert_values = array();
  332. foreach ((array)$data as $col => $value) {
  333. $insert_cols[] = $this->db->quote_identifier($col);
  334. $insert_values[] = $value;
  335. }
  336. $insert_cols[] = $this->db->quote_identifier('user_id');
  337. $insert_values[] = $this->ID;
  338. $sql = "INSERT INTO ".$this->db->table_name('identities', true).
  339. " (`changed`, ".join(', ', $insert_cols).")".
  340. " VALUES (".$this->db->now().", ".join(', ', array_pad(array(), sizeof($insert_values), '?')).")";
  341. call_user_func_array(array($this->db, 'query'),
  342. array_merge(array($sql), $insert_values));
  343. // clear the cache
  344. $this->identities = array();
  345. $this->emails = null;
  346. return $this->db->insert_id('identities');
  347. }
  348. /**
  349. * Mark the given identity as deleted
  350. *
  351. * @param int $iid Identity ID
  352. * @return boolean True if deleted successfully, false if nothing changed
  353. */
  354. function delete_identity($iid)
  355. {
  356. if (!$this->ID)
  357. return false;
  358. $sql_result = $this->db->query(
  359. "SELECT count(*) AS ident_count FROM ".$this->db->table_name('identities', true).
  360. " WHERE `user_id` = ? AND `del` <> 1",
  361. $this->ID);
  362. $sql_arr = $this->db->fetch_assoc($sql_result);
  363. // we'll not delete last identity
  364. if ($sql_arr['ident_count'] <= 1)
  365. return -1;
  366. $this->db->query(
  367. "UPDATE ".$this->db->table_name('identities', true).
  368. " SET `del` = 1, `changed` = ".$this->db->now().
  369. " WHERE `user_id` = ?".
  370. " AND `identity_id` = ?",
  371. $this->ID,
  372. $iid);
  373. // clear the cache
  374. $this->identities = array();
  375. $this->emails = null;
  376. return $this->db->affected_rows();
  377. }
  378. /**
  379. * Make this identity the default one for this user
  380. *
  381. * @param int $iid The identity ID
  382. */
  383. function set_default($iid)
  384. {
  385. if ($this->ID && $iid) {
  386. $this->db->query(
  387. "UPDATE ".$this->db->table_name('identities', true).
  388. " SET `standard` = '0'".
  389. " WHERE `user_id` = ? AND `identity_id` <> ?",
  390. $this->ID,
  391. $iid);
  392. unset($this->identities[0]);
  393. }
  394. }
  395. /**
  396. * Update user's last_login timestamp
  397. */
  398. function touch()
  399. {
  400. if ($this->ID) {
  401. $this->db->query(
  402. "UPDATE ".$this->db->table_name('users', true).
  403. " SET `last_login` = ".$this->db->now().
  404. " WHERE `user_id` = ?",
  405. $this->ID);
  406. }
  407. }
  408. /**
  409. * Update user's failed_login timestamp and counter
  410. */
  411. function failed_login()
  412. {
  413. if ($this->ID && ($rate = (int) $this->rc->config->get('login_rate_limit', 3))) {
  414. if (empty($this->data['failed_login'])) {
  415. $failed_login = new DateTime('now');
  416. $counter = 1;
  417. }
  418. else {
  419. $failed_login = new DateTime($this->data['failed_login']);
  420. $threshold = new DateTime('- 60 seconds');
  421. if ($failed_login < $threshold) {
  422. $failed_login = new DateTime('now');
  423. $counter = 1;
  424. }
  425. }
  426. $this->db->query(
  427. "UPDATE " . $this->db->table_name('users', true)
  428. . " SET `failed_login` = " . $this->db->fromunixtime($failed_login->format('U'))
  429. . ", `failed_login_counter` = " . ($counter ?: "`failed_login_counter` + 1")
  430. . " WHERE `user_id` = ?",
  431. $this->ID);
  432. }
  433. }
  434. /**
  435. * Checks if the account is locked, e.g. as a result of brute-force prevention
  436. */
  437. function is_locked()
  438. {
  439. if (empty($this->data['failed_login'])) {
  440. return false;
  441. }
  442. if ($rate = (int) $this->rc->config->get('login_rate_limit', 3)) {
  443. $last_failed = new DateTime($this->data['failed_login']);
  444. $threshold = new DateTime('- 60 seconds');
  445. if ($last_failed > $threshold && $this->data['failed_login_counter'] >= $rate) {
  446. return true;
  447. }
  448. }
  449. return false;
  450. }
  451. /**
  452. * Clear the saved object state
  453. */
  454. function reset()
  455. {
  456. $this->ID = null;
  457. $this->data = null;
  458. }
  459. /**
  460. * Find a user record matching the given name and host
  461. *
  462. * @param string $user IMAP user name
  463. * @param string $host IMAP host name
  464. * @return rcube_user New user instance
  465. */
  466. static function query($user, $host)
  467. {
  468. $dbh = rcube::get_instance()->get_dbh();
  469. $config = rcube::get_instance()->config;
  470. // query for matching user name
  471. $sql_result = $dbh->query("SELECT * FROM " . $dbh->table_name('users', true)
  472. ." WHERE `mail_host` = ? AND `username` = ?", $host, $user);
  473. $sql_arr = $dbh->fetch_assoc($sql_result);
  474. // username not found, try aliases from identities
  475. if (empty($sql_arr) && $config->get('user_aliases') && strpos($user, '@')) {
  476. $sql_result = $dbh->limitquery("SELECT u.*"
  477. ." FROM " . $dbh->table_name('users', true) . " u"
  478. ." JOIN " . $dbh->table_name('identities', true) . " i ON (i.`user_id` = u.`user_id`)"
  479. ." WHERE `email` = ? AND `del` <> 1", 0, 1, $user);
  480. $sql_arr = $dbh->fetch_assoc($sql_result);
  481. }
  482. // user already registered -> overwrite username
  483. if ($sql_arr) {
  484. return new rcube_user($sql_arr['user_id'], $sql_arr);
  485. }
  486. return false;
  487. }
  488. /**
  489. * Create a new user record and return a rcube_user instance
  490. *
  491. * @param string $user IMAP user name
  492. * @param string $host IMAP host
  493. * @return rcube_user New user instance
  494. */
  495. static function create($user, $host)
  496. {
  497. $user_name = '';
  498. $user_email = '';
  499. $rcube = rcube::get_instance();
  500. $dbh = $rcube->get_dbh();
  501. // try to resolve user in virtuser table and file
  502. if ($email_list = self::user2email($user, false, true)) {
  503. $user_email = is_array($email_list[0]) ? $email_list[0]['email'] : $email_list[0];
  504. }
  505. $data = $rcube->plugins->exec_hook('user_create', array(
  506. 'host' => $host,
  507. 'user' => $user,
  508. 'user_name' => $user_name,
  509. 'user_email' => $user_email,
  510. 'email_list' => $email_list,
  511. 'language' => $_SESSION['language'],
  512. ));
  513. // plugin aborted this operation
  514. if ($data['abort']) {
  515. return false;
  516. }
  517. $dbh->query(
  518. "INSERT INTO ".$dbh->table_name('users', true).
  519. " (`created`, `last_login`, `username`, `mail_host`, `language`)".
  520. " VALUES (".$dbh->now().", ".$dbh->now().", ?, ?, ?)",
  521. $data['user'],
  522. $data['host'],
  523. $data['language']);
  524. if ($user_id = $dbh->insert_id('users')) {
  525. // create rcube_user instance to make plugin hooks work
  526. $user_instance = new rcube_user($user_id, array(
  527. 'user_id' => $user_id,
  528. 'username' => $data['user'],
  529. 'mail_host' => $data['host'],
  530. 'language' => $data['language'],
  531. ));
  532. $rcube->user = $user_instance;
  533. $mail_domain = $rcube->config->mail_domain($data['host']);
  534. $user_name = $data['user_name'];
  535. $user_email = $data['user_email'];
  536. $email_list = $data['email_list'];
  537. if (empty($email_list)) {
  538. if (empty($user_email)) {
  539. $user_email = strpos($data['user'], '@') ? $user : sprintf('%s@%s', $data['user'], $mail_domain);
  540. }
  541. $email_list[] = $user_email;
  542. }
  543. // identities_level check
  544. else if (count($email_list) > 1 && $rcube->config->get('identities_level', 0) > 1) {
  545. $email_list = array($email_list[0]);
  546. }
  547. if (empty($user_name)) {
  548. $user_name = $data['user'];
  549. }
  550. // create new identities records
  551. $standard = 1;
  552. foreach ($email_list as $row) {
  553. $record = array();
  554. if (is_array($row)) {
  555. if (empty($row['email'])) {
  556. continue;
  557. }
  558. $record = $row;
  559. }
  560. else {
  561. $record['email'] = $row;
  562. }
  563. if (empty($record['name'])) {
  564. $record['name'] = $user_name != $record['email'] ? $user_name : '';
  565. }
  566. $record['user_id'] = $user_id;
  567. $record['standard'] = $standard;
  568. $plugin = $rcube->plugins->exec_hook('identity_create',
  569. array('login' => true, 'record' => $record));
  570. if (!$plugin['abort'] && $plugin['record']['email']) {
  571. $rcube->user->insert_identity($plugin['record']);
  572. }
  573. $standard = 0;
  574. }
  575. }
  576. else {
  577. rcube::raise_error(array(
  578. 'code' => 500,
  579. 'type' => 'php',
  580. 'line' => __LINE__,
  581. 'file' => __FILE__,
  582. 'message' => "Failed to create new user"), true, false);
  583. }
  584. return $user_id ? $user_instance : false;
  585. }
  586. /**
  587. * Resolve username using a virtuser plugins
  588. *
  589. * @param string $email E-mail address to resolve
  590. * @return string Resolved IMAP username
  591. */
  592. static function email2user($email)
  593. {
  594. $rcube = rcube::get_instance();
  595. $plugin = $rcube->plugins->exec_hook('email2user',
  596. array('email' => $email, 'user' => NULL));
  597. return $plugin['user'];
  598. }
  599. /**
  600. * Resolve e-mail address from virtuser plugins
  601. *
  602. * @param string $user User name
  603. * @param boolean $first If true returns first found entry
  604. * @param boolean $extended If true returns email as array (email and name for identity)
  605. * @return mixed Resolved e-mail address string or array of strings
  606. */
  607. static function user2email($user, $first=true, $extended=false)
  608. {
  609. $rcube = rcube::get_instance();
  610. $plugin = $rcube->plugins->exec_hook('user2email',
  611. array('email' => NULL, 'user' => $user,
  612. 'first' => $first, 'extended' => $extended));
  613. return empty($plugin['email']) ? NULL : $plugin['email'];
  614. }
  615. /**
  616. * Return a list of saved searches linked with this user
  617. *
  618. * @param int $type Search type
  619. *
  620. * @return array List of saved searches indexed by search ID
  621. */
  622. function list_searches($type)
  623. {
  624. $plugin = $this->rc->plugins->exec_hook('saved_search_list', array('type' => $type));
  625. if ($plugin['abort']) {
  626. return (array) $plugin['result'];
  627. }
  628. $result = array();
  629. $sql_result = $this->db->query(
  630. "SELECT `search_id` AS id, `name`"
  631. ." FROM ".$this->db->table_name('searches', true)
  632. ." WHERE `user_id` = ? AND `type` = ?"
  633. ." ORDER BY `name`",
  634. (int) $this->ID, (int) $type);
  635. while ($sql_arr = $this->db->fetch_assoc($sql_result)) {
  636. $sql_arr['data'] = unserialize($sql_arr['data']);
  637. $result[$sql_arr['id']] = $sql_arr;
  638. }
  639. return $result;
  640. }
  641. /**
  642. * Return saved search data.
  643. *
  644. * @param int $id Row identifier
  645. *
  646. * @return array Data
  647. */
  648. function get_search($id)
  649. {
  650. $plugin = $this->rc->plugins->exec_hook('saved_search_get', array('id' => $id));
  651. if ($plugin['abort']) {
  652. return $plugin['result'];
  653. }
  654. $sql_result = $this->db->query(
  655. "SELECT `name`, `data`, `type`"
  656. . " FROM ".$this->db->table_name('searches', true)
  657. . " WHERE `user_id` = ?"
  658. ." AND `search_id` = ?",
  659. (int) $this->ID, (int) $id);
  660. while ($sql_arr = $this->db->fetch_assoc($sql_result)) {
  661. return array(
  662. 'id' => $id,
  663. 'name' => $sql_arr['name'],
  664. 'type' => $sql_arr['type'],
  665. 'data' => unserialize($sql_arr['data']),
  666. );
  667. }
  668. return null;
  669. }
  670. /**
  671. * Deletes given saved search record
  672. *
  673. * @param int $sid Search ID
  674. *
  675. * @return boolean True if deleted successfully, false if nothing changed
  676. */
  677. function delete_search($sid)
  678. {
  679. if (!$this->ID)
  680. return false;
  681. $this->db->query(
  682. "DELETE FROM ".$this->db->table_name('searches', true)
  683. ." WHERE `user_id` = ?"
  684. ." AND `search_id` = ?",
  685. (int) $this->ID, $sid);
  686. return $this->db->affected_rows();
  687. }
  688. /**
  689. * Create a new saved search record linked with this user
  690. *
  691. * @param array $data Hash array with col->value pairs to save
  692. *
  693. * @return int The inserted search ID or false on error
  694. */
  695. function insert_search($data)
  696. {
  697. if (!$this->ID)
  698. return false;
  699. $insert_cols[] = 'user_id';
  700. $insert_values[] = (int) $this->ID;
  701. $insert_cols[] = $this->db->quote_identifier('type');
  702. $insert_values[] = (int) $data['type'];
  703. $insert_cols[] = $this->db->quote_identifier('name');
  704. $insert_values[] = $data['name'];
  705. $insert_cols[] = $this->db->quote_identifier('data');
  706. $insert_values[] = serialize($data['data']);
  707. $sql = "INSERT INTO ".$this->db->table_name('searches', true)
  708. ." (".join(', ', $insert_cols).")"
  709. ." VALUES (".join(', ', array_pad(array(), sizeof($insert_values), '?')).")";
  710. call_user_func_array(array($this->db, 'query'),
  711. array_merge(array($sql), $insert_values));
  712. return $this->db->insert_id('searches');
  713. }
  714. }