abook_database.php 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522
  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 &copy; 1999-2007 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 OR LOWER(lastname) LIKE '%s' %s)",
  235. $this->table, $this->owner, $expr, $escape, $expr, $escape);
  236. $res = $this->dbh->query($query);
  237. if (DB::isError($res)) {
  238. return $this->set_error(sprintf(_("Database error: %s"),
  239. DB::errorMessage($res)));
  240. }
  241. while ($row = $res->fetchRow(DB_FETCHMODE_ASSOC)) {
  242. array_push($ret, array('nickname' => $row['nickname'],
  243. 'name' => $this->fullname($row['firstname'], $row['lastname']),
  244. 'firstname' => $row['firstname'],
  245. 'lastname' => $row['lastname'],
  246. 'email' => $row['email'],
  247. 'label' => $row['label'],
  248. 'backend' => $this->bnum,
  249. 'source' => &$this->sname));
  250. }
  251. return $ret;
  252. }
  253. /**
  254. * Lookup an address by the indicated field.
  255. *
  256. * @param string $value The value to look up
  257. * @param integer $field The field to look in, should be one
  258. * of the SM_ABOOK_FIELD_* constants
  259. * defined in include/constants.php
  260. * (OPTIONAL; defaults to nickname field)
  261. * NOTE: uniqueness is only guaranteed
  262. * when the nickname field is used here;
  263. * otherwise, the first matching address
  264. * is returned.
  265. *
  266. * @return array Array with lookup results when the value
  267. * was found, an empty array if the value was
  268. * not found.
  269. *
  270. */
  271. function lookup($value, $field=SM_ABOOK_FIELD_NICKNAME) {
  272. if (empty($value)) {
  273. return array();
  274. }
  275. $value = strtolower($value);
  276. if (!$this->open()) {
  277. return false;
  278. }
  279. $db_field = $this->get_field_name($field);
  280. if ($db_field == 'ERROR') {
  281. return $this->set_error(sprintf(_("Unknown field name: %s"), $field));
  282. }
  283. $query = sprintf("SELECT * FROM %s WHERE owner = '%s' AND LOWER(%s) = '%s'",
  284. $this->table, $this->owner, $db_field,
  285. $this->dbh->quoteString($value));
  286. $res = $this->dbh->query($query);
  287. if (DB::isError($res)) {
  288. return $this->set_error(sprintf(_("Database error: %s"),
  289. DB::errorMessage($res)));
  290. }
  291. if ($row = $res->fetchRow(DB_FETCHMODE_ASSOC)) {
  292. return array('nickname' => $row['nickname'],
  293. 'name' => $this->fullname($row['firstname'], $row['lastname']),
  294. 'firstname' => $row['firstname'],
  295. 'lastname' => $row['lastname'],
  296. 'email' => $row['email'],
  297. 'label' => $row['label'],
  298. 'backend' => $this->bnum,
  299. 'source' => &$this->sname);
  300. }
  301. return array();
  302. }
  303. /**
  304. * List all addresses
  305. * @return array search results
  306. */
  307. function list_addr() {
  308. $ret = array();
  309. if (!$this->open()) {
  310. return false;
  311. }
  312. if(isset($this->listing) && !$this->listing) {
  313. return array();
  314. }
  315. $query = sprintf("SELECT * FROM %s WHERE owner='%s'",
  316. $this->table, $this->owner);
  317. $res = $this->dbh->query($query);
  318. if (DB::isError($res)) {
  319. return $this->set_error(sprintf(_("Database error: %s"),
  320. DB::errorMessage($res)));
  321. }
  322. while ($row = $res->fetchRow(DB_FETCHMODE_ASSOC)) {
  323. array_push($ret, array('nickname' => $row['nickname'],
  324. 'name' => $this->fullname($row['firstname'], $row['lastname']),
  325. 'firstname' => $row['firstname'],
  326. 'lastname' => $row['lastname'],
  327. 'email' => $row['email'],
  328. 'label' => $row['label'],
  329. 'backend' => $this->bnum,
  330. 'source' => &$this->sname));
  331. }
  332. return $ret;
  333. }
  334. /**
  335. * Add address
  336. * @param array $userdata added data
  337. * @return bool
  338. */
  339. function add($userdata) {
  340. if (!$this->writeable) {
  341. return $this->set_error(_("Address book is read-only"));
  342. }
  343. if (!$this->open()) {
  344. return false;
  345. }
  346. /* See if user exist already */
  347. $ret = $this->lookup($userdata['nickname']);
  348. if (!empty($ret)) {
  349. return $this->set_error(sprintf(_("User \"%s\" already exists"),$ret['nickname']));
  350. }
  351. /* Create query */
  352. $query = sprintf("INSERT INTO %s (owner, nickname, firstname, " .
  353. "lastname, email, label) VALUES('%s','%s','%s'," .
  354. "'%s','%s','%s')",
  355. $this->table, $this->owner,
  356. $this->dbh->quoteString($userdata['nickname']),
  357. $this->dbh->quoteString($userdata['firstname']),
  358. $this->dbh->quoteString((!empty($userdata['lastname'])?$userdata['lastname']:'')),
  359. $this->dbh->quoteString($userdata['email']),
  360. $this->dbh->quoteString((!empty($userdata['label'])?$userdata['label']:'')) );
  361. /* Do the insert */
  362. $r = $this->dbh->simpleQuery($query);
  363. /* Check for errors */
  364. if (DB::isError($r)) {
  365. return $this->set_error(sprintf(_("Database error: %s"),
  366. DB::errorMessage($r)));
  367. }
  368. return true;
  369. }
  370. /**
  371. * Deletes address book entries
  372. * @param array $alias aliases that have to be deleted. numerical
  373. * array with nickname values
  374. * @return bool
  375. */
  376. function remove($alias) {
  377. if (!$this->writeable) {
  378. return $this->set_error(_("Address book is read-only"));
  379. }
  380. if (!$this->open()) {
  381. return false;
  382. }
  383. /* Create query */
  384. $query = sprintf("DELETE FROM %s WHERE owner='%s' AND (",
  385. $this->table, $this->owner);
  386. $sepstr = '';
  387. while (list($undef, $nickname) = each($alias)) {
  388. $query .= sprintf("%s nickname='%s' ", $sepstr,
  389. $this->dbh->quoteString($nickname));
  390. $sepstr = 'OR';
  391. }
  392. $query .= ')';
  393. /* Delete entry */
  394. $r = $this->dbh->simpleQuery($query);
  395. /* Check for errors */
  396. if (DB::isError($r)) {
  397. return $this->set_error(sprintf(_("Database error: %s"),
  398. DB::errorMessage($r)));
  399. }
  400. return true;
  401. }
  402. /**
  403. * Modify address
  404. * @param string $alias modified alias
  405. * @param array $userdata new data
  406. * @return bool
  407. */
  408. function modify($alias, $userdata) {
  409. if (!$this->writeable) {
  410. return $this->set_error(_("Address book is read-only"));
  411. }
  412. if (!$this->open()) {
  413. return false;
  414. }
  415. /* See if user exist */
  416. $ret = $this->lookup($alias);
  417. if (empty($ret)) {
  418. return $this->set_error(sprintf(_("User \"%s\" does not exist"),$alias));
  419. }
  420. /* make sure that new nickname is not used */
  421. if (strtolower($alias) != strtolower($userdata['nickname'])) {
  422. /* same check as in add() */
  423. $ret = $this->lookup($userdata['nickname']);
  424. if (!empty($ret)) {
  425. $error = sprintf(_("User '%s' already exist."), $ret['nickname']);
  426. return $this->set_error($error);
  427. }
  428. }
  429. /* Create query */
  430. $query = sprintf("UPDATE %s SET nickname='%s', firstname='%s', ".
  431. "lastname='%s', email='%s', label='%s' ".
  432. "WHERE owner='%s' AND nickname='%s'",
  433. $this->table,
  434. $this->dbh->quoteString($userdata['nickname']),
  435. $this->dbh->quoteString($userdata['firstname']),
  436. $this->dbh->quoteString((!empty($userdata['lastname'])?$userdata['lastname']:'')),
  437. $this->dbh->quoteString($userdata['email']),
  438. $this->dbh->quoteString((!empty($userdata['label'])?$userdata['label']:'')),
  439. $this->owner,
  440. $this->dbh->quoteString($alias) );
  441. /* Do the insert */
  442. $r = $this->dbh->simpleQuery($query);
  443. /* Check for errors */
  444. if (DB::isError($r)) {
  445. return $this->set_error(sprintf(_("Database error: %s"),
  446. DB::errorMessage($r)));
  447. }
  448. return true;
  449. }
  450. } /* End of class abook_database */
  451. // vim: et ts=4