You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

rcmail_utils.php 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375
  1. <?php
  2. /**
  3. +-----------------------------------------------------------------------+
  4. | program/include/rcmail_utils.php |
  5. | |
  6. | This file is part of the Roundcube PHP suite |
  7. | Copyright (C) 2005-2015 The Roundcube Dev Team |
  8. | |
  9. | Licensed under the GNU General Public License version 3 or |
  10. | any later version with exceptions for skins & plugins. |
  11. | See the README file for a full license statement. |
  12. | |
  13. | CONTENTS: |
  14. | Roundcube utilities |
  15. | |
  16. +-----------------------------------------------------------------------+
  17. | Author: Thomas Bruederli <roundcube@gmail.com> |
  18. | Author: Aleksander Machniak <alec@alec.pl> |
  19. +-----------------------------------------------------------------------+
  20. */
  21. /**
  22. * Roundcube utilities
  23. *
  24. * @package Webmail
  25. * @subpackage Utils
  26. */
  27. class rcmail_utils
  28. {
  29. public static $db;
  30. /**
  31. * Initialize database object and connect
  32. *
  33. * @return rcube_db Database instance
  34. */
  35. public static function db()
  36. {
  37. if (self::$db === null) {
  38. $rc = rcube::get_instance();
  39. $db = rcube_db::factory($rc->config->get('db_dsnw'));
  40. $db->set_debug((bool)$rc->config->get('sql_debug'));
  41. // Connect to database
  42. $db->db_connect('w');
  43. if (!$db->is_connected()) {
  44. rcube::raise_error("Error connecting to database: " . $db->is_error(), false, true);
  45. }
  46. self::$db = $db;
  47. }
  48. return self::$db;
  49. }
  50. /**
  51. * Initialize database schema
  52. *
  53. * @param string $dir Directory with sql files
  54. */
  55. public static function db_init($dir)
  56. {
  57. $db = self::db();
  58. $file = $dir . '/' . $db->db_provider . '.initial.sql';
  59. if (!file_exists($file)) {
  60. rcube::raise_error("DDL file $file not found", false, true);
  61. }
  62. echo "Creating database schema... ";
  63. if ($sql = file_get_contents($file)) {
  64. if (!$db->exec_script($sql)) {
  65. $error = $db->is_error();
  66. }
  67. }
  68. else {
  69. $error = "Unable to read file $file or it is empty";
  70. }
  71. if ($error) {
  72. echo "[FAILED]\n";
  73. rcube::raise_error($error, false, true);
  74. }
  75. else {
  76. echo "[OK]\n";
  77. }
  78. }
  79. /**
  80. * Update database schema
  81. *
  82. * @param string $dir Directory with sql files
  83. * @param string $package Component name
  84. * @param string $ver Optional current version number
  85. * @param array $opts Parameters (errors, quiet)
  86. *
  87. * @return True on success, False on failure
  88. */
  89. public static function db_update($dir, $package, $ver = null, $opts = array())
  90. {
  91. // Check if directory exists
  92. if (!file_exists($dir)) {
  93. if ($opts['errors']) {
  94. rcube::raise_error("Specified database schema directory doesn't exist.", false, true);
  95. }
  96. return false;
  97. }
  98. $db = self::db();
  99. // Read DB schema version from database (if 'system' table exists)
  100. if (in_array($db->table_name('system'), (array)$db->list_tables())) {
  101. $db->query("SELECT `value`"
  102. . " FROM " . $db->table_name('system', true)
  103. . " WHERE `name` = ?",
  104. $package . '-version');
  105. $row = $db->fetch_array();
  106. $version = preg_replace('/[^0-9]/', '', $row[0]);
  107. }
  108. // DB version not found, but release version is specified
  109. if (!$version && $ver) {
  110. // Map old release version string to DB schema version
  111. // Note: This is for backward compat. only, do not need to be updated
  112. $map = array(
  113. '0.1-stable' => 1,
  114. '0.1.1' => 2008030300,
  115. '0.2-alpha' => 2008040500,
  116. '0.2-beta' => 2008060900,
  117. '0.2-stable' => 2008092100,
  118. '0.2.1' => 2008092100,
  119. '0.2.2' => 2008092100,
  120. '0.3-stable' => 2008092100,
  121. '0.3.1' => 2009090400,
  122. '0.4-beta' => 2009103100,
  123. '0.4' => 2010042300,
  124. '0.4.1' => 2010042300,
  125. '0.4.2' => 2010042300,
  126. '0.5-beta' => 2010100600,
  127. '0.5' => 2010100600,
  128. '0.5.1' => 2010100600,
  129. '0.5.2' => 2010100600,
  130. '0.5.3' => 2010100600,
  131. '0.5.4' => 2010100600,
  132. '0.6-beta' => 2011011200,
  133. '0.6' => 2011011200,
  134. '0.7-beta' => 2011092800,
  135. '0.7' => 2011111600,
  136. '0.7.1' => 2011111600,
  137. '0.7.2' => 2011111600,
  138. '0.7.3' => 2011111600,
  139. '0.7.4' => 2011111600,
  140. '0.8-beta' => 2011121400,
  141. '0.8-rc' => 2011121400,
  142. '0.8.0' => 2011121400,
  143. '0.8.1' => 2011121400,
  144. '0.8.2' => 2011121400,
  145. '0.8.3' => 2011121400,
  146. '0.8.4' => 2011121400,
  147. '0.8.5' => 2011121400,
  148. '0.8.6' => 2011121400,
  149. '0.9-beta' => 2012080700,
  150. );
  151. $version = $map[$ver];
  152. }
  153. // Assume last version before the 'system' table was added
  154. if (empty($version)) {
  155. $version = 2012080700;
  156. }
  157. $dir .= '/' . $db->db_provider;
  158. if (!file_exists($dir)) {
  159. if ($opts['errors']) {
  160. rcube::raise_error("DDL Upgrade files for " . $db->db_provider . " driver not found.", false, true);
  161. }
  162. return false;
  163. }
  164. $dh = opendir($dir);
  165. $result = array();
  166. while ($file = readdir($dh)) {
  167. if (preg_match('/^([0-9]+)\.sql$/', $file, $m) && $m[1] > $version) {
  168. $result[] = $m[1];
  169. }
  170. }
  171. sort($result, SORT_NUMERIC);
  172. foreach ($result as $v) {
  173. if (!$opts['quiet']) {
  174. echo "Updating database schema ($v)... ";
  175. }
  176. // Ignore errors here to print the error only once
  177. $db->set_option('ignore_errors', true);
  178. $error = self::db_update_schema($package, $v, "$dir/$v.sql");
  179. $db->set_option('ignore_errors', false);
  180. if ($error) {
  181. if (!$opts['quiet']) {
  182. echo "[FAILED]\n";
  183. }
  184. if ($opts['errors']) {
  185. rcube::raise_error("Error in DDL upgrade $v: $error", false, true);
  186. }
  187. return false;
  188. }
  189. else if (!$opts['quiet']) {
  190. echo "[OK]\n";
  191. }
  192. }
  193. return true;
  194. }
  195. /**
  196. * Run database update from a single sql file
  197. */
  198. protected static function db_update_schema($package, $version, $file)
  199. {
  200. $db = self::db();
  201. // read DDL file
  202. if ($sql = file_get_contents($file)) {
  203. if (!$db->exec_script($sql)) {
  204. return $db->is_error();
  205. }
  206. }
  207. // escape if 'system' table does not exist
  208. if ($version < 2013011000) {
  209. return;
  210. }
  211. $system_table = $db->table_name('system', true);
  212. $db->query("UPDATE " . $system_table
  213. . " SET `value` = ?"
  214. . " WHERE `name` = ?",
  215. $version, $package . '-version');
  216. if (!$db->is_error() && !$db->affected_rows()) {
  217. $db->query("INSERT INTO " . $system_table
  218. ." (`name`, `value`) VALUES (?, ?)",
  219. $package . '-version', $version);
  220. }
  221. return $db->is_error();
  222. }
  223. /**
  224. * Removes all deleted records older than X days
  225. *
  226. * @param int $days Number of days
  227. */
  228. public static function db_clean($days)
  229. {
  230. // mapping for table name => primary key
  231. $primary_keys = array(
  232. 'contacts' => 'contact_id',
  233. 'contactgroups' => 'contactgroup_id',
  234. );
  235. $db = self::db();
  236. $threshold = date('Y-m-d 00:00:00', time() - $days * 86400);
  237. foreach (array('contacts','contactgroups','identities') as $table) {
  238. $sqltable = $db->table_name($table, true);
  239. // also delete linked records
  240. // could be skipped for databases which respect foreign key constraints
  241. if ($db->db_provider == 'sqlite' && ($table == 'contacts' || $table == 'contactgroups')) {
  242. $pk = $primary_keys[$table];
  243. $memberstable = $db->table_name('contactgroupmembers');
  244. $db->query(
  245. "DELETE FROM " . $db->quote_identifier($memberstable)
  246. . " WHERE `$pk` IN ("
  247. . "SELECT `$pk` FROM $sqltable"
  248. . " WHERE `del` = 1 AND `changed` < ?"
  249. . ")",
  250. $threshold);
  251. echo $db->affected_rows() . " records deleted from '$memberstable'\n";
  252. }
  253. // delete outdated records
  254. $db->query("DELETE FROM $sqltable WHERE `del` = 1 AND `changed` < ?", $threshold);
  255. echo $db->affected_rows() . " records deleted from '$table'\n";
  256. }
  257. }
  258. /**
  259. * Reindex contacts
  260. */
  261. public static function indexcontacts()
  262. {
  263. $db = self::db();
  264. // iterate over all users
  265. $sql_result = $db->query("SELECT `user_id` FROM " . $db->table_name('users', true) . " ORDER BY `user_id`");
  266. while ($sql_result && ($sql_arr = $db->fetch_assoc($sql_result))) {
  267. echo "Indexing contacts for user " . $sql_arr['user_id'] . "...\n";
  268. $contacts = new rcube_contacts($db, $sql_arr['user_id']);
  269. $contacts->set_pagesize(9999);
  270. $result = $contacts->list_records();
  271. while ($result->count && ($row = $result->next())) {
  272. unset($row['words']);
  273. $contacts->update($row['ID'], $row);
  274. }
  275. }
  276. echo "done.\n";
  277. }
  278. /**
  279. * Modify user preferences
  280. *
  281. * @param string $name Option name
  282. * @param string $value Option value
  283. * @param int $userid Optional user identifier
  284. * @param string $type Optional value type (bool, int, string)
  285. */
  286. public static function mod_pref($name, $value, $userid = null, $type = 'string')
  287. {
  288. $db = self::db();
  289. if ($userid) {
  290. $query = '`user_id` = ' . intval($userid);
  291. }
  292. else {
  293. $query = '1=1';
  294. }
  295. $type = strtolower($type);
  296. if ($type == 'bool' || $type == 'boolean') {
  297. $value = rcube_utils::get_boolean($value);
  298. }
  299. else if ($type == 'int' || $type == 'integer') {
  300. $value = (int) $value;
  301. }
  302. // iterate over all users
  303. $sql_result = $db->query("SELECT * FROM " . $db->table_name('users', true) . " WHERE $query");
  304. while ($sql_result && ($sql_arr = $db->fetch_assoc($sql_result))) {
  305. echo "Updating prefs for user " . $sql_arr['user_id'] . "...";
  306. $user = new rcube_user($sql_arr['user_id'], $sql_arr);
  307. $prefs = $old_prefs = $user->get_prefs();
  308. $prefs[$name] = $value;
  309. if ($prefs != $old_prefs) {
  310. $user->save_prefs($prefs, true);
  311. echo "saved.\n";
  312. }
  313. else {
  314. echo "nothing changed.\n";
  315. }
  316. }
  317. }
  318. }