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