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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845
  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. $defaults = $config->all();
  165. // merge (partial) prefs array with existing settings
  166. $this->prefs = $save_prefs = $a_user_prefs + $old_prefs;
  167. unset($save_prefs['language']);
  168. // don't save prefs with default values if they haven't been changed yet
  169. // Warning: we use result of rcube_config::all() here instead of just get() (#5782)
  170. foreach ($a_user_prefs as $key => $value) {
  171. if ($value === null || (!isset($old_prefs[$key]) && $value === $defaults[$key])) {
  172. unset($save_prefs[$key]);
  173. }
  174. }
  175. $save_prefs = serialize($save_prefs);
  176. if (!$no_session) {
  177. $this->language = $_SESSION['language'];
  178. }
  179. $this->db->query(
  180. "UPDATE ".$this->db->table_name('users', true).
  181. " SET `preferences` = ?, `language` = ?".
  182. " WHERE `user_id` = ?",
  183. $save_prefs,
  184. $this->language,
  185. $this->ID);
  186. // Update success
  187. if ($this->db->affected_rows() !== false) {
  188. $this->data['preferences'] = $save_prefs;
  189. if (!$no_session) {
  190. $config->set_user_prefs($this->prefs);
  191. if (isset($_SESSION['preferences'])) {
  192. $this->rc->session->remove('preferences');
  193. $this->rc->session->remove('preferences_time');
  194. }
  195. }
  196. return true;
  197. }
  198. // Update error, but we are using replication (we have read-only DB connection)
  199. // and we are storing session not in the SQL database
  200. // we can store preferences in session and try to write later (see get_prefs())
  201. else if (!$no_session && $this->db->is_replicated()
  202. && $config->get('session_storage', 'db') != 'db'
  203. ) {
  204. $_SESSION['preferences'] = $save_prefs;
  205. $_SESSION['preferences_time'] = time();
  206. $config->set_user_prefs($this->prefs);
  207. $this->data['preferences'] = $save_prefs;
  208. }
  209. return false;
  210. }
  211. /**
  212. * Generate a unique hash to identify this user whith
  213. */
  214. function get_hash()
  215. {
  216. $prefs = $this->get_prefs();
  217. // generate a random hash and store it in user prefs
  218. if (empty($prefs['client_hash'])) {
  219. $prefs['client_hash'] = rcube_utils::random_bytes(16);
  220. $this->save_prefs(array('client_hash' => $prefs['client_hash']));
  221. }
  222. return $prefs['client_hash'];
  223. }
  224. /**
  225. * Return a list of all user emails (from identities)
  226. *
  227. * @param bool Return only default identity
  228. *
  229. * @return array List of emails (identity_id, name, email)
  230. */
  231. function list_emails($default = false)
  232. {
  233. if ($this->emails === null) {
  234. $this->emails = array();
  235. $sql_result = $this->db->query(
  236. "SELECT `identity_id`, `name`, `email`"
  237. ." FROM " . $this->db->table_name('identities', true)
  238. ." WHERE `user_id` = ? AND `del` <> 1"
  239. ." ORDER BY `standard` DESC, `name` ASC, `email` ASC, `identity_id` ASC",
  240. $this->ID);
  241. while ($sql_arr = $this->db->fetch_assoc($sql_result)) {
  242. $this->emails[] = $sql_arr;
  243. }
  244. }
  245. return $default ? $this->emails[0] : $this->emails;
  246. }
  247. /**
  248. * Get default identity of this user
  249. *
  250. * @param int $id Identity ID. If empty, the default identity is returned
  251. * @return array Hash array with all cols of the identity record
  252. */
  253. function get_identity($id = null)
  254. {
  255. $id = (int)$id;
  256. // cache identities for better performance
  257. if (!array_key_exists($id, $this->identities)) {
  258. $result = $this->list_identities($id ? "AND `identity_id` = $id" : '');
  259. $this->identities[$id] = $result[0];
  260. }
  261. return $this->identities[$id];
  262. }
  263. /**
  264. * Return a list of all identities linked with this user
  265. *
  266. * @param string $sql_add Optional WHERE clauses
  267. * @param bool $formatted Format identity email and name
  268. *
  269. * @return array List of identities
  270. */
  271. function list_identities($sql_add = '', $formatted = false)
  272. {
  273. $result = array();
  274. $sql_result = $this->db->query(
  275. "SELECT * FROM ".$this->db->table_name('identities', true).
  276. " WHERE `del` <> 1 AND `user_id` = ?".
  277. ($sql_add ? " ".$sql_add : "").
  278. " ORDER BY `standard` DESC, `name` ASC, `email` ASC, `identity_id` ASC",
  279. $this->ID);
  280. while ($sql_arr = $this->db->fetch_assoc($sql_result)) {
  281. if ($formatted) {
  282. $ascii_email = format_email($sql_arr['email']);
  283. $utf8_email = format_email(rcube_utils::idn_to_utf8($ascii_email));
  284. $sql_arr['email_ascii'] = $ascii_email;
  285. $sql_arr['email'] = $utf8_email;
  286. $sql_arr['ident'] = format_email_recipient($ascii_email, $sql_arr['name']);
  287. }
  288. $result[] = $sql_arr;
  289. }
  290. return $result;
  291. }
  292. /**
  293. * Update a specific identity record
  294. *
  295. * @param int $iid Identity ID
  296. * @param array $data Hash array with col->value pairs to save
  297. * @return boolean True if saved successfully, false if nothing changed
  298. */
  299. function update_identity($iid, $data)
  300. {
  301. if (!$this->ID)
  302. return false;
  303. $query_cols = $query_params = array();
  304. foreach ((array)$data as $col => $value) {
  305. $query_cols[] = $this->db->quote_identifier($col) . ' = ?';
  306. $query_params[] = $value;
  307. }
  308. $query_params[] = $iid;
  309. $query_params[] = $this->ID;
  310. $sql = "UPDATE ".$this->db->table_name('identities', true).
  311. " SET `changed` = ".$this->db->now().", ".join(', ', $query_cols).
  312. " WHERE `identity_id` = ?".
  313. " AND `user_id` = ?".
  314. " AND `del` <> 1";
  315. call_user_func_array(array($this->db, 'query'),
  316. array_merge(array($sql), $query_params));
  317. // clear the cache
  318. $this->identities = array();
  319. $this->emails = null;
  320. return $this->db->affected_rows();
  321. }
  322. /**
  323. * Create a new identity record linked with this user
  324. *
  325. * @param array $data Hash array with col->value pairs to save
  326. * @return int The inserted identity ID or false on error
  327. */
  328. function insert_identity($data)
  329. {
  330. if (!$this->ID)
  331. return false;
  332. unset($data['user_id']);
  333. $insert_cols = array();
  334. $insert_values = array();
  335. foreach ((array)$data as $col => $value) {
  336. $insert_cols[] = $this->db->quote_identifier($col);
  337. $insert_values[] = $value;
  338. }
  339. $insert_cols[] = $this->db->quote_identifier('user_id');
  340. $insert_values[] = $this->ID;
  341. $sql = "INSERT INTO ".$this->db->table_name('identities', true).
  342. " (`changed`, ".join(', ', $insert_cols).")".
  343. " VALUES (".$this->db->now().", ".join(', ', array_pad(array(), count($insert_values), '?')).")";
  344. $insert = $this->db->query($sql, $insert_values);
  345. // clear the cache
  346. $this->identities = array();
  347. $this->emails = null;
  348. return $this->db->insert_id('identities');
  349. }
  350. /**
  351. * Mark the given identity as deleted
  352. *
  353. * @param int $iid Identity ID
  354. * @return boolean True if deleted successfully, false if nothing changed
  355. */
  356. function delete_identity($iid)
  357. {
  358. if (!$this->ID)
  359. return false;
  360. $sql_result = $this->db->query(
  361. "SELECT count(*) AS ident_count FROM ".$this->db->table_name('identities', true).
  362. " WHERE `user_id` = ? AND `del` <> 1",
  363. $this->ID);
  364. $sql_arr = $this->db->fetch_assoc($sql_result);
  365. // we'll not delete last identity
  366. if ($sql_arr['ident_count'] <= 1)
  367. return -1;
  368. $this->db->query(
  369. "UPDATE ".$this->db->table_name('identities', true).
  370. " SET `del` = 1, `changed` = ".$this->db->now().
  371. " WHERE `user_id` = ?".
  372. " AND `identity_id` = ?",
  373. $this->ID,
  374. $iid);
  375. // clear the cache
  376. $this->identities = array();
  377. $this->emails = null;
  378. return $this->db->affected_rows();
  379. }
  380. /**
  381. * Make this identity the default one for this user
  382. *
  383. * @param int $iid The identity ID
  384. */
  385. function set_default($iid)
  386. {
  387. if ($this->ID && $iid) {
  388. $this->db->query(
  389. "UPDATE ".$this->db->table_name('identities', true).
  390. " SET `standard` = '0'".
  391. " WHERE `user_id` = ? AND `identity_id` <> ?",
  392. $this->ID,
  393. $iid);
  394. unset($this->identities[0]);
  395. }
  396. }
  397. /**
  398. * Update user's last_login timestamp
  399. */
  400. function touch()
  401. {
  402. if ($this->ID) {
  403. $this->db->query(
  404. "UPDATE ".$this->db->table_name('users', true).
  405. " SET `last_login` = ".$this->db->now().
  406. " WHERE `user_id` = ?",
  407. $this->ID);
  408. }
  409. }
  410. /**
  411. * Update user's failed_login timestamp and counter
  412. */
  413. function failed_login()
  414. {
  415. if ($this->ID && ($rate = (int) $this->rc->config->get('login_rate_limit', 3))) {
  416. if (empty($this->data['failed_login'])) {
  417. $failed_login = new DateTime('now');
  418. $counter = 1;
  419. }
  420. else {
  421. $failed_login = new DateTime($this->data['failed_login']);
  422. $threshold = new DateTime('- 60 seconds');
  423. if ($failed_login < $threshold) {
  424. $failed_login = new DateTime('now');
  425. $counter = 1;
  426. }
  427. }
  428. $this->db->query(
  429. "UPDATE " . $this->db->table_name('users', true)
  430. . " SET `failed_login` = ?"
  431. . ", `failed_login_counter` = " . ($counter ?: "`failed_login_counter` + 1")
  432. . " WHERE `user_id` = ?",
  433. $failed_login, $this->ID);
  434. }
  435. }
  436. /**
  437. * Checks if the account is locked, e.g. as a result of brute-force prevention
  438. */
  439. function is_locked()
  440. {
  441. if (empty($this->data['failed_login'])) {
  442. return false;
  443. }
  444. if ($rate = (int) $this->rc->config->get('login_rate_limit', 3)) {
  445. $last_failed = new DateTime($this->data['failed_login']);
  446. $threshold = new DateTime('- 60 seconds');
  447. if ($last_failed > $threshold && $this->data['failed_login_counter'] >= $rate) {
  448. return true;
  449. }
  450. }
  451. return false;
  452. }
  453. /**
  454. * Clear the saved object state
  455. */
  456. function reset()
  457. {
  458. $this->ID = null;
  459. $this->data = null;
  460. }
  461. /**
  462. * Find a user record matching the given name and host
  463. *
  464. * @param string $user IMAP user name
  465. * @param string $host IMAP host name
  466. * @return rcube_user New user instance
  467. */
  468. static function query($user, $host)
  469. {
  470. $dbh = rcube::get_instance()->get_dbh();
  471. $config = rcube::get_instance()->config;
  472. // query for matching user name
  473. $sql_result = $dbh->query("SELECT * FROM " . $dbh->table_name('users', true)
  474. ." WHERE `mail_host` = ? AND `username` = ?", $host, $user);
  475. $sql_arr = $dbh->fetch_assoc($sql_result);
  476. // username not found, try aliases from identities
  477. if (empty($sql_arr) && $config->get('user_aliases') && strpos($user, '@')) {
  478. $sql_result = $dbh->limitquery("SELECT u.*"
  479. ." FROM " . $dbh->table_name('users', true) . " u"
  480. ." JOIN " . $dbh->table_name('identities', true) . " i ON (i.`user_id` = u.`user_id`)"
  481. ." WHERE `email` = ? AND `del` <> 1", 0, 1, $user);
  482. $sql_arr = $dbh->fetch_assoc($sql_result);
  483. }
  484. // user already registered -> overwrite username
  485. if ($sql_arr) {
  486. return new rcube_user($sql_arr['user_id'], $sql_arr);
  487. }
  488. return false;
  489. }
  490. /**
  491. * Create a new user record and return a rcube_user instance
  492. *
  493. * @param string $user IMAP user name
  494. * @param string $host IMAP host
  495. * @return rcube_user New user instance
  496. */
  497. static function create($user, $host)
  498. {
  499. $user_name = '';
  500. $user_email = '';
  501. $rcube = rcube::get_instance();
  502. $dbh = $rcube->get_dbh();
  503. // try to resolve user in virtuser table and file
  504. if ($email_list = self::user2email($user, false, true)) {
  505. $user_email = is_array($email_list[0]) ? $email_list[0]['email'] : $email_list[0];
  506. }
  507. $data = $rcube->plugins->exec_hook('user_create', array(
  508. 'host' => $host,
  509. 'user' => $user,
  510. 'user_name' => $user_name,
  511. 'user_email' => $user_email,
  512. 'email_list' => $email_list,
  513. 'language' => $_SESSION['language'],
  514. ));
  515. // plugin aborted this operation
  516. if ($data['abort']) {
  517. return false;
  518. }
  519. $insert = $dbh->query(
  520. "INSERT INTO ".$dbh->table_name('users', true).
  521. " (`created`, `last_login`, `username`, `mail_host`, `language`)".
  522. " VALUES (".$dbh->now().", ".$dbh->now().", ?, ?, ?)",
  523. $data['user'],
  524. $data['host'],
  525. $data['language']);
  526. if ($dbh->affected_rows($insert) && ($user_id = $dbh->insert_id('users'))) {
  527. // create rcube_user instance to make plugin hooks work
  528. $user_instance = new rcube_user($user_id, array(
  529. 'user_id' => $user_id,
  530. 'username' => $data['user'],
  531. 'mail_host' => $data['host'],
  532. 'language' => $data['language'],
  533. ));
  534. $rcube->user = $user_instance;
  535. $mail_domain = $rcube->config->mail_domain($data['host']);
  536. $user_name = $data['user_name'];
  537. $user_email = $data['user_email'];
  538. $email_list = $data['email_list'];
  539. if (empty($email_list)) {
  540. if (empty($user_email)) {
  541. $user_email = strpos($data['user'], '@') ? $user : sprintf('%s@%s', $data['user'], $mail_domain);
  542. }
  543. $email_list[] = $user_email;
  544. }
  545. // identities_level check
  546. else if (count($email_list) > 1 && $rcube->config->get('identities_level', 0) > 1) {
  547. $email_list = array($email_list[0]);
  548. }
  549. if (empty($user_name)) {
  550. $user_name = $data['user'];
  551. }
  552. // create new identities records
  553. $standard = 1;
  554. foreach ($email_list as $row) {
  555. $record = array();
  556. if (is_array($row)) {
  557. if (empty($row['email'])) {
  558. continue;
  559. }
  560. $record = $row;
  561. }
  562. else {
  563. $record['email'] = $row;
  564. }
  565. if (empty($record['name'])) {
  566. $record['name'] = $user_name != $record['email'] ? $user_name : '';
  567. }
  568. $record['user_id'] = $user_id;
  569. $record['standard'] = $standard;
  570. $plugin = $rcube->plugins->exec_hook('identity_create',
  571. array('login' => true, 'record' => $record));
  572. if (!$plugin['abort'] && $plugin['record']['email']) {
  573. $rcube->user->insert_identity($plugin['record']);
  574. }
  575. $standard = 0;
  576. }
  577. }
  578. else {
  579. rcube::raise_error(array(
  580. 'code' => 500,
  581. 'type' => 'php',
  582. 'line' => __LINE__,
  583. 'file' => __FILE__,
  584. 'message' => "Failed to create new user"), true, false);
  585. }
  586. return $user_id ? $user_instance : false;
  587. }
  588. /**
  589. * Resolve username using a virtuser plugins
  590. *
  591. * @param string $email E-mail address to resolve
  592. * @return string Resolved IMAP username
  593. */
  594. static function email2user($email)
  595. {
  596. $rcube = rcube::get_instance();
  597. $plugin = $rcube->plugins->exec_hook('email2user',
  598. array('email' => $email, 'user' => NULL));
  599. return $plugin['user'];
  600. }
  601. /**
  602. * Resolve e-mail address from virtuser plugins
  603. *
  604. * @param string $user User name
  605. * @param boolean $first If true returns first found entry
  606. * @param boolean $extended If true returns email as array (email and name for identity)
  607. * @return mixed Resolved e-mail address string or array of strings
  608. */
  609. static function user2email($user, $first=true, $extended=false)
  610. {
  611. $rcube = rcube::get_instance();
  612. $plugin = $rcube->plugins->exec_hook('user2email',
  613. array('email' => NULL, 'user' => $user,
  614. 'first' => $first, 'extended' => $extended));
  615. return empty($plugin['email']) ? NULL : $plugin['email'];
  616. }
  617. /**
  618. * Return a list of saved searches linked with this user
  619. *
  620. * @param int $type Search type
  621. *
  622. * @return array List of saved searches indexed by search ID
  623. */
  624. function list_searches($type)
  625. {
  626. $plugin = $this->rc->plugins->exec_hook('saved_search_list', array('type' => $type));
  627. if ($plugin['abort']) {
  628. return (array) $plugin['result'];
  629. }
  630. $result = array();
  631. $sql_result = $this->db->query(
  632. "SELECT `search_id` AS id, `name`"
  633. ." FROM ".$this->db->table_name('searches', true)
  634. ." WHERE `user_id` = ? AND `type` = ?"
  635. ." ORDER BY `name`",
  636. (int) $this->ID, (int) $type);
  637. while ($sql_arr = $this->db->fetch_assoc($sql_result)) {
  638. $sql_arr['data'] = unserialize($sql_arr['data']);
  639. $result[$sql_arr['id']] = $sql_arr;
  640. }
  641. return $result;
  642. }
  643. /**
  644. * Return saved search data.
  645. *
  646. * @param int $id Row identifier
  647. *
  648. * @return array Data
  649. */
  650. function get_search($id)
  651. {
  652. $plugin = $this->rc->plugins->exec_hook('saved_search_get', array('id' => $id));
  653. if ($plugin['abort']) {
  654. return $plugin['result'];
  655. }
  656. $sql_result = $this->db->query(
  657. "SELECT `name`, `data`, `type`"
  658. . " FROM ".$this->db->table_name('searches', true)
  659. . " WHERE `user_id` = ?"
  660. ." AND `search_id` = ?",
  661. (int) $this->ID, (int) $id);
  662. while ($sql_arr = $this->db->fetch_assoc($sql_result)) {
  663. return array(
  664. 'id' => $id,
  665. 'name' => $sql_arr['name'],
  666. 'type' => $sql_arr['type'],
  667. 'data' => unserialize($sql_arr['data']),
  668. );
  669. }
  670. return null;
  671. }
  672. /**
  673. * Deletes given saved search record
  674. *
  675. * @param int $sid Search ID
  676. *
  677. * @return boolean True if deleted successfully, false if nothing changed
  678. */
  679. function delete_search($sid)
  680. {
  681. if (!$this->ID)
  682. return false;
  683. $this->db->query(
  684. "DELETE FROM ".$this->db->table_name('searches', true)
  685. ." WHERE `user_id` = ?"
  686. ." AND `search_id` = ?",
  687. (int) $this->ID, $sid);
  688. return $this->db->affected_rows();
  689. }
  690. /**
  691. * Create a new saved search record linked with this user
  692. *
  693. * @param array $data Hash array with col->value pairs to save
  694. *
  695. * @return int The inserted search ID or false on error
  696. */
  697. function insert_search($data)
  698. {
  699. if (!$this->ID)
  700. return false;
  701. $insert_cols[] = 'user_id';
  702. $insert_values[] = (int) $this->ID;
  703. $insert_cols[] = $this->db->quote_identifier('type');
  704. $insert_values[] = (int) $data['type'];
  705. $insert_cols[] = $this->db->quote_identifier('name');
  706. $insert_values[] = $data['name'];
  707. $insert_cols[] = $this->db->quote_identifier('data');
  708. $insert_values[] = serialize($data['data']);
  709. $sql = "INSERT INTO ".$this->db->table_name('searches', true)
  710. ." (".join(', ', $insert_cols).")"
  711. ." VALUES (".join(', ', array_pad(array(), count($insert_values), '?')).")";
  712. $insert = $this->db->query($sql, $insert_values);
  713. return $this->db->affected_rows($insert) ? $this->db->insert_id('searches') : false;
  714. }
  715. }