PageRenderTime 70ms CodeModel.GetById 1ms RepoModel.GetById 0ms app.codeStats 1ms

/include/db/mysql.php

https://bitbucket.org/webop/webop-forum
PHP | 7740 lines | 4257 code | 857 blank | 2626 comment | 539 complexity | 20abd1693334d73630673cbe96199826 MD5 | raw file
Possible License(s): LGPL-2.1
  1. <?php
  2. ////////////////////////////////////////////////////////////////////////////////
  3. // //
  4. // Copyright (C) 2010 Phorum Development Team //
  5. // http://www.phorum.org //
  6. // //
  7. // This program is free software. You can redistribute it and/or modify //
  8. // it under the terms of either the current Phorum License (viewable at //
  9. // phorum.org) or the Phorum License that was distributed with this file //
  10. // //
  11. // This program is distributed in the hope that it will be useful, //
  12. // but WITHOUT ANY WARRANTY, without even the implied warranty of //
  13. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. //
  14. // //
  15. // You should have received a copy of the Phorum License //
  16. // along with this program. //
  17. // //
  18. ////////////////////////////////////////////////////////////////////////////////
  19. /**
  20. * This script implements a MySQL Phorum database layer.
  21. *
  22. * The other Phorum code does not care how data is stored.
  23. * The only requirement is that it is returned from these functions
  24. * in the right way. This means each database can use as many or as
  25. * few tables as it likes. It can store the fields anyway it wants.
  26. *
  27. * The only thing to worry about is the table_prefix for the tables.
  28. * all tables for a Phorum install should be prefixed with the
  29. * table_prefix that will be entered in include/db/config.php. This
  30. * will allow multiple Phorum installations to use the same database.
  31. *
  32. * @todo
  33. * phorum_api_user_check_access() is used in this layer, but the
  34. * include file for that is not included here. Keep it like that
  35. * or add the required include? Or is it functionality that doesn't
  36. * belong here and could better go into the core maybe?
  37. *
  38. * @package PhorumDBLayer
  39. * @copyright 2010, Phorum Development Team
  40. * @license Phorum License, http://www.phorum.org/license.txt
  41. */
  42. // Bail out if we're not loaded from the Phorum code.
  43. if (!defined('PHORUM')) return;
  44. // ----------------------------------------------------------------------
  45. // Definitions
  46. // ----------------------------------------------------------------------
  47. // {{{ Constant and variable definitions
  48. // The table prefix, which allows for storing multiple Phorum data sets
  49. // in one single database.
  50. $prefix = $PHORUM['DBCONFIG']['table_prefix'];
  51. /**
  52. * These are the table names that are used by this database system.
  53. */
  54. $PHORUM['message_table'] = $prefix . '_messages';
  55. $PHORUM['user_newflags_table'] = $prefix . '_user_newflags';
  56. $PHORUM['subscribers_table'] = $prefix . '_subscribers';
  57. $PHORUM['files_table'] = $prefix . '_files';
  58. $PHORUM['search_table'] = $prefix . '_search';
  59. $PHORUM['settings_table'] = $prefix . '_settings';
  60. $PHORUM['forums_table'] = $prefix . '_forums';
  61. $PHORUM['user_table'] = $prefix . '_users';
  62. $PHORUM['user_permissions_table'] = $prefix . '_user_permissions';
  63. $PHORUM['groups_table'] = $prefix . '_groups';
  64. $PHORUM['forum_group_xref_table'] = $prefix . '_forum_group_xref';
  65. $PHORUM['user_group_xref_table'] = $prefix . '_user_group_xref';
  66. $PHORUM['user_custom_fields_table'] = $prefix . '_user_custom_fields';
  67. $PHORUM['banlist_table'] = $prefix . '_banlists';
  68. $PHORUM['pm_messages_table'] = $prefix . '_pm_messages';
  69. $PHORUM['pm_folders_table'] = $prefix . '_pm_folders';
  70. $PHORUM['pm_xref_table'] = $prefix . '_pm_xref';
  71. $PHORUM['pm_buddies_table'] = $prefix . '_pm_buddies';
  72. $PHORUM['message_tracking_table'] = $prefix . '_messages_edittrack';
  73. /**
  74. * Message fields which are always strings, even if they contain numbers only.
  75. * Used in post-message and update-message, otherwise strange things happen.
  76. */
  77. $PHORUM['string_fields_message'] = array('author', 'subject', 'body', 'email');
  78. /**
  79. * Forum fields which are always strings, even if they contain numbers only.
  80. */
  81. $PHORUM['string_fields_forum'] = array('name', 'description', 'template');
  82. /**
  83. * User fields which are always strings, even if they contain numbers only.
  84. */
  85. $PHORUM['string_fields_user'] = array('username', 'real_name', 'display_name',
  86. 'password', 'password_temp', 'sessid_lt', 'sessid_st', 'email', 'email_temp',
  87. 'signature', 'user_language', 'user_template', 'moderator_data', 'settings_data'
  88. );
  89. /**
  90. * Function call parameter $return for {@link phorum_db_interact()}.
  91. * Makes the function return a database connection handle.
  92. */
  93. define('DB_RETURN_CONN', 0);
  94. /**
  95. * Function call parameter $return for {@link phorum_db_interact()}.
  96. * Makes the function return a SQL quoted value.
  97. */
  98. define('DB_RETURN_QUOTED', 1);
  99. /**
  100. * Function call parameter $return for {@link phorum_db_interact()}.
  101. * Makes the function return the query statement handle for a SQL query.
  102. */
  103. define('DB_RETURN_RES', 2);
  104. /**
  105. * Function call parameter $return for {@link phorum_db_interact()}.
  106. * Makes the function return a single database row for a SQL query.
  107. */
  108. define('DB_RETURN_ROW', 3);
  109. /**
  110. * Function call parameter $return for {@link phorum_db_interact()}.
  111. * Makes the function return an array of rows for a SQL query.
  112. */
  113. define('DB_RETURN_ROWS', 4);
  114. /**
  115. * Function call parameter $return for {@link phorum_db_interact()}.
  116. * Makes the function return a single database row for a SQL query
  117. * as an associative array
  118. */
  119. define('DB_RETURN_ASSOC', 5);
  120. /**
  121. * Function call parameter $return for {@link phorum_db_interact()}.
  122. * Makes the function return an array of rows for a SQL query
  123. * as associative arrays.
  124. */
  125. define('DB_RETURN_ASSOCS', 6);
  126. /**
  127. * Function call parameter $return for {@link phorum_db_interact()}.
  128. * Makes the function return a single value for a SQL query.
  129. */
  130. define('DB_RETURN_VALUE', 7);
  131. /**
  132. * Function call parameter $return for {@link phorum_db_interact()}.
  133. * Makes the function return the number of selected rows for a SQL query.
  134. */
  135. define('DB_RETURN_ROWCOUNT', 8);
  136. /**
  137. * Function call parameter $return for {@link phorum_db_interact()}.
  138. * Makes the function return the new auto_increment id value for
  139. * an insert SQL query.
  140. */
  141. define('DB_RETURN_NEWID', 9);
  142. /**
  143. * Function call parameter $return for {@link phorum_db_interact()}.
  144. * Makes the function return an error for a SQL query or NULL if there
  145. * was no error.
  146. */
  147. define('DB_RETURN_ERROR', 10);
  148. /**
  149. * Function call parameter $return for {@link phorum_db_interact()}.
  150. * Makes the function close the connection to the database.
  151. * The function will return no data.
  152. */
  153. define('DB_CLOSE_CONN', 11);
  154. /**#@+
  155. * Constant for the phorum_db_interact() function call $flags parameter.
  156. */
  157. define('DB_NOCONNECTOK', 1);
  158. define('DB_MISSINGTABLEOK', 2);
  159. define('DB_DUPFIELDNAMEOK', 4);
  160. define('DB_DUPKEYNAMEOK', 8);
  161. define('DB_DUPKEYOK', 16);
  162. define('DB_TABLEEXISTSOK', 32);
  163. define('DB_GLOBALQUERY', 64);
  164. define('DB_MASTERQUERY', 128);
  165. /**#@-*/
  166. /**#@+
  167. * Constant for the phorum_db_get_recent_messages() function call
  168. * $list_type parameter.
  169. */
  170. define('LIST_RECENT_MESSAGES', 0);
  171. define('LIST_RECENT_THREADS', 1);
  172. define('LIST_UPDATED_THREADS', 2);
  173. /**#@-*/
  174. // }}}
  175. // ----------------------------------------------------------------------
  176. // Utility functions (not directly part of the Phorum db API)
  177. // ----------------------------------------------------------------------
  178. // {{{ Function: phorum_db_mysql_connect()
  179. /**
  180. * A wrapper function for connecting to the database.
  181. *
  182. * This function should not be used from the db layer code. Instead the
  183. * phorum_db_interact() function should be used in combination with the
  184. * DB_RETURN_CONN return type. This function is only implemented for
  185. * module writers that use this function in their code.
  186. *
  187. * @return $conn - A database connection resource handle.
  188. * @deprecated
  189. */
  190. function phorum_db_mysql_connect() {
  191. return phorum_db_interact(DB_RETURN_CONN, NULL, NULL, DB_MASTERQUERY);
  192. }
  193. // }}}
  194. // {{{ Function: phorum_db_sanitize_mixed()
  195. /**
  196. * This function will sanitize a mixed variable based on a given type
  197. * for safe use in SQL queries.
  198. *
  199. * @param mixed &$var
  200. * The variable to be sanitized. Passed by reference, so the original
  201. * variable will be updated. It can be either a single variable or an
  202. * array containing multiple variables.
  203. *
  204. * @param string $type
  205. * Either "int" or "string" (the default).
  206. */
  207. function phorum_db_sanitize_mixed(&$var, $type)
  208. {
  209. if (is_array($var)) {
  210. foreach ($var as $id => $val) {
  211. if ($type == 'int') {
  212. $var[$id] = (int)$val;
  213. } else {
  214. $var[$id] = phorum_db_interact(DB_RETURN_QUOTED, $val);
  215. }
  216. }
  217. } else {
  218. if ($type=='int') {
  219. $var = (int)$var;
  220. } else {
  221. $var = phorum_db_interact(DB_RETURN_QUOTED, $var);
  222. }
  223. }
  224. }
  225. // }}}
  226. // {{{ Function: phorum_db_validate_field()
  227. /**
  228. * Check if a value that will be used as a field name in a SQL query
  229. * contains only characters that would appear in a field name.
  230. *
  231. * @param string $field_name
  232. * The field name to check.
  233. *
  234. * @return boolean
  235. * Whether the field name is valid or not (TRUE or FALSE).
  236. */
  237. function phorum_db_validate_field($field_name)
  238. {
  239. $valid = preg_match('!^[a-zA-Z0-9_]+$!', $field_name);
  240. return (bool)$valid;
  241. }
  242. // }}}
  243. // ----------------------------------------------------------------------
  244. // API functions
  245. // ----------------------------------------------------------------------
  246. // {{{ Function: phorum_db_check_connection()
  247. /**
  248. * @todo
  249. * we can save a function call by directly calling
  250. * phorum_db_interact(). I'm also not sure if we need
  251. * to do this check from common.php. We could take care
  252. * of this in the db layer error handling too. Have to
  253. * think about this ...
  254. *
  255. * Checks if a database connection can be made.
  256. *
  257. * @return boolean
  258. * TRUE if a connection can be made, FALSE otherwise.
  259. */
  260. function phorum_db_check_connection()
  261. {
  262. return phorum_db_interact(
  263. DB_RETURN_CONN,
  264. NULL, NULL,
  265. DB_NOCONNECTOK | DB_MASTERQUERY
  266. ) ? TRUE : FALSE;
  267. }
  268. // }}}
  269. // {{{ Function: phorum_db_close_connection()
  270. /**
  271. * Close the database connection.
  272. */
  273. function phorum_db_close_connection()
  274. {
  275. phorum_db_interact(DB_CLOSE_CONN);
  276. }
  277. // }}}
  278. // {{{ Function: phorum_db_run_queries()
  279. /**
  280. * Execute an array of queries.
  281. *
  282. * @param array $queries
  283. * An array of SQL queries to execute.
  284. *
  285. * @return mixed
  286. * NULL if all queries were executed successfully or an error
  287. * message on failure.
  288. */
  289. function phorum_db_run_queries($queries)
  290. {
  291. $PHORUM = $GLOBALS['PHORUM'];
  292. $error = NULL;
  293. foreach ($queries as $sql)
  294. {
  295. // Because this function is used from the upgrade scripts,
  296. // we ignore errors about duplicate fields and keys. That
  297. // way running the same upgrade scripts twice (in case there
  298. // were problems during the first run) won't bring up fatal
  299. // errors in case fields or keys are created a second time.
  300. $error = phorum_db_interact(
  301. DB_RETURN_ERROR,
  302. $sql, NULL,
  303. DB_DUPFIELDNAMEOK | DB_DUPKEYNAMEOK | DB_TABLEEXISTSOK |
  304. DB_GLOBALQUERY | DB_MASTERQUERY
  305. );
  306. if ($error !== NULL) break;
  307. }
  308. return $error;
  309. }
  310. // }}}
  311. // {{{ Function: phorum_db_load_settings()
  312. /**
  313. * Load the Phorum settings in the $PHORUM array.
  314. *
  315. * These settings are key/value pairs that are read from the settings
  316. * table. In the settings table, a data type is provided for each setting.
  317. * The supported types are:
  318. *
  319. * - V = Value: the value of this field is used as is.
  320. * - S = Serialized: the value of this field is a serialzed PHP variable,
  321. * which will be unserialized before storing it in $PHORUM
  322. */
  323. function phorum_db_load_settings()
  324. {
  325. global $PHORUM;
  326. // At install time, there is no settings table.
  327. // So we ignore errors if we do not see that table.
  328. $settings = phorum_db_interact(
  329. DB_RETURN_ROWS,
  330. "SELECT name, data, type
  331. FROM {$PHORUM['settings_table']}",
  332. NULL,
  333. DB_MISSINGTABLEOK
  334. );
  335. foreach ($settings as $setting)
  336. {
  337. $val = $setting[2] == 'V'
  338. ? $setting[1]
  339. : unserialize($setting[1]);
  340. $PHORUM[$setting[0]] = $val;
  341. }
  342. }
  343. // }}}
  344. // {{{ Function: phorum_db_update_settings()
  345. /**
  346. * Store or update Phorum settings.
  347. *
  348. * @param array $settings
  349. * An array containing key/value pairs that have to be stored in the
  350. * settings table. Values can be either scalars or arrays. This
  351. * function will automatically serialize the arrays before storing them.
  352. *
  353. * @return boolean
  354. * TRUE if all settings were stored successfully. This function will
  355. * always return TRUE, so we could do without a return value. The
  356. * return value is here for backward compatibility.
  357. */
  358. function phorum_db_update_settings($settings)
  359. {
  360. global $PHORUM;
  361. if (count($settings) > 0)
  362. {
  363. foreach ($settings as $field => $value)
  364. {
  365. if (is_array($value)) {
  366. $value = serialize($value);
  367. $type = 'S';
  368. } else {
  369. $type = 'V';
  370. }
  371. $field = phorum_db_interact(DB_RETURN_QUOTED, $field);
  372. $value = phorum_db_interact(DB_RETURN_QUOTED, $value);
  373. // Try to insert a new settings record.
  374. $res = phorum_db_interact(
  375. DB_RETURN_RES,
  376. "INSERT INTO {$PHORUM['settings_table']}
  377. (data, type, name)
  378. VALUES ('$value', '$type', '$field')",
  379. NULL,
  380. DB_DUPKEYOK | DB_MASTERQUERY
  381. );
  382. // If no result was returned, then the query failed. This probably
  383. // means that we already have the settings record in the database.
  384. // So instead of inserting a record, we need to update one here.
  385. if (!$res) {
  386. phorum_db_interact(
  387. DB_RETURN_RES,
  388. "UPDATE {$PHORUM['settings_table']}
  389. SET data = '$value',
  390. type = '$type'
  391. WHERE name = '$field'",
  392. NULL,
  393. DB_MASTERQUERY
  394. );
  395. }
  396. }
  397. }
  398. else trigger_error(
  399. 'phorum_db_update_settings(): $settings cannot be empty',
  400. E_USER_ERROR
  401. );
  402. return TRUE;
  403. }
  404. // }}}
  405. // {{{ Function: phorum_db_get_thread_list()
  406. /**
  407. * Retrieve a list of visible messages for a given page offset.
  408. *
  409. * By default, the message body is not included in the fetch queries.
  410. * To retrieve bodies as well, a true value has to be passed for the
  411. * $include_bodies parameter.
  412. *
  413. * NOTE: ALL dates must be returned as Unix timestamps
  414. *
  415. * @param integer $page
  416. * The index of the page to return, starting with 0.
  417. *
  418. * @param boolean $include_bodies
  419. * Whether to include the message bodies in the return data or not.
  420. *
  421. * @return array
  422. * An array of messages, indexed by message id.
  423. */
  424. function phorum_db_get_thread_list($page, $include_bodies=FALSE)
  425. {
  426. $PHORUM = $GLOBALS['PHORUM'];
  427. settype($page, 'int');
  428. // The messagefields that we want to fetch from the database.
  429. $messagefields =
  430. 'author, datestamp, email, message_id, forum_id, meta,
  431. moderator_post, modifystamp, parent_id, msgid, sort, moved, status,
  432. subject, thread, thread_count, user_id, viewcount, threadviewcount,
  433. closed, ip, recent_message_id, recent_user_id, recent_author';
  434. // Include the message bodies in the thread list if requested.
  435. if ($include_bodies) {
  436. $messagefields .= ',body';
  437. }
  438. // The sort mechanism to use.
  439. if ($PHORUM['float_to_top']) {
  440. $sortfield = 'modifystamp';
  441. $index = 'list_page_float';
  442. } else {
  443. $sortfield = 'thread';
  444. $index = 'list_page_flat';
  445. }
  446. // Initialize the return array.
  447. $messages = array();
  448. // The groups of messages which we want to fetch from the database.
  449. // stickies : sticky messages (only on the first page)
  450. // threads : thread starter messages (always)
  451. // replies : thread reply messages (only in threaded list mode)
  452. $groups = array();
  453. if ($page == 0) $groups[] = 'stickies';
  454. $groups[] = 'threads';
  455. if ($PHORUM['threaded_list']) $groups[] = 'replies';
  456. // For remembering the message ids for which we want to fetch the replies.
  457. $replymsgids = array();
  458. // Process all groups.
  459. foreach ($groups as $group)
  460. {
  461. $sql = NULL;
  462. switch ($group)
  463. {
  464. // Stickies.
  465. case 'stickies':
  466. $sql = "SELECT $messagefields
  467. FROM {$PHORUM['message_table']}
  468. WHERE status=".PHORUM_STATUS_APPROVED." AND
  469. parent_id=0 AND
  470. sort=".PHORUM_SORT_STICKY." AND
  471. forum_id={$PHORUM['forum_id']}
  472. ORDER BY sort, $sortfield desc";
  473. break;
  474. // Threads.
  475. case 'threads':
  476. if ($PHORUM['threaded_list']) {
  477. $limit = $PHORUM['list_length_threaded'];
  478. } else {
  479. $limit = $PHORUM['list_length_flat'];
  480. }
  481. $start = $page * $limit;
  482. $sql = "SELECT $messagefields
  483. FROM {$PHORUM['message_table']}
  484. USE INDEX ($index)
  485. WHERE $sortfield > 0 AND
  486. forum_id = {$PHORUM['forum_id']} AND
  487. status = ".PHORUM_STATUS_APPROVED." AND
  488. parent_id = 0 AND
  489. sort > 1
  490. ORDER BY $sortfield DESC
  491. LIMIT $start, $limit";
  492. break;
  493. // Reply messages.
  494. case 'replies':
  495. // We're done if we did not collect any messages with replies.
  496. if (! count($replymsgids)) break;
  497. $sortorder = "sort, $sortfield DESC, message_id";
  498. if (!empty($PHORUM['reverse_threading']))
  499. $sortorder.=' DESC';
  500. $sql = "SELECT $messagefields
  501. FROM {$PHORUM['message_table']}
  502. WHERE status = ".PHORUM_STATUS_APPROVED." AND
  503. thread in (" . implode(",",$replymsgids) .")
  504. ORDER BY $sortorder";
  505. break;
  506. } // End of switch ($group)
  507. // Continue with the next group if no SQL query was formulated.
  508. if ($sql === NULL) continue;
  509. // Query the messages for the current group.
  510. $rows = phorum_db_interact(DB_RETURN_ASSOCS, $sql, 'message_id');
  511. foreach ($rows as $id => $row)
  512. {
  513. // Unpack the thread message meta data.
  514. $row['meta'] = empty($row['meta'])
  515. ? array()
  516. : unserialize($row['meta']);
  517. // Add the row to the list of messages.
  518. $messages[$id] = $row;
  519. // We need the message ids for fetching reply messages.
  520. if ($group == 'threads' && $row['thread_count'] > 1) {
  521. $replymsgids[] = $id;
  522. }
  523. }
  524. }
  525. return $messages;
  526. }
  527. // }}}
  528. // {{{ Function: phorum_db_get_recent_messages
  529. /**
  530. * Retrieve a list of recent messages for all forums for which the user has
  531. * read permission, for a particular forum, for a list of forums or for a
  532. * particular thread. Optionally, only top level thread messages can be
  533. * retrieved.
  534. *
  535. * The original version of this function came from Jim Winstead of mysql.com
  536. *
  537. * @param integer $length
  538. * Limit the number of returned messages to this number.
  539. *
  540. * @param integer $offset
  541. * When using the $length parameter to limit the number of returned
  542. * messages, this parameter can be used to specify the retrieval offset.
  543. *
  544. * @param integer $forum_id
  545. * A forum_id, an array of forum_ids or 0 (zero) to retrieve messages
  546. * from any forum.
  547. *
  548. * @param integer $thread
  549. * A thread id or 0 (zero) to retrieve messages from any thread.
  550. *
  551. * @param integer $list_type
  552. * This parameter determines the type of list that has to be returned.
  553. * Options for this parameter are:
  554. * - LIST_RECENT_MESSAGES: return a list of recent messages
  555. * - LIST_RECENT_THREADS: return a list of recent threads
  556. * - LIST_UPDATED_THREADS: return a list of recently updated threads
  557. *
  558. * @return array
  559. * An array of recent messages, indexed by message_id. One special key
  560. * "users" is set too. This one contains an array of all involved
  561. * user_ids.
  562. */
  563. function phorum_db_get_recent_messages($length, $offset = 0, $forum_id = 0, $thread = 0, $list_type = LIST_RECENT_MESSAGES)
  564. {
  565. $PHORUM = $GLOBALS['PHORUM'];
  566. // Backward compatibility for the old $threads_only parameter.
  567. if (is_bool($list_type)) {
  568. $list_type = $list_type ? LIST_RECENT_THREADS : LIST_RECENT_MESSAGES;
  569. }
  570. settype($length, 'int');
  571. settype($offset, 'int');
  572. settype($thread, 'int');
  573. settype($list_type, 'int');
  574. phorum_db_sanitize_mixed($forum_id, 'int');
  575. // In case -1 is used as "any" value by the caller.
  576. if ($forum_id < 0) $forum_id = 0;
  577. if ($thread < 0) $thread = 0;
  578. // Parameter checking.
  579. if ($list_type < 0 || $list_type > 3) trigger_error(
  580. "phorum_db_get_recent_messages(): illegal \$list_type parameter used",
  581. E_USER_ERROR
  582. );
  583. if ($list_type != LIST_RECENT_MESSAGES && $thread) trigger_error(
  584. "phorum_db_get_recent_messages(): \$thread parameter can only be " .
  585. "used with \$list_type = LIST_RECENT_MESSAGES",
  586. E_USER_ERROR
  587. );
  588. // We have to check what forums the active Phorum user can read first.
  589. // Even if a $thread is passed, we have to make sure that the user
  590. // can read the containing forum. Here we convert the $forum_id argument
  591. // into an argument that is usable for phorum_api_user_check_access(),
  592. // in such way that it will always return an array of accessible forum_ids.
  593. if ($forum_id == 0) {
  594. $forum_id = PHORUM_ACCESS_LIST;
  595. } elseif(!is_array($forum_id)) {
  596. $forum_id = array($forum_id => $forum_id);
  597. }
  598. $allowed_forums = phorum_api_user_check_access(
  599. PHORUM_USER_ALLOW_READ, $forum_id
  600. );
  601. // If the user is not allowed to see any forum,
  602. // then return an empty array.
  603. if (empty($allowed_forums)) return array();
  604. // We need to differentiate on which key to use.
  605. // If selecting on a specific thread, then the best index
  606. // to use would be the thread_message index.
  607. if ($thread) {
  608. $use_key = 'thread_message';
  609. }
  610. // Indexes to use if we query exactly one forum.
  611. elseif (count($allowed_forums) == 1)
  612. {
  613. switch($list_type) {
  614. case LIST_RECENT_MESSAGES:
  615. $use_key = 'new_count';
  616. break;
  617. case LIST_RECENT_THREADS:
  618. $use_key = 'new_threads';
  619. break;
  620. case LIST_UPDATED_THREADS:
  621. $use_key = 'list_page_float';
  622. break;
  623. }
  624. }
  625. // Indexes to use if we query more than one forum.
  626. else
  627. {
  628. switch($list_type) {
  629. case LIST_RECENT_MESSAGES:
  630. $use_key = 'PRIMARY';
  631. break;
  632. case LIST_RECENT_THREADS:
  633. $use_key = 'recent_threads';
  634. break;
  635. case LIST_UPDATED_THREADS:
  636. $use_key = 'updated_threads';
  637. break;
  638. }
  639. }
  640. // Build the SQL query.
  641. $sql = "SELECT *
  642. FROM {$PHORUM['message_table']}
  643. USE INDEX ($use_key)
  644. WHERE status=".PHORUM_STATUS_APPROVED;
  645. if (count($allowed_forums) == 1) {
  646. $sql .= " AND forum_id = " . array_shift($allowed_forums);
  647. } else {
  648. $sql .= " AND forum_id IN (".implode(",", $allowed_forums).")";
  649. }
  650. if ($thread) {
  651. $sql.=" AND thread = $thread";
  652. }
  653. $sql .= " AND moved = 0";
  654. if ($list_type == LIST_RECENT_THREADS ||
  655. $list_type == LIST_UPDATED_THREADS) {
  656. $sql .= ' AND parent_id = 0';
  657. }
  658. if ($list_type == LIST_UPDATED_THREADS) {
  659. $sql .= ' ORDER BY modifystamp DESC';
  660. } else {
  661. $sql .= ' ORDER BY message_id DESC';
  662. }
  663. if ($length) {
  664. if ($offset > 0) {
  665. $sql .= " LIMIT $offset, $length";
  666. } else {
  667. $sql .= " LIMIT $length";
  668. }
  669. }
  670. // Retrieve matching messages from the database.
  671. $messages = phorum_db_interact(DB_RETURN_ASSOCS, $sql, 'message_id');
  672. // Post processing of received messages.
  673. $involved_users = array();
  674. foreach ($messages as $id => $message)
  675. {
  676. // Unpack the message meta data.
  677. $messages[$id]['meta'] = empty($message['meta'])
  678. ? array()
  679. : unserialize($message['meta']);
  680. // Collect all involved users.
  681. if (isset($message['user_id'])) {
  682. $involved_users[$message['user_id']] = $message['user_id'];
  683. }
  684. }
  685. // Store the involved users in the message array.
  686. $messages['users'] = $involved_users;
  687. return $messages;
  688. }
  689. // }}}
  690. // {{{ Function: phorum_db_get_unapproved_list()
  691. /**
  692. * Retrieve a list of messages which have not yet been approved by a moderator.
  693. *
  694. * NOTE: ALL dates must be returned as Unix timestamps
  695. *
  696. * @param $forum_id - The forum id to work with or NULL in case all
  697. * forums have to be searched. You can also pass an
  698. * array of forum ids.
  699. * @param $on_hold_only - Only take into account messages which have to
  700. * be approved directly after posting. Do not include
  701. * messages which were hidden by a moderator.
  702. * @param $moddays - Limit the search to the last $moddays number of days.
  703. *
  704. * @return - An array of messages, indexed by message id.
  705. */
  706. function phorum_db_get_unapproved_list($forum_id = NULL, $on_hold_only=FALSE, $moddays=0, $countonly = FALSE)
  707. {
  708. $PHORUM = $GLOBALS['PHORUM'];
  709. settype($on_hold_only, 'bool');
  710. settype($moddays, 'int');
  711. settype($countonly, 'bool');
  712. phorum_db_sanitize_mixed($forum_id, 'int');
  713. // Select a message count or full message records?
  714. $sql = 'SELECT ' . ($countonly ? 'count(*) ' : '* ') .
  715. 'FROM ' . $PHORUM['message_table'] . ' WHERE ';
  716. if (is_array($forum_id)) {
  717. $sql .= 'forum_id IN (' . implode(', ', $forum_id) . ') AND ';
  718. } elseif ($forum_id !== NULL) {
  719. $sql .= "forum_id = $forum_id AND ";
  720. }
  721. if ($moddays > 0) {
  722. $checktime = time() - (86400*$moddays);
  723. $sql .= " datestamp > $checktime AND";
  724. }
  725. if ($on_hold_only) {
  726. $sql .= ' status = '.PHORUM_STATUS_HOLD;
  727. } else {
  728. // Use an UNION for speed. This is much faster than using
  729. // a (status=X or status=Y) query.
  730. $sql = "($sql status = ".PHORUM_STATUS_HOLD.") UNION " .
  731. "($sql status = ".PHORUM_STATUS_HIDDEN.")";
  732. }
  733. if (!$countonly) {
  734. $sql .= ' ORDER BY thread, message_id';
  735. }
  736. // Retrieve and return data for counting unapproved messages.
  737. if ($countonly) {
  738. $count_per_status = phorum_db_interact(DB_RETURN_ROWS, $sql);
  739. $sum = 0;
  740. foreach ($count_per_status as $count) $sum += $count[0];
  741. return $sum;
  742. }
  743. // Retrieve unapproved messages.
  744. $messages = phorum_db_interact(DB_RETURN_ASSOCS, $sql, 'message_id');
  745. // Post processing of received messages.
  746. foreach ($messages as $id => $message) {
  747. $messages[$id]['meta'] = empty($message['meta'])
  748. ? array()
  749. : unserialize($message['meta']);
  750. }
  751. return $messages;
  752. }
  753. // }}}
  754. // {{{ Function: phorum_db_post_message()
  755. /**
  756. * Store a new message in the database.
  757. *
  758. * The message will not be posted if it is a duplicate and if
  759. * $PHORUM['check_duplicate'] is set.
  760. *
  761. * The $message is passed by reference and in case the function completes
  762. * successfully, the "message_id" index will be set to the new value.
  763. * If the "thread" index is set to zero, a new thread will be started and the
  764. * "thread" index will be filled with the new thread id upon return.
  765. *
  766. * @param array &$message
  767. * The message to post. This is an array, which should contain the
  768. * following fields: forum_id, thread, parent_id, author, subject, email,
  769. * ip, user_id, moderator_post, status, sort, msgid, body, closed.
  770. * Additionally, the following optional fields can be set: meta,
  771. * modifystamp, viewcount, threadviewcount.
  772. *
  773. * @param boolean $convert
  774. * True in case the message is being inserted by a database conversion
  775. * script. This will let you set the datestamp and message_id of the
  776. * message from the $message data. Also, the duplicate message check
  777. * will be fully skipped.
  778. *
  779. * @return integer
  780. * The message_id that was assigned to the new message.
  781. */
  782. function phorum_db_post_message(&$message, $convert=FALSE)
  783. {
  784. $PHORUM = $GLOBALS['PHORUM'];
  785. settype($convert, 'bool');
  786. foreach ($message as $key => $value) {
  787. if (is_numeric($value) &&
  788. !in_array($key,$PHORUM['string_fields_message'])) {
  789. $message[$key] = (int)$value;
  790. } elseif (is_array($value)) {
  791. $value = serialize($value);
  792. $message[$key] = phorum_db_interact(DB_RETURN_QUOTED, $value);
  793. } else {
  794. $message[$key] = phorum_db_interact(DB_RETURN_QUOTED, $value);
  795. }
  796. }
  797. // When converting messages, the post time should be in the message.
  798. $NOW = $convert ? $message['datestamp'] : time();
  799. // Check for duplicate posting of messages, unless we are converting a db.
  800. if (isset($PHORUM['check_duplicate']) && $PHORUM['check_duplicate'] && !$convert) {
  801. // Check for duplicate messages in the last hour.
  802. $check_timestamp = $NOW - 3600;
  803. $sql = "SELECT message_id
  804. FROM {$PHORUM['message_table']}
  805. WHERE forum_id = {$message['forum_id']} AND
  806. author ='{$message['author']}' AND
  807. subject ='{$message['subject']}' AND
  808. body ='{$message['body']}' AND
  809. datestamp > $check_timestamp";
  810. // Return 0 if at least one message can be found.
  811. if (phorum_db_interact(DB_RETURN_ROWCOUNT, $sql) > 0) return 0;
  812. }
  813. $insertfields = array(
  814. 'forum_id' => $message['forum_id'],
  815. 'datestamp' => $NOW,
  816. 'thread' => $message['thread'],
  817. 'parent_id' => $message['parent_id'],
  818. 'author' => "'" . $message['author'] . "'",
  819. 'subject' => "'" . $message['subject'] . "'",
  820. 'email' => "'" . $message['email'] . "'",
  821. 'ip' => "'" . $message['ip'] . "'",
  822. 'user_id' => $message['user_id'],
  823. 'moderator_post' => $message['moderator_post'],
  824. 'status' => $message['status'],
  825. 'sort' => $message['sort'],
  826. 'msgid' => "'" . $message['msgid'] . "'",
  827. 'body' => "'" . $message['body'] . "'",
  828. 'closed' => $message['closed'],
  829. 'moved' => 0
  830. );
  831. // The meta field is optional.
  832. if (isset($message['meta'])) {
  833. $insertfields['meta'] = "'{$message['meta']}'";
  834. }
  835. // The moved field is optional.
  836. if (!empty($message['moved'])) {
  837. $insertfields['moved'] = 1;
  838. }
  839. // When handling a conversion, the message_id can be set.
  840. if ($convert && isset($message['message_id'])) {
  841. $insertfields['message_id'] = $message['message_id'];
  842. }
  843. if (isset($message['modifystamp'])) {
  844. $insertfields['modifystamp'] = $message['modifystamp'];
  845. }
  846. if (isset($message['viewcount'])) {
  847. $insertfields['viewcount'] = $message['viewcount'];
  848. }
  849. if (isset($message['threadviewcount'])) {
  850. $insertfields['threadviewcount'] = $message['threadviewcount'];
  851. }
  852. // Insert the message and get the new message_id.
  853. $message_id = phorum_db_interact(
  854. DB_RETURN_NEWID,
  855. "INSERT INTO {$PHORUM['message_table']}
  856. (".implode(', ', array_keys($insertfields)).")
  857. VALUES (".implode(', ', $insertfields).")",
  858. NULL,
  859. DB_MASTERQUERY
  860. );
  861. $message['message_id'] = $message_id;
  862. $message['datestamp'] = $NOW;
  863. // Updates for thread starter messages.
  864. if ($message['thread'] == 0)
  865. {
  866. phorum_db_interact(
  867. DB_RETURN_RES,
  868. "UPDATE {$PHORUM['message_table']}
  869. SET thread = $message_id
  870. WHERE message_id = $message_id",
  871. NULL,
  872. DB_MASTERQUERY
  873. );
  874. $message['thread'] = $message_id;
  875. }
  876. if(empty($PHORUM['DBCONFIG']['empty_search_table'])) {
  877. // Full text searching updates.
  878. $search_text = $message['author'] .' | '.
  879. $message['subject'] .' | '.
  880. $message['body'];
  881. phorum_db_interact(
  882. DB_RETURN_RES,
  883. "INSERT DELAYED INTO {$PHORUM['search_table']}
  884. (message_id, forum_id,
  885. search_text)
  886. VALUES ({$message['message_id']}, {$message['forum_id']},
  887. '$search_text')",
  888. NULL,
  889. DB_MASTERQUERY
  890. );
  891. }
  892. return $message_id;
  893. }
  894. // }}}
  895. // {{{ Function: phorum_db_update_message()
  896. /**
  897. * Update a message in the database.
  898. *
  899. * Note: an update of the full text searching database is only handled
  900. * if all fields that we incorporate in full text searching (author,
  901. * subject and body) are in the update fields. If one of the fields is
  902. * provided, without providing the other two, then changes in the field
  903. * will not reflect in the full text searching info.
  904. *
  905. * @param $message_id - The message_id of the message to update.
  906. * @param $message - An array containing the data for the message fields
  907. * that have to be updated. You can pass as many or
  908. * as few message fields as you wish to update.
  909. */
  910. function phorum_db_update_message($message_id, $message)
  911. {
  912. $PHORUM = $GLOBALS['PHORUM'];
  913. settype($message_id, 'int');
  914. if (count($message) == 0) trigger_error(
  915. '$message cannot be empty in phorum_update_message()',
  916. E_USER_ERROR
  917. );
  918. foreach ($message as $field => $value)
  919. {
  920. if (phorum_db_validate_field($field))
  921. {
  922. if (is_numeric($value) &&
  923. !in_array($field, $PHORUM['string_fields_message'])) {
  924. $fields[] = "$field = $value";
  925. } elseif (is_array($value)) {
  926. $value = phorum_db_interact(DB_RETURN_QUOTED,serialize($value));
  927. $message[$field] = $value;
  928. $fields[] = "$field = '$value'";
  929. } else {
  930. $value = phorum_db_interact(DB_RETURN_QUOTED, $value);
  931. $message[$field] = $value;
  932. $fields[] = "$field = '$value'";
  933. }
  934. }
  935. }
  936. phorum_db_interact(
  937. DB_RETURN_RES,
  938. "UPDATE {$PHORUM['message_table']}
  939. SET " . implode(', ', $fields) . "
  940. WHERE message_id = $message_id",
  941. NULL,
  942. DB_MASTERQUERY
  943. );
  944. // Full text searching updates.
  945. if (!empty($PHORUM['DBCONFIG']['mysql_use_ft']) &&
  946. isset($message['author']) &&
  947. isset($message['subject']) &&
  948. isset($message['body']) &&
  949. empty($PHORUM['DBCONFIG']['empty_search_table']) ) {
  950. $search_text = $message['author'] .' | '.
  951. $message['subject'] .' | '.
  952. $message['body'];
  953. phorum_db_interact(
  954. DB_RETURN_RES,
  955. "REPLACE DELAYED INTO {$PHORUM['search_table']}
  956. SET message_id = {$message_id},
  957. forum_id = {$message['forum_id']},
  958. search_text = '$search_text'",
  959. NULL,
  960. DB_MASTERQUERY
  961. );
  962. }
  963. }
  964. // }}}
  965. // {{{ Function: phorum_db_delete_message()
  966. /**
  967. * Delete a message or a message tree from the database.
  968. *
  969. * @param integer $message_id
  970. * The message_id of the message which should be deleted.
  971. *
  972. * @param integer $mode
  973. * The mode of deletion. This is one of:
  974. * - PHORUM_DELETE_MESSAGE: Delete a message and reconnect
  975. * its reply messages to the parent of the deleted message.
  976. * - PHORUM_DELETE_TREE: Delete a message and all its reply messages.
  977. */
  978. function phorum_db_delete_message($message_id, $mode = PHORUM_DELETE_MESSAGE)
  979. {
  980. $PHORUM = $GLOBALS['PHORUM'];
  981. settype($message_id, 'int');
  982. settype($mode, 'int');
  983. // Find the info for the message that has to be deleted.
  984. $msg = phorum_db_interact(
  985. DB_RETURN_ASSOC,
  986. "SELECT forum_id, message_id, thread, parent_id
  987. FROM {$PHORUM['message_table']}
  988. WHERE message_id = $message_id"
  989. );
  990. if (empty($msg)) trigger_error(
  991. "No message found for message_id $message_id", E_USER_ERROR
  992. );
  993. // Find all message_ids that have to be deleted, based on the mode.
  994. if ($mode == PHORUM_DELETE_TREE) {
  995. $mids = phorum_db_get_messagetree($message_id, $msg['forum_id']);
  996. $where = "message_id IN ($mids)";
  997. $mids = explode(',', $mids);
  998. } else {
  999. $mids = array($message_id);
  1000. $where = "message_id = $message_id";
  1001. }
  1002. // First, the messages are unapproved, so replies will not get posted
  1003. // during the time that we need for deleting them. There is still a
  1004. // race condition here, but this already makes things quite reliable.
  1005. phorum_db_interact(
  1006. DB_RETURN_RES,
  1007. "UPDATE {$PHORUM['message_table']}
  1008. SET status=".PHORUM_STATUS_HOLD."
  1009. WHERE $where",
  1010. NULL,
  1011. DB_MASTERQUERY
  1012. );
  1013. $thread = $msg['thread'];
  1014. // Change reply messages to point to the parent of the deleted message.
  1015. if ($mode == PHORUM_DELETE_MESSAGE)
  1016. {
  1017. // The forum_id is in here for speeding up the query
  1018. // (with the forum_id a lookup key will be used).
  1019. phorum_db_interact(
  1020. DB_RETURN_RES,
  1021. "UPDATE {$PHORUM['message_table']}
  1022. SET parent_id = {$msg['parent_id']}
  1023. WHERE forum_id = {$msg['forum_id']} AND
  1024. parent_id = {$msg['message_id']}",
  1025. NULL,
  1026. DB_MASTERQUERY
  1027. );
  1028. }
  1029. // Delete the messages.
  1030. phorum_db_interact(
  1031. DB_RETURN_RES,
  1032. "DELETE FROM {$PHORUM['message_table']}
  1033. WHERE $where",
  1034. NULL,
  1035. DB_MASTERQUERY
  1036. );
  1037. // Delete the read flags.
  1038. phorum_db_interact(
  1039. DB_RETURN_RES,
  1040. "DELETE FROM {$PHORUM['user_newflags_table']}
  1041. WHERE $where",
  1042. NULL,
  1043. DB_MASTERQUERY
  1044. );
  1045. // Delete the edit tracking.
  1046. phorum_db_interact(
  1047. DB_RETURN_RES,
  1048. "DELETE FROM {$PHORUM['message_tracking_table']}
  1049. WHERE $where",
  1050. NULL,
  1051. DB_MASTERQUERY
  1052. );
  1053. // Full text searching updates.
  1054. phorum_db_interact(
  1055. DB_RETURN_RES,
  1056. "DELETE FROM {$PHORUM['search_table']}
  1057. WHERE $where",
  1058. NULL,
  1059. DB_MASTERQUERY
  1060. );
  1061. // It kind of sucks to have this here, but it is the best way
  1062. // to ensure that thread info gets updated if messages are deleted.
  1063. // Leave this include down here, so it is included conditionally.
  1064. include_once('./include/thread_info.php');
  1065. phorum_update_thread_info($thread);
  1066. // We need to delete the subscriptions for the thread too.
  1067. phorum_db_interact(
  1068. DB_RETURN_RES,
  1069. "DELETE FROM {$PHORUM['subscribers_table']}
  1070. WHERE forum_id > 0 AND thread = $thread",
  1071. NULL,
  1072. DB_MASTERQUERY
  1073. );
  1074. // This function will be slow with a lot of messages.
  1075. phorum_db_update_forum_stats(TRUE);
  1076. return $mids;
  1077. }
  1078. // }}}
  1079. // {{{ Function: phorum_db_get_messagetree()
  1080. /**
  1081. * Build a tree of all child (reply) messages below a message_id.
  1082. *
  1083. * @param integer $message_id
  1084. * The message_id for which to build the message tree.
  1085. *
  1086. * @param integer $forum_id
  1087. * The forum_id for the message.
  1088. *
  1089. * @return string
  1090. * A string containing a comma separated list of child message_ids
  1091. * for the given message_id.
  1092. */
  1093. function phorum_db_get_messagetree($message_id, $forum_id)
  1094. {
  1095. $PHORUM = $GLOBALS['PHORUM'];
  1096. settype($message_id, 'int');
  1097. settype($forum_id, 'int');
  1098. // Find all children for the provided message_id.
  1099. $child_ids = phorum_db_interact(
  1100. DB_RETURN_ROWS,
  1101. "SELECT message_id
  1102. FROM {$PHORUM['message_table']}
  1103. WHERE forum_id = $forum_id AND
  1104. parent_id = $message_id"
  1105. );
  1106. // Recursively build the message tree.
  1107. $tree = "$message_id";
  1108. foreach ($child_ids as $child_id) {
  1109. $tree .= ',' . phorum_db_get_messagetree($child_id[0], $forum_id);
  1110. }
  1111. return $tree;
  1112. }
  1113. // }}}
  1114. // {{{ Function: phorum_db_get_message()
  1115. /**
  1116. * Retrieve message(s) from the messages table by comparing value(s)
  1117. * for a specified field in that table.
  1118. *
  1119. * You can provide either a single value or an array of values to search
  1120. * for. If a single value is provided, then the function will return the
  1121. * first matching message in the table. If an array of values is provided,
  1122. * the function will return all matching messages in an array.
  1123. *
  1124. * @param mixed $value
  1125. * The value that you want to search on in the messages table.
  1126. * This can be either a single value or an array of values.
  1127. *
  1128. * @param string $field
  1129. * The message field (database column) to search on.
  1130. *
  1131. * @param boolean $ignore_forum_id
  1132. * By default, this function will only search for messages within the
  1133. * active forum (as defined by $PHORUM["forum_id"). By setting this
  1134. * parameter to a true value, the function will search in any forum.
  1135. *
  1136. * @param boolean $write_server
  1137. * This value can be set to true to specify that the message should be
  1138. * retrieved from the master (aka write-server) in case replication
  1139. * is used.
  1140. *
  1141. * @return mixed
  1142. * Either a single message or an array of messages (indexed by
  1143. * message_id), depending on the $value parameter. If no message is
  1144. * found at all, then either an empty array or NULL is returned
  1145. * (also depending on the $value parameter).
  1146. */
  1147. function phorum_db_get_message($value, $field='message_id', $ignore_forum_id=FALSE, $write_server=FALSE)
  1148. {
  1149. $PHORUM = $GLOBALS['PHORUM'];
  1150. phorum_db_sanitize_mixed($value, 'string');
  1151. settype($ignore_forum_id, 'bool');
  1152. if (!phorum_db_validate_field($field)) trigger_error(
  1153. 'phorum_db_get_message(): Illegal database field ' .
  1154. '"' . htmlspecialchars($field) . '"', E_USER_ERROR
  1155. );
  1156. $forum_id_check = '';
  1157. if (!$ignore_forum_id && !empty($PHORUM['forum_id'])) {
  1158. $forum_id_check = "forum_id = {$PHORUM['forum_id']} AND ";
  1159. }
  1160. if (is_array($value)) {
  1161. $multiple = TRUE;
  1162. $checkvar = "$field IN ('".implode("','",$value)."')";
  1163. $limit = '';
  1164. } else {
  1165. $multiple=FALSE;
  1166. $checkvar = "$field = '$value'";
  1167. $limit = 'LIMIT 1';
  1168. }
  1169. $return = $multiple ? array() : NULL;
  1170. if($write_server) {
  1171. $flags = DB_MASTERQUERY;
  1172. } else {
  1173. $flags = 0;
  1174. }
  1175. $messages = phorum_db_interact(
  1176. DB_RETURN_ASSOCS,
  1177. "SELECT *
  1178. FROM {$PHORUM['message_table']}
  1179. WHERE $forum_id_check $checkvar
  1180. $limit",
  1181. NULL,
  1182. $flags
  1183. );
  1184. foreach ($messages as $message)
  1185. {
  1186. $message['meta'] = empty($message['meta'])
  1187. ? array()
  1188. : unserialize($message['meta']);
  1189. if (! $multiple) {
  1190. $return = $message;
  1191. break;
  1192. }
  1193. $return[$message['message_id']] = $message;
  1194. }
  1195. return $return;
  1196. }
  1197. // }}}
  1198. // {{{ Function: phorum_db_get_messages()
  1199. /**
  1200. * Retrieve messages from a specific thread.
  1201. *
  1202. * @param integer $thread
  1203. * The id of the thread.
  1204. *
  1205. * @param integer $page
  1206. * A page offset (based on the configured read_length) starting with 1.
  1207. * All messages are returned in case $page is 0.
  1208. *
  1209. * @param boolean $ignore_mod_perms
  1210. * If this parameter is set to a true value, then the function will
  1211. * return hidden messages, even if the active Phorum user is not
  1212. * a moderator.
  1213. *
  1214. * @param boolean $write_server
  1215. * This value can be set to true to specify that the message should be retrieved
  1216. * from the master (aka write-server) in case replication is used
  1217. *
  1218. * @return array
  1219. * An array of messages, indexed by message_id. One special key "users"
  1220. * is set too. This one contains an array of all involved user_ids.
  1221. */
  1222. function phorum_db_get_messages($thread, $page=0, $ignore_mod_perms=FALSE, $write_server = FALSE)
  1223. {
  1224. $PHORUM = $GLOBALS['PHORUM'];
  1225. settype($thread, 'int');
  1226. settype($page, 'int');
  1227. settype($ignore_mod_perms, 'int');
  1228. // Check if the forum_id has to be checked.
  1229. $forum_id_check = '';
  1230. if (!empty($PHORUM['forum_id'])) {
  1231. $forum_id_check = "forum_id = {$PHORUM['forum_id']} AND";
  1232. }
  1233. // Determine if not approved messages should be displayed.
  1234. $approvedval = '';
  1235. if (!$ignore_mod_perms &&
  1236. !phorum_api_user_check_access(PHORUM_USER_ALLOW_MODERATE_MESSAGES)) {
  1237. $approvedval = 'AND status ='.PHORUM_STATUS_APPROVED;
  1238. }
  1239. $sql = "SELECT *
  1240. FROM {$PHORUM['message_table']}
  1241. WHERE $forum_id_check
  1242. thread = $thread
  1243. $approvedval
  1244. ORDER BY message_id";
  1245. if ($page > 0) {
  1246. // Handle the page offset.
  1247. $start = $PHORUM['read_length'] * ($page-1);
  1248. $sql .= " LIMIT $start,".$PHORUM['read_length'];
  1249. } else {
  1250. // Handle reverse threading. This is only done if $page is 0.
  1251. // In that case, the messages for threaded read are retrieved.
  1252. if (!empty($PHORUM['reverse_threading']))
  1253. $sql.=' DESC';
  1254. }
  1255. if($write_server) {
  1256. $flags = DB_MASTERQUERY;
  1257. } else {
  1258. $flags = 0;
  1259. }
  1260. $messages = phorum_db_interact(DB_RETURN_ASSOCS, $sql, 'message_id', $flags);
  1261. $involved_users = array();
  1262. foreach ($messages as $id => $message)
  1263. {
  1264. // Unpack the message meta data.
  1265. $messages[$id]['meta'] = empty($message['meta'])
  1266. ? array()
  1267. : unserialize($message['meta']);
  1268. // Collect all involved users.
  1269. if ($message['user_id']) {
  1270. $involved_users[$message['user_id']] = $message['user_id'];
  1271. }
  1272. }
  1273. // Always include the thread starter message in the return data.
  1274. // It might not be in the messagelist if a page offset is used
  1275. // (since the thread starter is only on the first page).
  1276. if (count($messages) && !isset($messages[$thread]))
  1277. {
  1278. $starter = phorum_db_interact(
  1279. DB_RETURN_ASSOC,
  1280. "SELECT *
  1281. FROM {$PHORUM['message_table']}
  1282. WHERE $forum_id_check
  1283. message_id = $thread
  1284. $approvedval",
  1285. NULL,
  1286. $flags
  1287. );
  1288. if ($starter)
  1289. {
  1290. // Unpack the message meta data.
  1291. $starter['meta'] = empty($starter['meta'])
  1292. ? array()
  1293. : unserialize($starter['meta']);
  1294. $messages[$thread] = $starter;
  1295. // Add to involved users.
  1296. if ($starter['user_id']) {
  1297. $involved_users[$starter['user_id']] = $starter['user_id'];
  1298. }
  1299. }
  1300. }
  1301. // Store the involved users in the message array.
  1302. $messages['users'] = $involved_users;
  1303. return $messages;
  1304. }
  1305. // }}}
  1306. // {{{ Function: phorum_db_get_message_index()
  1307. /**
  1308. * Retrieve the index of a message (the offset from the thread starter
  1309. * message) within a thread.
  1310. *
  1311. * @param integer $thread
  1312. * The thread id.
  1313. *
  1314. * @param integer $message_id
  1315. * The message id for which to determine the index.
  1316. *
  1317. * @return integer
  1318. * The index of the message, starting with 0.
  1319. */
  1320. function phorum_db_get_message_index($thread=0, $message_id=0)
  1321. {
  1322. $PHORUM = $GLOBALS['PHORUM'];
  1323. // check for valid values
  1324. if (empty($thread) || empty($message_id)) {
  1325. return 0;
  1326. }
  1327. settype($thread, 'int');
  1328. settype($message_id, 'int');
  1329. $forum_id_check = '';
  1330. if (!empty($PHORUM['forum_id'])) {
  1331. $forum_id_check = "forum_id = {$PHORUM['forum_id']} AND";
  1332. }
  1333. $approvedval = '';
  1334. if (!phorum_api_user_check_access(PHORUM_USER_ALLOW_MODERATE_MESSAGES)) {
  1335. $approvedval='AND status ='.PHORUM_STATUS_APPROVED;
  1336. }
  1337. $index = phorum_db_interact(
  1338. DB_RETURN_VALUE,
  1339. "SELECT count(*)
  1340. FROM {$PHORUM['message_table']}
  1341. WHERE $forum_id_check
  1342. thread = $thread
  1343. $approvedval AND
  1344. message_id <= $message_id"
  1345. );
  1346. return $index;
  1347. }
  1348. // }}}
  1349. // {{{ Function: phorum_db_search()
  1350. /**
  1351. * Search the database using the provided search criteria and return
  1352. * an array containing the total count of matches and the visible
  1353. * messages based on the page $offset and $length.
  1354. *
  1355. * @param string $search
  1356. * The query to search on in messages and subjects.
  1357. *
  1358. * @param mixed $author
  1359. * The query to search on in the message authors or a numerical user_id
  1360. * if searching for all messages for a certain user_id.
  1361. *
  1362. * @param boolean $return_threads
  1363. * Whether to return the results as threads (TRUE) or messages (FALSE).
  1364. * When searching for a user ($match_type = USER_ID), then only the
  1365. * thread starter messages that were posted by the user are returned.
  1366. *
  1367. * @param integer $offset
  1368. * The result page offset starting with 0.
  1369. *
  1370. * @param integer $length
  1371. * The result page length (nr. of results per page).
  1372. *
  1373. * @param string $match_type
  1374. * The type of search. This can be one of:
  1375. * - ALL: search on all of the words (uses $search)
  1376. * - ANY: search on any of the words (uses $search)
  1377. * - PHRASE: search for an exact phrase (uses $search)
  1378. * - USER_ID: search for an author id (uses $author)
  1379. *
  1380. * @param integer $days
  1381. * The number of days to go back in the database for searching
  1382. * (last [x] days) or zero to search within all dates.
  1383. *
  1384. * @param string $match_forum
  1385. * The forum restriction. This can be either the string "ALL" to search
  1386. * in any of the readable forums or a comma separated list of forum_ids.
  1387. *
  1388. * @return array
  1389. * An array containing two fields:
  1390. * - "count" contains the total number of matching messages.
  1391. * - "rows" contains the messages that are visible, based on the page
  1392. * $offset and page $length. The messages are indexed by message_id.
  1393. */
  1394. function phorum_db_search($search, $author, $return_threads, $offset, $length, $match_type, $days, $match_forum)
  1395. {
  1396. $PHORUM = $GLOBALS['PHORUM'];
  1397. $fulltext_mode = isset($PHORUM['DBCONFIG']['mysql_use_ft']) &&
  1398. $PHORUM['DBCONFIG']['mysql_use_ft'];
  1399. $search = trim($search);
  1400. $author = trim($author);
  1401. settype($return_threads, 'bool');
  1402. settype($offset, 'int');
  1403. settype($length, 'int');
  1404. settype($days, 'int');
  1405. // For spreading search results over multiple pages.
  1406. $start = $offset * $length;
  1407. // Initialize the return data.
  1408. $return = array('count' => 0, 'rows' => array());
  1409. // Return immediately if the search queries are empty.
  1410. if ($search == '' && $author == '') return $return;
  1411. // Check what forums the active Phorum user can read.
  1412. $allowed_forums = phorum_api_user_check_access(
  1413. PHORUM_USER_ALLOW_READ, PHORUM_ACCESS_LIST
  1414. );
  1415. // If the user is not allowed to search any forum or the current
  1416. // active forum, then return the emtpy search results array.
  1417. if (empty($allowed_forums) ||
  1418. ($PHORUM['forum_id']>0 &&
  1419. !in_array($PHORUM['forum_id'], $allowed_forums))) return $return;
  1420. // Prepare forum_id restriction.
  1421. $match_forum_arr = explode(",", $match_forum);
  1422. $search_forums = array();
  1423. foreach ($match_forum_arr as $forum_id) {
  1424. if ($forum_id == "ALL") {
  1425. $search_forums = $allowed_forums;
  1426. break;
  1427. }
  1428. if (isset($allowed_forums[$forum_id])) {
  1429. $search_forums[] = $forum_id;
  1430. }
  1431. }
  1432. if (count($search_forums)){
  1433. $forum_where = "AND forum_id in (".implode(",", $search_forums).")";
  1434. } else {
  1435. // Hack attempt or error. Return empty search results.
  1436. return $return;
  1437. }
  1438. // Prepare day limit restriction.
  1439. if ($days > 0) {
  1440. $ts = time() - 86400*$days;
  1441. $datestamp_where = "AND datestamp >= $ts";
  1442. } else {
  1443. $datestamp_where = '';
  1444. }
  1445. // We make use of temporary tables for storing intermediate search
  1446. // results. These tables are stored in $tables during processing.
  1447. $tables = array();
  1448. // ----------------------------------------------------------------------
  1449. // Handle search for user_id only.
  1450. // ----------------------------------------------------------------------
  1451. if ($search == '' && $author != '' && $match_type == 'USER_ID')
  1452. {
  1453. $user_id = (int) $author;
  1454. if (empty($user_id)) return $return;
  1455. // Search for messages.
  1456. $where = "user_id = $user_id AND
  1457. status=".PHORUM_STATUS_APPROVED." AND
  1458. moved=0";
  1459. if ($return_threads) $where .= " AND parent_id = 0";
  1460. $sql = "SELECT SQL_CALC_FOUND_ROWS *
  1461. FROM {$PHORUM['message_table']}
  1462. USE KEY(user_messages)
  1463. WHERE $where $forum_where
  1464. ORDER BY message_id DESC
  1465. LIMIT $start, $length";
  1466. $rows = phorum_db_interact(DB_RETURN_ASSOCS, $sql,"message_id");
  1467. // Retrieve the number of found messages.
  1468. $count = phorum_db_interact(
  1469. DB_RETURN_VALUE,
  1470. "SELECT found_rows()"
  1471. );
  1472. // Fill the return data.
  1473. $return = array("count" => $count, "rows" => $rows);
  1474. return $return;
  1475. }
  1476. // ----------------------------------------------------------------------
  1477. // Handle search for message and subject.
  1478. // ----------------------------------------------------------------------
  1479. if ($search != '')
  1480. {
  1481. $match_str = '';
  1482. $tokens = array();
  1483. if ($match_type == "PHRASE")
  1484. {
  1485. $search = str_replace('"', '', $search);
  1486. $match_str = '"'.phorum_db_interact(DB_RETURN_QUOTED, $search).'"';
  1487. }
  1488. else
  1489. {
  1490. // Surround with spaces so matching is easier.
  1491. $search = " $search ";
  1492. // Pull out all grouped terms, e.g. (nano mini).
  1493. $paren_terms = array();
  1494. if (strstr($search, '(')) {
  1495. preg_match_all('/ ([+\-~]*\(.+?\)) /', $search, $m);
  1496. $search = preg_replace('/ [+\-~]*\(.+?\) /', ' ', $search);
  1497. $paren_terms = $m[1];
  1498. }
  1499. // Pull out all the double quoted strings,
  1500. // e.g. '"iMac DV" or -"iMac DV".
  1501. $quoted_terms = array();
  1502. if (strstr( $search, '"')) {
  1503. preg_match_all('/ ([+\-~]*".+?") /', $search, $m);
  1504. $search = preg_replace('/ [+\-~]*".+?" /', ' ', $search);
  1505. $quoted_terms = $m[1];
  1506. }
  1507. // Finally, pull out the rest words in the string.
  1508. $norm_terms = preg_split("/\s+/", $search, 0, PREG_SPLIT_NO_EMPTY);
  1509. // Merge all search terms together.
  1510. $tokens = array_merge($quoted_terms, $paren_terms, $norm_terms);
  1511. }
  1512. // Handle full text message / subject search.
  1513. if ($fulltext_mode)
  1514. {
  1515. // Create a match string based on the parsed query tokens.
  1516. if (count($tokens))
  1517. {
  1518. $match_str = '';
  1519. foreach ($tokens as $term)
  1520. {
  1521. if (!strstr("+-~", substr($term, 0, 1)))
  1522. {
  1523. if (strstr($term, ".") &&
  1524. !preg_match('!^".+"$!', $term) &&
  1525. substr($term, -1) != "*") {
  1526. $term = "\"$term\"";
  1527. }
  1528. if ($match_type == "ALL") {
  1529. $term = "+".$term;
  1530. }
  1531. }
  1532. $match_str .= "$term ";
  1533. }
  1534. $match_str = trim($match_str);
  1535. $match_str = phorum_db_interact(DB_RETURN_QUOTED, $match_str);
  1536. }
  1537. $table_name = $PHORUM['search_table']."_ft_".md5(microtime());
  1538. phorum_db_interact(
  1539. DB_RETURN_RES,
  1540. "CREATE TEMPORARY TABLE $table_name (
  1541. KEY (message_id)
  1542. ) ENGINE=HEAP
  1543. SELECT message_id
  1544. FROM {$PHORUM['search_table']}
  1545. WHERE MATCH (search_text)
  1546. AGAINST ('$match_str' IN BOOLEAN MODE)"
  1547. );
  1548. $tables[] = $table_name;
  1549. }
  1550. // Handle standard message / subject search.
  1551. else
  1552. {
  1553. if (count($tokens))
  1554. {
  1555. $condition = ($match_type == "ALL") ? "AND" : "OR";
  1556. foreach($tokens as $tid => $token) {
  1557. $tokens[$tid] = phorum_db_interact(DB_RETURN_QUOTED, $token);
  1558. }
  1559. $match_str = "search_text LIKE " .
  1560. "('%".implode("%' $condition '%", $tokens)."%')";
  1561. }
  1562. $table_name = $PHORUM['search_table']."_like_".md5(microtime());
  1563. phorum_db_interact(
  1564. DB_RETURN_RES,
  1565. "CREATE TEMPORARY TABLE $table_name (
  1566. KEY (message_id)
  1567. ) ENGINE=HEAP
  1568. SELECT message_id
  1569. FROM {$PHORUM['search_table']}
  1570. WHERE $match_str"
  1571. );
  1572. $tables[] = $table_name;
  1573. }
  1574. }
  1575. // ----------------------------------------------------------------------
  1576. // Handle search for author.
  1577. // ----------------------------------------------------------------------
  1578. if ($author != '')
  1579. {
  1580. $table_name = $PHORUM['search_table']."_author_".md5(microtime());
  1581. // Search either by user_id or by username.
  1582. if ($match_type == "USER_ID") {
  1583. $author = (int) $author;
  1584. $author_where = "user_id = $author";
  1585. } else {
  1586. $author = phorum_db_interact(DB_RETURN_QUOTED, $author);
  1587. $author_where = "author = '$author'";
  1588. }
  1589. phorum_db_interact(
  1590. DB_RETURN_RES,
  1591. "CREATE TEMPORARY TABLE $table_name (
  1592. KEY (message_id)
  1593. ) ENGINE=HEAP
  1594. SELECT message_id
  1595. FROM {$PHORUM["message_table"]}
  1596. WHERE $author_where"
  1597. );
  1598. $tables[] = $table_name;
  1599. }
  1600. // ----------------------------------------------------------------------
  1601. // Gather the results.
  1602. // ----------------------------------------------------------------------
  1603. if (count($tables))
  1604. {
  1605. // If we only have one temporary table, we can use it directly.
  1606. if (count($tables) == 1) {
  1607. $table = array_shift($tables);
  1608. }
  1609. // In case we have multiple temporary tables, we join them together
  1610. // in a new temporary table for retrieving the results.
  1611. else
  1612. {
  1613. $table = $PHORUM['search_table']."_final_".md5(microtime());
  1614. $joined_tables = "";
  1615. $main_table = array_shift($tables);
  1616. foreach ($tables as $tbl) {
  1617. $joined_tables.= "INNER JOIN $tbl USING (message_id)";
  1618. }
  1619. phorum_db_interact(
  1620. DB_RETURN_RES,
  1621. "CREATE TEMPORARY TABLE $table (
  1622. KEY (message_id)
  1623. ) ENGINE=HEAP
  1624. SELECT m.message_id
  1625. FROM $main_table m $joined_tables"
  1626. );
  1627. }
  1628. // When only threads need to be returned, then join the results
  1629. // that we have so far with the message table into a result set
  1630. // that only contains the threads for the results.
  1631. if ($return_threads)
  1632. {
  1633. $threads_table = $PHORUM['search_table']."_final_threads_".md5(microtime());
  1634. phorum_db_interact(
  1635. DB_RETURN_RES,
  1636. "CREATE TEMPORARY TABLE $threads_table (
  1637. KEY (message_id)
  1638. ) ENGINE=HEAP
  1639. SELECT distinct thread AS message_id
  1640. FROM {$PHORUM['message_table']}
  1641. INNER JOIN $table
  1642. USING (message_id)"
  1643. );
  1644. $table = $threads_table;
  1645. }
  1646. // Retrieve the found messages.
  1647. $rows = phorum_db_interact(
  1648. DB_RETURN_ASSOCS,
  1649. "SELECT SQL_CALC_FOUND_ROWS *
  1650. FROM {$PHORUM['message_table']}
  1651. INNER JOIN $table USING (message_id)
  1652. WHERE status=".PHORUM_STATUS_APPROVED."
  1653. $forum_where
  1654. $datestamp_where
  1655. ORDER BY datestamp DESC
  1656. LIMIT $start, $length",
  1657. "message_id"
  1658. );
  1659. // Retrieve the number of found messages.
  1660. $count = phorum_db_interact(
  1661. DB_RETURN_VALUE,
  1662. "SELECT found_rows()"
  1663. );
  1664. // Fill the return data.
  1665. $return = array("count" => $count, "rows" => $rows);
  1666. }
  1667. return $return;
  1668. }
  1669. // }}}
  1670. // {{{ Function: phorum_db_get_neighbour_thread()
  1671. /**
  1672. * Retrieve the closest neighbour thread. What "neighbour" is, depends on the
  1673. * float to top setting. If float to top is enabled, then the
  1674. * modifystamp is used for comparison (so the time at which the last
  1675. * message was posted to a thread). Otherwise, the thread id is used
  1676. * (so the time at which a thread was started).
  1677. *
  1678. * @param integer $key
  1679. * The key value of the message for which the neighbour must be returned.
  1680. * The key value is either the modifystamp (if float to top is enabled)
  1681. * or the thread id.
  1682. *
  1683. * @param string $direction
  1684. * Either "older" or "newer".
  1685. *
  1686. * @return integer
  1687. * The thread id for the requested neigbour thread or 0 (zero) if there
  1688. * is no neighbour available.
  1689. */
  1690. function phorum_db_get_neighbour_thread($key, $direction)
  1691. {
  1692. $PHORUM = $GLOBALS['PHORUM'];
  1693. settype($key, 'int');
  1694. $keyfield = $PHORUM['float_to_top'] ? 'modifystamp' : 'thread';
  1695. switch ($direction) {
  1696. case 'newer': $compare = '>'; $orderdir = 'ASC'; break;
  1697. case 'older': $compare = '<'; $orderdir = 'DESC'; break;
  1698. default:
  1699. trigger_error(
  1700. 'phorum_db_get_neighbour_thread(): ' .
  1701. 'Illegal direction "'.htmlspecialchars($direction).'"',
  1702. E_USER_ERROR
  1703. );
  1704. }
  1705. // If the active Phorum user is not a moderator for the forum, then
  1706. // the neighbour message should be approved.
  1707. $approvedval = '';
  1708. if (!phorum_api_user_check_access(PHORUM_USER_ALLOW_MODERATE_MESSAGES)) {
  1709. $approvedval = 'AND status = '.PHORUM_STATUS_APPROVED;
  1710. }
  1711. // Select the neighbour from the database.
  1712. $thread = phorum_db_interact(
  1713. DB_RETURN_VALUE,
  1714. "SELECT thread
  1715. FROM {$PHORUM['message_table']}
  1716. WHERE forum_id = {$PHORUM['forum_id']} AND
  1717. parent_id = 0
  1718. $approvedval AND
  1719. $keyfield $compare $key
  1720. ORDER BY $keyfield $orderdir
  1721. LIMIT 1"
  1722. );
  1723. return $thread;
  1724. }
  1725. // }}}
  1726. // {{{ Function: phorum_db_get_forums()
  1727. /**
  1728. * Retrieve a list of forums. The forums which are returned can be filtered
  1729. * through the function parameters. Note that only one parameter is
  1730. * effective at a time.
  1731. *
  1732. * @param mixed $forum_ids
  1733. * A single forum_id or an array of forum_ids for which to retrieve the
  1734. * forum data. If this parameter is 0 (zero), then the $parent_id
  1735. * parameter will be checked.
  1736. *
  1737. * @param mixed $parent_id
  1738. * Retrieve the forum data for all forums that have their parent_id set
  1739. * to $parent_id. If this parameter is NULL, then the $vroot parameter
  1740. * will be checked.
  1741. *
  1742. * @param mixed $vroot
  1743. * Retrieve the forum data for all forums that are in the given $vroot.
  1744. * If this parameter is NULL, then the $inherit_id parameter will be
  1745. * checked.
  1746. *
  1747. * @param mixed $inherit_id
  1748. * Retrieve the forum data for all forums that inherit their settings
  1749. * from the forum with id $inherit_id.
  1750. *
  1751. * @return array
  1752. * An array of forums, indexed by forum_id.
  1753. */
  1754. function phorum_db_get_forums($forum_ids = 0, $parent_id = NULL, $vroot = NULL, $inherit_id = NULL)
  1755. {
  1756. $PHORUM = $GLOBALS['PHORUM'];
  1757. phorum_db_sanitize_mixed($forum_ids, 'int');
  1758. if ($parent_id !== NULL) settype($parent_id, 'int');
  1759. if ($vroot !== NULL) settype($vroot, 'int');
  1760. if ($inherit_id !== NULL) settype($inherit_id, 'int');
  1761. // Backward compatibility: previously, -1 was used for $parent_id
  1762. // instead of NULL for indicating "any parent_id".
  1763. if ($parent_id !== NULL && $parent_id == -1) $parent_id = NULL;
  1764. $where = '';
  1765. if (!empty($forum_ids)) {
  1766. if (is_array($forum_ids)) {
  1767. $where .= 'forum_id IN ('.implode(', ', $forum_ids).')';
  1768. } else {
  1769. $where .= "forum_id = $forum_ids";
  1770. }
  1771. } elseif ($inherit_id !== NULL) {
  1772. $where .= "inherit_id = $inherit_id";
  1773. if (!defined('PHORUM_ADMIN')) $where.=' AND active=1';
  1774. } elseif ($parent_id !== NULL) {
  1775. $where .= "parent_id = $parent_id";
  1776. if (!defined('PHORUM_ADMIN')) $where.=' AND active=1';
  1777. } elseif ($vroot !== NULL) {
  1778. $where .= "vroot = $vroot";
  1779. } else {
  1780. $where .= 'forum_id <> 0';
  1781. }
  1782. $forums = phorum_db_interact(
  1783. DB_RETURN_ASSOCS,
  1784. "SELECT *
  1785. FROM {$PHORUM['forums_table']}
  1786. WHERE $where
  1787. ORDER BY display_order ASC, name",
  1788. 'forum_id'
  1789. );
  1790. return $forums;
  1791. }
  1792. // }}}
  1793. // {{{ Function: phorum_db_update_forum_stats()
  1794. /**
  1795. * Update the forums stats. This function will update the thread count,
  1796. * message count, sticky message count and last post timestamp for a forum.
  1797. * The function can either process delta values for the stats (this is
  1798. * the most friendly way of updating) or fill the stats from scratch by
  1799. * querying the database for the correct value.
  1800. *
  1801. * When the forum stats are updated, the cache_version for the forum
  1802. * will be raised by one. This will flag the cache system that cached
  1803. * data for the forum has to be refreshed.
  1804. *
  1805. * @param boolean $refresh
  1806. * If TRUE, the all stats will be filled from scratch by querying
  1807. * the database.
  1808. *
  1809. * @param integer $msg_count_change
  1810. * Delta for the message count or zero to query the value
  1811. * from the database.
  1812. *
  1813. * @param integer $timestamp
  1814. * The post time of the last message or zero to query the value from
  1815. * the database.
  1816. *
  1817. * @param integer $thread_count_change
  1818. * Delta for the thread count or zero to query the value
  1819. * from the database.
  1820. * @param integer $sticky_count_change
  1821. * Delta for the sticky message count or zero to query the value
  1822. * from the database.
  1823. */
  1824. function phorum_db_update_forum_stats($refresh=FALSE, $msg_count_change=0, $timestamp=0, $thread_count_change=0, $sticky_count_change=0)
  1825. {
  1826. $PHORUM = $GLOBALS['PHORUM'];
  1827. settype($refresh, 'bool');
  1828. settype($msg_count_change, 'int');
  1829. settype($timestamp, 'int');
  1830. settype($thread_count_change, 'int');
  1831. settype($sticky_count_change, 'int');
  1832. // Always do a full refresh on small forums.
  1833. if (isset($PHORUM['message_count']) && $PHORUM['message_count']<1000) {
  1834. $refresh = TRUE;
  1835. }
  1836. if ($refresh || empty($msg_count_change)) {
  1837. $message_count = phorum_db_interact(
  1838. DB_RETURN_VALUE,
  1839. "SELECT count(*)
  1840. FROM {$PHORUM['message_table']}
  1841. WHERE forum_id = {$PHORUM['forum_id']} AND
  1842. status = ".PHORUM_STATUS_APPROVED
  1843. );
  1844. } else {
  1845. $message_count = "message_count+$msg_count_change";
  1846. }
  1847. if ($refresh || empty($timestamp)) {
  1848. $last_post_time = phorum_db_interact(
  1849. DB_RETURN_VALUE,
  1850. "SELECT max(modifystamp)
  1851. FROM {$PHORUM['message_table']}
  1852. WHERE status = ".PHORUM_STATUS_APPROVED." AND
  1853. forum_id = {$PHORUM['forum_id']}"
  1854. );
  1855. // In case we're calling this function for an empty forum.
  1856. if ($last_post_time === NULL) {
  1857. $last_post_time = 0;
  1858. }
  1859. } else {
  1860. $last_post_time = phorum_db_interact(
  1861. DB_RETURN_VALUE,
  1862. "SELECT last_post_time
  1863. FROM {$PHORUM['forums_table']}
  1864. WHERE forum_id = {$PHORUM['forum_id']}"
  1865. );
  1866. if ($timestamp > $last_post_time) {
  1867. $last_post_time = $timestamp;
  1868. }
  1869. }
  1870. if ($refresh || empty($thread_count_change)) {
  1871. $thread_count = phorum_db_interact(
  1872. DB_RETURN_VALUE,
  1873. "SELECT count(*)
  1874. FROM {$PHORUM['message_table']}
  1875. WHERE forum_id = {$PHORUM['forum_id']} AND
  1876. parent_id = 0 AND
  1877. status = ".PHORUM_STATUS_APPROVED
  1878. );
  1879. } else {
  1880. $thread_count = "thread_count+$thread_count_change";
  1881. }
  1882. if ($refresh || empty($sticky_count_change)) {
  1883. $sticky_count = phorum_db_interact(
  1884. DB_RETURN_VALUE,
  1885. "SELECT count(*)
  1886. FROM {$PHORUM['message_table']}
  1887. WHERE forum_id = {$PHORUM['forum_id']} AND
  1888. sort = ".PHORUM_SORT_STICKY." AND
  1889. parent_id = 0 AND
  1890. status = ".PHORUM_STATUS_APPROVED
  1891. );
  1892. } else {
  1893. $sticky_count = "sticky_count+$sticky_count_change";
  1894. }
  1895. phorum_db_interact(
  1896. DB_RETURN_RES,
  1897. "UPDATE {$PHORUM['forums_table']}
  1898. SET cache_version = cache_version + 1,
  1899. thread_count = $thread_count,
  1900. message_count = $message_count,
  1901. sticky_count = $sticky_count,
  1902. last_post_time = $last_post_time
  1903. WHERE forum_id = {$PHORUM['forum_id']}",
  1904. NULL,
  1905. DB_MASTERQUERY
  1906. );
  1907. }
  1908. // }}}
  1909. // {{{ Function: phorum_db_move_thread()
  1910. /**
  1911. * Move a thread to another forum.
  1912. *
  1913. * @param integer $thread_id
  1914. * The id of the thread that has to be moved.
  1915. *
  1916. * @param integer
  1917. * The id of the destination forum.
  1918. */
  1919. function phorum_db_move_thread($thread_id, $toforum)
  1920. {
  1921. $PHORUM = $GLOBALS['PHORUM'];
  1922. settype($thread_id, 'int');
  1923. settype($toforum, 'int');
  1924. if ($toforum > 0 && $thread_id > 0)
  1925. {
  1926. // Retrieve the messages from the thread, so we know for which
  1927. // messages we have to update the newflags and search data below.
  1928. $thread_messages = phorum_db_get_messages($thread_id);
  1929. unset($thread_messages['users']);
  1930. // All we have to do to move the thread to a different forum,
  1931. // is update the forum_id for the messages in that thread.
  1932. // Simple, isn't it?
  1933. phorum_db_interact(
  1934. DB_RETURN_RES,
  1935. "UPDATE {$PHORUM['message_table']}
  1936. SET forum_id = $toforum
  1937. WHERE thread = $thread_id",
  1938. NULL,
  1939. DB_MASTERQUERY
  1940. );
  1941. // Update the stats for the source forum.
  1942. phorum_db_update_forum_stats(TRUE);
  1943. // Update the stats for the destination forum.
  1944. $old_id = $GLOBALS['PHORUM']['forum_id'];
  1945. $GLOBALS['PHORUM']['forum_id'] = $toforum;
  1946. phorum_db_update_forum_stats(TRUE);
  1947. $GLOBALS['PHORUM']['forum_id'] = $old_id;
  1948. // Move the newflags and search data to the destination forum.
  1949. /**
  1950. * @todo In the move thread code, there are some flaws. The
  1951. * newflags for the user that is moving the message
  1952. * are used as the source for deciding what flags
  1953. * to delete or move for all other users. This results
  1954. * in strange newflag problems.
  1955. *
  1956. * This main issue here is that the newflags should be
  1957. * handled separately for each user; no updates should be
  1958. * based on the newflags for the active user. The current
  1959. * algorithm will only make sure that the newflags will look
  1960. * correct for that specific user. The problem is that we
  1961. * do not yet have an idea on how to handle this with
  1962. * enough performance.
  1963. */
  1964. // First, gather information for doing the updates.
  1965. $new_newflags = phorum_db_newflag_get_flags($toforum);
  1966. $message_ids = array();
  1967. $delete_ids = array();
  1968. $search_ids = array();
  1969. foreach ($thread_messages as $mid => $data)
  1970. {
  1971. // Gather information for updating the newflags.
  1972. // Moving the newflag is only useful if it is higher than the
  1973. // min_id of the target forum.
  1974. if (!empty($new_newflags['min_id'][$toforum]) &&
  1975. $mid > $new_newflags['min_id'][$toforum]) {
  1976. $message_ids[] = $mid;
  1977. } else {
  1978. // Other newflags can be deleted.
  1979. $delete_ids[] = $mid;
  1980. }
  1981. // gather the information for updating the search table
  1982. $search_ids[] = $mid;
  1983. }
  1984. // Move newflags.
  1985. if (count($message_ids)) {
  1986. phorum_db_newflag_update_forum($message_ids);
  1987. }
  1988. // Update subscriptions.
  1989. if (count($message_ids)) {
  1990. $ids_str = implode(', ',$message_ids);
  1991. phorum_db_interact(
  1992. DB_RETURN_RES,
  1993. "UPDATE {$PHORUM['subscribers_table']}
  1994. SET forum_id = $toforum
  1995. WHERE thread IN ($ids_str)",
  1996. NULL,
  1997. DB_MASTERQUERY
  1998. );
  1999. }
  2000. // Delete newflags.
  2001. if (count($delete_ids)) {
  2002. $ids_str = implode(', ',$delete_ids);
  2003. phorum_db_interact(
  2004. DB_RETURN_RES,
  2005. "DELETE FROM {$PHORUM['user_newflags_table']}
  2006. WHERE message_id IN($ids_str)",
  2007. NULL,
  2008. DB_MASTERQUERY
  2009. );
  2010. }
  2011. // Update search data.
  2012. if (count($search_ids)) {
  2013. $ids_str = implode(', ',$search_ids);
  2014. phorum_db_interact(
  2015. DB_RETURN_RES,
  2016. "UPDATE {$PHORUM['search_table']}
  2017. SET forum_id = $toforum
  2018. WHERE message_id in ($ids_str)",
  2019. NULL,
  2020. DB_MASTERQUERY
  2021. );
  2022. }
  2023. }
  2024. }
  2025. // }}}
  2026. // {{{ Function: phorum_db_close_thread()
  2027. /**
  2028. * Close a thread for posting.
  2029. *
  2030. * @param integer
  2031. * The id of the thread that has to be closed.
  2032. */
  2033. function phorum_db_close_thread($thread_id)
  2034. {
  2035. $PHORUM = $GLOBALS['PHORUM'];
  2036. settype($thread_id, 'int');
  2037. if ($thread_id > 0) {
  2038. phorum_db_interact(
  2039. DB_RETURN_RES,
  2040. "UPDATE {$PHORUM['message_table']}
  2041. SET closed = 1
  2042. WHERE thread = $thread_id",
  2043. NULL,
  2044. DB_MASTERQUERY
  2045. );
  2046. }
  2047. }
  2048. // }}}
  2049. // {{{ Function: phorum_db_reopen_thread()
  2050. /**
  2051. * (Re)open a thread for posting.
  2052. *
  2053. * @param integer
  2054. * The id of the thread that has to be opened.
  2055. */
  2056. function phorum_db_reopen_thread($thread_id)
  2057. {
  2058. $PHORUM = $GLOBALS['PHORUM'];
  2059. settype($thread_id, 'int');
  2060. if ($thread_id > 0) {
  2061. phorum_db_interact(
  2062. DB_RETURN_RES,
  2063. "UPDATE {$PHORUM['message_table']}
  2064. SET closed = 0
  2065. WHERE thread = $thread_id",
  2066. NULL,
  2067. DB_MASTERQUERY
  2068. );
  2069. }
  2070. }
  2071. // }}}
  2072. // {{{ Function: phorum_db_add_forum()
  2073. /**
  2074. * Create a forum.
  2075. *
  2076. * @param array $forum
  2077. * The forum to create. This is an array, which should contain the
  2078. * following fields: name, active, description, template, folder_flag,
  2079. * parent_id, list_length_flat, list_length_threaded, read_length,
  2080. * moderation, threaded_list, threaded_read, float_to_top,
  2081. * display_ip_address, allow_email_notify, language, email_moderators,
  2082. * display_order, edit_post, pub_perms, reg_perms.
  2083. *
  2084. * @return integer
  2085. * The forum_id that was assigned to the new forum.
  2086. */
  2087. function phorum_db_add_forum($forum)
  2088. {
  2089. $PHORUM = $GLOBALS['PHORUM'];
  2090. // check for fields that must be set for mysql strict mode
  2091. if(empty($forum["description"])) $forum["description"] = "";
  2092. if(empty($forum["forum_path"])) $forum["forum_path"] = "";
  2093. if(empty($forum["template_settings"])) $forum["template_settings"] = "";
  2094. $insertfields = array();
  2095. foreach ($forum as $key => $value)
  2096. {
  2097. if (phorum_db_validate_field($key))
  2098. {
  2099. if (is_numeric($value) &&
  2100. !in_array($key,$PHORUM['string_fields_forum'])) {
  2101. $value = (int)$value;
  2102. $insertfields[$key] = $value;
  2103. /**
  2104. * @todo Wouldn't it be better to have this one set to a real
  2105. * NULL value from the script that calls this function?
  2106. * If for some reason somebody wants to use the string
  2107. * 'NULL' for a value (a geek setting up a Phorum
  2108. * probably ;-), then strange things will happen.
  2109. */
  2110. } elseif ($value == 'NULL') {
  2111. $insertfields[$key] = $value;
  2112. } else {
  2113. $value = phorum_db_interact(DB_RETURN_QUOTED, $value);
  2114. $insertfields[$key] = "'$value'";
  2115. }
  2116. }
  2117. }
  2118. $forum_id = phorum_db_interact(
  2119. DB_RETURN_NEWID,
  2120. "INSERT INTO {$PHORUM['forums_table']}
  2121. (".implode(', ', array_keys($insertfields)).")
  2122. VALUES (".implode(', ', $insertfields).")",
  2123. NULL,
  2124. DB_MASTERQUERY
  2125. );
  2126. return $forum_id;
  2127. }
  2128. // }}}
  2129. // {{{ Function: phorum_db_add_message_edit()
  2130. /**
  2131. * Add a message-edit item
  2132. *
  2133. * @param array $edit_data
  2134. * The edit_data to add. This is an array, which should contain the
  2135. * following fields: diff_body, diff_subject, time, message_id and user_id.
  2136. *
  2137. * @return integer
  2138. * The tracking_id that was assigned to that edit
  2139. */
  2140. function phorum_db_add_message_edit($edit_data)
  2141. {
  2142. $PHORUM = $GLOBALS['PHORUM'];
  2143. foreach ($edit_data as $key => $value) {
  2144. if (is_numeric($value)) {
  2145. $edit_data[$key] = (int)$value;
  2146. } elseif (is_array($value)) {
  2147. $value = serialize($value);
  2148. $edit_data[$key] = phorum_db_interact(DB_RETURN_QUOTED, $value);
  2149. } else {
  2150. $edit_data[$key] = phorum_db_interact(DB_RETURN_QUOTED, $value);
  2151. }
  2152. }
  2153. $insertfields = array(
  2154. 'message_id' => $edit_data['message_id'],
  2155. 'user_id' => $edit_data['user_id'],
  2156. 'time' => $edit_data['time'],
  2157. 'diff_body' => "'" . $edit_data['diff_body'] . "'",
  2158. 'diff_subject' => "'" . $edit_data['diff_subject'] . "'",
  2159. );
  2160. // Insert the tracking-entry and get the new tracking_id.
  2161. $tracking_id = phorum_db_interact(
  2162. DB_RETURN_NEWID,
  2163. "INSERT INTO {$PHORUM['message_tracking_table']}
  2164. (".implode(', ', array_keys($insertfields)).")
  2165. VALUES (".implode(', ', $insertfields).")",
  2166. NULL,
  2167. DB_MASTERQUERY
  2168. );
  2169. return $tracking_id;
  2170. }
  2171. // }}}
  2172. // {{{ Function: phorum_db_get_message_edits()
  2173. /**
  2174. * Retrieve a list of message-edits for a message
  2175. *
  2176. * @param integer $message_id
  2177. * The message id for which to retrieve the edits.
  2178. *
  2179. * @return array
  2180. * An array of message edits, indexed by track_id. The array elements
  2181. * are arrays containing the fields: user_id, time, diff_body
  2182. * and diff_subject.
  2183. */
  2184. function phorum_db_get_message_edits($message_id)
  2185. {
  2186. $PHORUM = $GLOBALS['PHORUM'];
  2187. settype($message_id, 'int');
  2188. // Select the message files from the database.
  2189. $edits = phorum_db_interact(
  2190. DB_RETURN_ASSOCS,
  2191. "SELECT user_id,
  2192. time,
  2193. diff_body,
  2194. diff_subject,
  2195. track_id
  2196. FROM {$PHORUM['message_tracking_table']}
  2197. WHERE message_id = $message_id
  2198. ORDER BY track_id ASC",
  2199. 'track_id'
  2200. );
  2201. foreach ($edits as $id => $edit)
  2202. {
  2203. // Unpack the message meta data.
  2204. $edits[$id]['diff_body'] = empty($edit['diff_body'])
  2205. ? array()
  2206. : unserialize($edit['diff_body']);
  2207. // Unpack the message meta data.
  2208. $edits[$id]['diff_subject'] = empty($edit['diff_subject'])
  2209. ? array()
  2210. : unserialize($edit['diff_subject']);
  2211. }
  2212. return $edits;
  2213. }
  2214. // }}}
  2215. // {{{ Function: phorum_db_drop_forum()
  2216. /**
  2217. * Drop a forum and all of its messages.
  2218. *
  2219. * @param integer $forum_id
  2220. * The id of the forum to drop.
  2221. */
  2222. function phorum_db_drop_forum($forum_id)
  2223. {
  2224. $PHORUM = $GLOBALS['PHORUM'];
  2225. settype($forum_id, 'int');
  2226. // These are the tables that hold forum related data.
  2227. $tables = array (
  2228. $PHORUM['message_table'],
  2229. $PHORUM['user_permissions_table'],
  2230. $PHORUM['user_newflags_table'],
  2231. $PHORUM['subscribers_table'],
  2232. $PHORUM['forum_group_xref_table'],
  2233. $PHORUM['forums_table'],
  2234. $PHORUM['banlist_table'],
  2235. $PHORUM['search_table']
  2236. );
  2237. // Delete the data for the $forum_id from all those tables.
  2238. foreach ($tables as $table) {
  2239. phorum_db_interact(
  2240. DB_RETURN_RES,
  2241. "DELETE FROM $table
  2242. WHERE forum_id = $forum_id",
  2243. NULL,
  2244. DB_MASTERQUERY
  2245. );
  2246. }
  2247. // Collect all orphin message attachment files from the database.
  2248. // These are all messages that are linked to a message, but for which
  2249. // the message_id does not exist in the message table (anymore).
  2250. // This might catch some more messages than only the ones for the
  2251. // deleted forum alone. That should never be a problem.
  2252. $files = phorum_db_interact(
  2253. DB_RETURN_ROWS,
  2254. "SELECT file_id
  2255. FROM {$PHORUM['files_table']}
  2256. LEFT JOIN {$PHORUM['message_table']}
  2257. USING (message_id)
  2258. WHERE {$PHORUM['files_table']}.message_id > 0 AND
  2259. link = '" . PHORUM_LINK_MESSAGE . "' AND
  2260. {$PHORUM['message_table']}.message_id is NULL",
  2261. 0 // keyfield 0 is the file_id
  2262. );
  2263. // Delete all orphan message attachment files.
  2264. if (!empty($files)) {
  2265. phorum_db_interact(
  2266. DB_RETURN_RES,
  2267. "DELETE FROM {$PHORUM['files_table']}
  2268. WHERE file_id IN (".implode(",", array_keys($files)).")",
  2269. NULL,
  2270. DB_MASTERQUERY
  2271. );
  2272. }
  2273. }
  2274. // }}}
  2275. // {{{ Function: phorum_db_drop_folder()
  2276. /**
  2277. * Drop a forum folder. If the folder contains child forums or folders,
  2278. * then the parent_id for those will be updated to point to the parent
  2279. * of the folder that is being dropped.
  2280. *
  2281. * @param integer $forum_id
  2282. * The id of the folder to drop.
  2283. */
  2284. function phorum_db_drop_folder($forum_id)
  2285. {
  2286. $PHORUM = $GLOBALS['PHORUM'];
  2287. settype($forum_id, 'int');
  2288. // See if the $forum_id is really a folder and find its
  2289. // parent_id, which we can use to reattach children of the folder.
  2290. $new_parent_id = phorum_db_interact(
  2291. DB_RETURN_VALUE,
  2292. "SELECT parent_id
  2293. FROM {$PHORUM['forums_table']}
  2294. WHERE forum_id = $forum_id AND
  2295. folder_flag = 1"
  2296. );
  2297. if ($new_parent_id === NULL) trigger_error(
  2298. "phorum_db_drop_folder(): id $forum_id not found or not a folder",
  2299. E_USER_ERROR
  2300. );
  2301. // Start with reattaching the folder's children to the new parent.
  2302. phorum_db_interact(
  2303. DB_RETURN_RES,
  2304. "UPDATE {$PHORUM['forums_table']}
  2305. SET parent_id = $new_parent_id
  2306. WHERE parent_id = $forum_id",
  2307. NULL,
  2308. DB_MASTERQUERY
  2309. );
  2310. // Now, drop the folder.
  2311. phorum_db_interact(
  2312. DB_RETURN_RES,
  2313. "DELETE FROM {$PHORUM['forums_table']}
  2314. WHERE forum_id = $forum_id",
  2315. NULL,
  2316. DB_MASTERQUERY
  2317. );
  2318. }
  2319. // }}}
  2320. // {{{ Function: phorum_db_update_forum()
  2321. /**
  2322. * Update the settings for one or more forums.
  2323. *
  2324. * @param array $forum
  2325. * The forum to update. This is an array, which should contain at least
  2326. * the field "forum_id" to indicate what forum to update. Next to that,
  2327. * one or more of the other fields from phorum_db_add_forum() can be
  2328. * used to describe changed values. The "forum_id" field can also
  2329. * contain an array of forum_ids. By using that, the settings can be
  2330. * updated for all the forum_ids at once.
  2331. *
  2332. * @return boolean
  2333. * True if all settings were stored successfully. This function will
  2334. * always return TRUE, so we could do without a return value. The
  2335. * return value is here for backward compatibility.
  2336. */
  2337. function phorum_db_update_forum($forum)
  2338. {
  2339. $PHORUM = $GLOBALS['PHORUM'];
  2340. // Check if the forum_id is set.
  2341. if (!isset($forum['forum_id']) || empty($forum['forum_id'])) trigger_error(
  2342. 'phorum_db_update_forum(): $forum["forum_id"] cannot be empty',
  2343. E_USER_ERROR
  2344. );
  2345. phorum_db_sanitize_mixed($forum['forum_id'], 'int');
  2346. // See what forum(s) to update.
  2347. if (is_array($forum['forum_id'])) {
  2348. $forumwhere = 'forum_id IN ('.implode(', ',$forum['forum_id']).')';
  2349. } else {
  2350. $forumwhere = 'forum_id = ' . $forum['forum_id'];
  2351. }
  2352. unset($forum['forum_id']);
  2353. // Prepare the SQL code for updating the fields.
  2354. $fields = array();
  2355. foreach ($forum as $key => $value)
  2356. {
  2357. if (phorum_db_validate_field($key))
  2358. {
  2359. if ($key == 'forum_path') {
  2360. $value = serialize($value);
  2361. $value = phorum_db_interact(DB_RETURN_QUOTED, $value);
  2362. $fields[] = "$key = '$value'";
  2363. } elseif (is_numeric($value) &&
  2364. !in_array($key,$PHORUM['string_fields_forum'])) {
  2365. $value = (int)$value;
  2366. $fields[] = "$key = $value";
  2367. } elseif ($value == 'NULL') {
  2368. $fields[] = "$key = $value";
  2369. } else {
  2370. $value = phorum_db_interact(DB_RETURN_QUOTED, $value);
  2371. $fields[] = "$key = '$value'";
  2372. }
  2373. }
  2374. }
  2375. // Run the update, if there are fields to update.
  2376. if (count($fields)) {
  2377. phorum_db_interact(
  2378. DB_RETURN_RES,
  2379. "UPDATE {$PHORUM['forums_table']}
  2380. SET " .implode(', ', $fields) . "
  2381. WHERE $forumwhere",
  2382. NULL,
  2383. DB_MASTERQUERY
  2384. );
  2385. }
  2386. return TRUE;
  2387. }
  2388. // }}}
  2389. // {{{ Function: phorum_db_get_groups()
  2390. /**
  2391. * Retrieve all groups or one specific group.
  2392. *
  2393. * @param mixed $group_id
  2394. * A single group id or an array of group ids for which to retrieve
  2395. * the group data. If this parameter is 0 (zero), then all groups will
  2396. * be returned.
  2397. *
  2398. * @param boolean $sorted
  2399. * If this parameter has a true value, then the list of groups will
  2400. * be sorted by the group name field.
  2401. *
  2402. * @return array
  2403. * An array of groups, indexed by group_id.
  2404. */
  2405. function phorum_db_get_groups($group_id = 0, $sorted = FALSE)
  2406. {
  2407. $PHORUM = $GLOBALS['PHORUM'];
  2408. phorum_db_sanitize_mixed($group_id,"int");
  2409. if(is_array($group_id) && count($group_id)) {
  2410. $group_str=implode(',',$group_id);
  2411. $group_where=" where group_id IN($group_str)";
  2412. } elseif(!is_array($group_id) && $group_id!=0) {
  2413. $group_where=" where group_id=$group_id";
  2414. } else {
  2415. $group_where="";
  2416. }
  2417. // Retrieve the group(s) from the database.
  2418. $groups = phorum_db_interact(
  2419. DB_RETURN_ASSOCS,
  2420. "SELECT *
  2421. FROM {$PHORUM['groups_table']}
  2422. $group_where",
  2423. 'group_id'
  2424. );
  2425. // Retrieve the group permissions from the database.
  2426. $perms = phorum_db_interact(
  2427. DB_RETURN_ASSOCS,
  2428. "SELECT *
  2429. FROM {$PHORUM['forum_group_xref_table']}
  2430. $group_where"
  2431. );
  2432. // Add the permissions to the group(s).
  2433. foreach ($groups as $id => $group) $groups[$id]['permissions'] = array();
  2434. foreach ($perms as $perm)
  2435. {
  2436. // Little safety net against orphin records (shouldn't happen).
  2437. if (!isset($groups[$perm['group_id']])) continue;
  2438. $groups[$perm['group_id']]['permissions'][$perm['forum_id']]
  2439. = $perm['permission'];
  2440. }
  2441. // Sort the list by group name.
  2442. if ($sorted) {
  2443. uasort($groups, 'phorum_db_sort_groups');
  2444. }
  2445. return $groups;
  2446. }
  2447. function phorum_db_sort_groups($a,$b) {
  2448. return strcasecmp($a["name"], $b["name"]);
  2449. }
  2450. // }}}
  2451. // {{{ Function: phorum_db_get_group_members()
  2452. /**
  2453. * Retrieve a list of members for a group or for a list of groups.
  2454. *
  2455. * If the member list for a list of groups is requested, any member matching
  2456. * the specified status in any of the groups will be included in the return
  2457. * array. There will be no group info in the return array however, so this
  2458. * function cannot be used to retrieve a full group to member mapping. This
  2459. * specific functionality is used from the Phorum scripts to see if there are
  2460. * unapproved group members in any of the forums for which the active user
  2461. * can moderate the group members.
  2462. *
  2463. * @param mixed $group_id
  2464. * A single group_id or an array of group_ids, for which to retrieve
  2465. * the members.
  2466. *
  2467. * @param integer $status
  2468. * A specific member status to look for. Defaults to all.
  2469. * Possible statuses are:
  2470. * - PHORUM_USER_GROUP_SUSPENDED: (temporarily) deactivated
  2471. * - PHORUM_USER_GROUP_UNAPPROVED: on hold, not yet approved
  2472. * - PHORUM_USER_GROUP_APPROVED: active in the group
  2473. * - PHORUM_USER_GROUP_MODERATOR: active + member moderator
  2474. *
  2475. * @return array $members
  2476. * An array containing members for the specified group(s). The array
  2477. * contains a simple mapping from user_id to group permission. Note
  2478. * that the permission is only useful in case a single group was
  2479. * requested (see the function description).
  2480. */
  2481. function phorum_db_get_group_members($group_id, $status = NULL)
  2482. {
  2483. $PHORUM = $GLOBALS['PHORUM'];
  2484. phorum_db_sanitize_mixed($group_id, 'int');
  2485. if ($status !== NULL) settype($status, 'int');
  2486. if (is_array($group_id)) {
  2487. $group_where = 'AND group_id IN (' . implode(', ', $group_id) . ')';
  2488. } else {
  2489. $group_where = "AND group_id = $group_id";
  2490. }
  2491. if ($status !== NULL) {
  2492. $status_where = "AND xref.status = $status";
  2493. } else {
  2494. $status_where = '';
  2495. }
  2496. // This join is only here so that the list of members comes out sorted.
  2497. // If phorum_db_user_get() sorts results itself, this join can go away.
  2498. $members = phorum_db_interact(
  2499. DB_RETURN_ROWS,
  2500. "SELECT xref.user_id AS user_id,
  2501. xref.status AS status
  2502. FROM {$PHORUM['user_table']} AS users,
  2503. {$PHORUM['user_group_xref_table']} AS xref
  2504. WHERE users.user_id = xref.user_id
  2505. $group_where
  2506. $status_where
  2507. ORDER BY username ASC",
  2508. 0
  2509. );
  2510. // The records are full rows, but we want a user_id -> status mapping.
  2511. foreach ($members as $id => $member) $members[$id] = $member[1];
  2512. return $members;
  2513. }
  2514. // }}}
  2515. // {{{ Function: phorum_db_add_group()
  2516. /**
  2517. * Add a group. This will merely create the group in the database. For
  2518. * changing settings for the group, the function phorum_db_update_group()
  2519. * has to be used.
  2520. *
  2521. * @param string $group_name
  2522. * The name to assign to the group.
  2523. *
  2524. * @param integer $group_id
  2525. * The group id to assign to the group or 0 (zero) to assign a new
  2526. * group id. Assigning a specific group id is and should only be
  2527. * used by conversion scripts.
  2528. *
  2529. * @return integer
  2530. * The group id of the newly created group.
  2531. */
  2532. function phorum_db_add_group($group_name, $group_id=0)
  2533. {
  2534. $PHORUM = $GLOBALS['PHORUM'];
  2535. settype($group_id, 'int');
  2536. $group_name = phorum_db_interact(DB_RETURN_QUOTED, $group_name);
  2537. $fields = $group_id > 0 ? 'name, group_id' : 'name';
  2538. $values = $group_id > 0 ? "'$group_name', $group_id" : "'$group_name'";
  2539. $group_id = phorum_db_interact(
  2540. DB_RETURN_NEWID,
  2541. "INSERT INTO {$PHORUM['groups_table']}
  2542. ($fields)
  2543. VALUES ($values)",
  2544. NULL,
  2545. DB_MASTERQUERY
  2546. );
  2547. return $group_id;
  2548. }
  2549. // }}}
  2550. // {{{ Function: phorum_db_update_group()
  2551. /**
  2552. * Update the settings for a group.
  2553. *
  2554. * @param array $group
  2555. * The group to update. This is an array, which should contain at least
  2556. * the field "group_id" to indicate what group to update. Next to that,
  2557. * one or more of the following fields can be used:
  2558. * - name:
  2559. * The name for the group.
  2560. * - open:
  2561. * This field determines how new members are added to the group.
  2562. * Available options are:
  2563. * - PHORUM_GROUP_CLOSED:
  2564. * Only the administrator can add users to this group.
  2565. * - PHORUM_GROUP_OPEN:
  2566. * The group is open for membership requests by users and
  2567. * membership is assigned on request immediately.
  2568. * - PHORUM_GROUP_REQUIRE_APPROVAL:
  2569. * The group is open for membership requests by users,
  2570. * but membership has to be approved by an administrator or
  2571. * a user moderator for the group.
  2572. * - permissions:
  2573. * An array containing forum permissions for the group
  2574. * (key = forum_id and value = permission value).
  2575. *
  2576. * @return boolean
  2577. * True if all settings were stored successfully. This function will
  2578. * always return TRUE, so we could do without a return value.
  2579. * The return value is here for backward compatibility.
  2580. */
  2581. function phorum_db_update_group($group)
  2582. {
  2583. $PHORUM = $GLOBALS['PHORUM'];
  2584. // Check if the group_id is set.
  2585. if (!isset($group['group_id']) || empty($group['group_id'])) trigger_error(
  2586. 'phorum_db_update_group(): $group["group_id"] cannot be empty',
  2587. E_USER_ERROR
  2588. );
  2589. settype($group['group_id'], 'int');
  2590. $group_where = 'group_id = ' . $group['group_id'];
  2591. // See what group fields we have to update.
  2592. $fields = array();
  2593. if (isset($group['name'])) {
  2594. $fields[] = "name = '" .
  2595. phorum_db_interact(DB_RETURN_QUOTED, $group['name']) ."'";
  2596. }
  2597. if (isset($group['open'])) {
  2598. $fields[] = 'open = ' . (int)$group['open'];
  2599. }
  2600. // Update group fields.
  2601. if (count($fields)) {
  2602. phorum_db_interact(
  2603. DB_RETURN_RES,
  2604. "UPDATE {$PHORUM['groups_table']}
  2605. SET ". implode(', ', $fields) . "
  2606. WHERE $group_where",
  2607. NULL,
  2608. DB_MASTERQUERY
  2609. );
  2610. }
  2611. // Update the group permissions if requested.
  2612. if (isset($group['permissions']))
  2613. {
  2614. // First, all existing forum permissions for the group are deleted.
  2615. phorum_db_interact(
  2616. DB_RETURN_RES,
  2617. "DELETE FROM {$PHORUM['forum_group_xref_table']}
  2618. WHERE $group_where",
  2619. NULL,
  2620. DB_MASTERQUERY
  2621. );
  2622. // Second, all new permissions are inserted.
  2623. foreach ($group['permissions'] as $forum_id => $permission)
  2624. {
  2625. settype($forum_id, 'int');
  2626. settype($permission, 'int');
  2627. phorum_db_interact(
  2628. DB_RETURN_RES,
  2629. "INSERT INTO {$PHORUM['forum_group_xref_table']}
  2630. (group_id, permission, forum_id)
  2631. VALUES ({$group['group_id']}, $permission, $forum_id)",
  2632. NULL,
  2633. DB_MASTERQUERY
  2634. );
  2635. }
  2636. }
  2637. return TRUE;
  2638. }
  2639. // }}}
  2640. // {{{ Function: phorum_db_delete_group()
  2641. /**
  2642. * Delete a group.
  2643. *
  2644. * @param integer $group_id
  2645. * The id of the group to delete.
  2646. */
  2647. function phorum_db_delete_group($group_id)
  2648. {
  2649. $PHORUM = $GLOBALS['PHORUM'];
  2650. settype($group_id, 'int');
  2651. // These are the tables that hold group related data.
  2652. $tables = array (
  2653. $PHORUM['groups_table'],
  2654. $PHORUM['user_group_xref_table'],
  2655. $PHORUM['forum_group_xref_table']
  2656. );
  2657. // Delete the data for the $group_id from all those tables.
  2658. foreach ($tables as $table) {
  2659. phorum_db_interact(
  2660. DB_RETURN_RES,
  2661. "DELETE FROM $table
  2662. WHERE group_id = $group_id",
  2663. NULL,
  2664. DB_MASTERQUERY
  2665. );
  2666. }
  2667. }
  2668. // }}}
  2669. // {{{ Function: phorum_db_user_get_moderators()
  2670. /**
  2671. * Retrieve a list of moderators for a particular forum.
  2672. *
  2673. * @param integer $forum_id
  2674. * The forum for which to retrieve the moderators.
  2675. *
  2676. * @param boolean $exclude_admin
  2677. * If this parameter has a true value, then the admin users are kept
  2678. * out of the list.
  2679. *
  2680. * @param boolean $for_email
  2681. * If this parameter has a true value, then a list of moderators is
  2682. * created for sending moderator mail messages. Moderators which
  2683. * have disabled the moderation_email option will be excluded from
  2684. * the list in this case.
  2685. *
  2686. * @return array
  2687. * An array of moderators. The keys are user_ids and
  2688. * the values are email addresses.
  2689. */
  2690. function phorum_db_user_get_moderators($forum_id, $exclude_admin=FALSE, $for_email=FALSE)
  2691. {
  2692. $PHORUM = $GLOBALS['PHORUM'];
  2693. settype($forum_id, 'int');
  2694. settype($exclude_admin, 'bool');
  2695. settype($for_email, 'bool');
  2696. // Exclude admins from the list, if requested.
  2697. $or_where_admin = $exclude_admin ? '' : 'OR user.admin=1';
  2698. // If we are gathering email addresses for mailing the moderators,
  2699. // then honour the moderation_email setting for the user.
  2700. $where_moderation_mail = $for_email ? 'AND user.moderation_email = 1' : '';
  2701. $moderators = array();
  2702. // Look up moderators which are configured through user permissions.
  2703. $usermods = phorum_db_interact(
  2704. DB_RETURN_ROWS,
  2705. "SELECT DISTINCT user.user_id AS user_id,
  2706. user.email AS email
  2707. FROM {$PHORUM['user_table']} AS user
  2708. LEFT JOIN {$PHORUM['user_permissions_table']} AS perm
  2709. ON perm.user_id = user.user_id
  2710. WHERE ((perm.permission>=".PHORUM_USER_ALLOW_MODERATE_MESSAGES." AND
  2711. (perm.permission & ".PHORUM_USER_ALLOW_MODERATE_MESSAGES.">0)
  2712. AND perm.forum_id = $forum_id) $or_where_admin)
  2713. $where_moderation_mail"
  2714. );
  2715. // Add them to the moderator list.
  2716. foreach ($usermods as $mod) $moderators[$mod[0]] = $mod[1];
  2717. unset($usermods);
  2718. // Look up moderators which are configured through group permissions.
  2719. $groupmods = phorum_db_interact(
  2720. DB_RETURN_ROWS,
  2721. "SELECT DISTINCT user.user_id AS user_id,
  2722. user.email AS email
  2723. FROM {$PHORUM['user_table']} AS user,
  2724. {$PHORUM['groups_table']} AS groups,
  2725. {$PHORUM['user_group_xref_table']} AS usergroup,
  2726. {$PHORUM['forum_group_xref_table']} AS forumgroup
  2727. WHERE user.user_id = usergroup.user_id AND
  2728. usergroup.group_id = groups.group_id AND
  2729. groups.group_id = forumgroup.group_id AND
  2730. forum_id = $forum_id AND
  2731. permission & ".PHORUM_USER_ALLOW_MODERATE_MESSAGES." > 0 AND
  2732. usergroup.status >= ".PHORUM_USER_GROUP_APPROVED."
  2733. $where_moderation_mail"
  2734. );
  2735. // Add them to the moderator list.
  2736. foreach ($groupmods as $mod) $moderators[$mod[0]] = $mod[1];
  2737. unset($groupmods);
  2738. return $moderators;
  2739. }
  2740. // }}}
  2741. // {{{ Function: phorum_db_user_count()
  2742. /**
  2743. * Count the total number of users in the Phorum system.
  2744. *
  2745. * @return integer
  2746. * The number of users.
  2747. */
  2748. function phorum_db_user_count()
  2749. {
  2750. $PHORUM = $GLOBALS["PHORUM"];
  2751. return phorum_db_interact(
  2752. DB_RETURN_VALUE,
  2753. "SELECT count(*)
  2754. FROM {$PHORUM['user_table']}"
  2755. );
  2756. }
  2757. // }}}
  2758. // {{{ Function: phorum_db_user_get_all()
  2759. /**
  2760. * Retrieve all users from the database.
  2761. *
  2762. * This function returns a query resource handle. This handle can be used
  2763. * to retrieve the users from the database one-by-one, by calling the
  2764. * phorum_db_fetch_row() function.
  2765. *
  2766. * @return resource
  2767. * A query resource handle is returned. This handle can be used
  2768. * to retrieve the users from the database one-by-one, by
  2769. * calling the phorum_db_fetch_row() function.
  2770. *
  2771. * @todo This function might be as well replaced with user search and get
  2772. * functionality from the user API, if search is extended with an
  2773. * option to return a resource handle.
  2774. */
  2775. function phorum_db_user_get_all($offset = 0, $length = 0)
  2776. {
  2777. $PHORUM = $GLOBALS["PHORUM"];
  2778. settype($offset, 'int');
  2779. settype($length, 'int');
  2780. $limit = '';
  2781. if ($length > 0) {
  2782. $limit = "LIMIT $offset, $length";
  2783. }
  2784. return phorum_db_interact(
  2785. DB_RETURN_RES,
  2786. "SELECT *
  2787. FROM {$PHORUM['user_table']}
  2788. $limit"
  2789. );
  2790. }
  2791. // }}}
  2792. // {{{ Function: phorum_db_user_get()
  2793. /**
  2794. * Retrieve one or more users.
  2795. *
  2796. * @param mixed $user_id
  2797. * The user_id or an array of user_ids for which to
  2798. * retrieve the user data.
  2799. *
  2800. * @param boolean $detailed
  2801. * If this parameter has a true value, then the user's
  2802. * permissions and groups are included in the return data.
  2803. *
  2804. * @param boolean $write_server
  2805. * This value can be set to true to specify that the user should be
  2806. * retrieved from the master (aka write-server) in case replication
  2807. * is used
  2808. *
  2809. * @return mixed
  2810. * If $user_id is a single user_id, then either a single user or NULL
  2811. * (in case the user_id was not found in the database) is returned.
  2812. * If $user_id is an array of user_ids, then an array of users is
  2813. * returned, indexed by user_id. For user_ids that cannot be found,
  2814. * there will be no array element at all.
  2815. */
  2816. function phorum_db_user_get($user_id, $detailed = FALSE, $write_server = FALSE, $raw_data = FALSE)
  2817. {
  2818. $PHORUM = $GLOBALS['PHORUM'];
  2819. phorum_db_sanitize_mixed($user_id, 'int');
  2820. if (is_array($user_id)) {
  2821. if (count($user_id)) {
  2822. $user_where = 'user_id IN ('.implode(', ', $user_id).')';
  2823. } else {
  2824. return array();
  2825. }
  2826. } else {
  2827. $user_where = "user_id = $user_id";
  2828. }
  2829. if($write_server) {
  2830. $flags = DB_MASTERQUERY;
  2831. } else {
  2832. $flags = 0;
  2833. }
  2834. // Retrieve the requested user(s) from the database.
  2835. $users = phorum_db_interact(
  2836. DB_RETURN_ASSOCS,
  2837. "SELECT *
  2838. FROM {$PHORUM['user_table']}
  2839. WHERE $user_where",
  2840. 'user_id',
  2841. $flags
  2842. );
  2843. // No users found?
  2844. if (count($users) == 0) return array();
  2845. // Unpack the settings_data.
  2846. foreach ($users as $id => $user) {
  2847. $users[$id]['settings_data'] = empty($user['settings_data'])
  2848. ? array()
  2849. : unserialize($user['settings_data']);
  2850. }
  2851. // Retrieve detailed group and permission information for the user(s).
  2852. if ($detailed)
  2853. {
  2854. // Retrieve forum user permissions for the requested users.
  2855. $forum_permissions = phorum_db_interact(
  2856. DB_RETURN_ROWS,
  2857. "SELECT user_id,
  2858. forum_id,
  2859. permission
  2860. FROM {$PHORUM['user_permissions_table']}
  2861. WHERE $user_where",
  2862. NULL,
  2863. $flags
  2864. );
  2865. // Add forum user permissions to the users.
  2866. foreach ($forum_permissions as $perm) {
  2867. $users[$perm[0]]['forum_permissions'][$perm[1]] = $perm[2];
  2868. }
  2869. // Retrieve forum group permissions and groups for the requested users.
  2870. // "status >= ..." is used to retrieve both approved group users
  2871. // and group moderators.
  2872. $group_permissions = phorum_db_interact(
  2873. DB_RETURN_ROWS,
  2874. "SELECT user_id,
  2875. {$PHORUM['user_group_xref_table']}.group_id AS group_id,
  2876. forum_id,
  2877. permission
  2878. FROM {$PHORUM['user_group_xref_table']}
  2879. LEFT JOIN {$PHORUM['forum_group_xref_table']}
  2880. USING (group_id)
  2881. WHERE $user_where AND
  2882. status >= ".PHORUM_USER_GROUP_APPROVED,
  2883. NULL,
  2884. $flags
  2885. );
  2886. // Add groups and forum group permissions to the users.
  2887. foreach ($group_permissions as $perm)
  2888. {
  2889. // Skip permissions for users which are not in our
  2890. // $users array. This should not happen, but it could
  2891. // happen in case some orphin group permissions are
  2892. // lingering in the database.
  2893. if (!isset($users[$perm[0]])) continue;
  2894. // Add the group_id to the user data.
  2895. $users[$perm[0]]['groups'][$perm[1]] = $perm[1];
  2896. // Are we handling a group permissions record?
  2897. if (!empty($perm[2]))
  2898. {
  2899. // Initialize group permissions for the forum_id in the
  2900. // user data.
  2901. if (!isset($users[$perm[0]]['group_permissions'][$perm[2]])) {
  2902. $users[$perm[0]]['group_permissions'][$perm[2]] = 0;
  2903. }
  2904. // Merge the group permission by logical OR-ing the permission
  2905. // with the permissions that we've got so far for the forum_id
  2906. // in the user data.
  2907. $users[$perm[0]]['group_permissions'][$perm[2]] |= $perm[3];
  2908. }
  2909. }
  2910. }
  2911. // Retrieve custom user profile fields for the requested users.
  2912. $custom_fields = phorum_db_interact(
  2913. DB_RETURN_ASSOCS,
  2914. "SELECT *
  2915. FROM {$PHORUM['user_custom_fields_table']}
  2916. WHERE $user_where",
  2917. NULL,
  2918. $flags
  2919. );
  2920. // Add custom user profile fields to the users.
  2921. foreach ($custom_fields as $fld)
  2922. {
  2923. // Skip profile fields for users which are not in our
  2924. // $users array. This should not happen, but it could
  2925. // happen in case some orphin custom user fields
  2926. // are lingering in the database.
  2927. if (!isset($users[$fld['user_id']])) continue;
  2928. // Skip unknown custom profile fields.
  2929. if (! isset($PHORUM['PROFILE_FIELDS'][$fld['type']])) continue;
  2930. // Fetch the name for the custom profile field.
  2931. $name = $PHORUM['PROFILE_FIELDS'][$fld['type']]['name'];
  2932. // For "html_disabled" fields, the data is XSS protected by
  2933. // replacing special HTML characters with their HTML entities.
  2934. if ($PHORUM['PROFILE_FIELDS'][$fld['type']]['html_disabled'] && $raw_data === FALSE) {
  2935. $users[$fld['user_id']][$name] = htmlspecialchars($fld['data']);
  2936. continue;
  2937. }
  2938. // Other fields can either contain raw values or serialized
  2939. // arrays. For serialized arrays, the field data is prefixed with
  2940. // a magic "P_SER:" (Phorum serialized) marker.
  2941. if (substr($fld['data'],0,6) == 'P_SER:') {
  2942. $users[$fld['user_id']][$name]=unserialize(substr($fld['data'],6));
  2943. continue;
  2944. }
  2945. // The rest of the fields contain raw field data.
  2946. $users[$fld['user_id']][$name] = $fld['data'];
  2947. }
  2948. if (is_array($user_id)) {
  2949. return $users;
  2950. } else {
  2951. return isset($users[$user_id]) ? $users[$user_id] : NULL;
  2952. }
  2953. }
  2954. // }}}
  2955. // {{{ Function: phorum_db_user_get_fields()
  2956. /**
  2957. * Retrieve the data for a couple of user table fields for one or more users.
  2958. *
  2959. * @param mixed $user_id
  2960. * The user_id or an array of user_ids for which to retrieve
  2961. * the field data.
  2962. *
  2963. * @param mixed $fields
  2964. * The field or an array of fields for which to retrieve the data.
  2965. *
  2966. * @return array $users
  2967. * An array of users (no matter what type of variable $user_id is),
  2968. * indexed by user_id. For user_ids that cannot be found, there
  2969. * will be no array element at all.
  2970. */
  2971. function phorum_db_user_get_fields($user_id, $fields)
  2972. {
  2973. $PHORUM = $GLOBALS['PHORUM'];
  2974. phorum_db_sanitize_mixed($user_id, 'int');
  2975. if (is_array($user_id)) {
  2976. if (count($user_id)) {
  2977. $user_where = 'user_id IN ('.implode(', ', $user_id).')';
  2978. } else {
  2979. return array();
  2980. }
  2981. } else {
  2982. $user_where = "user_id = $user_id";
  2983. }
  2984. if (!is_array($fields)) {
  2985. $fields = array($fields);
  2986. }
  2987. foreach ($fields as $key => $field) {
  2988. if (!phorum_db_validate_field($field)) {
  2989. unset($fields[$key]);
  2990. }
  2991. }
  2992. $users = phorum_db_interact(
  2993. DB_RETURN_ASSOCS,
  2994. "SELECT user_id, ".implode(', ', $fields)."
  2995. FROM {$PHORUM['user_table']}
  2996. WHERE $user_where",
  2997. 'user_id'
  2998. );
  2999. return $users;
  3000. }
  3001. // }}}
  3002. // {{{ Function: phorum_db_user_get_list()
  3003. /**
  3004. * Retrieve a list of all users for a given type.
  3005. *
  3006. * @param integer $type
  3007. * The type of users to retrieve. Available options are:
  3008. * - 0 = all users
  3009. * - 1 = all active users
  3010. * - 2 = all inactive users
  3011. *
  3012. * @return array $users
  3013. * An array of users, indexed by user_id. The values are arrays
  3014. * containing the fields "user_id", "username" and "display_name".
  3015. *
  3016. * @todo This function might be as well replaced with user search and get
  3017. * functionality from the user API,
  3018. */
  3019. function phorum_db_user_get_list($type = 0)
  3020. {
  3021. $PHORUM = $GLOBALS['PHORUM'];
  3022. settype($type, 'int');
  3023. $where = '';
  3024. if ($type == 1) $where = 'WHERE active = 1';
  3025. elseif ($type == 2) $where = 'WHERE active != 1';
  3026. $users = phorum_db_interact(
  3027. DB_RETURN_ASSOCS,
  3028. "SELECT user_id,
  3029. username,
  3030. display_name
  3031. FROM {$PHORUM['user_table']}
  3032. $where
  3033. ORDER BY username ASC",
  3034. 'user_id'
  3035. );
  3036. return $users;
  3037. }
  3038. // }}}
  3039. // {{{ Function: phorum_db_user_check_login()
  3040. /**
  3041. * Check if a user's authentication credentials are correct.
  3042. *
  3043. * @param string $username
  3044. * The username for the user.
  3045. *
  3046. * @param string $password
  3047. * The password for the user.
  3048. *
  3049. * @param boolean $temp_password
  3050. * If this parameter has a true value, the password_temp field will
  3051. * be checked instead of the password field.
  3052. *
  3053. * @return integer $user_id
  3054. * The user_id if the password is correct or 0 (zero)
  3055. * if the password is wrong.
  3056. */
  3057. function phorum_db_user_check_login($username, $password, $temp_password=FALSE)
  3058. {
  3059. $PHORUM = $GLOBALS['PHORUM'];
  3060. settype($temp_password, 'bool');
  3061. $username = phorum_db_interact(DB_RETURN_QUOTED, $username);
  3062. $password = phorum_db_interact(DB_RETURN_QUOTED, $password);
  3063. $pass_field = $temp_password ? 'password_temp' : 'password';
  3064. $user_id = phorum_db_interact(
  3065. DB_RETURN_VALUE,
  3066. "SELECT user_id
  3067. FROM {$PHORUM['user_table']}
  3068. WHERE username = '$username' AND
  3069. $pass_field = '$password'"
  3070. );
  3071. return $user_id ? $user_id : 0;
  3072. }
  3073. // }}}
  3074. // {{{ Function: phorum_db_user_search()
  3075. /**
  3076. * Search for users, based on a simple search condition,
  3077. * which can be used to search on any field in the user table.
  3078. *
  3079. * The parameters $field, $value and $operator (which are used for defining
  3080. * the search condition) can be arrays or single values. If arrays are used,
  3081. * then all three parameter arrays must contain the same number of elements
  3082. * and the key values in the arrays must be the same.
  3083. *
  3084. * @param mixed $field
  3085. * The user table field (string) or fields (array) to search on.
  3086. *
  3087. * @param mixed $value
  3088. * The value (string) or values (array) to search for.
  3089. *
  3090. * @param mixed $operator
  3091. * The operator (string) or operators (array) to use. Valid operators are
  3092. * "=", "!=", "<>", "<", ">", ">=" and "<=", "*", "?*", "*?", "()". The
  3093. * "*" operator is for executing a "LIKE '%value%'" matching query. The
  3094. * "?*" and "*?" operators are for executing a "LIKE 'value%'" or a
  3095. * "LIKE '%value' matching query. The "()" operator is for executing a
  3096. * "IN ('value[0]',value[1]')" matching query. The "()" operator requires
  3097. * its $value to be an array.
  3098. *
  3099. * @param boolean $return_array
  3100. * If this parameter has a true value, then an array of all matching
  3101. * user_ids will be returned. Else, a single user_id will be returned.
  3102. *
  3103. * @param string $type
  3104. * The type of search to perform. This can be one of:
  3105. * - AND match against all fields
  3106. * - OR match against any of the fields
  3107. *
  3108. * @param mixed $sort
  3109. * The field (string) or fields (array) to sort the results by. For
  3110. * ascending sort, "fieldname" or "+fieldname" can be used. For
  3111. * descending sort, "-fieldname" can be used. By default, the results
  3112. * will be sorted by user_id.
  3113. *
  3114. * @param integer $offset
  3115. * The result page offset starting with 0.
  3116. *
  3117. * @param integer $length
  3118. * The result page length (nr. of results per page)
  3119. * or 0 (zero, the default) to return all results.
  3120. *
  3121. * @param boolean $count_only
  3122. * Tells the function to just return the count of results for this
  3123. * search query.
  3124. *
  3125. * @return mixed
  3126. * An array of matching user_ids or a single user_id (based on the
  3127. * $return_array parameter) or a count of results (based on $count_only).
  3128. * If no user_ids can be found at all, then 0 (zero) will be returned.
  3129. */
  3130. function phorum_db_user_search($field, $value, $operator='=', $return_array=FALSE, $type='AND', $sort=NULL, $offset=0, $length=0, $count_only = false)
  3131. {
  3132. $PHORUM = $GLOBALS['PHORUM'];
  3133. settype($return_array, 'bool');
  3134. settype($offset, 'int');
  3135. settype($length, 'int');
  3136. // Convert all search condition parameters to arrays.
  3137. if (!is_array($field)) $field = array($field);
  3138. if (!is_array($value)) $value = array($value);
  3139. if (!is_array($operator)) $operator = array($operator);
  3140. if (!is_array($sort) && $sort!==NULL) $sort = array($sort);
  3141. // Basic check to see if all condition arrays contain the
  3142. // same number of elements.
  3143. if (count($field) != count($value) ||
  3144. count($field) != count($operator)) trigger_error(
  3145. 'phorum_db_user_search(): array parameters $field, $value, ' .
  3146. 'and $operator do not contain the same number of elements',
  3147. E_USER_ERROR
  3148. );
  3149. $type = strtoupper($type);
  3150. if ($type != 'AND' && $type != 'OR') trigger_error(
  3151. 'phorum_db_user_search(): Illegal search type parameter (must ' .
  3152. 'be either AND" or "OR")',
  3153. E_USER_ERROR
  3154. );
  3155. $valid_operators = array('=', '<>', '!=', '>', '<', '>=', '<=', '*', '?*', '*?','()');
  3156. // Construct the required "WHERE" clause.
  3157. $clauses = array();
  3158. foreach ($field as $key => $name) {
  3159. if (in_array($operator[$key], $valid_operators) &&
  3160. phorum_db_validate_field($name)) {
  3161. if ($operator[$key] != '()') $value[$key] = phorum_db_interact(DB_RETURN_QUOTED, $value[$key]);
  3162. if ($operator[$key] == '*') {
  3163. $clauses[] = "$name LIKE '%$value[$key]%'";
  3164. } else if ($operator[$key] == '?*') {
  3165. $clauses[] = "$name LIKE '$value[$key]%'";
  3166. } else if ($operator[$key] == '*?') {
  3167. $clauses[] = "$name LIKE '%$value[$key]'";
  3168. } else if ($operator[$key] == '()') {
  3169. foreach ($value[$key] as $in_key => $in_value) {
  3170. $value[$key][$in_key] = phorum_db_interact(DB_RETURN_QUOTED, $value[$key][$in_key]);
  3171. }
  3172. $clauses[] = "$name IN ('" . implode("','",$value[$key]) ."')";
  3173. } else {
  3174. $clauses[] = "$name $operator[$key] '$value[$key]'";
  3175. }
  3176. }
  3177. }
  3178. if (!empty($clauses)) {
  3179. $where = 'WHERE ' . implode(" $type ", $clauses);
  3180. } else {
  3181. $where = '';
  3182. }
  3183. // Construct the required "ORDER BY" clause.
  3184. if (!empty($sort)) {
  3185. foreach ($sort as $id => $spec) {
  3186. if (substr($spec, 0, 1) == '+') {
  3187. $fld = substr($spec, 1);
  3188. $dir = 'ASC';
  3189. } elseif (substr($spec, 0, 1) == '-') {
  3190. $fld = substr($spec, 1);
  3191. $dir = 'DESC';
  3192. } else {
  3193. $fld = $spec;
  3194. $dir = 'ASC';
  3195. }
  3196. if (!phorum_db_validate_field($fld)) trigger_error(
  3197. 'phorum_db_user_search(): Illegal sort field: ' .
  3198. htmlspecialchars($spec),
  3199. E_USER_ERROR
  3200. );
  3201. $sort[$id] = "$fld $dir";
  3202. }
  3203. $order = 'ORDER BY ' . implode(', ', $sort);
  3204. } else {
  3205. $order = '';
  3206. }
  3207. // Construct the required "LIMIT" clause.
  3208. if (!empty($length)) {
  3209. $limit = "LIMIT $offset, $length";
  3210. } else {
  3211. // If we do not need to return an array, the we can limit the
  3212. // query results to only one record.
  3213. $limit = $return_array ? '' : 'LIMIT 1';
  3214. }
  3215. if($count_only) {
  3216. // Retrieve the number of matching user_ids from the database.
  3217. $user_count = phorum_db_interact(
  3218. DB_RETURN_VALUE,
  3219. "SELECT count(*)
  3220. FROM {$PHORUM['user_table']}
  3221. $where $order $limit",
  3222. 0 // keyfield 0 is the user_id
  3223. );
  3224. $ret = $user_count;
  3225. } else {
  3226. // Retrieve the matching user_ids from the database.
  3227. $user_ids = phorum_db_interact(
  3228. DB_RETURN_ROWS,
  3229. "SELECT user_id
  3230. FROM {$PHORUM['user_table']}
  3231. $where $order $limit",
  3232. 0 // keyfield 0 is the user_id
  3233. );
  3234. // No user_ids found at all?
  3235. if (count($user_ids) == 0) return 0;
  3236. // Return an array of user_ids.
  3237. if ($return_array) {
  3238. foreach ($user_ids as $id => $user_id) $user_ids[$id] = $user_id[0];
  3239. $ret = $user_ids;
  3240. } else {
  3241. // Return a single user_id.
  3242. list ($user_id, $dummy) = each($user_ids);
  3243. $ret = $user_id;
  3244. }
  3245. }
  3246. return $ret;
  3247. }
  3248. // }}}
  3249. // {{{ Function: phorum_db_user_add()
  3250. /**
  3251. * Add a user.
  3252. *
  3253. * @param array $userdata
  3254. * An array containing the fields to insert into the user table.
  3255. * This array should contain at least a "username" field. See
  3256. * phorum_db_user_save() for some more info on the other data
  3257. * in this array.
  3258. *
  3259. * @return integer $user_id
  3260. * The user_id that was assigned to the new user.
  3261. */
  3262. function phorum_db_user_add($userdata)
  3263. {
  3264. $PHORUM = $GLOBALS['PHORUM'];
  3265. // We need at least the username for the user.
  3266. if (! isset($userdata['username'])) trigger_error(
  3267. 'phorum_db_user_add: Missing field in userdata: username',
  3268. E_USER_ERROR
  3269. );
  3270. $username = phorum_db_interact(DB_RETURN_QUOTED, $userdata['username']);
  3271. // We can set the user_id. If not, then we'll create a new user_id.
  3272. if (isset($userdata['user_id'])) {
  3273. $user_id = (int)$userdata['user_id'];
  3274. $fields = 'user_id, username, signature, moderator_data, settings_data';
  3275. $values = "$user_id, '$username', '', '', ''";
  3276. } else {
  3277. $fields = 'username, signature, moderator_data, settings_data';
  3278. $values = "'$username', '', '', ''";
  3279. }
  3280. // Insert a bare bone user in the database.
  3281. $user_id = phorum_db_interact(
  3282. DB_RETURN_NEWID,
  3283. "INSERT INTO {$PHORUM['user_table']}
  3284. ($fields)
  3285. VALUES ($values)",
  3286. NULL,
  3287. DB_MASTERQUERY
  3288. );
  3289. // Set the rest of the data using the phorum_db_user_save() function.
  3290. $userdata['user_id'] = $user_id;
  3291. phorum_db_user_save($userdata);
  3292. return $user_id;
  3293. }
  3294. // }}}
  3295. // {{{ Function: phorum_db_user_save()
  3296. /**
  3297. * Update a user.
  3298. *
  3299. * @param array $userdata
  3300. * An array containing the fields to update in the user table.
  3301. * The array should contain at least the user_id field to identify
  3302. * the user for which to update the data. The array can contain two
  3303. * special fields:
  3304. * - forum_permissions:
  3305. * This field can contain an array with forum permissions for the user.
  3306. * The keys are forum_ids and the values are permission values.
  3307. * - user_data:
  3308. * This field can contain an array of key/value pairs which will be
  3309. * inserted in the database as custom profile fields. The keys are
  3310. * profile type ids (as defined by $PHORUM["PROFILE_FIELDS"]).
  3311. *
  3312. * @return boolean
  3313. * True if all settings were stored successfully. This function will
  3314. * always return TRUE, so we could do without a return value.
  3315. * The return value is here for backward compatibility.
  3316. */
  3317. function phorum_db_user_save($userdata)
  3318. {
  3319. $PHORUM = $GLOBALS['PHORUM'];
  3320. // Pull some non user table fields from the userdata. These can be
  3321. // set in case the $userdata parameter that is used is coming from
  3322. // phorum_api_user_get() or phorum_db_user_get().
  3323. if (isset($userdata['permissions'])) {
  3324. unset($userdata['permissions']);
  3325. }
  3326. if (isset($userdata['groups'])) {
  3327. unset($userdata['groups']);
  3328. }
  3329. if (isset($userdata['group_permissions'])) {
  3330. unset($userdata['group_permissions']);
  3331. }
  3332. // Forum permissions and custom profile fields are handled by this
  3333. // function too, but they need to be extracted from the userdata, so
  3334. // they won't be used for updating the standard user fields.
  3335. if (isset($userdata['forum_permissions'])) {
  3336. if (is_array($userdata['forum_permissions'])) {
  3337. $forum_perms = $userdata['forum_permissions'];
  3338. }
  3339. unset($userdata['forum_permissions']);
  3340. }
  3341. if (isset($userdata['user_data'])) {
  3342. $custom_profile_data = $userdata['user_data'];
  3343. unset($userdata['user_data']);
  3344. }
  3345. // The user_id is required for doing the update.
  3346. if (!isset($userdata['user_id'])) trigger_error(
  3347. 'phorum_db_user_save(): the user_id field is missing in the ' .
  3348. '$userdata argument',
  3349. E_USER_ERROR
  3350. );
  3351. $user_id = $userdata['user_id'];
  3352. unset($userdata['user_id']);
  3353. // If there are standard user table fields in the userdata then
  3354. // update the user table for the user.
  3355. if (count($userdata))
  3356. {
  3357. // Prepare the user table fields.
  3358. $values = array();
  3359. foreach ($userdata as $key => $value) {
  3360. if (!phorum_db_validate_field($key)) continue;
  3361. if ($key === 'settings_data') {
  3362. if (is_array($value)) {
  3363. $value = serialize($value);
  3364. } else trigger_error(
  3365. 'Internal error: settings_data field for ' .
  3366. 'phorum_db_user_save() must be an array', E_USER_ERROR
  3367. );
  3368. }
  3369. $value = phorum_db_interact(DB_RETURN_QUOTED, $value);
  3370. if( in_array($key, $PHORUM['string_fields_user'] ) ) {
  3371. $values[] = "$key = '$value'";
  3372. } else {
  3373. $values[] = "$key = $value";
  3374. }
  3375. }
  3376. // Update the fields in the database.
  3377. phorum_db_interact(
  3378. DB_RETURN_RES,
  3379. "UPDATE {$PHORUM['user_table']}
  3380. SET ".implode(', ', $values)."
  3381. WHERE user_id = $user_id",
  3382. NULL,
  3383. DB_MASTERQUERY
  3384. );
  3385. }
  3386. // Update forum permissions for the user.
  3387. if (isset($forum_perms))
  3388. {
  3389. // Delete all the existing forum permissions.
  3390. phorum_db_interact(
  3391. DB_RETURN_RES,
  3392. "DELETE FROM {$PHORUM['user_permissions_table']}
  3393. WHERE user_id = $user_id",
  3394. NULL,
  3395. DB_MASTERQUERY
  3396. );
  3397. // Add new forum permissions.
  3398. foreach ($forum_perms as $forum_id => $permission) {
  3399. phorum_db_interact(
  3400. DB_RETURN_RES,
  3401. "INSERT INTO {$PHORUM['user_permissions_table']}
  3402. (user_id, forum_id, permission)
  3403. VALUES ($user_id, $forum_id, $permission)",
  3404. NULL,
  3405. DB_MASTERQUERY
  3406. );
  3407. }
  3408. }
  3409. // Update custom user fields for the user.
  3410. if (isset($custom_profile_data))
  3411. {
  3412. // Insert new custom profile fields.
  3413. foreach ($custom_profile_data as $key => $val)
  3414. {
  3415. settype($key, "int");
  3416. // Arrays need to be serialized. The serialized data is prefixed
  3417. // with "P_SER:" as a marker for serialization.
  3418. if (is_array($val)) $val = 'P_SER:'.serialize($val);
  3419. $val = phorum_db_interact(DB_RETURN_QUOTED, $val);
  3420. // Try to insert a new record.
  3421. $res = phorum_db_interact(
  3422. DB_RETURN_RES,
  3423. "INSERT INTO {$PHORUM['user_custom_fields_table']}
  3424. (user_id, type, data)
  3425. VALUES ($user_id, $key, '$val')",
  3426. NULL,
  3427. DB_DUPKEYOK | DB_MASTERQUERY
  3428. );
  3429. // If no result was returned, then the query failed. This probably
  3430. // means that we already have a record in the database.
  3431. // So instead of inserting a record, we need to update one here.
  3432. if (!$res) {
  3433. phorum_db_interact(
  3434. DB_RETURN_RES,
  3435. "UPDATE {$PHORUM['user_custom_fields_table']}
  3436. SET data = '$val'
  3437. WHERE user_id = $user_id AND type = $key",
  3438. NULL,
  3439. DB_MASTERQUERY
  3440. );
  3441. }
  3442. }
  3443. }
  3444. return TRUE;
  3445. }
  3446. // }}}
  3447. // {{{ Function: phorum_db_user_display_name_updates()
  3448. /**
  3449. * Run the updates that are needed after changing the display_name for a user.
  3450. *
  3451. * The display_name for users is stored redundant at several places
  3452. * in the database (this improves the speed of the system, because joins
  3453. * with the user table do not have to be made). This function will update
  3454. * that redundant information to match the active display_name field in
  3455. * the user data.
  3456. *
  3457. * @param array $userdata
  3458. * A userdata array containing at least the fields "user_id" and
  3459. * "display_name".
  3460. */
  3461. function phorum_db_user_display_name_updates($userdata)
  3462. {
  3463. $PHORUM = $GLOBALS['PHORUM'];
  3464. if (!isset($userdata['user_id'])) trigger_error(
  3465. 'phorum_db_user_display_name_updates(): Missing user_id field in ' .
  3466. 'the $userdata parameter',
  3467. E_USER_ERROR
  3468. );
  3469. if (!isset($userdata['display_name'])) trigger_error(
  3470. 'phorum_db_user_display_name_updates(): Missing display_name field ' .
  3471. 'in the $userdata parameter',
  3472. E_USER_ERROR
  3473. );
  3474. $author = phorum_db_interact(DB_RETURN_QUOTED, $userdata['display_name']);
  3475. $user_id = (int) $userdata['user_id'];
  3476. // Update forum message authors.
  3477. phorum_db_interact(
  3478. DB_RETURN_RES,
  3479. "UPDATE {$PHORUM['message_table']}
  3480. SET author = '$author'
  3481. WHERE user_id = $user_id",
  3482. NULL,
  3483. DB_GLOBALQUERY | DB_MASTERQUERY
  3484. );
  3485. // Update recent forum reply authors.
  3486. phorum_db_interact(
  3487. DB_RETURN_RES,
  3488. "UPDATE {$PHORUM['message_table']}
  3489. SET recent_author = '$author'
  3490. WHERE recent_user_id = $user_id",
  3491. NULL,
  3492. DB_GLOBALQUERY | DB_MASTERQUERY
  3493. );
  3494. // Update PM author data.
  3495. phorum_db_interact(
  3496. DB_RETURN_RES,
  3497. "UPDATE {$PHORUM['pm_messages_table']}
  3498. SET author = '$author'
  3499. WHERE user_id = $user_id",
  3500. NULL,
  3501. DB_MASTERQUERY
  3502. );
  3503. // Update PM recipient data.
  3504. $res = phorum_db_interact(
  3505. DB_RETURN_RES,
  3506. "SELECT m.pm_message_id AS pm_message_id, meta
  3507. FROM {$PHORUM['pm_messages_table']} AS m,
  3508. {$PHORUM['pm_xref_table']} AS x
  3509. WHERE m.pm_message_id = x.pm_message_id AND
  3510. x.user_id = $user_id AND
  3511. special_folder != 'outbox'",
  3512. NULL,
  3513. DB_MASTERQUERY
  3514. );
  3515. while ($row = phorum_db_fetch_row($res, DB_RETURN_ASSOC)) {
  3516. $meta = unserialize($row['meta']);
  3517. $meta['recipients'][$user_id]['display_name'] = $author;
  3518. $meta = phorum_db_interact(DB_RETURN_QUOTED, serialize($meta));
  3519. phorum_db_interact(
  3520. DB_RETURN_RES,
  3521. "UPDATE {$PHORUM['pm_messages_table']}
  3522. SET meta='$meta'
  3523. WHERE pm_message_id = {$row['pm_message_id']}",
  3524. NULL,
  3525. DB_MASTERQUERY
  3526. );
  3527. }
  3528. }
  3529. // }}}
  3530. // {{{ Function: phorum_db_user_save_groups()
  3531. /**
  3532. * Save the group memberships for a user.
  3533. *
  3534. * @param integer $user_id
  3535. * The user_id for which to save the group memberships.
  3536. *
  3537. * @param array $groups
  3538. * The group memberships to save. This is an array in which the keys
  3539. * are group_ids and the values are group statuses.
  3540. *
  3541. * @return boolean
  3542. * True if all settings were stored successfully. This function will
  3543. * always return TRUE, so we could do without a return value.
  3544. * The return value is here for backward compatibility.
  3545. */
  3546. function phorum_db_user_save_groups($user_id, $groups)
  3547. {
  3548. $PHORUM = $GLOBALS['PHORUM'];
  3549. settype($user_id, 'int');
  3550. // Delete all existing group memberships.
  3551. phorum_db_interact(
  3552. DB_RETURN_RES,
  3553. "DELETE FROM {$PHORUM['user_group_xref_table']}
  3554. WHERE user_id = $user_id",
  3555. NULL,
  3556. DB_MASTERQUERY
  3557. );
  3558. // Insert new group memberships.
  3559. foreach ($groups as $group_id => $group_status) {
  3560. $group_id = (int)$group_id;
  3561. $group_status = (int)$group_status;
  3562. phorum_db_interact(
  3563. DB_RETURN_RES,
  3564. "INSERT INTO {$PHORUM['user_group_xref_table']}
  3565. (user_id, group_id, status)
  3566. VALUES ($user_id, $group_id, $group_status)",
  3567. NULL,
  3568. DB_MASTERQUERY
  3569. );
  3570. }
  3571. return TRUE;
  3572. }
  3573. // }}}
  3574. // {{{ Function: phorum_db_user_subscribe()
  3575. /**
  3576. * Subscribe a user to a forum or thread.
  3577. *
  3578. * Remark: Currently, there is no active support for subscription type
  3579. * PHORUM_SUBSCRIPTION_DIGEST in the Phorum core.
  3580. *
  3581. * @param integer $user_id
  3582. * The id of the user to create the subscription for.
  3583. *
  3584. * @param integer $thread
  3585. * The id of the thread to describe to.
  3586. *
  3587. * @param integer $forum_id
  3588. * The if of the forum to subscribe to.
  3589. *
  3590. * @param integer $type
  3591. * The type of subscription. Available types are:
  3592. * - {@link PHORUM_SUBSCRIPTION_NONE}
  3593. * Explicitly note that the user has no subscription at all.
  3594. * - {@link PHORUM_SUBSCRIPTION_MESSAGE}
  3595. * Send a mail message for every new message.
  3596. * - {@link PHORUM_SUBSCRIPTION_BOOKMARK}
  3597. * Make new messages visible from the followed threads interface.
  3598. * - {@link PHORUM_SUBSCRIPTION_DIGEST}
  3599. * Periodically, send a mail message containing a list of new messages.
  3600. *
  3601. * @return boolean
  3602. * True if the subscription was stored successfully.
  3603. */
  3604. function phorum_db_user_subscribe($user_id, $thread, $forum_id, $type)
  3605. {
  3606. $PHORUM = $GLOBALS['PHORUM'];
  3607. settype($user_id, 'int');
  3608. settype($forum_id, 'int');
  3609. settype($thread, 'int');
  3610. settype($type, 'int');
  3611. // Try to insert a new record.
  3612. $res = phorum_db_interact(
  3613. DB_RETURN_RES,
  3614. "INSERT INTO {$PHORUM['subscribers_table']}
  3615. (user_id, forum_id, thread, sub_type)
  3616. VALUES ($user_id, $forum_id, $thread, $type)",
  3617. NULL,
  3618. DB_DUPKEYOK | DB_MASTERQUERY
  3619. );
  3620. // If no result was returned, then the query failed. This probably
  3621. // means that we already have the record in the database.
  3622. // So instead of inserting a record, we need to update one here.
  3623. if (!$res) {
  3624. phorum_db_interact(
  3625. DB_RETURN_RES,
  3626. "UPDATE {$PHORUM['subscribers_table']}
  3627. SET sub_type = $type
  3628. WHERE user_id = $user_id AND
  3629. forum_id = $forum_id AND
  3630. thread = $thread",
  3631. NULL,
  3632. DB_MASTERQUERY
  3633. );
  3634. }
  3635. return TRUE;
  3636. }
  3637. // }}}
  3638. // {{{ Function: phorum_db_user_unsubscribe()
  3639. /**
  3640. * Unsubscribe a user from a forum/thread.
  3641. *
  3642. * @param integer $user_id
  3643. * The id of the user to remove a suscription for.
  3644. *
  3645. * @param integer $thread
  3646. * The id of the thread to unsubscribe from.
  3647. *
  3648. * @param integer $forum_id
  3649. * The id of the forum to unsubscribe from (or 0 (zero)
  3650. * to simply unsubscribe by the thread id alone).
  3651. *
  3652. * @return boolean
  3653. * True if the subscription was stored successfully.
  3654. */
  3655. function phorum_db_user_unsubscribe($user_id, $thread, $forum_id=0)
  3656. {
  3657. $PHORUM = $GLOBALS['PHORUM'];
  3658. settype($user_id, 'int');
  3659. settype($forum_id, 'int');
  3660. settype($thread, 'int');
  3661. $forum_where = $forum_id ? "AND forum_id = $forum_id" : '';
  3662. phorum_db_interact(
  3663. DB_RETURN_RES,
  3664. "DELETE FROM {$PHORUM['subscribers_table']}
  3665. WHERE user_id = $user_id AND
  3666. thread = $thread
  3667. $forum_where",
  3668. NULL,
  3669. DB_MASTERQUERY
  3670. );
  3671. return TRUE;
  3672. }
  3673. // }}}
  3674. // {{{ Function: phorum_db_user_increment_posts()
  3675. /**
  3676. * Increment the posts counter for a user.
  3677. *
  3678. * @param integer $user_id
  3679. * The user_id for which to increment the posts counter.
  3680. */
  3681. function phorum_db_user_increment_posts($user_id)
  3682. {
  3683. settype($user_id, 'int');
  3684. if (!empty($user_id)) {
  3685. phorum_db_interact(
  3686. DB_RETURN_RES,
  3687. "UPDATE {$GLOBALS['PHORUM']['user_table']}
  3688. SET posts = posts + 1
  3689. WHERE user_id = $user_id",
  3690. NULL,
  3691. DB_MASTERQUERY
  3692. );
  3693. }
  3694. }
  3695. // }}}
  3696. // {{{ Function: phorum_db_user_get_groups()
  3697. /**
  3698. * Retrieve a list of group memberships and their statuses for a user.
  3699. *
  3700. * @param integer $user_id
  3701. * The user id for which to retrieve the groups.
  3702. *
  3703. * @return array
  3704. * An array of groups for the user. The keys are group_ids and the
  3705. * values are the membership statuses.
  3706. */
  3707. function phorum_db_user_get_groups($user_id)
  3708. {
  3709. $PHORUM = $GLOBALS['PHORUM'];
  3710. settype($user_id, 'int');
  3711. // Retrieve the groups for the user_id from the database.
  3712. $groups = phorum_db_interact(
  3713. DB_RETURN_ROWS,
  3714. "SELECT group_id,
  3715. status
  3716. FROM {$PHORUM['user_group_xref_table']}
  3717. WHERE user_id = $user_id
  3718. ORDER BY status DESC",
  3719. 0 // keyfield 0 is the group_id
  3720. );
  3721. // The records are full rows, but we want a group_id -> status mapping.
  3722. foreach ($groups as $id => $group) $groups[$id] = $group[1];
  3723. return $groups;
  3724. }
  3725. // }}}
  3726. // {{{ Function: phorum_db_user_get_unapproved()
  3727. /**
  3728. * Retrieve the users that await signup approval.
  3729. *
  3730. * @return $users
  3731. * An array or users, indexed by user_id, that await approval.
  3732. * The elements of the array are arrays containing the fields:
  3733. * user_id, username and email.
  3734. *
  3735. * @todo This function might be as well replaced with user search and get
  3736. * functionality from the user API.
  3737. */
  3738. function phorum_db_user_get_unapproved()
  3739. {
  3740. $PHORUM = $GLOBALS['PHORUM'];
  3741. $users = phorum_db_interact(
  3742. DB_RETURN_ASSOCS,
  3743. "SELECT user_id,
  3744. username,
  3745. email
  3746. FROM {$PHORUM['user_table']}
  3747. WHERE active in (".PHORUM_USER_PENDING_BOTH.",
  3748. ".PHORUM_USER_PENDING_MOD.")
  3749. ORDER BY username",
  3750. 'user_id'
  3751. );
  3752. return $users;
  3753. }
  3754. // }}}
  3755. // {{{ Function: phorum_db_user_delete
  3756. /**
  3757. * Delete a user completely. Messages that were posted by the user in the
  3758. * forums, will be changed into anonymous messages (user_id = 0). If the
  3759. * constant PHORUM_DELETE_CHANGE_AUTHOR is set to a true value, then the
  3760. * author name of all postings will be set to {LANG->AnonymousUser}. If
  3761. * it is set to a false value, then the original author name will be kept.
  3762. *
  3763. * @param integer $user_id
  3764. * The id of the user to delete.
  3765. *
  3766. * @return boolean
  3767. * True if the user was deleted successfully.
  3768. */
  3769. function phorum_db_user_delete($user_id)
  3770. {
  3771. $PHORUM = $GLOBALS['PHORUM'];
  3772. settype($user_id, 'int');
  3773. // Retrieve a list of private mesage xrefs for this user. After we delete
  3774. // the pm xrefs for this user in the code afterwards, we might have
  3775. // created orphin PM messages (messages with no xrefs linked to them),
  3776. // so we'll have to check for that later on.
  3777. $pmxrefs = phorum_db_interact(
  3778. DB_RETURN_ROWS,
  3779. "SELECT pm_message_id
  3780. FROM {$PHORUM['pm_xref_table']}
  3781. WHERE user_id = $user_id",
  3782. NULL,
  3783. DB_MASTERQUERY
  3784. );
  3785. // These are tables that hold user related data.
  3786. $tables = array (
  3787. $PHORUM['user_table'],
  3788. $PHORUM['user_permissions_table'],
  3789. $PHORUM['user_newflags_table'],
  3790. $PHORUM['subscribers_table'],
  3791. $PHORUM['user_group_xref_table'],
  3792. $PHORUM['pm_buddies_table'],
  3793. $PHORUM['pm_folders_table'],
  3794. $PHORUM['pm_xref_table'],
  3795. $PHORUM['user_custom_fields_table']
  3796. );
  3797. // Delete the data for the $user_id from all those tables.
  3798. foreach ($tables as $table) {
  3799. phorum_db_interact(
  3800. DB_RETURN_RES,
  3801. "DELETE FROM $table
  3802. WHERE user_id = $user_id",
  3803. NULL,
  3804. DB_GLOBALQUERY | DB_MASTERQUERY
  3805. );
  3806. }
  3807. // See if we created any orphin private messages. We do this in
  3808. // a loop using the standard phorum_db_pm_update_message_info()
  3809. // function and not a single SQL statement with something like
  3810. // pm_message_id IN (...) in it, because MySQL won't use an index
  3811. // for that, making the full lookup very slow on large PM tables.
  3812. foreach ($pmxrefs as $row) {
  3813. phorum_db_pm_update_message_info($row[0]);
  3814. }
  3815. // Change the forum postings into anonymous postings.
  3816. // If PHORUM_DELETE_CHANGE_AUTHOR is set, then the author field is
  3817. // updated to {LANG->AnonymousUser}.
  3818. $author = 'author';
  3819. if (defined('PHORUM_DELETE_CHANGE_AUTHOR') && PHORUM_DELETE_CHANGE_AUTHOR) {
  3820. $anonymous = $PHORUM['DATA']['LANG']['AnonymousUser'];
  3821. $author = "'".phorum_db_interact(DB_RETURN_QUOTED, $anonymous)."'";
  3822. }
  3823. phorum_db_interact(
  3824. DB_RETURN_RES,
  3825. "UPDATE {$PHORUM['message_table']}
  3826. SET user_id = 0,
  3827. email = '',
  3828. author = $author
  3829. WHERE user_id = $user_id",
  3830. NULL,
  3831. DB_GLOBALQUERY | DB_MASTERQUERY
  3832. );
  3833. phorum_db_interact(
  3834. DB_RETURN_RES,
  3835. "UPDATE {$PHORUM['message_table']}
  3836. SET recent_user_id = 0,
  3837. recent_author = $author
  3838. WHERE recent_user_id = $user_id",
  3839. NULL,
  3840. DB_GLOBALQUERY | DB_MASTERQUERY
  3841. );
  3842. return TRUE;
  3843. }
  3844. // }}}
  3845. // {{{ Function: phorum_db_get_file_list()
  3846. /**
  3847. * Retrieve a list of files from the database.
  3848. *
  3849. * @param string $link_type
  3850. * The type of link to retrieve from the database. Normally this is one
  3851. * of the Phorum built-in link types, but it can also be a custom
  3852. * link type (e.g. if a module uses the file storage on its own).
  3853. * This parameter can be NULL to retrieve any link type.
  3854. *
  3855. * @param integer $user_id
  3856. * The user_id to retrieve files for or NULL to retrieve files for
  3857. * any user_id.
  3858. *
  3859. * @param integer $message_id
  3860. * The message_id to retrieve files for or NULL to retrieve files for
  3861. * any message_id.
  3862. *
  3863. * @return array
  3864. * An array of files, indexed by file_id.
  3865. * The array elements are arrays containing the fields:
  3866. * file_id, filename, filesize and add_datetime.
  3867. */
  3868. function phorum_db_get_file_list($link_type = NULL, $user_id = NULL, $message_id = NULL)
  3869. {
  3870. $PHORUM = $GLOBALS["PHORUM"];
  3871. $where = '';
  3872. $clauses = array();
  3873. if ($link_type !== NULL) {
  3874. $qtype = phorum_db_interact(DB_RETURN_QUOTED, $link_type);
  3875. $clauses[] = "link = '$qtype'";
  3876. }
  3877. if ($user_id !== NULL) {
  3878. $clauses[] = 'user_id = ' . (int) $user_id;
  3879. }
  3880. if ($message_id !== NULL) {
  3881. $clauses[] = 'message_id = ' . (int) $message_id;
  3882. }
  3883. if (count($clauses)) {
  3884. $where = 'WHERE ' . implode(' AND ', $clauses);
  3885. }
  3886. return phorum_db_interact(
  3887. DB_RETURN_ASSOCS,
  3888. "SELECT file_id,
  3889. filename,
  3890. filesize,
  3891. add_datetime
  3892. FROM {$PHORUM['files_table']}
  3893. $where
  3894. ORDER BY file_id",
  3895. 'file_id'
  3896. );
  3897. }
  3898. // }}}
  3899. // {{{ Function: phorum_db_get_user_file_list()
  3900. /**
  3901. * Retrieve a list of personal files for a user.
  3902. *
  3903. * @param integer $user_id
  3904. * The user id for which to retrieve the file list.
  3905. *
  3906. * @return array
  3907. * An array of personal user files, indexed by file_id.
  3908. * The array elements are arrays containing the fields:
  3909. * file_id, filename, filesize and add_datetime.
  3910. */
  3911. function phorum_db_get_user_file_list($user_id)
  3912. {
  3913. return phorum_db_get_file_list(PHORUM_LINK_USER, $user_id, 0);
  3914. }
  3915. // }}}
  3916. // {{{ Function: phorum_db_get_message_file_list()
  3917. /**
  3918. * Retrieve a list of files for a message (a.k.a. attachments).
  3919. *
  3920. * @param integer $message_id
  3921. * The message id for which to retrieve the file list.
  3922. *
  3923. * @return array
  3924. * An array of message files, indexed by file_id.
  3925. * The array elements are arrays containing the fields:
  3926. * file_id, filename, filesize and add_datetime.
  3927. */
  3928. function phorum_db_get_message_file_list($message_id)
  3929. {
  3930. return phorum_db_get_file_list(PHORUM_LINK_MESSAGE, NULL, $message_id);
  3931. }
  3932. // }}}
  3933. // {{{ Function: phorum_db_file_get()
  3934. /**
  3935. * Retrieve a file.
  3936. *
  3937. * @param integer $file_id
  3938. * The file id to retrieve from the database.
  3939. *
  3940. * @param boolean $include_file_data
  3941. * If this parameter is set to a false value (TRUE is the default),
  3942. * the file data will not be included in the return data.
  3943. *
  3944. * @return array
  3945. * An array, containing the data for all file table fields.
  3946. * If the file id cannot be found in the database, an empty
  3947. * array will be returned instead.
  3948. */
  3949. function phorum_db_file_get($file_id, $include_file_data = TRUE)
  3950. {
  3951. $PHORUM = $GLOBALS['PHORUM'];
  3952. settype($file_id, 'int');
  3953. $fields = "file_id, user_id, filename, filesize, " .
  3954. "add_datetime, message_id, link";
  3955. if ($include_file_data) $fields .= ",file_data";
  3956. // Select the file from the database.
  3957. $files = phorum_db_interact(
  3958. DB_RETURN_ASSOCS,
  3959. "SELECT $fields
  3960. FROM {$PHORUM['files_table']}
  3961. WHERE file_id = $file_id"
  3962. );
  3963. if (count($files) == 0) {
  3964. return array();
  3965. } else {
  3966. return $files[0];
  3967. }
  3968. }
  3969. // }}}
  3970. // {{{ Function: phorum_db_file_save()
  3971. /**
  3972. * Add or updates a file.
  3973. *
  3974. * @todo Update docs, because it now is based on separate parameters,
  3975. * while the function itself requires an array now.
  3976. *
  3977. * @param integer $user_id
  3978. * The id of the user for which to add the file.
  3979. * If this file is linked to a message by an anonymous
  3980. * user, then this value can be 0 (zero).
  3981. *
  3982. * @param string $filename
  3983. * The name of the file.
  3984. *
  3985. * @param integer $filesize
  3986. * The size of the file in bytes.
  3987. *
  3988. * @param string $file_data
  3989. * The file contents. This should be data which is safe to store in a
  3990. * TEXT field in the database. The calling application has to take
  3991. * care of this. The database layer will simply store and retrieve
  3992. * the file data as provided by the caller.
  3993. *
  3994. * @param integer $message_id
  3995. * The message_id to link the file to. If this file is not linked to
  3996. * a posted message (the link type PHORUM_LINK_MESSAGE) then this value
  3997. * can be 0 (zero).
  3998. *
  3999. * @param string $link
  4000. * A file can be linked to a number of different types of objects.
  4001. * The available link types are:
  4002. * - PHORUM_LINK_USER:
  4003. * The file is linked to a user. This means that the file is
  4004. * available from within the files section in the user's Control Center.
  4005. * - PHORUM_LINK_MESSAGE:
  4006. * The file is linked to a message. This means that the file is
  4007. * available as an attachment on a posted forum message.
  4008. * - PHORUM_LINK_EDITOR
  4009. * The file is linked to the editor. This means that the file was
  4010. * uploaded as part of editing a forum message. This message was
  4011. * not yet posted. As soon as the message is posted for final, the
  4012. * link type for the message will be updated to
  4013. * PHORUM_LINK_MESSAGE.
  4014. * Note: these are the official link types. Calling functions are
  4015. * allowed to provide different custom link types if they need to.
  4016. *
  4017. * @param integer $file_id
  4018. * If the $file_id is set, then this will be used for updating the
  4019. * existing file data for the given $file_id.
  4020. *
  4021. * @return integer
  4022. * The file_id that was assigned to the new file or the file_id of
  4023. * the existing file if the $file_id parameter was used.
  4024. */
  4025. function phorum_db_file_save($file)
  4026. {
  4027. $PHORUM = $GLOBALS['PHORUM'];
  4028. // If a link type is not provided, we'll guess for the type of link.
  4029. // This is done to provide some backward compatibility.
  4030. if ($file["link"] === NULL) {
  4031. if ($file["message_id"]) $file["link"] = PHORUM_LINK_MESSAGE;
  4032. elseif ($file["user_id"]) $file["link"] = PHORUM_LINK_USER;
  4033. else trigger_error(
  4034. 'phorum_db_file_save(): Missing link field in the $file parameter',
  4035. E_USER_ERROR
  4036. );
  4037. }
  4038. $user_id = (int)$file["user_id"];
  4039. $message_id = (int)$file["message_id"];
  4040. $filesize = (int)$file["filesize"];
  4041. $file_id = !isset($file["file_id"]) || $file["file_id"] === NULL
  4042. ? NULL : (int)$file["file_id"];
  4043. $link = phorum_db_interact(DB_RETURN_QUOTED, $file["link"]);
  4044. $filename = phorum_db_interact(DB_RETURN_QUOTED, $file["filename"]);
  4045. $file_data = phorum_db_interact(DB_RETURN_QUOTED, $file["file_data"]);
  4046. $datetime = empty($file['add_datetime'])
  4047. ? time() : (int)$file['add_datetime'];
  4048. // Create a new file record.
  4049. if ($file_id === NULL) {
  4050. $file_id = phorum_db_interact(
  4051. DB_RETURN_NEWID,
  4052. "INSERT INTO {$PHORUM['files_table']}
  4053. (user_id, message_id, link,
  4054. filename, filesize, file_data, add_datetime)
  4055. VALUES ($user_id, $message_id, '$link',
  4056. '$filename', $filesize, '$file_data', $datetime)",
  4057. NULL,
  4058. DB_MASTERQUERY
  4059. );
  4060. }
  4061. // Update an existing file record.
  4062. else {
  4063. phorum_db_interact(
  4064. DB_RETURN_RES,
  4065. "UPDATE {$PHORUM['files_table']}
  4066. SET user_id = $user_id,
  4067. message_id = $message_id,
  4068. link = '$link',
  4069. filename = '$filename',
  4070. filesize = $filesize,
  4071. file_data = '$file_data'
  4072. WHERE file_id = $file_id",
  4073. NULL,
  4074. DB_MASTERQUERY
  4075. );
  4076. }
  4077. return $file_id;
  4078. }
  4079. // }}}
  4080. // {{{ Function: phorum_db_file_delete()
  4081. /**
  4082. * Delete a file.
  4083. *
  4084. * @param integer $file_id
  4085. * The id of the file to delete.
  4086. *
  4087. * @return boolean
  4088. * True if the file was deleted successfully. This function will
  4089. * always return TRUE, so we could do without a return value.
  4090. * The return value is here for backward compatibility.
  4091. */
  4092. function phorum_db_file_delete($file_id)
  4093. {
  4094. $PHORUM = $GLOBALS['PHORUM'];
  4095. settype($file_id, 'int');
  4096. phorum_db_interact(
  4097. DB_RETURN_RES,
  4098. "DELETE FROM {$PHORUM['files_table']}
  4099. WHERE file_id = $file_id",
  4100. NULL,
  4101. DB_MASTERQUERY
  4102. );
  4103. return TRUE;
  4104. }
  4105. // }}}
  4106. // {{{ Function: phorum_db_file_link()
  4107. /**
  4108. * Update the message to which a file is linked and/or the link type.
  4109. *
  4110. * @param integer $file_id
  4111. * The id of the file to update.
  4112. *
  4113. * @param integer $message_id
  4114. * The id of the message to link the file to.
  4115. *
  4116. * @param string $link
  4117. * A file can be linked to a number of different types of objects.
  4118. * See phorum_db_file_save() for the possible link types.
  4119. *
  4120. * @return boolean
  4121. * True if the file link was updated successfully. This function will
  4122. * always return TRUE, so we could do without a return value.
  4123. * The return value is here for backward compatibility.
  4124. */
  4125. function phorum_db_file_link($file_id, $message_id, $link = NULL)
  4126. {
  4127. $PHORUM = $GLOBALS['PHORUM'];
  4128. settype($file_id, 'int');
  4129. settype($message_id, 'int');
  4130. $link = $link === NULL
  4131. ? PHORUM_LINK_MESSAGE
  4132. : phorum_db_interact(DB_RETURN_QUOTED, $link);
  4133. phorum_db_interact(
  4134. DB_RETURN_RES,
  4135. "UPDATE {$PHORUM['files_table']}
  4136. SET message_id = $message_id,
  4137. link = '$link'
  4138. WHERE file_id = $file_id",
  4139. NULL,
  4140. DB_MASTERQUERY
  4141. );
  4142. return TRUE;
  4143. }
  4144. // }}}
  4145. // {{{ Function: phorum_db_get_user_filesize_total()
  4146. /**
  4147. * Retrieve the total size for all personal files for a user.
  4148. *
  4149. * @param integer $user_id
  4150. * The user to compute the total size for.
  4151. *
  4152. * @return integer
  4153. * The total size in bytes.
  4154. */
  4155. function phorum_db_get_user_filesize_total($user_id)
  4156. {
  4157. $PHORUM = $GLOBALS['PHORUM'];
  4158. settype($user_id, 'int');
  4159. $size = phorum_db_interact(
  4160. DB_RETURN_VALUE,
  4161. "SELECT SUM(filesize)
  4162. FROM {$PHORUM['files_table']}
  4163. WHERE user_id = $user_id AND
  4164. message_id = 0 AND
  4165. link = '".PHORUM_LINK_USER."'"
  4166. );
  4167. return $size;
  4168. }
  4169. // }}}
  4170. // {{{ Function: phorum_db_list_stale_files()
  4171. /**
  4172. * Retrieve a list of stale files from the database.
  4173. *
  4174. * Stale files are files that are not linked to anything anymore.'
  4175. * These can for example be caused by users that are writing a message
  4176. * with attachments, but never post it.
  4177. *
  4178. * @return array
  4179. * An array of stale Phorum files, indexed by file_id. Every item in
  4180. * this array is an array on its own, containing the fields:
  4181. * - file_id: the file id of the stale file
  4182. * - filename: the name of the stale file
  4183. * - filesize: the size of the file
  4184. * - add_datetime: the time at which the file was added
  4185. * - reason: the reason why it's a stale file
  4186. */
  4187. function phorum_db_list_stale_files()
  4188. {
  4189. $PHORUM = $GLOBALS['PHORUM'];
  4190. // Select orphin editor files.
  4191. // These are files that are linked to the editor and that were added
  4192. // a while ago. These are from posts that were abandoned before posting.
  4193. $stale_files = phorum_db_interact(
  4194. DB_RETURN_ASSOCS,
  4195. "SELECT file_id,
  4196. filename,
  4197. filesize,
  4198. add_datetime,
  4199. 'Attachments, left behind by unposted messages' AS reason
  4200. FROM {$PHORUM['files_table']}
  4201. WHERE link = '".PHORUM_LINK_EDITOR."'
  4202. AND
  4203. add_datetime < ". (time()-PHORUM_MAX_EDIT_TIME),
  4204. 'file_id',
  4205. DB_GLOBALQUERY
  4206. );
  4207. return $stale_files;
  4208. }
  4209. // }}}
  4210. // {{{ Function: phorum_db_newflag_allread()
  4211. /**
  4212. * Mark all messages for a forum read for the active Phorum user.
  4213. *
  4214. * @param integer $forum_id
  4215. * The forum to mark read or 0 (zero) to mark the current forum read.
  4216. */
  4217. function phorum_db_newflag_allread($forum_id=0)
  4218. {
  4219. $PHORUM = $GLOBALS['PHORUM'];
  4220. if (empty($forum_id)) $forum_id = $PHORUM['forum_id'];
  4221. settype($forum_id, 'int');
  4222. // Delete all the existing newflags for this user for this forum.
  4223. phorum_db_newflag_delete(0, $forum_id);
  4224. // Retrieve the maximum message_id in this forum.
  4225. $max_id = phorum_db_interact(
  4226. DB_RETURN_VALUE,
  4227. "SELECT max(message_id)
  4228. FROM {$PHORUM['message_table']}
  4229. WHERE forum_id = $forum_id"
  4230. );
  4231. // Set this message_id as the min-id for the forum.
  4232. if ($max_id) {
  4233. phorum_db_newflag_add_read(array(
  4234. 0 => array(
  4235. 'id' => $max_id,
  4236. 'forum' => $forum_id
  4237. )
  4238. ));
  4239. }
  4240. }
  4241. // }}}
  4242. // {{{ Function: phorum_db_newflag_get_flags()
  4243. /**
  4244. * Retrieve the read messages for a forum for the active Phorum user.
  4245. *
  4246. * @param integer $forum_id
  4247. * The forum to retrieve the read messages for or 0 (zero) to
  4248. * retrieve them for the current forum.
  4249. *
  4250. * @return array
  4251. * An array containing the message_ids that have been read for the
  4252. * forum (key and value are both the message_id). Also an element
  4253. * for the key "min_id", which holds the minimum read message_id. All
  4254. * message_ids lower than the min_id are also considered to be read.
  4255. */
  4256. function phorum_db_newflag_get_flags($forum_id=NULL)
  4257. {
  4258. $PHORUM = $GLOBALS['PHORUM'];
  4259. if ($forum_id === NULL) $forum_id = $PHORUM['forum_id'];
  4260. settype($forum_id, 'int');
  4261. // Initialize the read messages array.
  4262. $read_msgs = array('min_id' => 0);
  4263. // Select the read messages from the newflags table.
  4264. $newflags = phorum_db_interact(
  4265. DB_RETURN_ROWS,
  4266. "SELECT message_id
  4267. FROM {$PHORUM['user_newflags_table']}
  4268. WHERE user_id = {$PHORUM['user']['user_id']} AND
  4269. forum_id = $forum_id
  4270. ORDER BY message_id ASC"
  4271. );
  4272. // Add the newflags to the $read_msgs.
  4273. // The first newflags element also determines the min_id.
  4274. foreach ($newflags as $index => $newflag) {
  4275. if ($index == 0) $read_msgs['min_id'] = $newflag[0];
  4276. $read_msgs[$newflag[0]] = $newflag[0];
  4277. }
  4278. return $read_msgs;
  4279. }
  4280. // }}}
  4281. // {{{ Function: phorum_db_newflag_check()
  4282. /**
  4283. * Checks if there are new messages in the forums given in forum_ids
  4284. *
  4285. * @param array $forum_ids
  4286. * The forums to check for new messages
  4287. *
  4288. * @return array
  4289. * An array containing forum_ids as the key and a boolean for
  4290. * the values.
  4291. */
  4292. function phorum_db_newflag_check($forum_ids)
  4293. {
  4294. $PHORUM = $GLOBALS['PHORUM'];
  4295. phorum_db_sanitize_mixed($forum_ids, 'int');
  4296. $sql = "select forum_id, min(message_id) as message_id
  4297. from {$PHORUM['user_newflags_table']}
  4298. where user_id=".$PHORUM["user"]["user_id"]."
  4299. group by forum_id";
  4300. $list = phorum_db_interact(DB_RETURN_ASSOCS, $sql, "forum_id");
  4301. $sql = "select forum_id, count(*) as count
  4302. from {$PHORUM['user_newflags_table']}
  4303. where user_id=".$PHORUM["user"]["user_id"]."
  4304. group by forum_id";
  4305. $counts = phorum_db_interact(DB_RETURN_ASSOCS, $sql, "forum_id");
  4306. $new_checks = array();
  4307. foreach($forum_ids as $forum_id){
  4308. if(empty($list[$forum_id]) || empty($counts[$forum_id])){
  4309. $new_checks[$forum_id] = FALSE;
  4310. } else {
  4311. // check for new messages
  4312. $sql = "select count(*) as count from {$PHORUM['message_table']}
  4313. where forum_id=".$forum_id." and
  4314. message_id>=".$list[$forum_id]["message_id"]." and
  4315. status=".PHORUM_STATUS_APPROVED." and
  4316. moved=0";
  4317. list($count) = phorum_db_interact(DB_RETURN_ROW, $sql);
  4318. $new_checks[$forum_id] = ($count > $counts[$forum_id]["count"]);
  4319. }
  4320. }
  4321. return $new_checks;
  4322. }
  4323. // }}}
  4324. // {{{ Function: phorum_db_newflag_count()
  4325. /**
  4326. * Gets a count of new messages and threads for the forum ids given
  4327. *
  4328. * @param array $forum_ids
  4329. * The forums to check for new messages.
  4330. *
  4331. * @return array
  4332. * An array containing forum_ids as the key and a two element array
  4333. * for each entry with messages and threads counts.
  4334. */
  4335. function phorum_db_newflag_count($forum_ids)
  4336. {
  4337. $PHORUM = $GLOBALS['PHORUM'];
  4338. phorum_db_sanitize_mixed($forum_ids, 'int');
  4339. // get a list of forum_ids and minimum message ids from the newflags table
  4340. $sql = "select forum_id, min(message_id) as message_id
  4341. from {$PHORUM['user_newflags_table']}
  4342. where user_id=".$PHORUM["user"]["user_id"]."
  4343. group by forum_id";
  4344. $list = phorum_db_interact(DB_RETURN_ASSOCS, $sql, "forum_id");
  4345. // get the total number of messages the user has read in each forum
  4346. $sql = "select {$PHORUM['user_newflags_table']}.forum_id, count(*) as count
  4347. from {$PHORUM['user_newflags_table']}
  4348. inner join {$PHORUM['message_table']} using (message_id, forum_id)
  4349. where {$PHORUM['user_newflags_table']}.user_id=".$PHORUM["user"]["user_id"]." and
  4350. status=".PHORUM_STATUS_APPROVED."
  4351. group by forum_id";
  4352. $message_counts = phorum_db_interact(DB_RETURN_ASSOCS, $sql, "forum_id");
  4353. // get the number of threads the user has read in each forum
  4354. $sql = "select {$PHORUM['user_newflags_table']}.forum_id, count(*) as count
  4355. from {$PHORUM['user_newflags_table']}
  4356. inner join {$PHORUM['message_table']} using (message_id, forum_id)
  4357. where {$PHORUM['user_newflags_table']}.user_id=".$PHORUM["user"]["user_id"]." and
  4358. parent_id=0 and
  4359. status=".PHORUM_STATUS_APPROVED."
  4360. group by forum_id";
  4361. $thread_counts = phorum_db_interact(DB_RETURN_ASSOCS, $sql, "forum_id");
  4362. $new_checks = array();
  4363. foreach($forum_ids as $forum_id){
  4364. if(empty($list[$forum_id])){
  4365. $new_checks[$forum_id] = array("messages"=>0, "threads"=>0);
  4366. } else {
  4367. if(empty($message_counts[$forum_id])){
  4368. $new_checks[$forum_id]["messages"] = 0;
  4369. } else {
  4370. // check for new messages
  4371. $sql = "select count(*) as count from {$PHORUM['message_table']}
  4372. where forum_id=".$forum_id." and
  4373. message_id>=".$list[$forum_id]["message_id"]." and
  4374. status=".PHORUM_STATUS_APPROVED." and
  4375. moved=0";
  4376. list($count) = phorum_db_interact(DB_RETURN_ROW, $sql);
  4377. $new_checks[$forum_id]["messages"] = max(0, $count - $message_counts[$forum_id]["count"]);
  4378. }
  4379. // no this is not a typo
  4380. // we need to calculate their thread count if they have ANY read messages
  4381. // in the table. the only way to do that is to see if message count was set
  4382. if(empty($message_counts[$forum_id])){
  4383. $new_checks[$forum_id]["threads"] = 0;
  4384. } else {
  4385. // check for new threads
  4386. $sql = "select count(*) as count from {$PHORUM['message_table']}
  4387. where forum_id=".$forum_id." and
  4388. message_id>=".$list[$forum_id]["message_id"]." and
  4389. parent_id=0 and
  4390. status=".PHORUM_STATUS_APPROVED." and
  4391. moved=0";
  4392. list($count) = phorum_db_interact(DB_RETURN_ROW, $sql);
  4393. if(isset($thread_counts[$forum_id]["count"])){
  4394. $new_checks[$forum_id]["threads"] = max(0, $count - $thread_counts[$forum_id]["count"]);
  4395. } else {
  4396. $new_checks[$forum_id]["threads"] = max(0, $count);
  4397. }
  4398. }
  4399. }
  4400. }
  4401. return $new_checks;
  4402. }
  4403. // }}}
  4404. // {{{ Function: phorum_db_newflag_get_unread_count()
  4405. /**
  4406. * Retrieve the number of new threads and messages for a forum for the
  4407. * active Phorum user.
  4408. *
  4409. * @param integer $forum_id
  4410. * The forum to retrieve the new counts for or
  4411. * 0 (zero) to retrieve them for the current forum.
  4412. *
  4413. * @return array
  4414. * An array containing two elements. The first element is the number
  4415. * of new messages. The second one is the number of new threads.
  4416. */
  4417. function phorum_db_newflag_get_unread_count($forum_id=NULL)
  4418. {
  4419. $PHORUM = $GLOBALS['PHORUM'];
  4420. if ($forum_id === NULL) $forum_id = $PHORUM['forum_id'];
  4421. settype($forum_id, 'int');
  4422. // Retrieve the minimum message_id from newflags for the forum.
  4423. $min_message_id = phorum_db_interact(
  4424. DB_RETURN_VALUE,
  4425. "SELECT min(message_id)
  4426. FROM {$PHORUM['user_newflags_table']}
  4427. WHERE user_id = {$PHORUM['user']['user_id']} AND
  4428. forum_id = {$forum_id}"
  4429. );
  4430. // No result found? Then we know that the user never read a
  4431. // message from this forum. We won't count the new messages
  4432. // in that case. Return an empty result.
  4433. if (!$min_message_id) return array(0,0);
  4434. // Retrieve the unread thread count.
  4435. $new_threads = phorum_db_interact(
  4436. DB_RETURN_VALUE,
  4437. "SELECT count(*)
  4438. FROM {$PHORUM['message_table']} AS m
  4439. LEFT JOIN {$PHORUM['user_newflags_table']} AS n ON
  4440. m.message_id = n.message_id AND
  4441. n.user_id = {$PHORUM['user']['user_id']}
  4442. WHERE m.forum_id = {$forum_id} AND
  4443. m.message_id > $min_message_id AND
  4444. n.message_id IS NULL AND
  4445. m.parent_id = 0 AND
  4446. m.status = ".PHORUM_STATUS_APPROVED." AND
  4447. m.thread = m.message_id"
  4448. );
  4449. // Retrieve the unread message count.
  4450. $new_messages = phorum_db_interact(
  4451. DB_RETURN_VALUE,
  4452. "SELECT count(*)
  4453. FROM {$PHORUM['message_table']} AS m
  4454. LEFT JOIN {$PHORUM['user_newflags_table']} AS n ON
  4455. m.message_id = n.message_id AND
  4456. m.forum_id = n.forum_id AND
  4457. n.user_id = {$PHORUM['user']['user_id']}
  4458. WHERE m.forum_id = {$forum_id} AND
  4459. m.message_id > $min_message_id AND
  4460. n.message_id IS NULL AND
  4461. m.status = ".PHORUM_STATUS_APPROVED
  4462. );
  4463. $counts = array(
  4464. $new_messages,
  4465. $new_threads
  4466. );
  4467. return $counts;
  4468. }
  4469. // }}}
  4470. // {{{ Function: phorum_db_newflag_add_read()
  4471. /**
  4472. * Mark a message as read for the active Phorum user.
  4473. *
  4474. * @param mixed $message_ids
  4475. * The message_id of the message to mark read in the active forum or an
  4476. * array description of messages to mark read. Elements in this array
  4477. * can be:
  4478. * - Simple message_id values, to mark messages read in the active forum.
  4479. * - An array containing two fields: "forum" containing a forum_id and
  4480. * "id" containing a message_id. This notation can be used to mark
  4481. * messages read in other forums than te active one.
  4482. */
  4483. function phorum_db_newflag_add_read($message_ids)
  4484. {
  4485. $PHORUM = $GLOBALS['PHORUM'];
  4486. // Find the number of newflags for the user
  4487. $num_newflags = phorum_db_newflag_get_count();
  4488. if (!is_array($message_ids)) {
  4489. $message_ids = array(0 => $message_ids);
  4490. }
  4491. // Delete newflags which would exceed the maximum number of
  4492. // newflags that are allowed in the database per user.
  4493. $num_end = $num_newflags + count($message_ids);
  4494. if ($num_end > PHORUM_MAX_READ_COUNT_PER_FORUM) {
  4495. phorum_db_newflag_delete($num_end - PHORUM_MAX_READ_COUNT_PER_FORUM);
  4496. }
  4497. // Insert newflags.
  4498. $inserts = array();
  4499. foreach ($message_ids as $id => $data)
  4500. {
  4501. if (is_array($data)) {
  4502. $user_id = $PHORUM['user']['user_id'];
  4503. $forum_id = (int)$data['forum'];
  4504. $message_id = (int)$data['id'];
  4505. } else {
  4506. $user_id = $PHORUM['user']['user_id'];
  4507. $forum_id = $PHORUM['forum_id'];
  4508. $message_id = (int)$data;
  4509. }
  4510. $values = "($user_id,$forum_id,$message_id)";
  4511. $inserts[$values] = $values;
  4512. }
  4513. if(count($inserts)) {
  4514. $inserts_str = implode(",",$inserts);
  4515. // Try to insert the values.
  4516. $res = phorum_db_interact(
  4517. DB_RETURN_RES,
  4518. "INSERT INTO {$PHORUM['user_newflags_table']}
  4519. (user_id, forum_id, message_id)
  4520. VALUES $inserts_str",
  4521. NULL,
  4522. DB_DUPKEYOK | DB_MASTERQUERY
  4523. );
  4524. // If inserting the values failed, then this most probably means
  4525. // that one of the values already existed in the database, causing
  4526. // a duplicate key error. In this case, fallback to one-by-one
  4527. // insertion, so the other records in the list will be created.
  4528. if (!$res && count($inserts) > 1)
  4529. {
  4530. foreach ($inserts as $values)
  4531. {
  4532. $res = phorum_db_interact(
  4533. DB_RETURN_RES,
  4534. "INSERT INTO {$PHORUM['user_newflags_table']}
  4535. (user_id, forum_id, message_id)
  4536. VALUES $values",
  4537. NULL,
  4538. DB_DUPKEYOK | DB_MASTERQUERY
  4539. );
  4540. }
  4541. }
  4542. }
  4543. }
  4544. // }}}
  4545. // {{{ Function: phorum_db_newflag_get_count()
  4546. /**
  4547. * Retrieve the total number of newflags for a forum for the active
  4548. * Phorum user.
  4549. *
  4550. * @param integer $forum_id
  4551. * The forum to retrieve the count for or 0 (zero) to retrieve it
  4552. * for the current forum.
  4553. *
  4554. * @return integer
  4555. * The total number of newflags.
  4556. */
  4557. function phorum_db_newflag_get_count($forum_id=0)
  4558. {
  4559. $PHORUM = $GLOBALS['PHORUM'];
  4560. if (empty($forum_id)) $forum_id = $PHORUM['forum_id'];
  4561. settype($forum_id, 'int');
  4562. $count = phorum_db_interact(
  4563. DB_RETURN_VALUE,
  4564. "SELECT count(*)
  4565. FROM {$PHORUM['user_newflags_table']}
  4566. WHERE user_id = {$PHORUM['user']['user_id']} AND
  4567. forum_id = {$forum_id}"
  4568. );
  4569. return $count;
  4570. }
  4571. // }}}
  4572. // {{{ Function: phorum_db_newflag_delete()
  4573. /**
  4574. * Remove newflags for a forum for the active Phorum user.
  4575. *
  4576. * @param integer $numdelete
  4577. * The number of newflags to delete or 0 (zero) to delete them all.
  4578. *
  4579. * @param integer $forum_id
  4580. * The forum for which to delete the newflags or 0 (zero) to
  4581. * delete them for the current forum.
  4582. */
  4583. function phorum_db_newflag_delete($numdelete=0,$forum_id=0)
  4584. {
  4585. $PHORUM = $GLOBALS['PHORUM'];
  4586. if (empty($forum_id)) $forum_id = $PHORUM['forum_id'];
  4587. settype($numdelete, 'int');
  4588. settype($forum_id, 'int');
  4589. $limit = $numdelete > 0 ? "ORDER BY message_id ASC LIMIT $numdelete" : '';
  4590. // Delete the provided amount of newflags.
  4591. phorum_db_interact(
  4592. DB_RETURN_RES,
  4593. "DELETE FROM {$PHORUM['user_newflags_table']}
  4594. WHERE user_id = {$PHORUM['user']['user_id']} AND
  4595. forum_id = {$forum_id}
  4596. $limit",
  4597. NULL,
  4598. DB_MASTERQUERY
  4599. );
  4600. }
  4601. // }}}
  4602. // {{{ Function: phorum_db_newflag_update_forum()
  4603. /**
  4604. * Update the forum_id for the newflags. The newsflags are updated by setting
  4605. * their forum_ids to the forum_ids of the referenced message table records.
  4606. *
  4607. * @param array $message_ids
  4608. * An array of message_ids which should be updated.
  4609. */
  4610. function phorum_db_newflag_update_forum($message_ids)
  4611. {
  4612. phorum_db_sanitize_mixed($message_ids, 'int');
  4613. $ids_str = implode(', ', $message_ids);
  4614. phorum_db_interact(
  4615. DB_RETURN_RES,
  4616. "UPDATE IGNORE {$GLOBALS['PHORUM']['user_newflags_table']} AS flags,
  4617. {$GLOBALS['PHORUM']['message_table']} AS msg
  4618. SET flags.forum_id = msg.forum_id
  4619. WHERE flags.message_id = msg.message_id AND
  4620. flags.message_id IN ($ids_str)",
  4621. NULL,
  4622. DB_MASTERQUERY
  4623. );
  4624. }
  4625. // }}}
  4626. // {{{ Function: phorum_db_user_list_subscribers()
  4627. /**
  4628. * Retrieve the email addresses of the active users that are subscribed to a
  4629. * forum/thread, grouped by the preferred language for these users.
  4630. *
  4631. * @param integer $forum_id
  4632. * The forum_id to check on.
  4633. *
  4634. * @param integer $thread
  4635. * The thread id to check on.
  4636. *
  4637. * @param integer $type
  4638. * The type of subscription to check on. See the documentation for the
  4639. * function {@link phorum_db_user_subscribe()} for available
  4640. * subscription types.
  4641. *
  4642. * @param boolean $ignore_active_user
  4643. * If this parameter is set to FALSE (it is TRUE by default), then the
  4644. * active Phorum user will be excluded from the list.
  4645. *
  4646. * @return array $addresses
  4647. * An array containing the subscriber email addresses. The keys in the
  4648. * result array are language names. The values are arrays. Each array
  4649. * contains a list of email addresses of users which are using the
  4650. * language from the key field.
  4651. */
  4652. function phorum_db_user_list_subscribers($forum_id, $thread, $type, $ignore_active_user=TRUE)
  4653. {
  4654. $PHORUM = $GLOBALS['PHORUM'];
  4655. settype($forum_id, 'int');
  4656. settype($thread, 'int');
  4657. settype($type, 'int');
  4658. settype($ignore_active_user, 'bool');
  4659. $userignore = '';
  4660. if ($ignore_active_user && $PHORUM['DATA']['LOGGEDIN'])
  4661. $userignore = "AND u.user_id != {$PHORUM['user']['user_id']}";
  4662. // Select all subscriptions for the requested thread.
  4663. // This query also checks on s.thread = 0. This is for
  4664. // subscriptions that are set on a full forum. This is a
  4665. // feature which never really made it to the core (because the
  4666. // posting performance would get too low with a lot of forum
  4667. // subscribers, but we'll leave it in the query here, in case
  4668. // somebody wants to write a module for handling this functionality.
  4669. $users = phorum_db_interact(
  4670. DB_RETURN_ROWS,
  4671. "SELECT DISTINCT(u.email) AS email,
  4672. user_language
  4673. FROM {$PHORUM['subscribers_table']} AS s,
  4674. {$PHORUM['user_table']} AS u
  4675. WHERE s.forum_id = $forum_id AND
  4676. (s.thread = $thread or s.thread = 0) AND
  4677. s.sub_type = $type AND
  4678. u.user_id = s.user_id AND
  4679. u.active = ".PHORUM_USER_ACTIVE."
  4680. $userignore"
  4681. );
  4682. $addresses = array();
  4683. // Add the subscriptions to the addresses array.
  4684. foreach ($users as $user)
  4685. {
  4686. // Determine what language to use for this user.
  4687. $lang = empty($user[1]) ? $PHORUM['language'] : $user[1];
  4688. $addresses[$lang][] = $user[0];
  4689. }
  4690. return $addresses;
  4691. }
  4692. // }}}
  4693. // {{{ Function: phorum_db_user_list_subscriptions()
  4694. /**
  4695. * Retrieve a list of threads to which a user is subscribed. The list can be
  4696. * limited to those threads which did receive contributions recently.
  4697. *
  4698. * @param integer $user_id
  4699. * The id of the user for which to retrieve the subscribed threads.
  4700. *
  4701. * @param integer $days
  4702. * If set to 0 (zero), then all subscriptions will be returned. If set to
  4703. * a different value, then only threads which have received contributions
  4704. * within the last $days days will be included in the list.
  4705. *
  4706. * @param integer $forum_ids
  4707. * If this parameter is NULL, then subscriptions from all forums will
  4708. * be included. This parameter can also be an array of forum_ids, in
  4709. * which case the search will be limited to the forums in this array.
  4710. *
  4711. * @return array $threads
  4712. * An array of matching threads, indexed by thread id. One special key
  4713. * "forum_ids" is set too. This one contains an array of all involved
  4714. * forum_ids.
  4715. */
  4716. function phorum_db_user_list_subscriptions($user_id, $days=0, $forum_ids=NULL)
  4717. {
  4718. $PHORUM = $GLOBALS['PHORUM'];
  4719. settype($user_id, 'int');
  4720. settype($days, 'int');
  4721. if ($forum_ids !== NULL) phorum_db_sanitize_mixed($forums_ids, 'int');
  4722. $time_where = $days > 0
  4723. ? " AND (".time()." - m.modifystamp) <= ($days * 86400)"
  4724. : '';
  4725. $forum_where = ($forum_ids !== NULL and is_array($forum_ids))
  4726. ? " AND s.forum_id IN (" . implode(",", $forum_ids) . ")"
  4727. : '';
  4728. // Retrieve all subscribed threads from the database for which the
  4729. // latest message in the thread was posted within the provided time limit.
  4730. $threads = phorum_db_interact(
  4731. DB_RETURN_ASSOCS,
  4732. "SELECT s.thread AS thread,
  4733. s.forum_id AS forum_id,
  4734. s.sub_type AS sub_type,
  4735. m.subject AS subject,
  4736. m.modifystamp AS modifystamp,
  4737. m.author AS author,
  4738. m.user_id AS user_id,
  4739. m.email AS email,
  4740. m.recent_author AS recent_author,
  4741. m.recent_user_id AS recent_user_id,
  4742. m.meta AS meta
  4743. FROM {$PHORUM['subscribers_table']} AS s,
  4744. {$PHORUM['message_table']} AS m
  4745. WHERE s.user_id = $user_id AND
  4746. m.message_id = s.thread AND
  4747. (s.sub_type = ".PHORUM_SUBSCRIPTION_MESSAGE." OR
  4748. s.sub_type = ".PHORUM_SUBSCRIPTION_BOOKMARK.")
  4749. $time_where
  4750. $forum_where
  4751. ORDER BY m.modifystamp DESC",
  4752. 'thread'
  4753. );
  4754. // An array for keeping track of all involved forum ids.
  4755. $forum_ids = array();
  4756. foreach ($threads as $id => $thread)
  4757. {
  4758. // Unpack the thread's meta data.
  4759. $threads[$id]['meta'] = empty($thread['meta'])
  4760. ? array() : unserialize($thread['meta']);
  4761. $forum_ids[$thread['forum_id']] = $thread['forum_id'];
  4762. }
  4763. // Store the involved forum_ids in the thread array.
  4764. $threads['forum_ids'] = $forum_ids;
  4765. return $threads;
  4766. }
  4767. // }}}
  4768. // {{{ Function: phorum_db_user_get_subscription()
  4769. /**
  4770. * Retrieve the subscription of a user for a thread.
  4771. *
  4772. * @param integer $user_id
  4773. * The user_id to retrieve the subscription for.
  4774. *
  4775. * @param integer $forum_id
  4776. * The forum_id to retrieve the subscription for.
  4777. *
  4778. * @param integer $thread
  4779. * The thread id to retrieve the subscription for.
  4780. *
  4781. * @return integer
  4782. * The type of subscription if there is a subscription available or
  4783. * NULL in case no subscription was found. For a list of available
  4784. * subscription types see the documentation for function
  4785. * phorum_db_user_subscribe().
  4786. */
  4787. function phorum_db_user_get_subscription($user_id, $forum_id, $thread)
  4788. {
  4789. $PHORUM = $GLOBALS['PHORUM'];
  4790. settype($user_id, 'int');
  4791. settype($forum_id, 'int');
  4792. settype($thread, 'int');
  4793. settype($type, 'int');
  4794. $type = phorum_db_interact(
  4795. DB_RETURN_VALUE,
  4796. "SELECT sub_type
  4797. FROM {$PHORUM['subscribers_table']}
  4798. WHERE forum_id = $forum_id AND
  4799. thread = $thread AND
  4800. user_id = $user_id"
  4801. );
  4802. return $type;
  4803. }
  4804. // }}}
  4805. // {{{ Function: phorum_db_get_banlists()
  4806. /**
  4807. * Retrieve the ban lists for the active forum.
  4808. *
  4809. * @param boolean $ordered
  4810. * If this parameter has a true value (default is FALSE),
  4811. * then the results will be ordered by ban type and string.
  4812. *
  4813. * @return array
  4814. * An array of active bans, indexed by the type of ban. For available
  4815. * ban types, see the documentation for the function
  4816. * phorum_db_mod_banlists(). Each value for a ban type is an array of
  4817. * bans. Each ban in those arrays is an array containing the fields:
  4818. * prce, string and forum_id.
  4819. */
  4820. function phorum_db_get_banlists($ordered=FALSE)
  4821. {
  4822. $PHORUM = $GLOBALS['PHORUM'];
  4823. settype($ordered, 'bool');
  4824. $forum_where = '';
  4825. if (isset($PHORUM['forum_id']) && !empty($PHORUM['forum_id']))
  4826. {
  4827. $forum_where = "WHERE forum_id = {$PHORUM['forum_id']} " .
  4828. // forum_id = 0 is used for GLOBAL ban items
  4829. 'OR forum_id = 0';
  4830. // If we're inside a vroot, then retrieve the ban items that apply
  4831. // to this vroot as well.
  4832. if (isset($PHORUM['vroot']) && !empty($PHORUM['vroot'])) {
  4833. $forum_where .= " OR forum_id = {$PHORUM['vroot']}";
  4834. }
  4835. }
  4836. $order = $ordered ? 'ORDER BY type, string' : '';
  4837. $bans = phorum_db_interact(
  4838. DB_RETURN_ASSOCS,
  4839. "SELECT *
  4840. FROM {$PHORUM['banlist_table']}
  4841. $forum_where
  4842. $order"
  4843. );
  4844. $banlists = array();
  4845. foreach ($bans as $ban) {
  4846. $banlists[$ban['type']][$ban['id']] = array (
  4847. 'pcre' => $ban['pcre'],
  4848. 'string' => $ban['string'],
  4849. 'comments' => $ban['comments'],
  4850. 'forum_id' => $ban['forum_id']
  4851. );
  4852. }
  4853. return $banlists;
  4854. }
  4855. // }}}
  4856. // {{{ Function: phorum_db_get_banitem
  4857. /**
  4858. * Retrieve a single ban item from the ban lists.
  4859. *
  4860. * @param integer $banid
  4861. * The id of the ban item to retrieve.
  4862. *
  4863. * @return array
  4864. * A ban item array, containing the fields: pcre, string, forum_id,
  4865. * type. If no ban can be found for the $banid, then an empty array
  4866. * is returned instead.
  4867. */
  4868. function phorum_db_get_banitem($banid)
  4869. {
  4870. $PHORUM = $GLOBALS['PHORUM'];
  4871. settype($banid, 'int');
  4872. $bans = phorum_db_interact(
  4873. DB_RETURN_ASSOCS,
  4874. "SELECT *
  4875. FROM {$PHORUM['banlist_table']}
  4876. WHERE id = $banid"
  4877. );
  4878. if (count($bans)) {
  4879. $ban = array(
  4880. 'pcre' => $bans[0]['pcre'],
  4881. 'string' => $bans[0]['string'],
  4882. 'forum_id' => $bans[0]['forum_id'],
  4883. 'type' => $bans[0]['type'],
  4884. 'comments' => $bans[0]['comments']
  4885. );
  4886. } else {
  4887. $ban = array();
  4888. }
  4889. return $ban;
  4890. }
  4891. // }}}
  4892. // {{{ Function: phorum_db_del_banitem
  4893. /**
  4894. * Delete a single ban item from the ban lists.
  4895. *
  4896. * @param integer $banid
  4897. * The id of the ban item to delete.
  4898. */
  4899. function phorum_db_del_banitem($banid)
  4900. {
  4901. $PHORUM = $GLOBALS['PHORUM'];
  4902. settype($banid, 'int');
  4903. phorum_db_interact(
  4904. DB_RETURN_RES,
  4905. "DELETE FROM {$PHORUM['banlist_table']}
  4906. WHERE id = $banid",
  4907. NULL,
  4908. DB_MASTERQUERY
  4909. );
  4910. }
  4911. // }}}
  4912. // {{{ Function: phorum_db_mod_banlists()
  4913. /**
  4914. * Add or modify a single ban list item.
  4915. *
  4916. * @param $type
  4917. * The type of ban list item. Available ban types are:
  4918. * - PHORUM_BAD_IPS: Match IP address or hostname.
  4919. * - PHORUM_BAD_NAMES: Mach name or username.
  4920. * - PHORUM_BAD_EMAILS: Match the email address.
  4921. * - PHORUM_BAD_USERID: Match the user_id.
  4922. * - PHORUM_BAD_SPAM_WORDS: Match for spam words.
  4923. *
  4924. * @param boolean $pcre
  4925. * Whether the ban string has to be handled as a standard match
  4926. * string or as a pcre (Perl Compatible Regular Expression).
  4927. *
  4928. * @param string $string
  4929. * The ban string for performing the match.
  4930. *
  4931. * @param integer $forum_id
  4932. * The forum_id to link the ban to. This can be a real forum_id, a
  4933. * vroot id or 0 (zero) to indicate a GLOBAL ban item.
  4934. *
  4935. * @param string $comments
  4936. * Comments to add to the ban item. This can be used for documenting the
  4937. * ban item (why was the ban created, when was this done or generally
  4938. * any info that an administrator finds useful).
  4939. *
  4940. * @param integer $banid
  4941. * This parameter can be set to the id of a ban item to let the
  4942. * function update an existing ban. If set to 0 (zero), a new ban
  4943. * item will be created.
  4944. *
  4945. * @return boolean
  4946. * True if the ban item was created or updated successfully.
  4947. */
  4948. function phorum_db_mod_banlists($type, $pcre, $string, $forum_id, $comments, $banid=0)
  4949. {
  4950. $PHORUM = $GLOBALS['PHORUM'];
  4951. $retarr = array();
  4952. settype($type, 'int');
  4953. settype($pcre, 'int');
  4954. settype($forum_id, 'int');
  4955. settype($banid, 'int');
  4956. $string = phorum_db_interact(DB_RETURN_QUOTED, $string);
  4957. $comments = phorum_db_interact(DB_RETURN_QUOTED, $comments);
  4958. // Update an existing ban item.
  4959. if ($banid > 0) {
  4960. phorum_db_interact(
  4961. DB_RETURN_RES,
  4962. "UPDATE {$PHORUM['banlist_table']}
  4963. SET forum_id = $forum_id,
  4964. type = $type,
  4965. pcre = $pcre,
  4966. string = '$string',
  4967. comments = '$comments'
  4968. WHERE id = $banid",
  4969. NULL,
  4970. DB_MASTERQUERY
  4971. );
  4972. }
  4973. // Create a new ban item.
  4974. else {
  4975. phorum_db_interact(
  4976. DB_RETURN_RES,
  4977. "INSERT INTO {$PHORUM['banlist_table']}
  4978. (forum_id, type, pcre, string, comments)
  4979. VALUES ($forum_id, $type, $pcre, '$string', '$comments')",
  4980. NULL,
  4981. DB_MASTERQUERY
  4982. );
  4983. }
  4984. return TRUE;
  4985. }
  4986. // }}}
  4987. // {{{ Function: phorum_db_pm_list()
  4988. /**
  4989. * Retrieve all private messages for a user in a folder.
  4990. *
  4991. * @param mixed $folder
  4992. * The folder to use. Either a special folder (PHORUM_PM_INBOX or
  4993. * PHORUM_PM_OUTBOX) or the id of a custom user folder.
  4994. *
  4995. * @param integer $user_id
  4996. * The user to retrieve messages for or NULL to use the active
  4997. * Phorum user (default).
  4998. *
  4999. * @param boolean $reverse
  5000. * If set to a true value (default), sorting of messages is done
  5001. * in reverse (newest first).
  5002. *
  5003. * @return array
  5004. * An array of private messages for the folder.
  5005. */
  5006. function phorum_db_pm_list($folder, $user_id = NULL, $reverse = TRUE)
  5007. {
  5008. $PHORUM = $GLOBALS['PHORUM'];
  5009. if ($user_id === NULL) $user_id = $PHORUM['user']['user_id'];
  5010. settype($user_id, 'int');
  5011. settype($reverse, 'bool');
  5012. if (is_numeric($folder)) {
  5013. $folder_where = "pm_folder_id = $folder";
  5014. } elseif ($folder == PHORUM_PM_INBOX || $folder == PHORUM_PM_OUTBOX) {
  5015. $folder_where = "(pm_folder_id = 0 AND special_folder = '$folder')";
  5016. } else trigger_error(
  5017. 'phorum_db_pm_list(): Illegal folder "'.htmlspecialchars($folder).'" '.
  5018. 'requested for user id "'.$user_id.'"',
  5019. E_USER_ERROR
  5020. );
  5021. // Retrieve the messages from the folder.
  5022. $messages = phorum_db_interact(
  5023. DB_RETURN_ASSOCS,
  5024. "SELECT m.pm_message_id AS pm_message_id,
  5025. m.user_id, author,
  5026. subject, datestamp,
  5027. meta, pm_xref_id,
  5028. pm_folder_id, special_folder,
  5029. read_flag, reply_flag
  5030. FROM {$PHORUM['pm_messages_table']} AS m,
  5031. {$PHORUM['pm_xref_table']} AS x
  5032. WHERE x.user_id = $user_id AND
  5033. $folder_where AND
  5034. x.pm_message_id = m.pm_message_id
  5035. ORDER BY x.pm_message_id " . ($reverse ? 'DESC' : 'ASC'),
  5036. 'pm_message_id'
  5037. );
  5038. // Add the recipient information unserialized to the messages.
  5039. foreach ($messages as $id => $message) {
  5040. $meta = unserialize($message['meta']);
  5041. $messages[$id]['recipients'] = $meta['recipients'];
  5042. }
  5043. return $messages;
  5044. }
  5045. // }}}
  5046. // {{{ Function: phorum_db_pm_get()
  5047. /**
  5048. * Retrieve a private message from the database.
  5049. *
  5050. * @param integer $pm_id
  5051. * The id for the private message to retrieve.
  5052. *
  5053. * @param mixed $folder
  5054. * The folder to retrieve the message from or NULL if the folder
  5055. * does not matter.
  5056. *
  5057. * @param integer $user_id
  5058. * The user to retrieve the message for or NULL to use the active
  5059. * Phorum user (default).
  5060. *
  5061. * @return array
  5062. * Return the private message on success or NULL if the message
  5063. * could not be found.
  5064. */
  5065. function phorum_db_pm_get($pm_id, $folder = NULL, $user_id = NULL)
  5066. {
  5067. $PHORUM = $GLOBALS['PHORUM'];
  5068. if ($user_id === NULL) $user_id = $PHORUM['user']['user_id'];
  5069. settype($user_id, 'int');
  5070. settype($pm_id, 'int');
  5071. if ($folder === NULL) {
  5072. $folder_where = '';
  5073. } elseif (is_numeric($folder)) {
  5074. $folder_where = "pm_folder_id = $folder AND ";
  5075. } elseif ($folder == PHORUM_PM_INBOX || $folder == PHORUM_PM_OUTBOX) {
  5076. $folder_where = "pm_folder_id = 0 AND special_folder = '$folder' AND ";
  5077. } else trigger_error(
  5078. 'phorum_db_pm_get(): Illegal folder "'.htmlspecialchars($folder).'" '.
  5079. 'requested for user id "'.$user_id.'"',
  5080. E_USER_ERROR
  5081. );
  5082. // Retrieve the private message.
  5083. $messages = phorum_db_interact(
  5084. DB_RETURN_ASSOCS,
  5085. "SELECT m.pm_message_id AS pm_message_id,
  5086. m.user_id AS user_id,
  5087. m.author AS author,
  5088. m.subject AS subject,
  5089. m.message AS message,
  5090. m.datestamp AS datestamp,
  5091. m.meta AS meta,
  5092. x.pm_xref_id AS pm_xref_id,
  5093. x.pm_folder_id AS pm_folder_id,
  5094. x.special_folder AS special_folder,
  5095. x.pm_message_id AS pm_message_id,
  5096. x.read_flag AS read_flag,
  5097. x.reply_flag AS reply_flag
  5098. FROM {$PHORUM['pm_messages_table']} AS m,
  5099. {$PHORUM['pm_xref_table']} AS x
  5100. WHERE $folder_where
  5101. x.pm_message_id = $pm_id AND
  5102. x.user_id = $user_id AND
  5103. x.pm_message_id = m.pm_message_id"
  5104. );
  5105. // Prepare the return data.
  5106. if (count($messages) == 0) {
  5107. $message = NULL;
  5108. } else {
  5109. $message = $messages[0];
  5110. // Add the recipient information unserialized to the message..
  5111. $meta = unserialize($message['meta']);
  5112. $message['recipients'] = $meta['recipients'];
  5113. }
  5114. return $message;
  5115. }
  5116. // }}}
  5117. // {{{ Function: phorum_db_pm_create_folder()
  5118. /**
  5119. * Create a new private messages folder for a user.
  5120. *
  5121. * @param string $foldername
  5122. * The name of the folder to create.
  5123. *
  5124. * @param mixed $user_id
  5125. * The id of the user to create the folder for or NULL to use the
  5126. * active Phorum user (default).
  5127. *
  5128. * @return integer $pm_folder_id
  5129. * The id that was assigned to the new folder.
  5130. */
  5131. function phorum_db_pm_create_folder($foldername, $user_id = NULL)
  5132. {
  5133. $PHORUM = $GLOBALS['PHORUM'];
  5134. if ($user_id === NULL) $user_id = $PHORUM['user']['user_id'];
  5135. settype($user_id, 'int');
  5136. $foldername = phorum_db_interact(DB_RETURN_QUOTED, $foldername);
  5137. $pm_folder_id = phorum_db_interact(
  5138. DB_RETURN_NEWID,
  5139. "INSERT INTO {$PHORUM['pm_folders_table']}
  5140. (user_id, foldername)
  5141. VALUES ($user_id, '$foldername')",
  5142. NULL,
  5143. DB_MASTERQUERY
  5144. );
  5145. return $pm_folder_id;
  5146. }
  5147. // }}}
  5148. // {{{ Function: phorum_db_pm_rename_folder()
  5149. /**
  5150. * Rename a private message folder for a user.
  5151. *
  5152. * @param integer $folder_id
  5153. * The id of the folder to rename.
  5154. *
  5155. * @param string $newname
  5156. * The new name for the folder.
  5157. *
  5158. * @param mixed $user_id
  5159. * The user to rename the folder for or NULL to use the active
  5160. * Phorum user (default).
  5161. */
  5162. function phorum_db_pm_rename_folder($folder_id, $newname, $user_id = NULL)
  5163. {
  5164. $PHORUM = $GLOBALS['PHORUM'];
  5165. if ($user_id === NULL) $user_id = $PHORUM['user']['user_id'];
  5166. settype($user_id, 'int');
  5167. settype($folder_id, 'int');
  5168. $newname = phorum_db_interact(DB_RETURN_QUOTED, $newname);
  5169. phorum_db_interact(
  5170. DB_RETURN_RES,
  5171. "UPDATE {$PHORUM['pm_folders_table']}
  5172. SET foldername = '$newname'
  5173. WHERE pm_folder_id = $folder_id AND
  5174. user_id = $user_id",
  5175. NULL,
  5176. DB_MASTERQUERY
  5177. );
  5178. }
  5179. // }}}
  5180. // {{{ Function: phorum_db_pm_delete_folder()
  5181. /**
  5182. * Delete a private message folder for a user. Along with the folder,
  5183. * all contained messages are deleted as well.
  5184. *
  5185. * @param integer $folder_id
  5186. * The id of the folder to delete.
  5187. *
  5188. * @param mixed $user_id
  5189. * The user to delete the folder for or NULL to use the active
  5190. * Phorum user (default).
  5191. */
  5192. function phorum_db_pm_delete_folder($folder_id, $user_id = NULL)
  5193. {
  5194. $PHORUM = $GLOBALS['PHORUM'];
  5195. if ($user_id === NULL) $user_id = $PHORUM['user']['user_id'];
  5196. settype($user_id, 'int');
  5197. settype($folder_id, 'int');
  5198. // Retrieve the private messages in this folder and delete them.
  5199. $list = phorum_db_pm_list($folder_id, $user_id);
  5200. foreach ($list as $id => $data) {
  5201. phorum_db_pm_delete($id, $folder_id, $user_id);
  5202. }
  5203. // Delete the folder itself.
  5204. phorum_db_interact(
  5205. DB_RETURN_RES,
  5206. "DELETE FROM {$PHORUM['pm_folders_table']}
  5207. WHERE pm_folder_id = $folder_id AND
  5208. user_id = $user_id",
  5209. NULL,
  5210. DB_MASTERQUERY
  5211. );
  5212. }
  5213. // }}}
  5214. // {{{ Function: phorum_db_pm_getfolders()
  5215. /**
  5216. * Retrieve a list of private message folders for a user.
  5217. *
  5218. * @param mixed $user_id
  5219. * The user to retrieve folders for or NULL to use the active
  5220. * Phorum user (default).
  5221. *
  5222. * @param boolean $count
  5223. * If this parameter is set to a true value, the number of messages
  5224. * for each folder is included in the return data. By default,
  5225. * this is not done.
  5226. *
  5227. * @return array
  5228. * An array of private message folders, indexed by the folder id.
  5229. * The values are arrays, containing the fields "id" and "name".
  5230. * If $count is enabled, the keys "total" and "new" will be added
  5231. * to the forum info.
  5232. */
  5233. function phorum_db_pm_getfolders($user_id = NULL, $count = FALSE)
  5234. {
  5235. $PHORUM = $GLOBALS['PHORUM'];
  5236. if ($user_id === NULL) $user_id = $PHORUM['user']['user_id'];
  5237. settype($user_id, 'int');
  5238. settype($count, 'bool');
  5239. // Initialize the list of folders. Our special folders are
  5240. // not in the database, so these are added hard-coded to the list.
  5241. // Add the incoming folder.
  5242. $folders = array(
  5243. PHORUM_PM_INBOX => array(
  5244. 'id' => PHORUM_PM_INBOX,
  5245. 'name' => $PHORUM['DATA']['LANG']['INBOX'],
  5246. ),
  5247. );
  5248. // Select all custom folders for the user.
  5249. $customfolders = phorum_db_interact(
  5250. DB_RETURN_ASSOCS,
  5251. "SELECT pm_folder_id AS id,
  5252. foldername AS name
  5253. FROM {$PHORUM['pm_folders_table']}
  5254. WHERE user_id = $user_id
  5255. ORDER BY foldername",
  5256. 'id'
  5257. );
  5258. // Add them to the folder list.
  5259. foreach ($customfolders as $id => $customfolder) {
  5260. $folders[$id] = $customfolder;
  5261. }
  5262. // Add the outgoing folder.
  5263. $folders[PHORUM_PM_OUTBOX] = array(
  5264. 'id' => PHORUM_PM_OUTBOX,
  5265. 'name' => $PHORUM['DATA']['LANG']['SentItems'],
  5266. );
  5267. // Count the number of messages in the folders if requested.
  5268. if ($count)
  5269. {
  5270. // Initialize counters.
  5271. foreach ($folders as $id => $data) {
  5272. $folders[$id]['total'] = 0;
  5273. $folders[$id]['new'] = 0;
  5274. }
  5275. // Collect count information.
  5276. $countinfo = phorum_db_interact(
  5277. DB_RETURN_ASSOCS,
  5278. "SELECT pm_folder_id,
  5279. special_folder,
  5280. count(*) AS total,
  5281. (count(*) - sum(read_flag)) AS new
  5282. FROM {$PHORUM['pm_xref_table']}
  5283. WHERE user_id = $user_id
  5284. GROUP BY pm_folder_id, special_folder"
  5285. );
  5286. // Merge the count information with the folders.
  5287. foreach ($countinfo as $info)
  5288. {
  5289. $folder_id = $info['pm_folder_id']
  5290. ? $info['pm_folder_id']
  5291. : $info['special_folder'];
  5292. // If there are stale messages for no longer existing folders
  5293. // (shouldn't happen), we do not want them to create non-existent
  5294. // mailboxes in the list.
  5295. if (isset($folders[$folder_id])) {
  5296. $folders[$folder_id]['total'] = $info['total'];
  5297. $folders[$folder_id]['new'] = $info['new'];
  5298. }
  5299. }
  5300. }
  5301. return $folders;
  5302. }
  5303. // }}}
  5304. // {{{ Function: phorum_db_pm_messagecount
  5305. /**
  5306. * Compute the total number of private messages a user has and return
  5307. * both the total number of messages and the number of unread messages.
  5308. *
  5309. * @param mixed $folder
  5310. * The id of the folder to use. Either a special folder
  5311. * (PHORUM_PM_INBOX or PHORUM_PM_OUTBOX), the id of a user's custom
  5312. * folder or PHORUM_PM_ALLFOLDERS for looking at all folders.
  5313. *
  5314. * @param mixed $user_id
  5315. * The user to retrieve messages for or NULL to use the
  5316. * active Phorum user (default).
  5317. *
  5318. * @return array
  5319. * An array containing the elements "total" and "new".
  5320. */
  5321. function phorum_db_pm_messagecount($folder, $user_id = NULL)
  5322. {
  5323. $PHORUM = $GLOBALS['PHORUM'];
  5324. if ($user_id === NULL) $user_id = $PHORUM['user']['user_id'];
  5325. settype($user_id, 'int');
  5326. if (is_numeric($folder)) {
  5327. $folder_where = "pm_folder_id = $folder AND";
  5328. } elseif ($folder == PHORUM_PM_INBOX || $folder == PHORUM_PM_OUTBOX) {
  5329. $folder_where = "pm_folder_id = 0 AND special_folder = '$folder' AND";
  5330. } elseif ($folder == PHORUM_PM_ALLFOLDERS) {
  5331. $folder_where = '';
  5332. } else trigger_error(
  5333. 'phorum_db_pm_messagecount(): Illegal folder "' .
  5334. htmlspecialchars($folder).'" requested for user id "'.$user_id.'"',
  5335. E_USER_ERROR
  5336. );
  5337. $counters = phorum_db_interact(
  5338. DB_RETURN_ASSOCS,
  5339. "SELECT count(*) AS total,
  5340. (count(*) - sum(read_flag)) AS new
  5341. FROM {$PHORUM['pm_xref_table']}
  5342. WHERE $folder_where user_id = $user_id"
  5343. );
  5344. $count = array( 'total' => 0, 'new' => 0 );
  5345. if (count($counters) > 0) {
  5346. $count['total'] = $counters[0]['total'];
  5347. $count['new'] = ($counters[0]['new'] >= 1) ? $counters[0]['new'] : 0;
  5348. }
  5349. return $count;
  5350. }
  5351. // }}}
  5352. // {{{ Function: phorum_db_pm_checknew()
  5353. /**
  5354. * Check if the user has any new private messages. This is useful in case
  5355. * you only want to know whether the user has new messages or not and when
  5356. * you are not interested in the exact amount of new messages.
  5357. *
  5358. * @param mixed $user_id
  5359. * The user to check for or NULL to use the active Phorum user (default).
  5360. *
  5361. * @return boolean
  5362. * TRUE in case there are new messages, FALSE otherwise.
  5363. */
  5364. function phorum_db_pm_checknew($user_id = NULL)
  5365. {
  5366. $PHORUM = $GLOBALS['PHORUM'];
  5367. if ($user_id === NULL) $user_id = $PHORUM['user']['user_id'];
  5368. settype($user_id, 'int');
  5369. $new = phorum_db_interact(
  5370. DB_RETURN_VALUE,
  5371. "SELECT user_id
  5372. FROM {$PHORUM['pm_xref_table']}
  5373. WHERE user_id = $user_id AND
  5374. read_flag = 0 LIMIT 1"
  5375. );
  5376. return (bool)$new;
  5377. }
  5378. // }}}
  5379. // {{{ Function: phorum_db_pm_send
  5380. /**
  5381. * Send a private message.
  5382. *
  5383. * @param string $subject
  5384. * The subject for the private message.
  5385. *
  5386. * @param string $message
  5387. * The message text for the private message.
  5388. *
  5389. * @param mixed $to
  5390. * A single user_id or an array of user_ids for specifying the
  5391. * recipient(s).
  5392. *
  5393. * @param mixed $from
  5394. * The id of the sending user or NULL to use the active Phorum user
  5395. * (default).
  5396. *
  5397. * @param $keepcopy
  5398. * If set to a true value, a copy of the mail will be kept in the
  5399. * outbox of the user. Default value is FALSE.
  5400. *
  5401. * @return integer
  5402. * The id that was assigned to the new message.
  5403. */
  5404. function phorum_db_pm_send($subject, $message, $to, $from=NULL, $keepcopy=FALSE)
  5405. {
  5406. $PHORUM = $GLOBALS['PHORUM'];
  5407. // Prepare the sender.
  5408. if ($from === NULL) $from = $PHORUM['user']['user_id'];
  5409. settype($from, 'int');
  5410. $fromuser = phorum_db_user_get($from, FALSE);
  5411. if (! $fromuser) trigger_error(
  5412. "phorum_db_pm_send(): Unknown sender user_id '$from'",
  5413. E_USER_ERROR
  5414. );
  5415. $fromuser = phorum_db_interact(DB_RETURN_QUOTED, $fromuser['display_name']);
  5416. $subject = phorum_db_interact(DB_RETURN_QUOTED, $subject);
  5417. $message = phorum_db_interact(DB_RETURN_QUOTED, $message);
  5418. // Prepare the list of recipients and xref entries.
  5419. $xref_entries = array();
  5420. $rcpts = array();
  5421. if (! is_array($to)) $to = array($to);
  5422. foreach ($to as $user_id)
  5423. {
  5424. settype($user_id, 'int');
  5425. $user = phorum_db_user_get($user_id, FALSE);
  5426. if (! $user) trigger_error(
  5427. "phorum_db_pm_send(): Unknown recipient user_id '$user_id'",
  5428. E_USER_ERROR
  5429. );
  5430. $rcpts[$user_id] = array(
  5431. 'user_id' => $user_id,
  5432. 'display_name' => $user['display_name'],
  5433. 'read_flag' => 0,
  5434. );
  5435. $xref_entries[] = array(
  5436. 'user_id' => $user_id,
  5437. 'pm_folder_id' => 0,
  5438. 'special_folder' => PHORUM_PM_INBOX,
  5439. 'read_flag' => 0,
  5440. );
  5441. }
  5442. // Keep copy of this message in outbox?
  5443. if ($keepcopy) {
  5444. $xref_entries[] = array(
  5445. 'user_id' => $from,
  5446. 'pm_folder_id' => 0,
  5447. 'special_folder' => PHORUM_PM_OUTBOX,
  5448. 'read_flag' => 1,
  5449. );
  5450. }
  5451. // Prepare message meta data.
  5452. $meta = phorum_db_interact(DB_RETURN_QUOTED, serialize(array(
  5453. 'recipients' => $rcpts
  5454. )));
  5455. // Create the message.
  5456. $pm_id = phorum_db_interact(
  5457. DB_RETURN_NEWID,
  5458. "INSERT INTO {$PHORUM['pm_messages_table']}
  5459. (user_id, author, subject,
  5460. message, datestamp, meta)
  5461. VALUES ($from, '$fromuser', '$subject',
  5462. '$message', '".time()."', '$meta')",
  5463. NULL,
  5464. DB_MASTERQUERY
  5465. );
  5466. // Put the message in the recipient inboxes.
  5467. foreach ($xref_entries as $xref)
  5468. {
  5469. phorum_db_interact(
  5470. DB_RETURN_RES,
  5471. "INSERT INTO {$PHORUM['pm_xref_table']}
  5472. (user_id, pm_folder_id,
  5473. special_folder, pm_message_id,
  5474. read_flag, reply_flag)
  5475. VALUES ({$xref['user_id']}, {$xref['pm_folder_id']},
  5476. '{$xref['special_folder']}', $pm_id,
  5477. {$xref['read_flag']}, 0)",
  5478. NULL,
  5479. DB_MASTERQUERY
  5480. );
  5481. }
  5482. return $pm_id;
  5483. }
  5484. // }}}
  5485. // {{{ Function: phorum_db_pm_setflag()
  5486. /**
  5487. * Update a flag for a private message.
  5488. *
  5489. * @param integer $pm_id
  5490. * The id of the message to update.
  5491. *
  5492. * @param integer $flag
  5493. * The flag to update. Possible flags are: PHORUM_PM_READ_FLAG and
  5494. * PHORUM_PM_REPLY_FLAG.
  5495. *
  5496. * @param boolean $value
  5497. * The value for the flag (either TRUE or FALSE).
  5498. *
  5499. * @param $user_id
  5500. * The user to set a flag for or NULL to use the active Phorum
  5501. * user (default).
  5502. */
  5503. function phorum_db_pm_setflag($pm_id, $flag, $value, $user_id = NULL)
  5504. {
  5505. $PHORUM = $GLOBALS['PHORUM'];
  5506. settype($pm_id, 'int');
  5507. if ($flag != PHORUM_PM_READ_FLAG &&
  5508. $flag != PHORUM_PM_REPLY_FLAG) trigger_error(
  5509. 'phorum_db_pm_setflag(): Illegal value "' . htmlspecialchars($flag) .
  5510. '" for parameter $flag',
  5511. E_USER_WARNING
  5512. );
  5513. $value = $value ? 1 : 0;
  5514. if ($user_id === NULL) $user_id = $PHORUM['user']['user_id'];
  5515. settype($user_id, 'int');
  5516. // Update the flag in the database.
  5517. phorum_db_interact(
  5518. DB_RETURN_RES,
  5519. "UPDATE {$PHORUM['pm_xref_table']}
  5520. SET $flag = $value
  5521. WHERE pm_message_id = $pm_id AND
  5522. user_id = $user_id",
  5523. NULL,
  5524. DB_MASTERQUERY
  5525. );
  5526. // Update message counters.
  5527. if ($flag == PHORUM_PM_READ_FLAG) {
  5528. phorum_db_pm_update_message_info($pm_id);
  5529. }
  5530. }
  5531. // }}}
  5532. // {{{ Function: phorum_db_pm_delete()
  5533. /**
  5534. * Delete a private message from a folder.
  5535. *
  5536. * @param integer $pm_id
  5537. * The id of the private message to delete
  5538. *
  5539. * @param mixed $folder
  5540. * The folder from which to delete the message
  5541. *
  5542. * @param integer $user_id
  5543. * The id of the user to delete the message for
  5544. * or NULL to use the active Phorum user (default).
  5545. */
  5546. function phorum_db_pm_delete($pm_id, $folder, $user_id = NULL)
  5547. {
  5548. $PHORUM = $GLOBALS['PHORUM'];
  5549. settype($pm_id, 'int');
  5550. if ($user_id === NULL) $user_id = $PHORUM['user']['user_id'];
  5551. settype($user_id, 'int');
  5552. if (is_numeric($folder)) {
  5553. $folder_where = "pm_folder_id = $folder";
  5554. } elseif ($folder == PHORUM_PM_INBOX || $folder == PHORUM_PM_OUTBOX) {
  5555. $folder_where = "(pm_folder_id = 0 AND special_folder = '$folder')";
  5556. } else trigger_error(
  5557. 'phorum_db_pm_delete(): Illegal folder "' .
  5558. htmlspecialchars($folder).'" requested for user id "'.$user_id.'"',
  5559. E_USER_ERROR
  5560. );
  5561. phorum_db_interact(
  5562. DB_RETURN_RES,
  5563. "DELETE FROM {$PHORUM['pm_xref_table']}
  5564. WHERE user_id = $user_id AND
  5565. pm_message_id = $pm_id AND
  5566. $folder_where",
  5567. NULL,
  5568. DB_MASTERQUERY
  5569. );
  5570. // Update message counters.
  5571. phorum_db_pm_update_message_info($pm_id);
  5572. }
  5573. // }}}
  5574. // {{{ Function: phorum_db_pm_move()
  5575. /**
  5576. * Move a private message to a different folder.
  5577. *
  5578. * @param integer $pm_id
  5579. * The id of the private message to move.
  5580. *
  5581. * @param mixed $from
  5582. * The folder to move the message from.
  5583. *
  5584. * @param mixed $to
  5585. * The folder to move the message to.
  5586. *
  5587. * @param mixed $user_id
  5588. * The id or the user to move the message for
  5589. * or NULL to use the active Phorum user (default).
  5590. */
  5591. function phorum_db_pm_move($pm_id, $from, $to, $user_id = NULL)
  5592. {
  5593. $PHORUM = $GLOBALS['PHORUM'];
  5594. settype($pm_id, 'int');
  5595. if ($user_id === NULL) $user_id = $PHORUM['user']['user_id'];
  5596. settype($user_id, 'int');
  5597. if (is_numeric($from)) {
  5598. $folder_where = "pm_folder_id = $from";
  5599. } elseif ($from == PHORUM_PM_INBOX || $from == PHORUM_PM_OUTBOX) {
  5600. $folder_where = "(pm_folder_id = 0 AND special_folder = '$from')";
  5601. } else trigger_error(
  5602. 'phorum_db_pm_move(): Illegal source folder "' .
  5603. htmlspecialchars($from).'" requested for user id "'.$user_id.'"',
  5604. E_USER_ERROR
  5605. );
  5606. if (is_numeric($to)) {
  5607. $pm_folder_id = $to;
  5608. $special_folder = 'NULL';
  5609. } elseif ($to == PHORUM_PM_INBOX || $to == PHORUM_PM_OUTBOX) {
  5610. $pm_folder_id = 0;
  5611. $special_folder = "'$to'";
  5612. } else trigger_error(
  5613. 'phorum_db_pm_move(): Illegal target folder "' .
  5614. htmlspecialchars($to).'" requested for user_id "'.$user_id.'"',
  5615. E_USER_ERROR
  5616. );
  5617. phorum_db_interact(
  5618. DB_RETURN_RES,
  5619. "UPDATE {$PHORUM['pm_xref_table']}
  5620. SET pm_folder_id = $pm_folder_id,
  5621. special_folder = $special_folder
  5622. WHERE user_id = $user_id AND
  5623. pm_message_id = $pm_id AND
  5624. $folder_where",
  5625. NULL,
  5626. DB_MASTERQUERY
  5627. );
  5628. }
  5629. // }}}
  5630. // {{{ Function: phorum_db_pm_update_message_info()
  5631. /**
  5632. * Update the meta information for a message.
  5633. *
  5634. * This function will update the meta information using the information
  5635. * from the xrefs table. If we find that no xrefs are available for the
  5636. * message anymore, the message will be deleted from the database.
  5637. *
  5638. * @param integer $pm_id
  5639. * The id of the private message for which to update the meta information.
  5640. */
  5641. function phorum_db_pm_update_message_info($pm_id)
  5642. {
  5643. $PHORUM = $GLOBALS['PHORUM'];
  5644. settype($pm_id, 'int');
  5645. // Retrieve the meta data for the private message.
  5646. $pm = phorum_db_interact(
  5647. DB_RETURN_ASSOC,
  5648. "SELECT meta
  5649. FROM {$PHORUM['pm_messages_table']}
  5650. WHERE pm_message_id = $pm_id",
  5651. NULL,
  5652. DB_MASTERQUERY
  5653. );
  5654. # Return immediately if no message was found.
  5655. if (empty($pm)) return;
  5656. // Find the xrefs for this message.
  5657. $xrefs = phorum_db_interact(
  5658. DB_RETURN_ROWS,
  5659. "SELECT user_id, read_flag
  5660. FROM {$PHORUM['pm_xref_table']}
  5661. WHERE pm_message_id = $pm_id",
  5662. NULL,
  5663. DB_MASTERQUERY
  5664. );
  5665. // No xrefs left? Then the message can be fully deleted.
  5666. if (count($xrefs) == 0) {
  5667. phorum_db_interact(
  5668. DB_RETURN_RES,
  5669. "DELETE FROM {$PHORUM['pm_messages_table']}
  5670. WHERE pm_message_id = $pm_id",
  5671. NULL,
  5672. DB_MASTERQUERY
  5673. );
  5674. return;
  5675. }
  5676. // Update the read flags for the recipients in the meta data.
  5677. $meta = unserialize($pm['meta']);
  5678. $rcpts = $meta['recipients'];
  5679. foreach ($xrefs as $xref) {
  5680. // Only update if available. A copy that is kept in the outbox will
  5681. // not be in the meta list, so if the copy is read, the meta data
  5682. // does not have to be updated here.
  5683. if (isset($rcpts[$xref[0]])) {
  5684. $rcpts[$xref[0]]['read_flag'] = $xref[1];
  5685. }
  5686. }
  5687. $meta['recipients'] = $rcpts;
  5688. // Store the new meta data.
  5689. $meta = phorum_db_interact(DB_RETURN_QUOTED, serialize($meta));
  5690. phorum_db_interact(
  5691. DB_RETURN_RES,
  5692. "UPDATE {$PHORUM['pm_messages_table']}
  5693. SET meta = '$meta'
  5694. WHERE pm_message_id = $pm_id",
  5695. NULL,
  5696. DB_MASTERQUERY
  5697. );
  5698. }
  5699. // }}}
  5700. // {{{ Function: phorum_db_pm_is_buddy()
  5701. /**
  5702. * Check if a user is buddy of another user.
  5703. *
  5704. * @param integer $buddy_user_id
  5705. * The user_id for which to check if the user is a buddy.
  5706. *
  5707. * @param mixed $user_id
  5708. * The user_id for which the buddy list must be checked
  5709. * or NULL to use the active Phorum user (default).
  5710. *
  5711. * @return mixed
  5712. * If the user is a buddy, then the pm_buddy_id for the buddy will be
  5713. * returned. If not, then NULL will be returned.
  5714. */
  5715. function phorum_db_pm_is_buddy($buddy_user_id, $user_id = NULL)
  5716. {
  5717. $PHORUM = $GLOBALS['PHORUM'];
  5718. settype($buddy_user_id, 'int');
  5719. if ($user_id === NULL) $user_id = $PHORUM['user']['user_id'];
  5720. settype($user_id, 'int');
  5721. $pm_buddy_id = phorum_db_interact(
  5722. DB_RETURN_VALUE,
  5723. "SELECT pm_buddy_id
  5724. FROM {$PHORUM['pm_buddies_table']}
  5725. WHERE user_id = $user_id AND
  5726. buddy_user_id = $buddy_user_id"
  5727. );
  5728. return $pm_buddy_id;
  5729. }
  5730. // }}}
  5731. // {{{ Function: phorum_db_pm_buddy_add()
  5732. /**
  5733. * Add a buddy for a user.
  5734. *
  5735. * @param integer $buddy_user_id
  5736. * The user_id that has to be added as a buddy.
  5737. *
  5738. * @param mixed $user_id
  5739. * The user_id the buddy has to be added for
  5740. * or NULL to use the active Phorum user (default).
  5741. *
  5742. * @return mixed
  5743. * The id that was assigned to the new buddy or the existing id if
  5744. * the buddy already existed. If no user can be found for the
  5745. * $buddy_user_id parameter, then NULL will be returned.
  5746. */
  5747. function phorum_db_pm_buddy_add($buddy_user_id, $user_id = NULL)
  5748. {
  5749. $PHORUM = $GLOBALS['PHORUM'];
  5750. settype($buddy_user_id, 'int');
  5751. if ($user_id === NULL) $user_id = $PHORUM['user']['user_id'];
  5752. settype($user_id, 'int');
  5753. // Check if the buddy_user_id is a valid user_id.
  5754. $valid = phorum_db_user_get($buddy_user_id, FALSE);
  5755. if (! $valid) return NULL;
  5756. // See if the user is already a buddy.
  5757. $pm_buddy_id = phorum_db_pm_is_buddy($buddy_user_id);
  5758. // If not, then create insert a new buddy relation.
  5759. if ($pm_buddy_id === NULL) {
  5760. $pm_buddy_id = phorum_db_interact(
  5761. DB_RETURN_NEWID,
  5762. "INSERT INTO {$PHORUM['pm_buddies_table']}
  5763. (user_id, buddy_user_id)
  5764. VALUES ($user_id, $buddy_user_id)",
  5765. NULL,
  5766. DB_MASTERQUERY
  5767. );
  5768. }
  5769. return $pm_buddy_id;
  5770. }
  5771. // }}}
  5772. // {{{ Function: phorum_db_pm_buddy_delete()
  5773. /**
  5774. * Delete a buddy for a user.
  5775. *
  5776. * @param integer $buddy_user_id
  5777. * The user_id that has to be deleted as a buddy.
  5778. *
  5779. * @param mixed $user_id
  5780. * The user_id the buddy has to be delete for
  5781. * or NULL to use the active Phorum user (default).
  5782. */
  5783. function phorum_db_pm_buddy_delete($buddy_user_id, $user_id = NULL)
  5784. {
  5785. $PHORUM = $GLOBALS['PHORUM'];
  5786. settype($buddy_user_id, 'int');
  5787. if ($user_id === NULL) $user_id = $PHORUM['user']['user_id'];
  5788. settype($user_id, 'int');
  5789. phorum_db_interact(
  5790. DB_RETURN_RES,
  5791. "DELETE FROM {$PHORUM['pm_buddies_table']}
  5792. WHERE buddy_user_id = $buddy_user_id AND
  5793. user_id = $user_id",
  5794. NULL,
  5795. DB_MASTERQUERY
  5796. );
  5797. }
  5798. // }}}
  5799. // {{{ Function: phorum_db_pm_buddy_list()
  5800. /**
  5801. * Retrieve a list of buddies for a user.
  5802. *
  5803. * @param mixed $user_id
  5804. * The user_id for which to retrieve the buddies or NULL to user the
  5805. * active Phorum user (default).
  5806. *
  5807. * @param boolean $find_mutual
  5808. * Whether to find mutual buddies or not (default FALSE).
  5809. *
  5810. * @return array
  5811. * An array of buddies. The keys in this array are user_ids. The values
  5812. * are arrays, which contain the field "user_id" and possibly the
  5813. * boolean field "mutual" if the $find_mutual parameter was set to
  5814. * a true value.
  5815. */
  5816. function phorum_db_pm_buddy_list($user_id = NULL, $find_mutual = FALSE)
  5817. {
  5818. $PHORUM = $GLOBALS['PHORUM'];
  5819. if ($user_id === NULL) $user_id = $PHORUM['user']['user_id'];
  5820. settype($user_id, 'int');
  5821. settype($find_mutual, 'bool');
  5822. // Retrieve all buddies for this user.
  5823. $buddies = phorum_db_interact(
  5824. DB_RETURN_ASSOCS,
  5825. "SELECT buddy_user_id AS user_id
  5826. FROM {$PHORUM['pm_buddies_table']}
  5827. WHERE user_id = $user_id",
  5828. 'user_id'
  5829. );
  5830. // If we do not have to lookup mutual buddies, we're done.
  5831. if (! $find_mutual) return $buddies;
  5832. // Initialize mutual buddy value.
  5833. foreach ($buddies as $id => $data) {
  5834. $buddies[$id]['mutual'] = FALSE;
  5835. }
  5836. // Retrieve all mutual buddies.
  5837. $mutuals = phorum_db_interact(
  5838. DB_RETURN_ROWS,
  5839. "SELECT DISTINCT a.buddy_user_id AS buddy_user_id
  5840. FROM {$PHORUM['pm_buddies_table']} AS a,
  5841. {$PHORUM['pm_buddies_table']} AS b
  5842. WHERE a.user_id = $user_id AND
  5843. b.user_id = a.buddy_user_id AND
  5844. b.buddy_user_id = $user_id"
  5845. );
  5846. // Merge the mutual buddies with the buddies array.
  5847. foreach ($mutuals as $mutual) {
  5848. $buddies[$mutual[0]]['mutual'] = TRUE;
  5849. }
  5850. return $buddies;
  5851. }
  5852. // }}}
  5853. // {{{ Function: phorum_db_split_thread()
  5854. /**
  5855. * Split a thread.
  5856. *
  5857. * @param integer $message_id
  5858. * The id of the message at which to split a thread.
  5859. *
  5860. * @param integer $forum_id
  5861. * The id of the forum in which the message can be found.
  5862. */
  5863. function phorum_db_split_thread($message_id, $forum_id)
  5864. {
  5865. settype($message_id, 'int');
  5866. settype($forum_id, 'int');
  5867. if ($message_id > 0 && $forum_id > 0)
  5868. {
  5869. // Retrieve the message tree for all messages below the split message.
  5870. // This tree is used for updating the thread ids of the children
  5871. // below the split message.
  5872. $tree = phorum_db_get_messagetree($message_id, $forum_id);
  5873. // Turn the message into a thread starter message.
  5874. phorum_db_interact(
  5875. DB_RETURN_RES,
  5876. "UPDATE {$GLOBALS['PHORUM']['message_table']}
  5877. SET thread = $message_id,
  5878. parent_id = 0
  5879. WHERE message_id = $message_id",
  5880. NULL,
  5881. DB_MASTERQUERY
  5882. );
  5883. // Link the messages below the split message to the split off thread.
  5884. phorum_db_interact(
  5885. DB_RETURN_RES,
  5886. "UPDATE {$GLOBALS['PHORUM']['message_table']}
  5887. SET thread = $message_id
  5888. WHERE message_id IN ($tree)",
  5889. NULL,
  5890. DB_MASTERQUERY
  5891. );
  5892. }
  5893. }
  5894. // }}}
  5895. // {{{ Function: phorum_db_get_max_messageid()
  5896. /**
  5897. * Retrieve the maximum message_id in the database.
  5898. *
  5899. * @return integer $max_id
  5900. * The maximum available message_id or 0 (zero)
  5901. * if no message was found at all.
  5902. */
  5903. function phorum_db_get_max_messageid()
  5904. {
  5905. $PHORUM = $GLOBALS['PHORUM'];
  5906. $maxid = phorum_db_interact(
  5907. DB_RETURN_VALUE,
  5908. "SELECT max(message_id)
  5909. FROM {$PHORUM["message_table"]}"
  5910. );
  5911. return $maxid === NULL ? 0 : $maxid;
  5912. }
  5913. // }}}
  5914. // {{{ Function: phorum_db_increment_viewcount()
  5915. /**
  5916. * Increment the viewcount field for a post.
  5917. *
  5918. * @param integer $message_id
  5919. * The id of the message for which to increment the viewcount.
  5920. *
  5921. * @param boolean $thread_id
  5922. * If this parameter is set to a thread_id, then the threadviewcount
  5923. * for that thread will be incremented as well.
  5924. */
  5925. function phorum_db_increment_viewcount($message_id, $thread_id = NULL)
  5926. {
  5927. settype($message_id, 'int');
  5928. if ($thread_id !== NULL) settype($thread_id, 'int');
  5929. // Check if the message is the thread starter, in which case we can
  5930. // handle the increment with only one SQL query later on in this function.
  5931. $tvc = '';
  5932. if ($thread_id !== NULL) {
  5933. if ($thread_id == $message_id) {
  5934. $tvc = ',threadviewcount = threadviewcount + 1';
  5935. } else {
  5936. phorum_db_interact(
  5937. DB_RETURN_RES,
  5938. "UPDATE {$GLOBALS['PHORUM']['message_table']}
  5939. SET threadviewcount = threadviewcount + 1
  5940. WHERE message_id = $thread_id",
  5941. NULL,
  5942. DB_MASTERQUERY
  5943. );
  5944. }
  5945. }
  5946. phorum_db_interact(
  5947. DB_RETURN_RES,
  5948. "UPDATE {$GLOBALS['PHORUM']['message_table']}
  5949. SET viewcount = viewcount + 1
  5950. $tvc
  5951. WHERE message_id = $message_id",
  5952. NULL,
  5953. DB_MASTERQUERY
  5954. );
  5955. }
  5956. // }}}
  5957. // {{{ Function: phorum_db_rebuild_search_data()
  5958. /**
  5959. * Rebuild the search table data from scratch.
  5960. */
  5961. function phorum_db_rebuild_search_data()
  5962. {
  5963. $PHORUM = $GLOBALS['PHORUM'];
  5964. // Delete all records from the search table.
  5965. phorum_db_interact(
  5966. DB_RETURN_RES,
  5967. "TRUNCATE TABLE {$PHORUM['search_table']}",
  5968. NULL,
  5969. DB_GLOBALQUERY | DB_MASTERQUERY
  5970. );
  5971. // Rebuild all search data from scratch.
  5972. phorum_db_interact(
  5973. DB_RETURN_RES,
  5974. "INSERT INTO {$PHORUM['search_table']}
  5975. (message_id, search_text, forum_id)
  5976. SELECT message_id,
  5977. concat(author, ' | ', subject, ' | ', body),
  5978. forum_id
  5979. FROM {$PHORUM['message_table']}",
  5980. NULL,
  5981. DB_GLOBALQUERY | DB_MASTERQUERY
  5982. );
  5983. }
  5984. // }}}
  5985. // {{{ Function: phorum_db_rebuild_user_posts()
  5986. /**
  5987. * Rebuild the user post counts from scratch.
  5988. */
  5989. function phorum_db_rebuild_user_posts()
  5990. {
  5991. $PHORUM = $GLOBALS['PHORUM'];
  5992. // Reset the post count for all users.
  5993. phorum_db_interact(
  5994. DB_RETURN_RES,
  5995. "UPDATE {$PHORUM['user_table']}
  5996. SET posts = 0",
  5997. NULL,
  5998. DB_GLOBALQUERY | DB_MASTERQUERY
  5999. );
  6000. // Retrieve the post counts for all user_ids in the message table.
  6001. $postcounts = phorum_db_interact(
  6002. DB_RETURN_ROWS,
  6003. "SELECT user_id, count(*)
  6004. FROM {$PHORUM['message_table']}
  6005. GROUP BY user_id",
  6006. NULL,
  6007. DB_GLOBALQUERY | DB_MASTERQUERY
  6008. );
  6009. // Set the post counts for the users to their correct values.
  6010. foreach ($postcounts as $postcount) {
  6011. phorum_db_interact(
  6012. DB_RETURN_RES,
  6013. "UPDATE {$PHORUM['user_table']}
  6014. SET posts = {$postcount[1]}
  6015. WHERE user_id = {$postcount[0]}",
  6016. NULL,
  6017. DB_MASTERQUERY
  6018. );
  6019. }
  6020. }
  6021. // }}}
  6022. // {{{ Function: phorum_db_user_search_custom_profile_field()
  6023. /**
  6024. * Search for users, based on a simple search condition,
  6025. * which can be used to search on custom profile fields.
  6026. *
  6027. * The parameters $field_id, $value and $operator (which are used for defining
  6028. * the search condition) can be arrays or single values. If arrays are used,
  6029. * then all three parameter arrays must contain the same number of elements
  6030. * and the key values in the arrays must be the same.
  6031. *
  6032. * @param mixed $field_id
  6033. * The custom profile field id (integer) or ids (array) to search on.
  6034. *
  6035. * @param mixed $value
  6036. * The value (string) or values (array) to search for.
  6037. *
  6038. * @param mixed $operator
  6039. * The operator (string) or operators (array) to use. Valid operators are
  6040. * "=", "!=", "<>", "<", ">", ">=" and "<=", "*", '?*', '*?'. The
  6041. * "*" operator is for executing a "LIKE '%value%'" matching query. The
  6042. * "?*" and "*?" operators are for executing a "LIKE 'value%'" or a
  6043. * "LIKE '%value' matching query.
  6044. *
  6045. * @param boolean $return_array
  6046. * If this parameter has a true value, then an array of all matching
  6047. * user_ids will be returned. Else, a single user_id will be returned.
  6048. *
  6049. * @param string $type
  6050. * The type of search to perform. This can be one of:
  6051. * - AND match against all fields
  6052. * - OR match against any of the fields
  6053. *
  6054. * @param integer $offset
  6055. * The result page offset starting with 0.
  6056. *
  6057. * @param integer $length
  6058. * The result page length (nr. of results per page)
  6059. * or 0 (zero, the default) to return all results.
  6060. *
  6061. * @return mixed
  6062. * An array of matching user_ids or a single user_id (based on the
  6063. * $return_array parameter). If no user_ids can be found at all,
  6064. * then 0 (zero) will be returned.
  6065. */
  6066. function phorum_db_user_search_custom_profile_field($field_id, $value, $operator = '=', $return_array = FALSE, $type = 'AND', $offset = 0, $length = 0)
  6067. {
  6068. $PHORUM = $GLOBALS['PHORUM'];
  6069. settype($return_array, 'bool');
  6070. settype($offset, 'int');
  6071. settype($length, 'int');
  6072. // Convert all search condition parameters to arrays.
  6073. if (!is_array($field_id)) $field_id = array($field_id);
  6074. if (!is_array($value)) $value = array($value);
  6075. if (!is_array($operator)) $operator = array($operator);
  6076. // Basic check to see if all condition arrays contain the
  6077. // same number of elements.
  6078. if (count($field_id) != count($value) ||
  6079. count($field_id) != count($operator)) trigger_error(
  6080. 'phorum_db_user_search_custom_profile_field(): ' .
  6081. 'array parameters $field_id, $value, and $operator do not contain ' .
  6082. 'the same number of elements',
  6083. E_USER_ERROR
  6084. );
  6085. $type = strtoupper($type);
  6086. if ($type != 'AND' && $type != 'OR') trigger_error(
  6087. 'phorum_db_user_search_custom_profile_field(): ' .
  6088. 'Illegal search type parameter (must be either AND" or "OR")',
  6089. E_USER_ERROR
  6090. );
  6091. $valid_operators = array('=', '<>', '!=', '>', '<', '>=', '<=', '*', '?*', '*?');
  6092. // Construct the required "WHERE" clause.
  6093. $clauses = array();
  6094. foreach ($field_id as $key => $id) {
  6095. if (in_array($operator[$key], $valid_operators)) {
  6096. settype($id, 'int');
  6097. $value[$key] = phorum_db_interact(DB_RETURN_QUOTED, $value[$key]);
  6098. if ($operator[$key] == '*') {
  6099. $clauses[] = "(type = $id AND data LIKE '%$value[$key]%')";
  6100. } else if ($operator[$key] == '?*') {
  6101. $clauses[] = "(type = $id AND data LIKE '$value[$key]%')";
  6102. } else if ($operator[$key] == '*?') {
  6103. $clauses[] = "(type = $id AND data LIKE '%$value[$key]')";
  6104. } else {
  6105. $clauses[] = "(type = $id AND data $operator[$key] '$value[$key]')";
  6106. }
  6107. }
  6108. }
  6109. if (!empty($clauses)) {
  6110. $where = 'WHERE ' . implode(" OR ", $clauses);
  6111. } else {
  6112. $where = '';
  6113. }
  6114. // Construct the required "LIMIT" clause.
  6115. if (!empty($length)) {
  6116. $limit = "LIMIT $offset, $length";
  6117. } else {
  6118. // If we do not need to return an array, the we can limit the
  6119. // query results to only one record.
  6120. $limit = $return_array ? '' : 'LIMIT 1';
  6121. }
  6122. // Build the final query.
  6123. if ($type == 'OR' || count($clauses) == 1)
  6124. {
  6125. $sql = "SELECT DISTINCT(user_id)
  6126. FROM {$PHORUM['user_custom_fields_table']}
  6127. $where
  6128. $limit";
  6129. } else {
  6130. $sql = "SELECT user_id
  6131. FROM {$PHORUM['user_custom_fields_table']}
  6132. $where
  6133. GROUP BY user_id
  6134. HAVING count(*) = " . count($clauses) . " " .
  6135. $limit;
  6136. }
  6137. // Retrieve the matching user_ids from the database.
  6138. $user_ids = phorum_db_interact(
  6139. DB_RETURN_ROWS, $sql, 0 // keyfield 0 is the user_id
  6140. );
  6141. // No user_ids found at all?
  6142. if (count($user_ids) == 0) return 0;
  6143. // Return an array of user_ids.
  6144. if ($return_array) {
  6145. foreach ($user_ids as $id => $user_id) $user_ids[$id] = $user_id[0];
  6146. return $user_ids;
  6147. }
  6148. // Return a single user_id.
  6149. list ($user_id, $dummy) = each($user_ids);
  6150. return $user_id;
  6151. }
  6152. // }}}
  6153. // {{{ Function: phorum_db_metaquery_compile()
  6154. /**
  6155. * Translates a message searching meta query into a real SQL WHERE
  6156. * statement for this database backend. The meta query can be used to
  6157. * define extended SQL queries, based on a meta description of the
  6158. * search that has to be performed on the database.
  6159. *
  6160. * The meta query is an array, containing:
  6161. * - query conditions
  6162. * - grouping using "(" and ")"
  6163. * - AND/OR specifications using "AND" and "OR".
  6164. *
  6165. * The query conditions are arrays, containing the following elements:
  6166. * <ul>
  6167. * <li>condition<br>
  6168. * <br>
  6169. * A description of a condition. The syntax for this is:
  6170. * <field name to query> <operator> <match specification><br>
  6171. * <br>
  6172. * The <field name to query> is a field in the message query that
  6173. * we are running in this function.<br>
  6174. * <br>
  6175. * The <operator> can be one of "=", "!=", "<", "<=", ">", ">=".
  6176. * Note that there is nothing like "LIKE" or "NOT LIKE". If a "LIKE"
  6177. * query has to be done, then that is setup through the
  6178. * <match specification> (see below).<br>
  6179. * <br>
  6180. * The <match specification> tells us with what the field should be
  6181. * matched. The string "QUERY" inside the specification is preserved to
  6182. * specify at which spot in the query the "query" element from the
  6183. * condition array should be inserted. If "QUERY" is not available in
  6184. * the specification, then a match is made on the exact value in the
  6185. * specification. To perform "LIKE" searches (case insensitive wildcard
  6186. * searches), you can use the "*" wildcard character in the specification
  6187. * to do so.<br><br>
  6188. * </li>
  6189. * <li>query<br>
  6190. * <br>
  6191. * The data to use in the query, in case the condition element has a
  6192. * <match specification> that uses "QUERY" in it.
  6193. * </li>
  6194. * </ul>
  6195. * Example:
  6196. * <code>
  6197. * $metaquery = array(
  6198. * array(
  6199. * "condition" => "field1 = *QUERY*",
  6200. * "query" => "test data"
  6201. * ),
  6202. * "AND",
  6203. * "(",
  6204. * array("condition" => "field2 = whatever"),
  6205. * "OR",
  6206. * array("condition" => "field2 = something else"),
  6207. * ")"
  6208. * );
  6209. * </code>
  6210. *
  6211. * For MySQL, this would be turned into the MySQL WHERE statement:
  6212. * <code>
  6213. * ... WHERE field1 LIKE '%test data%'
  6214. * AND (field2 = 'whatever' OR field2 = 'something else')
  6215. * </code>
  6216. *
  6217. * @param array $metaquery
  6218. * A meta query description array.
  6219. *
  6220. * @return array
  6221. * An array containing two elements. The first element is either
  6222. * TRUE or FALSE, based on the success state of the function call
  6223. * (FALSE means that there was an error). The second argument contains
  6224. * either a WHERE statement or an error message.
  6225. */
  6226. function phorum_db_metaquery_compile($metaquery)
  6227. {
  6228. $where = '';
  6229. $expect_condition = TRUE;
  6230. $expect_groupstart = TRUE;
  6231. $expect_groupend = FALSE;
  6232. $expect_combine = FALSE;
  6233. $in_group = 0;
  6234. foreach ($metaquery as $part)
  6235. {
  6236. // Found a new condition.
  6237. if ($expect_condition && is_array($part))
  6238. {
  6239. $cond = trim($part['condition']);
  6240. if (preg_match('/^([\w_\.]+)\s+(!?=|<=?|>=?)\s+(\S*)$/', $cond, $m))
  6241. {
  6242. $field = $m[1];
  6243. $comp = $m[2];
  6244. $match = $m[3];
  6245. $matchtokens = preg_split(
  6246. '/(\*|QUERY|NULL)/',
  6247. $match, -1,
  6248. PREG_SPLIT_DELIM_CAPTURE|PREG_SPLIT_NO_EMPTY
  6249. );
  6250. $matchsql = "'";
  6251. $is_like_query = FALSE;
  6252. foreach ($matchtokens as $m) {
  6253. if ($m == '*') {
  6254. $is_like_query = TRUE;
  6255. $matchsql .= '%';
  6256. } elseif ($m == 'QUERY') {
  6257. $p = $part['query'];
  6258. $matchsql .= phorum_db_interact(DB_RETURN_QUOTED, $p);
  6259. } else {
  6260. $matchsql .= phorum_db_interact(DB_RETURN_QUOTED, $m);
  6261. }
  6262. }
  6263. $matchsql .= "'";
  6264. if ($is_like_query)
  6265. {
  6266. if ($comp == '=') { $comp = ' LIKE '; }
  6267. elseif ($comp == '!=') { $comp = ' NOT LIKE '; }
  6268. else return array(
  6269. FALSE,
  6270. 'Illegal metaquery token ' . htmlspecialchars($cond) .
  6271. ": wildcard match does not combine with $comp operator"
  6272. );
  6273. }
  6274. $where .= "$field $comp $matchsql ";
  6275. } else {
  6276. return array(
  6277. FALSE,
  6278. 'Illegal metaquery token ' . htmlspecialchars($cond) .
  6279. ': condition does not match the required format'
  6280. );
  6281. }
  6282. $expect_condition = FALSE;
  6283. $expect_groupstart = FALSE;
  6284. $expect_groupend = $in_group;
  6285. $expect_combine = TRUE;
  6286. }
  6287. // Found a new group start.
  6288. elseif ($expect_groupstart && $part == '(')
  6289. {
  6290. $where .= '(';
  6291. $in_group ++;
  6292. $expect_condition = TRUE;
  6293. $expect_groupstart = FALSE;
  6294. $expect_groupend = FALSE;
  6295. $expect_combine = FALSE;
  6296. }
  6297. // Found a new group end.
  6298. elseif ($expect_groupend && $part == ')')
  6299. {
  6300. $where .= ') ';
  6301. $in_group --;
  6302. $expect_condition = FALSE;
  6303. $expect_groupstart = FALSE;
  6304. $expect_groupend = $in_group;
  6305. $expect_combine = TRUE;
  6306. }
  6307. // Found a combine token (AND or OR).
  6308. elseif ($expect_combine && preg_match('/^(OR|AND)$/i', $part, $m))
  6309. {
  6310. $where .= strtoupper($m[1]) . ' ';
  6311. $expect_condition = TRUE;
  6312. $expect_groupstart = TRUE;
  6313. $expect_groupend = FALSE;
  6314. $expect_combine = FALSE;
  6315. }
  6316. // Unexpected or illegal token.
  6317. else trigger_error(
  6318. 'Internal error: unexpected token in metaquery description: ' .
  6319. (is_array($part) ? 'condition' : htmlspecialchars($part)),
  6320. E_USER_ERROR
  6321. );
  6322. }
  6323. if ($expect_groupend) die ('Internal error: unclosed group in metaquery');
  6324. // If the metaquery is empty, then provide a safe true WHERE statement.
  6325. if ($where == '') { $where = '1 = 1'; }
  6326. return array(TRUE, $where);
  6327. }
  6328. // }}}
  6329. // {{{ Function: phorum_db_metaquery_messagesearch()
  6330. /**
  6331. * Run a search on the messages, using a metaquery. See the documentation
  6332. * for the phorum_db_metaquery_compile() function for more info on the
  6333. * metaquery syntax.
  6334. *
  6335. * The query that is run here, does create a view on the messages, which
  6336. * includes some thread and user info. This is used so these can also
  6337. * be taken into account when selecting messages. For the condition elements
  6338. * in the meta query, you can use fully qualified field names for the
  6339. * <field name to query>. You can use message.*, user.* and thread.* for this.
  6340. *
  6341. * The primary goal for this function is to provide a backend for the
  6342. * message pruning interface.
  6343. *
  6344. * @param array $metaquery
  6345. * A metaquery array. See {@link phorum_db_metaquery_compile()} for
  6346. * more information about the metaquery syntax.
  6347. *
  6348. * @return array
  6349. * An array of message records.
  6350. */
  6351. function phorum_db_metaquery_messagesearch($metaquery)
  6352. {
  6353. $PHORUM = $GLOBALS['PHORUM'];
  6354. // Compile the metaquery into a where statement.
  6355. list($success, $where) = phorum_db_metaquery_compile($metaquery);
  6356. if (!$success) trigger_error($where, E_USER_ERROR);
  6357. // Retrieve matching messages.
  6358. $messages = phorum_db_interact(
  6359. DB_RETURN_ASSOCS,
  6360. "SELECT message.message_id AS message_id,
  6361. message.thread AS thread,
  6362. message.parent_id AS parent_id,
  6363. message.forum_id AS forum_id,
  6364. message.subject AS subject,
  6365. message.author AS author,
  6366. message.datestamp AS datestamp,
  6367. message.body AS body,
  6368. message.ip AS ip,
  6369. message.status AS status,
  6370. message.user_id AS user_id,
  6371. user.username AS user_username,
  6372. thread.closed AS thread_closed,
  6373. thread.modifystamp AS thread_modifystamp,
  6374. thread.thread_count AS thread_count
  6375. FROM {$PHORUM['message_table']} AS thread,
  6376. {$PHORUM['message_table']} AS message
  6377. LEFT JOIN {$PHORUM['user_table']} AS user
  6378. ON message.user_id = user.user_id
  6379. WHERE message.thread = thread.message_id AND
  6380. ($where)
  6381. ORDER BY message_id ASC",
  6382. 'message_id'
  6383. );
  6384. return $messages;
  6385. }
  6386. // }}}
  6387. // {{{ Function: phorum_db_create_tables()
  6388. /**
  6389. * Create the tables that are needed in the database. This function will
  6390. * only be called at install time. After installation, changes in the
  6391. * database schema will be handled by the database upgrade system.
  6392. *
  6393. * @return mixed
  6394. * NULL on success or an error message on failure.
  6395. *
  6396. * @todo It might be nice to have some feedback mechanism through a
  6397. * callback. Using that, table create status can be provided
  6398. * to the interface which is creating the tables. This is especially
  6399. * useful in case the create process fails at some point, in which
  6400. * case you currently have no good feedback about the create
  6401. * table progress.
  6402. */
  6403. function phorum_db_create_tables()
  6404. {
  6405. $PHORUM = $GLOBALS['PHORUM'];
  6406. $lang = PHORUM_DEFAULT_LANGUAGE;
  6407. $charset = empty($PHORUM['DBCONFIG']['charset'])
  6408. ? ''
  6409. : "DEFAULT CHARACTER SET {$PHORUM['DBCONFIG']['charset']}";
  6410. $create_table_queries = array(
  6411. "CREATE TABLE {$PHORUM['forums_table']} (
  6412. forum_id int unsigned NOT NULL auto_increment,
  6413. name varchar(50) NOT NULL default '',
  6414. active tinyint(1) NOT NULL default '0',
  6415. description text NOT NULL,
  6416. template varchar(50) NOT NULL default '',
  6417. folder_flag tinyint(1) NOT NULL default '0',
  6418. parent_id int unsigned NOT NULL default '0',
  6419. list_length_flat int unsigned NOT NULL default '0',
  6420. list_length_threaded int unsigned NOT NULL default '0',
  6421. moderation int unsigned NOT NULL default '0',
  6422. threaded_list tinyint(1) NOT NULL default '0',
  6423. threaded_read tinyint(1) NOT NULL default '0',
  6424. float_to_top tinyint(1) NOT NULL default '0',
  6425. check_duplicate tinyint(1) NOT NULL default '0',
  6426. allow_attachment_types varchar(100) NOT NULL default '',
  6427. max_attachment_size int unsigned NOT NULL default '0',
  6428. max_totalattachment_size int unsigned NOT NULL default '0',
  6429. max_attachments int unsigned NOT NULL default '0',
  6430. pub_perms int unsigned NOT NULL default '0',
  6431. reg_perms int unsigned NOT NULL default '0',
  6432. display_ip_address tinyint(1) NOT NULL default '1',
  6433. allow_email_notify tinyint(1) NOT NULL default '1',
  6434. language varchar(100) NOT NULL default '$lang',
  6435. email_moderators tinyint(1) NOT NULL default '0',
  6436. message_count int unsigned NOT NULL default '0',
  6437. sticky_count int unsigned NOT NULL default '0',
  6438. thread_count int unsigned NOT NULL default '0',
  6439. last_post_time int unsigned NOT NULL default '0',
  6440. display_order int unsigned NOT NULL default '0',
  6441. read_length int unsigned NOT NULL default '0',
  6442. vroot int unsigned NOT NULL default '0',
  6443. edit_post tinyint(1) NOT NULL default '1',
  6444. template_settings text NOT NULL,
  6445. forum_path text NOT NULL,
  6446. count_views tinyint(1) NOT NULL default '0',
  6447. count_views_per_thread tinyint(1) NOT NULL default '0',
  6448. display_fixed tinyint(1) NOT NULL default '0',
  6449. reverse_threading tinyint(1) NOT NULL default '0',
  6450. inherit_id int unsigned NULL default NULL,
  6451. cache_version int unsigned NOT NULL default '0',
  6452. PRIMARY KEY (forum_id),
  6453. KEY name (name),
  6454. KEY active (active, parent_id),
  6455. KEY group_id (parent_id)
  6456. ) $charset",
  6457. "CREATE TABLE {$PHORUM['message_table']} (
  6458. message_id int unsigned NOT NULL auto_increment,
  6459. forum_id int unsigned NOT NULL default '0',
  6460. thread int unsigned NOT NULL default '0',
  6461. parent_id int unsigned NOT NULL default '0',
  6462. user_id int unsigned NOT NULL default '0',
  6463. author varchar(255) NOT NULL default '',
  6464. subject varchar(255) NOT NULL default '',
  6465. body text NOT NULL,
  6466. email varchar(100) NOT NULL default '',
  6467. ip varchar(255) NOT NULL default '',
  6468. status tinyint(4) NOT NULL default '2',
  6469. msgid varchar(100) NOT NULL default '',
  6470. modifystamp int unsigned NOT NULL default '0',
  6471. thread_count int unsigned NOT NULL default '0',
  6472. moderator_post tinyint(1) NOT NULL default '0',
  6473. sort tinyint(4) NOT NULL default '2',
  6474. datestamp int unsigned NOT NULL default '0',
  6475. meta mediumtext NULL,
  6476. viewcount int unsigned NOT NULL default '0',
  6477. threadviewcount int unsigned NOT NULL default '0',
  6478. closed tinyint(1) NOT NULL default '0',
  6479. recent_message_id int unsigned NOT NULL default '0',
  6480. recent_user_id int unsigned NOT NULL default '0',
  6481. recent_author varchar(255) NOT NULL default '',
  6482. moved tinyint(1) NOT NULL default '0',
  6483. PRIMARY KEY (message_id),
  6484. KEY thread_message (thread,message_id),
  6485. KEY thread_forum (thread,forum_id),
  6486. KEY special_threads (sort,forum_id),
  6487. KEY status_forum (status,forum_id),
  6488. KEY list_page_float (forum_id,parent_id,modifystamp),
  6489. KEY list_page_flat (forum_id,parent_id,thread),
  6490. KEY new_count (forum_id,status,moved,message_id),
  6491. KEY new_threads (forum_id,status,parent_id,moved,message_id),
  6492. KEY recent_threads (status, parent_id, message_id, forum_id),
  6493. KEY updated_threads (status, parent_id, modifystamp),
  6494. KEY dup_check (forum_id,author(50),subject,datestamp),
  6495. KEY forum_max_message (forum_id,message_id,status,parent_id),
  6496. KEY last_post_time (forum_id,status,modifystamp),
  6497. KEY next_prev_thread (forum_id,status,thread),
  6498. KEY recent_user_id (recent_user_id),
  6499. KEY user_messages (user_id, message_id)
  6500. ) $charset",
  6501. "CREATE TABLE {$PHORUM['settings_table']} (
  6502. name varchar(255) NOT NULL default '',
  6503. type enum('V','S') NOT NULL default 'V',
  6504. data text NOT NULL,
  6505. PRIMARY KEY (name)
  6506. ) $charset",
  6507. "CREATE TABLE {$PHORUM['subscribers_table']} (
  6508. user_id int unsigned NOT NULL default '0',
  6509. forum_id int unsigned NOT NULL default '0',
  6510. sub_type tinyint(4) NOT NULL default '0',
  6511. thread int unsigned NOT NULL default '0',
  6512. PRIMARY KEY (user_id,forum_id,thread),
  6513. KEY forum_id (forum_id,thread,sub_type)
  6514. ) $charset",
  6515. "CREATE TABLE {$PHORUM['user_permissions_table']} (
  6516. user_id int unsigned NOT NULL default '0',
  6517. forum_id int unsigned NOT NULL default '0',
  6518. permission int unsigned NOT NULL default '0',
  6519. PRIMARY KEY (user_id,forum_id),
  6520. KEY forum_id (forum_id,permission)
  6521. ) $charset",
  6522. // When creating extra fields, then mind to update the file
  6523. // include/api/custom_profile_fields.php script too (it contains a
  6524. // list of reserved names for custom profile fields).
  6525. "CREATE TABLE {$PHORUM['user_table']} (
  6526. user_id int unsigned NOT NULL auto_increment,
  6527. username varchar(50) NOT NULL default '',
  6528. real_name varchar(255) NOT NULL default '',
  6529. display_name varchar(255) NOT NULL default '',
  6530. password varchar(50) NOT NULL default '',
  6531. password_temp varchar(50) NOT NULL default '',
  6532. sessid_lt varchar(50) NOT NULL default '',
  6533. sessid_st varchar(50) NOT NULL default '',
  6534. sessid_st_timeout int unsigned NOT NULL default '0',
  6535. email varchar(100) NOT NULL default '',
  6536. email_temp varchar(110) NOT NULL default '',
  6537. hide_email tinyint(1) NOT NULL default '0',
  6538. active tinyint(1) NOT NULL default '0',
  6539. signature text NOT NULL,
  6540. threaded_list tinyint(1) NOT NULL default '0',
  6541. posts int(10) NOT NULL default '0',
  6542. admin tinyint(1) NOT NULL default '0',
  6543. threaded_read tinyint(1) NOT NULL default '0',
  6544. date_added int unsigned NOT NULL default '0',
  6545. date_last_active int unsigned NOT NULL default '0',
  6546. last_active_forum int unsigned NOT NULL default '0',
  6547. hide_activity tinyint(1) NOT NULL default '0',
  6548. show_signature tinyint(1) NOT NULL default '0',
  6549. email_notify tinyint(1) NOT NULL default '0',
  6550. pm_email_notify tinyint(1) NOT NULL default '1',
  6551. tz_offset float(4,2) NOT NULL default '-99.00',
  6552. is_dst tinyint(1) NOT NULL default '0',
  6553. user_language varchar(100) NOT NULL default '',
  6554. user_template varchar(100) NOT NULL default '',
  6555. moderator_data text NOT NULL,
  6556. moderation_email tinyint(1) NOT NULL default '1',
  6557. settings_data mediumtext NOT NULL,
  6558. PRIMARY KEY (user_id),
  6559. UNIQUE KEY username (username),
  6560. KEY active (active),
  6561. KEY userpass (username,password),
  6562. KEY sessid_st (sessid_st),
  6563. KEY sessid_lt (sessid_lt),
  6564. KEY activity (date_last_active,hide_activity,last_active_forum),
  6565. KEY date_added (date_added),
  6566. KEY email_temp (email_temp)
  6567. ) $charset",
  6568. "CREATE TABLE {$PHORUM['user_newflags_table']} (
  6569. user_id int unsigned NOT NULL default '0',
  6570. forum_id int unsigned NOT NULL default '0',
  6571. message_id int unsigned NOT NULL default '0',
  6572. PRIMARY KEY (user_id,forum_id,message_id),
  6573. KEY move (message_id, forum_id)
  6574. ) $charset",
  6575. "CREATE TABLE {$PHORUM['groups_table']} (
  6576. group_id int unsigned NOT NULL auto_increment,
  6577. name varchar(255) NOT NULL default '',
  6578. open tinyint(1) NOT NULL default '0',
  6579. PRIMARY KEY (group_id)
  6580. ) $charset",
  6581. "CREATE TABLE {$PHORUM['forum_group_xref_table']} (
  6582. forum_id int unsigned NOT NULL default '0',
  6583. group_id int unsigned NOT NULL default '0',
  6584. permission int unsigned NOT NULL default '0',
  6585. PRIMARY KEY (forum_id,group_id),
  6586. KEY group_id (group_id)
  6587. ) $charset",
  6588. "CREATE TABLE {$PHORUM['user_group_xref_table']} (
  6589. user_id int unsigned NOT NULL default '0',
  6590. group_id int unsigned NOT NULL default '0',
  6591. status tinyint(4) NOT NULL default '1',
  6592. PRIMARY KEY (user_id,group_id)
  6593. ) $charset",
  6594. "CREATE TABLE {$PHORUM['files_table']} (
  6595. file_id int unsigned NOT NULL auto_increment,
  6596. user_id int unsigned NOT NULL default '0',
  6597. filename varchar(255) NOT NULL default '',
  6598. filesize int unsigned NOT NULL default '0',
  6599. file_data mediumtext NOT NULL,
  6600. add_datetime int unsigned NOT NULL default '0',
  6601. message_id int unsigned NOT NULL default '0',
  6602. link varchar(10) NOT NULL default '',
  6603. PRIMARY KEY (file_id),
  6604. KEY add_datetime (add_datetime),
  6605. KEY message_id_link (message_id,link),
  6606. KEY user_id_link (user_id,link)
  6607. ) $charset",
  6608. "CREATE TABLE {$PHORUM['banlist_table']} (
  6609. id int unsigned NOT NULL auto_increment,
  6610. forum_id int unsigned NOT NULL default '0',
  6611. type tinyint(4) NOT NULL default '0',
  6612. pcre tinyint(1) NOT NULL default '0',
  6613. string varchar(255) NOT NULL default '',
  6614. comments text NOT NULL,
  6615. PRIMARY KEY (id),
  6616. KEY forum_id (forum_id)
  6617. ) $charset",
  6618. "CREATE TABLE {$PHORUM['search_table']} (
  6619. message_id int unsigned NOT NULL default '0',
  6620. forum_id int unsigned NOT NULL default '0',
  6621. search_text mediumtext NOT NULL,
  6622. PRIMARY KEY (message_id),
  6623. KEY forum_id (forum_id),
  6624. FULLTEXT KEY search_text (search_text)
  6625. ) ENGINE=MyISAM $charset",
  6626. "CREATE TABLE {$PHORUM['user_custom_fields_table']} (
  6627. user_id int unsigned NOT NULL default '0',
  6628. type int unsigned NOT NULL default '0',
  6629. data text NOT NULL,
  6630. PRIMARY KEY (user_id, type)
  6631. ) $charset",
  6632. "CREATE TABLE {$PHORUM['pm_messages_table']} (
  6633. pm_message_id int unsigned NOT NULL auto_increment,
  6634. user_id int unsigned NOT NULL default '0',
  6635. author varchar(255) NOT NULL default '',
  6636. subject varchar(100) NOT NULL default '',
  6637. message text NOT NULL,
  6638. datestamp int unsigned NOT NULL default '0',
  6639. meta mediumtext NOT NULL,
  6640. PRIMARY KEY (pm_message_id),
  6641. KEY user_id (user_id)
  6642. ) $charset",
  6643. "CREATE TABLE {$PHORUM['pm_folders_table']} (
  6644. pm_folder_id int unsigned NOT NULL auto_increment,
  6645. user_id int unsigned NOT NULL default '0',
  6646. foldername varchar(20) NOT NULL default '',
  6647. PRIMARY KEY (pm_folder_id)
  6648. ) $charset",
  6649. "CREATE TABLE {$PHORUM['pm_xref_table']} (
  6650. pm_xref_id int unsigned NOT NULL auto_increment,
  6651. user_id int unsigned NOT NULL default '0',
  6652. pm_folder_id int unsigned NOT NULL default '0',
  6653. special_folder varchar(10) NULL default NULL,
  6654. pm_message_id int unsigned NOT NULL default '0',
  6655. read_flag tinyint(1) NOT NULL default '0',
  6656. reply_flag tinyint(1) NOT NULL default '0',
  6657. PRIMARY KEY (pm_xref_id),
  6658. KEY xref (user_id,pm_folder_id,pm_message_id),
  6659. KEY read_flag (read_flag)
  6660. ) $charset",
  6661. "CREATE TABLE {$PHORUM['pm_buddies_table']} (
  6662. pm_buddy_id int unsigned NOT NULL auto_increment,
  6663. user_id int unsigned NOT NULL default '0',
  6664. buddy_user_id int unsigned NOT NULL default '0',
  6665. PRIMARY KEY pm_buddy_id (pm_buddy_id),
  6666. UNIQUE KEY userids (user_id, buddy_user_id),
  6667. KEY buddy_user_id (buddy_user_id)
  6668. ) $charset",
  6669. "CREATE TABLE {$PHORUM['message_tracking_table']} (
  6670. track_id int unsigned NOT NULL auto_increment,
  6671. message_id int unsigned NOT NULL default '0',
  6672. user_id int unsigned NOT NULL default '0',
  6673. time int unsigned NOT NULL default '0',
  6674. diff_body text NULL ,
  6675. diff_subject text NULL ,
  6676. PRIMARY KEY track_id (track_id),
  6677. KEY message_id (message_id)
  6678. ) $charset",
  6679. );
  6680. foreach ($create_table_queries as $sql) {
  6681. $error = phorum_db_interact(DB_RETURN_ERROR, $sql, NULL, DB_MASTERQUERY);
  6682. if ($error !== NULL) {
  6683. return $error;
  6684. }
  6685. }
  6686. return NULL;
  6687. }
  6688. // }}}
  6689. // {{{ Function: phorum_db_maxpacketsize()
  6690. /**
  6691. * This function is used by the sanity checking system in the admin
  6692. * interface to determine how much data can be transferred in one query.
  6693. * This is used to detect problems with uploads that are larger than the
  6694. * database server can handle. The function returns the size in bytes.
  6695. * For database implementations which do not have this kind of limit,
  6696. * NULL can be returned.
  6697. *
  6698. * @return integer
  6699. * The maximum packet size in bytes.
  6700. */
  6701. function phorum_db_maxpacketsize()
  6702. {
  6703. $maxsize = phorum_db_interact(
  6704. DB_RETURN_VALUE,
  6705. 'SELECT @@global.max_allowed_packet',
  6706. NULL,
  6707. DB_MASTERQUERY
  6708. );
  6709. return $maxsize;
  6710. }
  6711. // }}}
  6712. // {{{ Function: phorum_db_sanitychecks()
  6713. /**
  6714. * This function is used by the sanity checking system to let the
  6715. * database layer do sanity checks of its own. This function can
  6716. * be used by every database layer to implement specific checks.
  6717. *
  6718. * The return value for this function should be exactly the same
  6719. * as the return value expected for regular sanity checking
  6720. * function (see include/admin/sanity_checks.php for information).
  6721. *
  6722. * There's no need to load the sanity_check.php file for the needed
  6723. * constants, because this function should only be called from the
  6724. * sanity checking system.
  6725. *
  6726. * @return array
  6727. * A return value as expected by Phorum's sanity checking system.
  6728. */
  6729. function phorum_db_sanitychecks()
  6730. {
  6731. global $PHORUM;
  6732. // For Phorum 5.2+, we need the "charset" option to be set
  6733. // in the config.php.
  6734. if (!isset($PHORUM['DBCONFIG']['charset'])) return array(
  6735. PHORUM_SANITY_CRIT,
  6736. "Database configuration parameter \"charset\" missing.",
  6737. "The option \"charset\" is missing in your database configuration.
  6738. This might indicate that you are using a config.php from an
  6739. older Phorum version, which does not yet contain this option.
  6740. Please, copy include/db/config.php.sample to
  6741. include/db/config.php and edit this new config.php. Read
  6742. Phorum's install.txt for installation instructions."
  6743. );
  6744. // Retrieve the MySQL server version.
  6745. $version = phorum_db_interact(
  6746. DB_RETURN_VALUE,
  6747. 'SELECT @@global.version',
  6748. NULL,
  6749. DB_MASTERQUERY
  6750. );
  6751. if (!$version) return array(
  6752. PHORUM_SANITY_WARN,
  6753. "The database layer could not retrieve the version of the
  6754. running MySQL server",
  6755. "This probably means that you are running a really old MySQL
  6756. server, which does not support \"SELECT @@global.version\"
  6757. as a SQL command. If you are not running a MySQL server
  6758. with version 4.0.18 or higher, then please upgrade your
  6759. MySQL server. Else, contact the Phorum developers to see
  6760. where this warning is coming from"
  6761. );
  6762. // See if we recognize the version numbering.
  6763. if (!preg_match('/^(\d+)\.(\d+)\.(\d+)/', $version, $ver)) return array(
  6764. PHORUM_SANITY_WARN,
  6765. "The database layer was unable to recognize the MySQL server's
  6766. version number \"" . htmlspecialchars($version) . "\". Therefore,
  6767. checking if the right version of MySQL is used is not possible.",
  6768. "Contact the Phorum developers and report this specific
  6769. version number, so the checking scripts can be updated."
  6770. );
  6771. // MySQL before version 4.
  6772. if ($ver[1] < 5) return array(
  6773. PHORUM_SANITY_CRIT,
  6774. "The MySQL database server that is used is too old. The
  6775. running version is \"" . htmlspecialchars($version) . "\",
  6776. while MySQL version 5.0.x or higher is required.",
  6777. "Upgrade your MySQL server to a newer version. If your
  6778. website is hosted with a service provider, please contact
  6779. the service provider to upgrade your MySQL database."
  6780. );
  6781. // THE FOLLOWING TWO CHECKS ARE NO LONGER NEEDED WITH THE ABOVE CHECK
  6782. // MAKING MYSQL5 A REQUIREMENT
  6783. // MySQL before version 4.0.18, with full text search enabled.
  6784. /*
  6785. if (isset($PHORUM['DBCONFIG']['mysql_use_ft']) &&
  6786. $PHORUM['DBCONFIG']['mysql_use_ft'] &&
  6787. $ver[2] == 4 && $ver[2] == 0 && $ver[3] < 18) return array(
  6788. PHORUM_SANITY_WARN,
  6789. "The MySQL database server that is used does not
  6790. support all Phorum features. The running version is
  6791. \"" . htmlspecialchars($version) . "\", while MySQL version
  6792. 4.0.18 or higher is recommended.",
  6793. "Upgrade your MySQL server to a newer version. If your
  6794. website is hosted with a service provider, please contact
  6795. the service provider to upgrade your MySQL database."
  6796. );
  6797. // MySQL before version 5.0
  6798. if ($ver[1] < 5) return array(
  6799. PHORUM_SANITY_WARN,
  6800. "The MySQL database server that is used does not
  6801. support all Phorum features. The running version is
  6802. \"" . htmlspecialchars($version) . "\", while MySQL version
  6803. 5.0 or higher is recommended. MySQL has discontinued active development
  6804. for all versions below 5.0. The Phorum teams uses 5.0 for all
  6805. development. Phorum has been known to work with MySQL 4.1 and some
  6806. later 4.0 versions. However, there is no testing with these versions.
  6807. It is recommended that all users upgrade to 5.0 as soon as possible
  6808. to get the most out of MySQL and Phorum.",
  6809. "Upgrade your MySQL server to a newer version. If your
  6810. website is hosted with a service provider, please contact
  6811. the service provider to upgrade your MySQL database."
  6812. );
  6813. */
  6814. // All checks are okay.
  6815. return array (PHORUM_SANITY_OK, NULL);
  6816. }
  6817. // }}}
  6818. // ----------------------------------------------------------------------
  6819. // Load specific code for the required PHP database module.
  6820. // ----------------------------------------------------------------------
  6821. // PHP has support for MySQL connections through multiple extensions.
  6822. // If the config.php specifies a PHP database extension, then this one is
  6823. // used for loading the specific PHP database extension code. Otherwise,
  6824. // we try to auto-detect which one is available.
  6825. $ext = NULL;
  6826. // could be unset in Phorum < 5.2.7
  6827. if(!isset($PHORUM['DBCONFIG']['socket'])) {
  6828. $PHORUM['DBCONFIG']['socket']=NULL;
  6829. }
  6830. if(!isset($PHORUM['DBCONFIG']['port'])) {
  6831. $PHORUM['DBCONFIG']['port']=NULL;
  6832. }
  6833. if (isset($PHORUM['DBCONFIG']['mysql_php_extension'])) {
  6834. $ext = basename($PHORUM['DBCONFIG']['mysql_php_extension']);
  6835. } elseif (function_exists('mysqli_connect')) {
  6836. $ext = "mysqli";
  6837. } elseif (function_exists('mysql_connect')) {
  6838. $ext = "mysql";
  6839. // build the right hostname for the mysql extension
  6840. // not having separate args for port and socket
  6841. if(!empty($PHORUM['DBCONFIG']['socket'])) {
  6842. $PHORUM['DBCONFIG']['server'].=":".$PHORUM['DBCONFIG']['socket'];
  6843. } elseif(!empty($PHORUM['DBCONFIG']['port'])) {
  6844. $PHORUM['DBCONFIG']['server'].=":".$PHORUM['DBCONFIG']['port'];
  6845. }
  6846. } else {
  6847. // Up to here, no PHP extension was found. This probably means that no
  6848. // MySQL extension is loaded. Here we'll try to dynamically load an
  6849. // extension ourselves.
  6850. if(function_exists('dl')) {
  6851. @dl("mysqli.so");
  6852. if (function_exists('mysqli_connect')) {
  6853. $ext = "mysqli";
  6854. } else {
  6855. @dl("mysql.so");
  6856. if (function_exists('mysql_connect')) {
  6857. $ext = "mysql";
  6858. }
  6859. }
  6860. }
  6861. }
  6862. // If we have no extension by now, we are very much out of luck.
  6863. if ($ext === NULL) trigger_error(
  6864. "The Phorum MySQL database layer is unable to determine the PHP " .
  6865. "MySQL extension to use. This might indicate that there is no " .
  6866. "extension loaded from the php.ini.",
  6867. E_USER_ERROR
  6868. );
  6869. // Load the specific code for the PHP extension that we use.
  6870. $extfile = "./include/db/mysql/{$ext}.php";
  6871. if (!file_exists($extfile)) trigger_error(
  6872. "The Phorum MySQL database layer is unable to find the extension " .
  6873. "file $extfile on the system. Check if all Phorum files are uploaded " .
  6874. "and if you did specify the correct \"mysql_php_extension\" in the file " .
  6875. "include/db/config.php (valid options are \"mysql\" and \"mysqli\").",
  6876. E_USER_ERROR
  6877. );
  6878. include($extfile);
  6879. ?>