abook_database.php 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526
  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-2015 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 the DB functions
  24. * Don't display errors here. Error will be set in class constructor function.
  25. */
  26. @include_once('DB.php');
  27. /**
  28. * Address book in a database backend
  29. *
  30. * Backend for personal/shared address book stored in a database,
  31. * accessed using the DB-classes in PEAR.
  32. *
  33. * IMPORTANT: The PEAR modules must be in the include path
  34. * for this class to work.
  35. *
  36. * An array with the following elements must be passed to
  37. * the class constructor (elements marked ? are optional):
  38. * <pre>
  39. * dsn => database DNS (see PEAR for syntax)
  40. * table => table to store addresses in (must exist)
  41. * owner => current user (owner of address data)
  42. * ? name => name of address book
  43. * ? writeable => set writeable flag (true/false)
  44. * ? listing => enable/disable listing
  45. * </pre>
  46. * The table used should have the following columns:
  47. * owner, nickname, firstname, lastname, email, label
  48. * The pair (owner,nickname) should be unique (primary key).
  49. *
  50. * NOTE. This class should not be used directly. Use the
  51. * "AddressBook" class instead.
  52. * @package squirrelmail
  53. * @subpackage addressbook
  54. */
  55. class abook_database extends addressbook_backend {
  56. /**
  57. * Backend type
  58. * @var string
  59. */
  60. var $btype = 'local';
  61. /**
  62. * Backend name
  63. * @var string
  64. */
  65. var $bname = 'database';
  66. /**
  67. * Data Source Name (connection description)
  68. * @var string
  69. */
  70. var $dsn = '';
  71. /**
  72. * Table that stores addresses
  73. * @var string
  74. */
  75. var $table = '';
  76. /**
  77. * Owner name
  78. *
  79. * Limits list of database entries visible to end user
  80. * @var string
  81. */
  82. var $owner = '';
  83. /**
  84. * Database Handle
  85. * @var resource
  86. */
  87. var $dbh = false;
  88. /**
  89. * Enable/disable writing into address book
  90. * @var bool
  91. */
  92. var $writeable = true;
  93. /**
  94. * Enable/disable address book listing
  95. * @var bool
  96. */
  97. var $listing = true;
  98. /* ========================== Private ======================= */
  99. /**
  100. * Constructor
  101. * @param array $param address book backend options
  102. */
  103. function abook_database($param) {
  104. $this->sname = _("Personal Address Book");
  105. /* test if Pear DB class is available and freak out if it is not */
  106. if (! class_exists('DB')) {
  107. // same error also in db_prefs.php
  108. $error = _("Could not include PEAR database functions required for the database backend.") . "\n";
  109. $error .= sprintf(_("Is PEAR installed, and is the include path set correctly to find %s?"),
  110. 'DB.php') . "\n";
  111. $error .= _("Please contact your system administrator and report this error.");
  112. return $this->set_error($error);
  113. }
  114. if (is_array($param)) {
  115. if (empty($param['dsn']) ||
  116. empty($param['table']) ||
  117. empty($param['owner'])) {
  118. return $this->set_error('Invalid parameters');
  119. }
  120. $this->dsn = $param['dsn'];
  121. $this->table = $param['table'];
  122. $this->owner = $param['owner'];
  123. if (!empty($param['name'])) {
  124. $this->sname = $param['name'];
  125. }
  126. if (isset($param['writeable'])) {
  127. $this->writeable = $param['writeable'];
  128. }
  129. if (isset($param['listing'])) {
  130. $this->listing = $param['listing'];
  131. }
  132. $this->open(true);
  133. }
  134. else {
  135. return $this->set_error('Invalid argument to constructor');
  136. }
  137. }
  138. /**
  139. * Open the database.
  140. * @param bool $new new connection if it is true
  141. * @return bool
  142. */
  143. function open($new = false) {
  144. $this->error = '';
  145. /* Return true is file is open and $new is unset */
  146. if ($this->dbh && !$new) {
  147. return true;
  148. }
  149. /* Close old file, if any */
  150. if ($this->dbh) {
  151. $this->close();
  152. }
  153. $dbh = DB::connect($this->dsn, true);
  154. if (DB::isError($dbh)) {
  155. return $this->set_error(sprintf(_("Database error: %s"),
  156. DB::errorMessage($dbh)));
  157. }
  158. $this->dbh = $dbh;
  159. /**
  160. * field names are lowercased.
  161. * We use unquoted identifiers and they use upper case in Oracle
  162. */
  163. $this->dbh->setOption('portability', DB_PORTABILITY_LOWERCASE);
  164. return true;
  165. }
  166. /**
  167. * Close the file and forget the filehandle
  168. */
  169. function close() {
  170. $this->dbh->disconnect();
  171. $this->dbh = false;
  172. }
  173. /**
  174. * Determine internal database field name given one of
  175. * the SquirrelMail SM_ABOOK_FIELD_* constants
  176. *
  177. * @param integer $field The SM_ABOOK_FIELD_* contant to look up
  178. *
  179. * @return string The desired field name, or the string "ERROR"
  180. * if the $field is not understood (the caller
  181. * is responsible for handing errors)
  182. *
  183. */
  184. function get_field_name($field) {
  185. switch ($field) {
  186. case SM_ABOOK_FIELD_NICKNAME:
  187. return 'nickname';
  188. case SM_ABOOK_FIELD_FIRSTNAME:
  189. return 'firstname';
  190. case SM_ABOOK_FIELD_LASTNAME:
  191. return 'lastname';
  192. case SM_ABOOK_FIELD_EMAIL:
  193. return 'email';
  194. case SM_ABOOK_FIELD_LABEL:
  195. return 'label';
  196. default:
  197. return 'ERROR';
  198. }
  199. }
  200. /* ========================== Public ======================== */
  201. /**
  202. * Search the database
  203. *
  204. * Backend supports only * and ? wildcards. Complex eregs are not supported.
  205. * Search is case insensitive.
  206. * @param string $expr search expression
  207. * @return array search results. boolean false on error
  208. */
  209. function search($expr) {
  210. $ret = array();
  211. if(!$this->open()) {
  212. return false;
  213. }
  214. /* To be replaced by advanded search expression parsing */
  215. if (is_array($expr)) {
  216. return;
  217. }
  218. // don't allow wide search when listing is disabled.
  219. if ($expr=='*' && ! $this->listing)
  220. return array();
  221. /* lowercase expression in order to make it case insensitive */
  222. $expr = strtolower($expr);
  223. /* escape SQL wildcards */
  224. $expr = str_replace('_', '\\_', $expr);
  225. $expr = str_replace('%', '\\%', $expr);
  226. /* Convert wildcards to SQL syntax */
  227. $expr = str_replace('?', '_', $expr);
  228. $expr = str_replace('*', '%', $expr);
  229. $expr = $this->dbh->quoteString($expr);
  230. $expr = "%$expr%";
  231. /* create escape expression */
  232. $escape = 'ESCAPE \'' . $this->dbh->quoteString('\\') . '\'';
  233. $query = sprintf("SELECT * FROM %s WHERE owner='%s' AND " .
  234. "(LOWER(firstname) LIKE '%s' %s " .
  235. "OR LOWER(lastname) LIKE '%s' %s " .
  236. "OR LOWER(email) LIKE '%s' %s " .
  237. "OR LOWER(nickname) LIKE '%s' %s)",
  238. $this->table, $this->owner, $expr, $escape, $expr, $escape,
  239. $expr, $escape, $expr, $escape);
  240. $res = $this->dbh->query($query);
  241. if (DB::isError($res)) {
  242. return $this->set_error(sprintf(_("Database error: %s"),
  243. DB::errorMessage($res)));
  244. }
  245. while ($row = $res->fetchRow(DB_FETCHMODE_ASSOC)) {
  246. array_push($ret, array('nickname' => $row['nickname'],
  247. 'name' => $this->fullname($row['firstname'], $row['lastname']),
  248. 'firstname' => $row['firstname'],
  249. 'lastname' => $row['lastname'],
  250. 'email' => $row['email'],
  251. 'label' => $row['label'],
  252. 'backend' => $this->bnum,
  253. 'source' => &$this->sname));
  254. }
  255. return $ret;
  256. }
  257. /**
  258. * Lookup an address by the indicated field.
  259. *
  260. * @param string $value The value to look up
  261. * @param integer $field The field to look in, should be one
  262. * of the SM_ABOOK_FIELD_* constants
  263. * defined in include/constants.php
  264. * (OPTIONAL; defaults to nickname field)
  265. * NOTE: uniqueness is only guaranteed
  266. * when the nickname field is used here;
  267. * otherwise, the first matching address
  268. * is returned.
  269. *
  270. * @return array Array with lookup results when the value
  271. * was found, an empty array if the value was
  272. * not found.
  273. *
  274. */
  275. function lookup($value, $field=SM_ABOOK_FIELD_NICKNAME) {
  276. if (empty($value)) {
  277. return array();
  278. }
  279. $value = strtolower($value);
  280. if (!$this->open()) {
  281. return false;
  282. }
  283. $db_field = $this->get_field_name($field);
  284. if ($db_field == 'ERROR') {
  285. return $this->set_error(sprintf(_("Unknown field name: %s"), $field));
  286. }
  287. $query = sprintf("SELECT * FROM %s WHERE owner = '%s' AND LOWER(%s) = '%s'",
  288. $this->table, $this->owner, $db_field,
  289. $this->dbh->quoteString($value));
  290. $res = $this->dbh->query($query);
  291. if (DB::isError($res)) {
  292. return $this->set_error(sprintf(_("Database error: %s"),
  293. DB::errorMessage($res)));
  294. }
  295. if ($row = $res->fetchRow(DB_FETCHMODE_ASSOC)) {
  296. return array('nickname' => $row['nickname'],
  297. 'name' => $this->fullname($row['firstname'], $row['lastname']),
  298. 'firstname' => $row['firstname'],
  299. 'lastname' => $row['lastname'],
  300. 'email' => $row['email'],
  301. 'label' => $row['label'],
  302. 'backend' => $this->bnum,
  303. 'source' => &$this->sname);
  304. }
  305. return array();
  306. }
  307. /**
  308. * List all addresses
  309. * @return array search results
  310. */
  311. function list_addr() {
  312. $ret = array();
  313. if (!$this->open()) {
  314. return false;
  315. }
  316. if(isset($this->listing) && !$this->listing) {
  317. return array();
  318. }
  319. $query = sprintf("SELECT * FROM %s WHERE owner='%s'",
  320. $this->table, $this->owner);
  321. $res = $this->dbh->query($query);
  322. if (DB::isError($res)) {
  323. return $this->set_error(sprintf(_("Database error: %s"),
  324. DB::errorMessage($res)));
  325. }
  326. while ($row = $res->fetchRow(DB_FETCHMODE_ASSOC)) {
  327. array_push($ret, array('nickname' => $row['nickname'],
  328. 'name' => $this->fullname($row['firstname'], $row['lastname']),
  329. 'firstname' => $row['firstname'],
  330. 'lastname' => $row['lastname'],
  331. 'email' => $row['email'],
  332. 'label' => $row['label'],
  333. 'backend' => $this->bnum,
  334. 'source' => &$this->sname));
  335. }
  336. return $ret;
  337. }
  338. /**
  339. * Add address
  340. * @param array $userdata added data
  341. * @return bool
  342. */
  343. function add($userdata) {
  344. if (!$this->writeable) {
  345. return $this->set_error(_("Address book is read-only"));
  346. }
  347. if (!$this->open()) {
  348. return false;
  349. }
  350. /* See if user exist already */
  351. $ret = $this->lookup($userdata['nickname']);
  352. if (!empty($ret)) {
  353. return $this->set_error(sprintf(_("User \"%s\" already exists"),$ret['nickname']));
  354. }
  355. /* Create query */
  356. $query = sprintf("INSERT INTO %s (owner, nickname, firstname, " .
  357. "lastname, email, label) VALUES('%s','%s','%s'," .
  358. "'%s','%s','%s')",
  359. $this->table, $this->owner,
  360. $this->dbh->quoteString($userdata['nickname']),
  361. $this->dbh->quoteString($userdata['firstname']),
  362. $this->dbh->quoteString((!empty($userdata['lastname'])?$userdata['lastname']:'')),
  363. $this->dbh->quoteString($userdata['email']),
  364. $this->dbh->quoteString((!empty($userdata['label'])?$userdata['label']:'')) );
  365. /* Do the insert */
  366. $r = $this->dbh->simpleQuery($query);
  367. /* Check for errors */
  368. if (DB::isError($r)) {
  369. return $this->set_error(sprintf(_("Database error: %s"),
  370. DB::errorMessage($r)));
  371. }
  372. return true;
  373. }
  374. /**
  375. * Deletes address book entries
  376. * @param array $alias aliases that have to be deleted. numerical
  377. * array with nickname values
  378. * @return bool
  379. */
  380. function remove($alias) {
  381. if (!$this->writeable) {
  382. return $this->set_error(_("Address book is read-only"));
  383. }
  384. if (!$this->open()) {
  385. return false;
  386. }
  387. /* Create query */
  388. $query = sprintf("DELETE FROM %s WHERE owner='%s' AND (",
  389. $this->table, $this->owner);
  390. $sepstr = '';
  391. while (list($undef, $nickname) = each($alias)) {
  392. $query .= sprintf("%s nickname='%s' ", $sepstr,
  393. $this->dbh->quoteString($nickname));
  394. $sepstr = 'OR';
  395. }
  396. $query .= ')';
  397. /* Delete entry */
  398. $r = $this->dbh->simpleQuery($query);
  399. /* Check for errors */
  400. if (DB::isError($r)) {
  401. return $this->set_error(sprintf(_("Database error: %s"),
  402. DB::errorMessage($r)));
  403. }
  404. return true;
  405. }
  406. /**
  407. * Modify address
  408. * @param string $alias modified alias
  409. * @param array $userdata new data
  410. * @return bool
  411. */
  412. function modify($alias, $userdata) {
  413. if (!$this->writeable) {
  414. return $this->set_error(_("Address book is read-only"));
  415. }
  416. if (!$this->open()) {
  417. return false;
  418. }
  419. /* See if user exist */
  420. $ret = $this->lookup($alias);
  421. if (empty($ret)) {
  422. return $this->set_error(sprintf(_("User \"%s\" does not exist"),$alias));
  423. }
  424. /* make sure that new nickname is not used */
  425. if (strtolower($alias) != strtolower($userdata['nickname'])) {
  426. /* same check as in add() */
  427. $ret = $this->lookup($userdata['nickname']);
  428. if (!empty($ret)) {
  429. $error = sprintf(_("User '%s' already exist."), $ret['nickname']);
  430. return $this->set_error($error);
  431. }
  432. }
  433. /* Create query */
  434. $query = sprintf("UPDATE %s SET nickname='%s', firstname='%s', ".
  435. "lastname='%s', email='%s', label='%s' ".
  436. "WHERE owner='%s' AND nickname='%s'",
  437. $this->table,
  438. $this->dbh->quoteString($userdata['nickname']),
  439. $this->dbh->quoteString($userdata['firstname']),
  440. $this->dbh->quoteString((!empty($userdata['lastname'])?$userdata['lastname']:'')),
  441. $this->dbh->quoteString($userdata['email']),
  442. $this->dbh->quoteString((!empty($userdata['label'])?$userdata['label']:'')),
  443. $this->owner,
  444. $this->dbh->quoteString($alias) );
  445. /* Do the insert */
  446. $r = $this->dbh->simpleQuery($query);
  447. /* Check for errors */
  448. if (DB::isError($r)) {
  449. return $this->set_error(sprintf(_("Database error: %s"),
  450. DB::errorMessage($r)));
  451. }
  452. return true;
  453. }
  454. } /* End of class abook_database */
  455. // vim: et ts=4