abook_database.php 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808
  1. <?php
  2. /**
  3. * abook_database.php
  4. *
  5. * Supported database schema
  6. * <pre>
  7. * owner varchar(128) NOT NULL
  8. * nickname varchar(16) NOT NULL
  9. * firstname varchar(128)
  10. * lastname varchar(128)
  11. * email varchar(128) NOT NULL
  12. * label varchar(255)
  13. * PRIMARY KEY (owner,nickname)
  14. * </pre>
  15. *
  16. * @copyright 1999-2017 The SquirrelMail Project Team
  17. * @license http://opensource.org/licenses/gpl-license.php GNU Public License
  18. * @version $Id$
  19. * @package squirrelmail
  20. * @subpackage addressbook
  21. */
  22. /**
  23. * Needs either PDO or the DB functions
  24. * Don't display errors here. Error will be set in class constructor function.
  25. */
  26. global $use_pdo, $disable_pdo;
  27. if (empty($disable_pdo) && class_exists('PDO'))
  28. $use_pdo = TRUE;
  29. else
  30. $use_pdo = FALSE;
  31. if (!$use_pdo)
  32. @include_once('DB.php');
  33. /**
  34. * Address book in a database backend
  35. *
  36. * Backend for personal/shared address book stored in a database,
  37. * accessed using the DB-classes in PEAR or PDO, the latter taking
  38. * precedence if available..
  39. *
  40. * IMPORTANT: If PDO is not available (it should be installed by
  41. * default since PHP 5.1), then the PEAR modules must
  42. * be in the include path for this class to work.
  43. *
  44. * An array with the following elements must be passed to
  45. * the class constructor (elements marked ? are optional):
  46. * <pre>
  47. * dsn => database DNS (see PEAR for syntax, but more or
  48. * less it is: mysql://user:pass@hostname/dbname)
  49. * table => table to store addresses in (must exist)
  50. * owner => current user (owner of address data)
  51. * ? name => name of address book
  52. * ? writeable => set writeable flag (true/false)
  53. * ? listing => enable/disable listing
  54. * </pre>
  55. * The table used should have the following columns:
  56. * owner, nickname, firstname, lastname, email, label
  57. * The pair (owner,nickname) should be unique (primary key).
  58. *
  59. * NOTE. This class should not be used directly. Use the
  60. * "AddressBook" class instead.
  61. *
  62. * Three settings that control PDO behavior can be specified in
  63. * config/config_local.php if needed:
  64. * boolean $disable_pdo SquirrelMail uses PDO by default to access the
  65. * user preferences and address book databases, but
  66. * setting this to TRUE will cause SquirrelMail to
  67. * fall back to using Pear DB instead.
  68. * boolean $pdo_show_sql_errors When database errors are encountered,
  69. * setting this to TRUE causes the actual
  70. * database error to be displayed, otherwise
  71. * generic errors are displayed, preventing
  72. * internal database information from being
  73. * exposed. This should be enabled only for
  74. * debugging purposes.
  75. * string $pdo_identifier_quote_char By default, SquirrelMail will quote
  76. * table and field names in database
  77. * queries with what it thinks is the
  78. * appropriate quote character for the
  79. * database type being used (backtick
  80. * for MySQL (and thus MariaDB), double
  81. * quotes for all others), but you can
  82. * override the character used by
  83. * putting it here, or tell SquirrelMail
  84. * NOT to quote identifiers by setting
  85. * this to "none"
  86. *
  87. * @package squirrelmail
  88. * @subpackage addressbook
  89. */
  90. class abook_database extends addressbook_backend {
  91. /**
  92. * Backend type
  93. * @var string
  94. */
  95. var $btype = 'local';
  96. /**
  97. * Backend name
  98. * @var string
  99. */
  100. var $bname = 'database';
  101. /**
  102. * Data Source Name (connection description)
  103. * @var string
  104. */
  105. var $dsn = '';
  106. /**
  107. * Character used to quote database table
  108. * and field names
  109. * @var string
  110. */
  111. var $identifier_quote_char = '';
  112. /**
  113. * Table that stores addresses
  114. * @var string
  115. */
  116. var $table = '';
  117. /**
  118. * Owner name
  119. *
  120. * Limits list of database entries visible to end user
  121. * @var string
  122. */
  123. var $owner = '';
  124. /**
  125. * Database Handle
  126. * @var resource
  127. */
  128. var $dbh = false;
  129. /**
  130. * Enable/disable writing into address book
  131. * @var bool
  132. */
  133. var $writeable = true;
  134. /**
  135. * Enable/disable address book listing
  136. * @var bool
  137. */
  138. var $listing = true;
  139. /* ========================== Private ======================= */
  140. /**
  141. * Constructor (PHP5 style, required in some future version of PHP)
  142. * @param array $param address book backend options
  143. */
  144. function __construct($param) {
  145. $this->sname = _("Personal Address Book");
  146. /* test if PDO or Pear DB classes are available and freak out if necessary */
  147. global $use_pdo;
  148. if (!$use_pdo && !class_exists('DB')) {
  149. // same error also in db_prefs.php
  150. $error = _("Could not find or include PHP PDO or PEAR database functions required for the database backend.") . "\n";
  151. $error .= sprintf(_("PDO should come preinstalled with PHP version 5.1 or higher. Otherwise, is PEAR installed, and is the include path set correctly to find %s?"), 'DB.php') . "\n";
  152. $error .= _("Please contact your system administrator and report this error.");
  153. return $this->set_error($error);
  154. }
  155. if (is_array($param)) {
  156. if (empty($param['dsn']) ||
  157. empty($param['table']) ||
  158. empty($param['owner'])) {
  159. return $this->set_error('Invalid parameters');
  160. }
  161. $this->dsn = $param['dsn'];
  162. $this->table = $param['table'];
  163. $this->owner = $param['owner'];
  164. if (!empty($param['name'])) {
  165. $this->sname = $param['name'];
  166. }
  167. if (isset($param['writeable'])) {
  168. $this->writeable = $param['writeable'];
  169. }
  170. if (isset($param['listing'])) {
  171. $this->listing = $param['listing'];
  172. }
  173. // figure out identifier quoting (only used for PDO, though we could change that)
  174. global $pdo_identifier_quote_char;
  175. if (empty($pdo_identifier_quote_char)) {
  176. if (strpos($this->dsn, 'mysql') === 0)
  177. $this->identifier_quote_char = '`';
  178. else
  179. $this->identifier_quote_char = '"';
  180. } else if ($pdo_identifier_quote_char === 'none')
  181. $this->identifier_quote_char = '';
  182. else
  183. $this->identifier_quote_char = $pdo_identifier_quote_char;
  184. $this->open(true);
  185. }
  186. else {
  187. return $this->set_error('Invalid argument to constructor');
  188. }
  189. }
  190. /**
  191. * Constructor (PHP4 style, kept for compatibility reasons)
  192. * @param array $param address book backend options
  193. */
  194. function abook_database($param) {
  195. return self::__construct($param);
  196. }
  197. /**
  198. * Open the database.
  199. * @param bool $new new connection if it is true
  200. * @return bool
  201. */
  202. function open($new = false) {
  203. global $use_pdo;
  204. $this->error = '';
  205. /* Return true is file is open and $new is unset */
  206. if ($this->dbh && !$new) {
  207. return true;
  208. }
  209. /* Close old file, if any */
  210. if ($this->dbh) {
  211. $this->close();
  212. }
  213. if ($use_pdo) {
  214. // parse and convert DSN to PDO style
  215. // Pear's full DSN syntax is one of the following:
  216. // phptype(dbsyntax)://username:password@protocol+hostspec/database?option=value
  217. // phptype(syntax)://user:pass@protocol(proto_opts)/database
  218. //
  219. // $matches will contain:
  220. // 1: database type
  221. // 2: username
  222. // 3: password
  223. // 4: hostname (and possible port number) OR protocol (and possible protocol options)
  224. // 5: database name (and possible options)
  225. // 6: port number (moved from match number 4)
  226. // 7: options (moved from match number 5)
  227. // 8: protocol (instead of hostname)
  228. // 9: protocol options (moved from match number 4/8)
  229. //TODO: do we care about supporting cases where no password is given? (this is a legal DSN, but causes an error below)
  230. if (!preg_match('|^(.+)://(.+):(.+)@(.+)/(.+)$|i', $this->dsn, $matches)) {
  231. return $this->set_error(_("Could not parse prefs DSN"));
  232. }
  233. $matches[6] = NULL;
  234. $matches[7] = NULL;
  235. $matches[8] = NULL;
  236. $matches[9] = NULL;
  237. if (preg_match('|^(.+):(\d+)$|', $matches[4], $host_port_matches)) {
  238. $matches[4] = $host_port_matches[1];
  239. $matches[6] = $host_port_matches[2];
  240. }
  241. if (preg_match('|^(.+?)\((.+)\)$|', $matches[4], $protocol_matches)) {
  242. $matches[8] = $protocol_matches[1];
  243. $matches[9] = $protocol_matches[2];
  244. $matches[4] = NULL;
  245. $matches[6] = NULL;
  246. }
  247. //TODO: currently we just ignore options specified on the end of the DSN
  248. if (preg_match('|^(.+?)\?(.+)$|', $matches[5], $database_name_options_matches)) {
  249. $matches[5] = $database_name_options_matches[1];
  250. $matches[7] = $database_name_options_matches[2];
  251. }
  252. if ($matches[8] === 'unix' && !empty($matches[9]))
  253. $pdo_prefs_dsn = $matches[1] . ':unix_socket=' . $matches[9] . ';dbname=' . $matches[5];
  254. else
  255. $pdo_prefs_dsn = $matches[1] . ':host=' . $matches[4] . (!empty($matches[6]) ? ';port=' . $matches[6] : '') . ';dbname=' . $matches[5];
  256. try {
  257. $dbh = new PDO($pdo_prefs_dsn, $matches[2], $matches[3]);
  258. } catch (Exception $e) {
  259. return $this->set_error(sprintf(_("Database error: %s"), $e->getMessage()));
  260. }
  261. $dbh->setAttribute(PDO::ATTR_CASE, PDO::CASE_LOWER);
  262. } else {
  263. $dbh = DB::connect($this->dsn, true);
  264. if (DB::isError($dbh)) {
  265. return $this->set_error(sprintf(_("Database error: %s"),
  266. DB::errorMessage($dbh)));
  267. }
  268. /**
  269. * field names are lowercased.
  270. * We use unquoted identifiers and they use upper case in Oracle
  271. */
  272. $dbh->setOption('portability', DB_PORTABILITY_LOWERCASE);
  273. }
  274. $this->dbh = $dbh;
  275. return true;
  276. }
  277. /**
  278. * Close the file and forget the filehandle
  279. */
  280. function close() {
  281. global $use_pdo;
  282. if ($use_pdo) {
  283. $this->dbh = NULL;
  284. } else {
  285. $this->dbh->disconnect();
  286. $this->dbh = false;
  287. }
  288. }
  289. /**
  290. * Determine internal database field name given one of
  291. * the SquirrelMail SM_ABOOK_FIELD_* constants
  292. *
  293. * @param integer $field The SM_ABOOK_FIELD_* contant to look up
  294. *
  295. * @return string The desired field name, or the string "ERROR"
  296. * if the $field is not understood (the caller
  297. * is responsible for handing errors)
  298. *
  299. */
  300. function get_field_name($field) {
  301. switch ($field) {
  302. case SM_ABOOK_FIELD_NICKNAME:
  303. return 'nickname';
  304. case SM_ABOOK_FIELD_FIRSTNAME:
  305. return 'firstname';
  306. case SM_ABOOK_FIELD_LASTNAME:
  307. return 'lastname';
  308. case SM_ABOOK_FIELD_EMAIL:
  309. return 'email';
  310. case SM_ABOOK_FIELD_LABEL:
  311. return 'label';
  312. default:
  313. return 'ERROR';
  314. }
  315. }
  316. /* ========================== Public ======================== */
  317. /**
  318. * Search the database
  319. *
  320. * Backend supports only * and ? wildcards. Complex eregs are not supported.
  321. * Search is case insensitive.
  322. * @param string $expr search expression
  323. * @return array search results. boolean false on error
  324. */
  325. function search($expr) {
  326. $ret = array();
  327. if(!$this->open()) {
  328. return false;
  329. }
  330. /* To be replaced by advanded search expression parsing */
  331. if (is_array($expr)) {
  332. return;
  333. }
  334. // don't allow wide search when listing is disabled.
  335. if ($expr=='*' && ! $this->listing)
  336. return array();
  337. /* lowercase expression in order to make it case insensitive */
  338. $expr = strtolower($expr);
  339. /* escape SQL wildcards */
  340. $expr = str_replace('_', '\\_', $expr);
  341. $expr = str_replace('%', '\\%', $expr);
  342. /* Convert wildcards to SQL syntax */
  343. $expr = str_replace('?', '_', $expr);
  344. $expr = str_replace('*', '%', $expr);
  345. $expr = "%$expr%";
  346. global $use_pdo, $pdo_show_sql_errors;
  347. if ($use_pdo) {
  348. if (!($sth = $this->dbh->prepare('SELECT * FROM ' . $this->identifier_quote_char . $this->table . $this->identifier_quote_char . ' WHERE ' . $this->identifier_quote_char . 'owner' . $this->identifier_quote_char . ' = ? AND (LOWER(' . $this->identifier_quote_char . 'firstname' . $this->identifier_quote_char . ') LIKE ? ESCAPE ? OR LOWER(' . $this->identifier_quote_char . 'lastname' . $this->identifier_quote_char . ') LIKE ? ESCAPE ? OR LOWER(' . $this->identifier_quote_char . 'email' . $this->identifier_quote_char . ') LIKE ? ESCAPE ? OR LOWER(' . $this->identifier_quote_char . 'nickname' . $this->identifier_quote_char . ') LIKE ? ESCAPE ?)'))) {
  349. if ($pdo_show_sql_errors)
  350. return $this->set_error(sprintf(_("Database error: %s"), implode(' - ', $this->dbh->errorInfo())));
  351. else
  352. return $this->set_error(sprintf(_("Database error: %s"), _("Could not prepare query")));
  353. }
  354. if (!($res = $sth->execute(array($this->owner, $expr, '\\', $expr, '\\', $expr, '\\', $expr, '\\')))) {
  355. if ($pdo_show_sql_errors)
  356. return $this->set_error(sprintf(_("Database error: %s"), implode(' - ', $sth->errorInfo())));
  357. else
  358. return $this->set_error(sprintf(_("Database error: %s"), _("Could not execute query")));
  359. }
  360. while ($row = $sth->fetch(PDO::FETCH_ASSOC)) {
  361. array_push($ret, array('nickname' => $row['nickname'],
  362. 'name' => $this->fullname($row['firstname'], $row['lastname']),
  363. 'firstname' => $row['firstname'],
  364. 'lastname' => $row['lastname'],
  365. 'email' => $row['email'],
  366. 'label' => $row['label'],
  367. 'backend' => $this->bnum,
  368. 'source' => &$this->sname));
  369. }
  370. } else {
  371. $expr = $this->dbh->quoteString($expr);
  372. /* create escape expression */
  373. $escape = 'ESCAPE \'' . $this->dbh->quoteString('\\') . '\'';
  374. $query = sprintf("SELECT * FROM %s WHERE owner='%s' AND " .
  375. "(LOWER(firstname) LIKE '%s' %s " .
  376. "OR LOWER(lastname) LIKE '%s' %s " .
  377. "OR LOWER(email) LIKE '%s' %s " .
  378. "OR LOWER(nickname) LIKE '%s' %s)",
  379. $this->table, $this->owner, $expr, $escape, $expr, $escape,
  380. $expr, $escape, $expr, $escape);
  381. $res = $this->dbh->query($query);
  382. if (DB::isError($res)) {
  383. return $this->set_error(sprintf(_("Database error: %s"),
  384. DB::errorMessage($res)));
  385. }
  386. while ($row = $res->fetchRow(DB_FETCHMODE_ASSOC)) {
  387. array_push($ret, array('nickname' => $row['nickname'],
  388. 'name' => $this->fullname($row['firstname'], $row['lastname']),
  389. 'firstname' => $row['firstname'],
  390. 'lastname' => $row['lastname'],
  391. 'email' => $row['email'],
  392. 'label' => $row['label'],
  393. 'backend' => $this->bnum,
  394. 'source' => &$this->sname));
  395. }
  396. }
  397. return $ret;
  398. }
  399. /**
  400. * Lookup an address by the indicated field.
  401. *
  402. * @param string $value The value to look up
  403. * @param integer $field The field to look in, should be one
  404. * of the SM_ABOOK_FIELD_* constants
  405. * defined in include/constants.php
  406. * (OPTIONAL; defaults to nickname field)
  407. * NOTE: uniqueness is only guaranteed
  408. * when the nickname field is used here;
  409. * otherwise, the first matching address
  410. * is returned.
  411. *
  412. * @return array Array with lookup results when the value
  413. * was found, an empty array if the value was
  414. * not found.
  415. *
  416. */
  417. function lookup($value, $field=SM_ABOOK_FIELD_NICKNAME) {
  418. if (empty($value)) {
  419. return array();
  420. }
  421. $value = strtolower($value);
  422. if (!$this->open()) {
  423. return false;
  424. }
  425. $db_field = $this->get_field_name($field);
  426. if ($db_field == 'ERROR') {
  427. return $this->set_error(sprintf(_("Unknown field name: %s"), $field));
  428. }
  429. global $use_pdo, $pdo_show_sql_errors;
  430. if ($use_pdo) {
  431. if (!($sth = $this->dbh->prepare('SELECT * FROM ' . $this->identifier_quote_char . $this->table . $this->identifier_quote_char . ' WHERE ' . $this->identifier_quote_char . 'owner' . $this->identifier_quote_char . ' = ? AND LOWER(' . $this->identifier_quote_char . $db_field . $this->identifier_quote_char . ') = ?'))) {
  432. if ($pdo_show_sql_errors)
  433. return $this->set_error(sprintf(_("Database error: %s"), implode(' - ', $this->dbh->errorInfo())));
  434. else
  435. return $this->set_error(sprintf(_("Database error: %s"), _("Could not prepare query")));
  436. }
  437. if (!($res = $sth->execute(array($this->owner, $value)))) {
  438. if ($pdo_show_sql_errors)
  439. return $this->set_error(sprintf(_("Database error: %s"), implode(' - ', $sth->errorInfo())));
  440. else
  441. return $this->set_error(sprintf(_("Database error: %s"), _("Could not execute query")));
  442. }
  443. if ($row = $sth->fetch(PDO::FETCH_ASSOC)) {
  444. return array('nickname' => $row['nickname'],
  445. 'name' => $this->fullname($row['firstname'], $row['lastname']),
  446. 'firstname' => $row['firstname'],
  447. 'lastname' => $row['lastname'],
  448. 'email' => $row['email'],
  449. 'label' => $row['label'],
  450. 'backend' => $this->bnum,
  451. 'source' => &$this->sname);
  452. }
  453. } else {
  454. $query = sprintf("SELECT * FROM %s WHERE owner = '%s' AND LOWER(%s) = '%s'",
  455. $this->table, $this->owner, $db_field,
  456. $this->dbh->quoteString($value));
  457. $res = $this->dbh->query($query);
  458. if (DB::isError($res)) {
  459. return $this->set_error(sprintf(_("Database error: %s"),
  460. DB::errorMessage($res)));
  461. }
  462. if ($row = $res->fetchRow(DB_FETCHMODE_ASSOC)) {
  463. return array('nickname' => $row['nickname'],
  464. 'name' => $this->fullname($row['firstname'], $row['lastname']),
  465. 'firstname' => $row['firstname'],
  466. 'lastname' => $row['lastname'],
  467. 'email' => $row['email'],
  468. 'label' => $row['label'],
  469. 'backend' => $this->bnum,
  470. 'source' => &$this->sname);
  471. }
  472. }
  473. return array();
  474. }
  475. /**
  476. * List all addresses
  477. * @return array search results
  478. */
  479. function list_addr() {
  480. $ret = array();
  481. if (!$this->open()) {
  482. return false;
  483. }
  484. if(isset($this->listing) && !$this->listing) {
  485. return array();
  486. }
  487. global $use_pdo, $pdo_show_sql_errors;
  488. if ($use_pdo) {
  489. if (!($sth = $this->dbh->prepare('SELECT * FROM ' . $this->identifier_quote_char . $this->table . $this->identifier_quote_char . ' WHERE ' . $this->identifier_quote_char . 'owner' . $this->identifier_quote_char . ' = ?'))) {
  490. if ($pdo_show_sql_errors)
  491. return $this->set_error(sprintf(_("Database error: %s"), implode(' - ', $this->dbh->errorInfo())));
  492. else
  493. return $this->set_error(sprintf(_("Database error: %s"), _("Could not prepare query")));
  494. }
  495. if (!($res = $sth->execute(array($this->owner)))) {
  496. if ($pdo_show_sql_errors)
  497. return $this->set_error(sprintf(_("Database error: %s"), implode(' - ', $sth->errorInfo())));
  498. else
  499. return $this->set_error(sprintf(_("Database error: %s"), _("Could not execute query")));
  500. }
  501. while ($row = $sth->fetch(PDO::FETCH_ASSOC)) {
  502. array_push($ret, array('nickname' => $row['nickname'],
  503. 'name' => $this->fullname($row['firstname'], $row['lastname']),
  504. 'firstname' => $row['firstname'],
  505. 'lastname' => $row['lastname'],
  506. 'email' => $row['email'],
  507. 'label' => $row['label'],
  508. 'backend' => $this->bnum,
  509. 'source' => &$this->sname));
  510. }
  511. } else {
  512. $query = sprintf("SELECT * FROM %s WHERE owner='%s'",
  513. $this->table, $this->owner);
  514. $res = $this->dbh->query($query);
  515. if (DB::isError($res)) {
  516. return $this->set_error(sprintf(_("Database error: %s"),
  517. DB::errorMessage($res)));
  518. }
  519. while ($row = $res->fetchRow(DB_FETCHMODE_ASSOC)) {
  520. array_push($ret, array('nickname' => $row['nickname'],
  521. 'name' => $this->fullname($row['firstname'], $row['lastname']),
  522. 'firstname' => $row['firstname'],
  523. 'lastname' => $row['lastname'],
  524. 'email' => $row['email'],
  525. 'label' => $row['label'],
  526. 'backend' => $this->bnum,
  527. 'source' => &$this->sname));
  528. }
  529. }
  530. return $ret;
  531. }
  532. /**
  533. * Add address
  534. * @param array $userdata added data
  535. * @return bool
  536. */
  537. function add($userdata) {
  538. if (!$this->writeable) {
  539. return $this->set_error(_("Address book is read-only"));
  540. }
  541. if (!$this->open()) {
  542. return false;
  543. }
  544. // NB: if you want to check for some unwanted characters
  545. // or other problems, do so here like this:
  546. // TODO: Should pull all validation code out into a separate function
  547. //if (strpos($userdata['nickname'], ' ')) {
  548. // return $this->set_error(_("Nickname contains illegal characters"));
  549. //}
  550. /* See if user exist already */
  551. $ret = $this->lookup($userdata['nickname']);
  552. if (!empty($ret)) {
  553. return $this->set_error(sprintf(_("User \"%s\" already exists"), $ret['nickname']));
  554. }
  555. global $use_pdo, $pdo_show_sql_errors;
  556. if ($use_pdo) {
  557. if (!($sth = $this->dbh->prepare('INSERT INTO ' . $this->identifier_quote_char . $this->table . $this->identifier_quote_char . ' (' . $this->identifier_quote_char . 'owner' . $this->identifier_quote_char . ', ' . $this->identifier_quote_char . 'nickname' . $this->identifier_quote_char . ', ' . $this->identifier_quote_char . 'firstname' . $this->identifier_quote_char . ', ' . $this->identifier_quote_char . 'lastname' . $this->identifier_quote_char . ', ' . $this->identifier_quote_char . 'email' . $this->identifier_quote_char . ', ' . $this->identifier_quote_char . 'label' . $this->identifier_quote_char . ') VALUES (?, ?, ?, ?, ?, ?)'))) {
  558. if ($pdo_show_sql_errors)
  559. return $this->set_error(sprintf(_("Database error: %s"), implode(' - ', $this->dbh->errorInfo())));
  560. else
  561. return $this->set_error(sprintf(_("Database error: %s"), _("Could not prepare query")));
  562. }
  563. if (!($res = $sth->execute(array($this->owner, $userdata['nickname'], $userdata['firstname'], (!empty($userdata['lastname']) ? $userdata['lastname'] : ''), $userdata['email'], (!empty($userdata['label']) ? $userdata['label'] : ''))))) {
  564. if ($pdo_show_sql_errors)
  565. return $this->set_error(sprintf(_("Database error: %s"), implode(' - ', $sth->errorInfo())));
  566. else
  567. return $this->set_error(sprintf(_("Database error: %s"), _("Could not execute query")));
  568. }
  569. } else {
  570. /* Create query */
  571. $query = sprintf("INSERT INTO %s (owner, nickname, firstname, " .
  572. "lastname, email, label) VALUES('%s','%s','%s'," .
  573. "'%s','%s','%s')",
  574. $this->table, $this->owner,
  575. $this->dbh->quoteString($userdata['nickname']),
  576. $this->dbh->quoteString($userdata['firstname']),
  577. $this->dbh->quoteString((!empty($userdata['lastname'])?$userdata['lastname']:'')),
  578. $this->dbh->quoteString($userdata['email']),
  579. $this->dbh->quoteString((!empty($userdata['label'])?$userdata['label']:'')) );
  580. /* Do the insert */
  581. $r = $this->dbh->simpleQuery($query);
  582. /* Check for errors */
  583. if (DB::isError($r)) {
  584. return $this->set_error(sprintf(_("Database error: %s"),
  585. DB::errorMessage($r)));
  586. }
  587. }
  588. return true;
  589. }
  590. /**
  591. * Deletes address book entries
  592. * @param array $alias aliases that have to be deleted. numerical
  593. * array with nickname values
  594. * @return bool
  595. */
  596. function remove($alias) {
  597. if (!$this->writeable) {
  598. return $this->set_error(_("Address book is read-only"));
  599. }
  600. if (!$this->open()) {
  601. return false;
  602. }
  603. global $use_pdo, $pdo_show_sql_errors;
  604. if ($use_pdo) {
  605. $sepstr = '';
  606. $where_clause = '';
  607. $where_clause_args = array();
  608. while (list($undef, $nickname) = each($alias)) {
  609. $where_clause .= $sepstr . $this->identifier_quote_char . 'nickname' . $this->identifier_quote_char . ' = ?';
  610. $where_clause_args[] = $nickname;
  611. $sepstr = ' OR ';
  612. }
  613. if (!($sth = $this->dbh->prepare('DELETE FROM ' . $this->identifier_quote_char . $this->table . $this->identifier_quote_char . ' WHERE ' . $this->identifier_quote_char . 'owner' . $this->identifier_quote_char . ' = ? AND (' . $where_clause . ')'))) {
  614. if ($pdo_show_sql_errors)
  615. return $this->set_error(sprintf(_("Database error: %s"), implode(' - ', $this->dbh->errorInfo())));
  616. else
  617. return $this->set_error(sprintf(_("Database error: %s"), _("Could not prepare query")));
  618. }
  619. array_unshift($where_clause_args, $this->owner);
  620. if (!($res = $sth->execute($where_clause_args))) {
  621. if ($pdo_show_sql_errors)
  622. return $this->set_error(sprintf(_("Database error: %s"), implode(' - ', $sth->errorInfo())));
  623. else
  624. return $this->set_error(sprintf(_("Database error: %s"), _("Could not execute query")));
  625. }
  626. } else {
  627. /* Create query */
  628. $query = sprintf("DELETE FROM %s WHERE owner='%s' AND (",
  629. $this->table, $this->owner);
  630. $sepstr = '';
  631. while (list($undef, $nickname) = each($alias)) {
  632. $query .= sprintf("%s nickname='%s' ", $sepstr,
  633. $this->dbh->quoteString($nickname));
  634. $sepstr = 'OR';
  635. }
  636. $query .= ')';
  637. /* Delete entry */
  638. $r = $this->dbh->simpleQuery($query);
  639. /* Check for errors */
  640. if (DB::isError($r)) {
  641. return $this->set_error(sprintf(_("Database error: %s"),
  642. DB::errorMessage($r)));
  643. }
  644. }
  645. return true;
  646. }
  647. /**
  648. * Modify address
  649. * @param string $alias modified alias
  650. * @param array $userdata new data
  651. * @return bool
  652. */
  653. function modify($alias, $userdata) {
  654. if (!$this->writeable) {
  655. return $this->set_error(_("Address book is read-only"));
  656. }
  657. if (!$this->open()) {
  658. return false;
  659. }
  660. // NB: if you want to check for some unwanted characters
  661. // or other problems, do so here like this:
  662. // TODO: Should pull all validation code out into a separate function
  663. //if (strpos($userdata['nickname'], ' ')) {
  664. // return $this->set_error(_("Nickname contains illegal characters"));
  665. //}
  666. /* See if user exist */
  667. $ret = $this->lookup($alias);
  668. if (empty($ret)) {
  669. return $this->set_error(sprintf(_("User \"%s\" does not exist"),$alias));
  670. }
  671. /* make sure that new nickname is not used */
  672. if (strtolower($alias) != strtolower($userdata['nickname'])) {
  673. /* same check as in add() */
  674. $ret = $this->lookup($userdata['nickname']);
  675. if (!empty($ret)) {
  676. $error = sprintf(_("User '%s' already exist."), $ret['nickname']);
  677. return $this->set_error($error);
  678. }
  679. }
  680. global $use_pdo, $pdo_show_sql_errors;
  681. if ($use_pdo) {
  682. if (!($sth = $this->dbh->prepare('UPDATE ' . $this->identifier_quote_char . $this->table . $this->identifier_quote_char . ' SET ' . $this->identifier_quote_char . 'nickname' . $this->identifier_quote_char . ' = ?, ' . $this->identifier_quote_char . 'firstname' . $this->identifier_quote_char . ' = ?, ' . $this->identifier_quote_char . 'lastname' . $this->identifier_quote_char . ' = ?, ' . $this->identifier_quote_char . 'email' . $this->identifier_quote_char . ' = ?, ' . $this->identifier_quote_char . 'label' . $this->identifier_quote_char . ' = ? WHERE ' . $this->identifier_quote_char . 'owner' . $this->identifier_quote_char . ' = ? AND ' . $this->identifier_quote_char . 'nickname' . $this->identifier_quote_char . ' = ?'))) {
  683. if ($pdo_show_sql_errors)
  684. return $this->set_error(sprintf(_("Database error: %s"), implode(' - ', $this->dbh->errorInfo())));
  685. else
  686. return $this->set_error(sprintf(_("Database error: %s"), _("Could not prepare query")));
  687. }
  688. if (!($res = $sth->execute(array($userdata['nickname'], $userdata['firstname'], (!empty($userdata['lastname']) ? $userdata['lastname'] : ''), $userdata['email'], (!empty($userdata['label']) ? $userdata['label'] : ''), $this->owner, $alias)))) {
  689. if ($pdo_show_sql_errors)
  690. return $this->set_error(sprintf(_("Database error: %s"), implode(' - ', $sth->errorInfo())));
  691. else
  692. return $this->set_error(sprintf(_("Database error: %s"), _("Could not execute query")));
  693. }
  694. } else {
  695. /* Create query */
  696. $query = sprintf("UPDATE %s SET nickname='%s', firstname='%s', ".
  697. "lastname='%s', email='%s', label='%s' ".
  698. "WHERE owner='%s' AND nickname='%s'",
  699. $this->table,
  700. $this->dbh->quoteString($userdata['nickname']),
  701. $this->dbh->quoteString($userdata['firstname']),
  702. $this->dbh->quoteString((!empty($userdata['lastname'])?$userdata['lastname']:'')),
  703. $this->dbh->quoteString($userdata['email']),
  704. $this->dbh->quoteString((!empty($userdata['label'])?$userdata['label']:'')),
  705. $this->owner,
  706. $this->dbh->quoteString($alias) );
  707. /* Do the insert */
  708. $r = $this->dbh->simpleQuery($query);
  709. /* Check for errors */
  710. if (DB::isError($r)) {
  711. return $this->set_error(sprintf(_("Database error: %s"),
  712. DB::errorMessage($r)));
  713. }
  714. }
  715. return true;
  716. }
  717. } /* End of class abook_database */
  718. // vim: et ts=4