1#!/usr/bin/env php 2<?php 3 4/** 5 * Kolab storage cache modification script 6 * 7 * @version 3.1 8 * @author Thomas Bruederli <bruederli@kolabsys.com> 9 * 10 * Copyright (C) 2012-2014, Kolab Systems AG <contact@kolabsys.com> 11 * 12 * This program is free software: you can redistribute it and/or modify 13 * it under the terms of the GNU Affero General Public License as 14 * published by the Free Software Foundation, either version 3 of the 15 * License, or (at your option) any later version. 16 * 17 * This program is distributed in the hope that it will be useful, 18 * but WITHOUT ANY WARRANTY; without even the implied warranty of 19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 * GNU Affero General Public License for more details. 21 * 22 * You should have received a copy of the GNU Affero General Public License 23 * along with this program. If not, see <http://www.gnu.org/licenses/>. 24 */ 25 26define('INSTALL_PATH', __DIR__ . '/../../../'); 27ini_set('display_errors', 1); 28 29require_once INSTALL_PATH . 'program/include/clisetup.php'; 30 31function print_usage() 32{ 33 print "Usage: modcache.sh ACTION [OPTIONS] [USERNAME ARGS ...]\n"; 34 print "Possible actions are: expunge, clear, prewarm\n"; 35 print "-a, --all Clear/expunge all caches\n"; 36 print "-h, --host IMAP host name\n"; 37 print "-u, --user IMAP user name to authenticate\n"; 38 print "-t, --type Object types to clear/expunge cache\n"; 39 print "-l, --limit Limit the number of records to be expunged\n"; 40} 41 42// read arguments 43$opts = rcube_utils::get_opt(array( 44 'a' => 'all', 45 'h' => 'host', 46 'u' => 'user', 47 'p' => 'password', 48 't' => 'type', 49 'l' => 'limit', 50 'v' => 'verbose', 51)); 52 53$opts['username'] = !empty($opts[1]) ? $opts[1] : $opts['user']; 54$action = $opts[0]; 55 56$rcmail = rcube::get_instance(rcube::INIT_WITH_DB | rcube::INIT_WITH_PLUGINS); 57 58// Make --host argument optional where the default_host is a simple string 59if (empty($opts['host'])) { 60 $opts['host'] = imap_host(); 61} 62 63// connect to database 64$db = $rcmail->get_dbh(); 65$db->db_connect('w'); 66if (!$db->is_connected() || $db->is_error()) 67 die("No DB connection\n"); 68 69ini_set('display_errors', 1); 70 71// All supported object types 72$all_types = array('contact','configuration','event','file','journal','note','task'); 73 74/* 75 * Script controller 76 */ 77switch (strtolower($action)) { 78 79/* 80 * Clear/expunge all cache records 81 */ 82case 'expunge': 83 $folder_types = $opts['type'] ? explode(',', $opts['type']) : $all_types; 84 $folder_types_db = array_map(array($db, 'quote'), $folder_types); 85 $expire = strtotime(!empty($opts[2]) ? $opts[2] : 'now - 10 days'); 86 $sql_where = "type IN (" . join(',', $folder_types_db) . ")"; 87 88 if ($opts['username']) { 89 $sql_where .= ' AND resource LIKE ?'; 90 } 91 92 $sql_query = "DELETE FROM %s WHERE folder_id IN (SELECT folder_id FROM kolab_folders WHERE $sql_where) AND created <= " . $db->quote(date('Y-m-d 00:00:00', $expire)); 93 if ($opts['limit']) { 94 $sql_query .= ' LIMIT ' . intval($opts['limit']); 95 } 96 foreach ($folder_types as $type) { 97 $table_name = 'kolab_cache_' . $type; 98 $db->query(sprintf($sql_query, $table_name), resource_prefix($opts).'%'); 99 echo $db->affected_rows() . " records deleted from '$table_name'\n"; 100 } 101 102 $db->query("UPDATE kolab_folders SET ctag='' WHERE $sql_where", resource_prefix($opts).'%'); 103 break; 104 105case 'clear': 106 $folder_types = $opts['type'] ? explode(',', $opts['type']) : $all_types; 107 $folder_types_db = array_map(array($db, 'quote'), $folder_types); 108 109 if ($opts['all']) { 110 $sql_query = "DELETE FROM kolab_folders WHERE 1"; 111 } 112 else if ($opts['username']) { 113 $sql_query = "DELETE FROM kolab_folders WHERE type IN (" . join(',', $folder_types_db) . ") AND resource LIKE ?"; 114 } 115 116 if ($sql_query) { 117 $db->query($sql_query, resource_prefix($opts).'%'); 118 echo $db->affected_rows() . " records deleted from 'kolab_folders'\n"; 119 } 120 break; 121 122 123/* 124 * Prewarm cache by synchronizing objects for the given user 125 */ 126case 'prewarm': 127 // make sure libkolab classes are loaded 128 $rcmail->plugins->load_plugin('libkolab'); 129 130 if (authenticate($opts)) { 131 $folder_types = $opts['type'] ? explode(',', $opts['type']) : $all_types; 132 foreach ($folder_types as $type) { 133 // sync every folder of the given type 134 foreach (kolab_storage::get_folders($type) as $folder) { 135 echo "Synching " . $folder->name . " ($type) ... "; 136 echo $folder->count($type) . "\n"; 137 138 // also sync distribution lists in contact folders 139 if ($type == 'contact') { 140 echo "Synching " . $folder->name . " (distribution-list) ... "; 141 echo $folder->count('distribution-list') . "\n"; 142 } 143 } 144 } 145 } 146 else 147 die("Authentication failed for " . $opts['user']); 148 break; 149 150/** 151 * Update the cache meta columns from the serialized/xml data 152 * (might be run after a schema update) 153 */ 154case 'update': 155 // make sure libkolab classes are loaded 156 $rcmail->plugins->load_plugin('libkolab'); 157 158 $folder_types = $opts['type'] ? explode(',', $opts['type']) : $all_types; 159 foreach ($folder_types as $type) { 160 $class = 'kolab_storage_cache_' . $type; 161 $sql_result = $db->query("SELECT folder_id FROM kolab_folders WHERE type=? AND synclock = 0", $type); 162 while ($sql_result && ($sql_arr = $db->fetch_assoc($sql_result))) { 163 $folder = new $class; 164 $folder->select_by_id($sql_arr['folder_id']); 165 echo "Updating " . $sql_arr['folder_id'] . " ($type) "; 166 foreach ($folder->select() as $object) { 167 $object['_formatobj']->to_array(); // load data 168 $folder->save($object['_msguid'], $object, $object['_msguid']); 169 echo "."; 170 } 171 echo "done.\n"; 172 } 173 } 174 break; 175 176 177/* 178 * Unknown action => show usage 179 */ 180default: 181 print_usage(); 182 exit; 183} 184 185 186/** 187 * Compose cache resource URI prefix for the given user credentials 188 */ 189function resource_prefix($opts) 190{ 191 return 'imap://' . str_replace('%', '\\%', urlencode($opts['username'])) . '@' . $opts['host'] . '/'; 192} 193 194 195/** 196 * Authenticate to the IMAP server with the given user credentials 197 */ 198function authenticate(&$opts) 199{ 200 global $rcmail; 201 202 // prompt for password 203 if (empty($opts['password']) && ($opts['username'] || $opts['user'])) { 204 $opts['password'] = prompt_silent("Password: "); 205 } 206 207 // simulate "login as" feature 208 if ($opts['user'] && $opts['user'] != $opts['username']) 209 $_POST['_loginas'] = $opts['username']; 210 else if (empty($opts['user'])) 211 $opts['user'] = $opts['username']; 212 213 // let the kolab_auth plugin do its magic 214 $auth = $rcmail->plugins->exec_hook('authenticate', array( 215 'host' => trim($opts['host']), 216 'user' => trim($opts['user']), 217 'pass' => $opts['password'], 218 'cookiecheck' => false, 219 'valid' => !empty($opts['user']) && !empty($opts['host']), 220 )); 221 222 if ($auth['valid']) { 223 $storage = $rcmail->get_storage(); 224 if ($storage->connect($auth['host'], $auth['user'], $auth['pass'], 143, false)) { 225 if ($opts['verbose']) 226 echo "IMAP login succeeded.\n"; 227 if (($user = rcube_user::query($opts['username'], $auth['host'])) && $user->ID) 228 $rcmail->user = $user; 229 } 230 else 231 die("Login to IMAP server failed!\n"); 232 } 233 else { 234 die("Invalid login credentials!\n"); 235 } 236 237 return $auth['valid']; 238} 239 240function imap_host() 241{ 242 global $rcmail; 243 244 $default_host = $rcmail->config->get('default_host'); 245 246 if (is_array($default_host)) { 247 $key = key($default_host); 248 $imap_host = is_numeric($key) ? $default_host[$key] : $key; 249 } 250 else { 251 $imap_host = $default_host; 252 } 253 254 // strip protocol prefix 255 $uri = parse_url($imap_host); 256 if (!empty($uri['host'])) { 257 return $uri['host']; 258 } 259} 260