abook_database.php 35 KB

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