abook_database.php 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475
  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. /* ========================== Public ======================== */
  174. /**
  175. * Search the database
  176. *
  177. * Backend supports only * and ? wildcards. Complex eregs are not supported.
  178. * Search is case insensitive.
  179. * @param string $expr search expression
  180. * @return array search results. boolean false on error
  181. */
  182. function search($expr) {
  183. $ret = array();
  184. if(!$this->open()) {
  185. return false;
  186. }
  187. /* To be replaced by advanded search expression parsing */
  188. if (is_array($expr)) {
  189. return;
  190. }
  191. // don't allow wide search when listing is disabled.
  192. if ($expr=='*' && ! $this->listing)
  193. return array();
  194. /* lowercase expression in order to make it case insensitive */
  195. $expr = strtolower($expr);
  196. /* escape SQL wildcards */
  197. $expr = str_replace('_', '\\_', $expr);
  198. $expr = str_replace('%', '\\%', $expr);
  199. /* Convert wildcards to SQL syntax */
  200. $expr = str_replace('?', '_', $expr);
  201. $expr = str_replace('*', '%', $expr);
  202. $expr = $this->dbh->quoteString($expr);
  203. $expr = "%$expr%";
  204. /* create escape expression */
  205. $escape = 'ESCAPE \'' . $this->dbh->quoteString('\\') . '\'';
  206. $query = sprintf("SELECT * FROM %s WHERE owner='%s' AND " .
  207. "(LOWER(firstname) LIKE '%s' %s OR LOWER(lastname) LIKE '%s' %s)",
  208. $this->table, $this->owner, $expr, $escape, $expr, $escape);
  209. $res = $this->dbh->query($query);
  210. if (DB::isError($res)) {
  211. return $this->set_error(sprintf(_("Database error: %s"),
  212. DB::errorMessage($res)));
  213. }
  214. while ($row = $res->fetchRow(DB_FETCHMODE_ASSOC)) {
  215. array_push($ret, array('nickname' => $row['nickname'],
  216. 'name' => $this->fullname($row['firstname'], $row['lastname']),
  217. 'firstname' => $row['firstname'],
  218. 'lastname' => $row['lastname'],
  219. 'email' => $row['email'],
  220. 'label' => $row['label'],
  221. 'backend' => $this->bnum,
  222. 'source' => &$this->sname));
  223. }
  224. return $ret;
  225. }
  226. /**
  227. * Lookup alias
  228. * @param string $alias alias
  229. * @return array search results
  230. */
  231. function lookup($alias) {
  232. if (empty($alias)) {
  233. return array();
  234. }
  235. $alias = strtolower($alias);
  236. if (!$this->open()) {
  237. return false;
  238. }
  239. $query = sprintf("SELECT * FROM %s WHERE owner='%s' AND LOWER(nickname)='%s'",
  240. $this->table, $this->owner, $this->dbh->quoteString($alias));
  241. $res = $this->dbh->query($query);
  242. if (DB::isError($res)) {
  243. return $this->set_error(sprintf(_("Database error: %s"),
  244. DB::errorMessage($res)));
  245. }
  246. if ($row = $res->fetchRow(DB_FETCHMODE_ASSOC)) {
  247. return array('nickname' => $row['nickname'],
  248. 'name' => $this->fullname($row['firstname'], $row['lastname']),
  249. 'firstname' => $row['firstname'],
  250. 'lastname' => $row['lastname'],
  251. 'email' => $row['email'],
  252. 'label' => $row['label'],
  253. 'backend' => $this->bnum,
  254. 'source' => &$this->sname);
  255. }
  256. return array();
  257. }
  258. /**
  259. * List all addresses
  260. * @return array search results
  261. */
  262. function list_addr() {
  263. $ret = array();
  264. if (!$this->open()) {
  265. return false;
  266. }
  267. if(isset($this->listing) && !$this->listing) {
  268. return array();
  269. }
  270. $query = sprintf("SELECT * FROM %s WHERE owner='%s'",
  271. $this->table, $this->owner);
  272. $res = $this->dbh->query($query);
  273. if (DB::isError($res)) {
  274. return $this->set_error(sprintf(_("Database error: %s"),
  275. DB::errorMessage($res)));
  276. }
  277. while ($row = $res->fetchRow(DB_FETCHMODE_ASSOC)) {
  278. array_push($ret, array('nickname' => $row['nickname'],
  279. 'name' => $this->fullname($row['firstname'], $row['lastname']),
  280. 'firstname' => $row['firstname'],
  281. 'lastname' => $row['lastname'],
  282. 'email' => $row['email'],
  283. 'label' => $row['label'],
  284. 'backend' => $this->bnum,
  285. 'source' => &$this->sname));
  286. }
  287. return $ret;
  288. }
  289. /**
  290. * Add address
  291. * @param array $userdata added data
  292. * @return bool
  293. */
  294. function add($userdata) {
  295. if (!$this->writeable) {
  296. return $this->set_error(_("Address book is read-only"));
  297. }
  298. if (!$this->open()) {
  299. return false;
  300. }
  301. /* See if user exist already */
  302. $ret = $this->lookup($userdata['nickname']);
  303. if (!empty($ret)) {
  304. return $this->set_error(sprintf(_("User \"%s\" already exists"),$ret['nickname']));
  305. }
  306. /* Create query */
  307. $query = sprintf("INSERT INTO %s (owner, nickname, firstname, " .
  308. "lastname, email, label) VALUES('%s','%s','%s'," .
  309. "'%s','%s','%s')",
  310. $this->table, $this->owner,
  311. $this->dbh->quoteString($userdata['nickname']),
  312. $this->dbh->quoteString($userdata['firstname']),
  313. $this->dbh->quoteString((!empty($userdata['lastname'])?$userdata['lastname']:'')),
  314. $this->dbh->quoteString($userdata['email']),
  315. $this->dbh->quoteString((!empty($userdata['label'])?$userdata['label']:'')) );
  316. /* Do the insert */
  317. $r = $this->dbh->simpleQuery($query);
  318. /* Check for errors */
  319. if (DB::isError($r)) {
  320. return $this->set_error(sprintf(_("Database error: %s"),
  321. DB::errorMessage($r)));
  322. }
  323. return true;
  324. }
  325. /**
  326. * Deletes address book entries
  327. * @param array $alias aliases that have to be deleted. numerical
  328. * array with nickname values
  329. * @return bool
  330. */
  331. function remove($alias) {
  332. if (!$this->writeable) {
  333. return $this->set_error(_("Address book is read-only"));
  334. }
  335. if (!$this->open()) {
  336. return false;
  337. }
  338. /* Create query */
  339. $query = sprintf("DELETE FROM %s WHERE owner='%s' AND (",
  340. $this->table, $this->owner);
  341. $sepstr = '';
  342. while (list($undef, $nickname) = each($alias)) {
  343. $query .= sprintf("%s nickname='%s' ", $sepstr,
  344. $this->dbh->quoteString($nickname));
  345. $sepstr = 'OR';
  346. }
  347. $query .= ')';
  348. /* Delete entry */
  349. $r = $this->dbh->simpleQuery($query);
  350. /* Check for errors */
  351. if (DB::isError($r)) {
  352. return $this->set_error(sprintf(_("Database error: %s"),
  353. DB::errorMessage($r)));
  354. }
  355. return true;
  356. }
  357. /**
  358. * Modify address
  359. * @param string $alias modified alias
  360. * @param array $userdata new data
  361. * @return bool
  362. */
  363. function modify($alias, $userdata) {
  364. if (!$this->writeable) {
  365. return $this->set_error(_("Address book is read-only"));
  366. }
  367. if (!$this->open()) {
  368. return false;
  369. }
  370. /* See if user exist */
  371. $ret = $this->lookup($alias);
  372. if (empty($ret)) {
  373. return $this->set_error(sprintf(_("User \"%s\" does not exist"),$alias));
  374. }
  375. /* make sure that new nickname is not used */
  376. if (strtolower($alias) != strtolower($userdata['nickname'])) {
  377. /* same check as in add() */
  378. $ret = $this->lookup($userdata['nickname']);
  379. if (!empty($ret)) {
  380. $error = sprintf(_("User '%s' already exist."), $ret['nickname']);
  381. return $this->set_error($error);
  382. }
  383. }
  384. /* Create query */
  385. $query = sprintf("UPDATE %s SET nickname='%s', firstname='%s', ".
  386. "lastname='%s', email='%s', label='%s' ".
  387. "WHERE owner='%s' AND nickname='%s'",
  388. $this->table,
  389. $this->dbh->quoteString($userdata['nickname']),
  390. $this->dbh->quoteString($userdata['firstname']),
  391. $this->dbh->quoteString((!empty($userdata['lastname'])?$userdata['lastname']:'')),
  392. $this->dbh->quoteString($userdata['email']),
  393. $this->dbh->quoteString((!empty($userdata['label'])?$userdata['label']:'')),
  394. $this->owner,
  395. $this->dbh->quoteString($alias) );
  396. /* Do the insert */
  397. $r = $this->dbh->simpleQuery($query);
  398. /* Check for errors */
  399. if (DB::isError($r)) {
  400. return $this->set_error(sprintf(_("Database error: %s"),
  401. DB::errorMessage($r)));
  402. }
  403. return true;
  404. }
  405. } /* End of class abook_database */
  406. // vim: et ts=4