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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372
  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 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 Directory with sql files
  83. * @param string Component name
  84. * @param string Optional current version number
  85. * @param array 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. $error = self::db_update_schema($package, $v, "$dir/$v.sql");
  177. if ($error) {
  178. if (!$opts['quiet']) {
  179. echo "[FAILED]\n";
  180. }
  181. if ($opts['errors']) {
  182. rcube::raise_error("Error in DDL upgrade $v: $error", false, true);
  183. }
  184. return false;
  185. }
  186. else if (!$opts['quiet']) {
  187. echo "[OK]\n";
  188. }
  189. }
  190. return true;
  191. }
  192. /**
  193. * Run database update from a single sql file
  194. */
  195. protected static function db_update_schema($package, $version, $file)
  196. {
  197. $db = self::db();
  198. // read DDL file
  199. if ($sql = file_get_contents($file)) {
  200. if (!$db->exec_script($sql)) {
  201. return $db->is_error();
  202. }
  203. }
  204. // escape if 'system' table does not exist
  205. if ($version < 2013011000) {
  206. return;
  207. }
  208. $system_table = $db->table_name('system', true);
  209. $db->query("UPDATE " . $system_table
  210. . " SET `value` = ?"
  211. . " WHERE `name` = ?",
  212. $version, $package . '-version');
  213. if (!$db->is_error() && !$db->affected_rows()) {
  214. $db->query("INSERT INTO " . $system_table
  215. ." (`name`, `value`) VALUES (?, ?)",
  216. $package . '-version', $version);
  217. }
  218. return $db->is_error();
  219. }
  220. /**
  221. * Removes all deleted records older than X days
  222. *
  223. * @param int Number of days
  224. */
  225. public static function db_clean($days)
  226. {
  227. // mapping for table name => primary key
  228. $primary_keys = array(
  229. 'contacts' => 'contact_id',
  230. 'contactgroups' => 'contactgroup_id',
  231. );
  232. $db = self::db();
  233. $threshold = date('Y-m-d 00:00:00', time() - $days * 86400);
  234. foreach (array('contacts','contactgroups','identities') as $table) {
  235. $sqltable = $db->table_name($table, true);
  236. // also delete linked records
  237. // could be skipped for databases which respect foreign key constraints
  238. if ($db->db_provider == 'sqlite' && ($table == 'contacts' || $table == 'contactgroups')) {
  239. $pk = $primary_keys[$table];
  240. $memberstable = $db->table_name('contactgroupmembers');
  241. $db->query(
  242. "DELETE FROM " . $db->quote_identifier($memberstable)
  243. . " WHERE `$pk` IN ("
  244. . "SELECT `$pk` FROM $sqltable"
  245. . " WHERE `del` = 1 AND `changed` < ?"
  246. . ")",
  247. $threshold);
  248. echo $db->affected_rows() . " records deleted from '$memberstable'\n";
  249. }
  250. // delete outdated records
  251. $db->query("DELETE FROM $sqltable WHERE `del` = 1 AND `changed` < ?", $threshold);
  252. echo $db->affected_rows() . " records deleted from '$table'\n";
  253. }
  254. }
  255. /**
  256. * Reindex contacts
  257. */
  258. public static function indexcontacts()
  259. {
  260. $db = self::db();
  261. // iterate over all users
  262. $sql_result = $db->query("SELECT `user_id` FROM " . $db->table_name('users', true) . " ORDER BY `user_id`");
  263. while ($sql_result && ($sql_arr = $db->fetch_assoc($sql_result))) {
  264. echo "Indexing contacts for user " . $sql_arr['user_id'] . "...\n";
  265. $contacts = new rcube_contacts($db, $sql_arr['user_id']);
  266. $contacts->set_pagesize(9999);
  267. $result = $contacts->list_records();
  268. while ($result->count && ($row = $result->next())) {
  269. unset($row['words']);
  270. $contacts->update($row['ID'], $row);
  271. }
  272. }
  273. echo "done.\n";
  274. }
  275. /**
  276. * Modify user preferences
  277. *
  278. * @param string Option name
  279. * @param string Option value
  280. * @param int Optional user identifier
  281. * @param string Optional value type (bool, int, string)
  282. */
  283. public static function mod_pref($name, $value, $userid = null, $type = 'string')
  284. {
  285. $db = self::db();
  286. if ($userid) {
  287. $query = '`user_id` = ' . intval($userid);
  288. }
  289. else {
  290. $query = '1=1';
  291. }
  292. $type = strtolower($type);
  293. if ($type == 'bool' || $type == 'boolean') {
  294. $value = rcube_utils::get_boolean($value);
  295. }
  296. else if ($type == 'int' || $type == 'integer') {
  297. $value = (int) $value;
  298. }
  299. // iterate over all users
  300. $sql_result = $db->query("SELECT * FROM " . $db->table_name('users', true) . " WHERE $query");
  301. while ($sql_result && ($sql_arr = $db->fetch_assoc($sql_result))) {
  302. echo "Updating prefs for user " . $sql_arr['user_id'] . "...";
  303. $user = new rcube_user($sql_arr['user_id'], $sql_arr);
  304. $prefs = $old_prefs = $user->get_prefs();
  305. $prefs[$name] = $value;
  306. if ($prefs != $old_prefs) {
  307. $user->save_prefs($prefs, true);
  308. echo "saved.\n";
  309. }
  310. else {
  311. echo "nothing changed.\n";
  312. }
  313. }
  314. }
  315. }