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.

rcube_sieve_engine.php 108KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431
  1. <?php
  2. /**
  3. * Managesieve (Sieve Filters) Engine
  4. *
  5. * Engine part of Managesieve plugin implementing UI and backend access.
  6. *
  7. * Copyright (C) 2008-2014, The Roundcube Dev Team
  8. * Copyright (C) 2011-2014, Kolab Systems AG
  9. *
  10. * This program is free software: you can redistribute it and/or modify
  11. * it under the terms of the GNU General Public License as published by
  12. * the Free Software Foundation, either version 3 of the License, or
  13. * (at your option) any later version.
  14. *
  15. * This program is distributed in the hope that it will be useful,
  16. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  17. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  18. * GNU General Public License for more details.
  19. *
  20. * You should have received a copy of the GNU General Public License
  21. * along with this program. If not, see http://www.gnu.org/licenses/.
  22. */
  23. class rcube_sieve_engine
  24. {
  25. protected $rc;
  26. protected $sieve;
  27. protected $errors;
  28. protected $form;
  29. protected $tips = array();
  30. protected $script = array();
  31. protected $exts = array();
  32. protected $list;
  33. protected $active = array();
  34. protected $headers = array(
  35. 'subject' => 'Subject',
  36. 'from' => 'From',
  37. 'to' => 'To',
  38. );
  39. protected $addr_headers = array(
  40. // Required
  41. "from", "to", "cc", "bcc", "sender", "resent-from", "resent-to",
  42. // Additional (RFC 822 / RFC 2822)
  43. "reply-to", "resent-reply-to", "resent-sender", "resent-cc", "resent-bcc",
  44. // Non-standard (RFC 2076, draft-palme-mailext-headers-08.txt)
  45. "for-approval", "for-handling", "for-comment", "apparently-to", "errors-to",
  46. "delivered-to", "return-receipt-to", "x-admin", "read-receipt-to",
  47. "x-confirm-reading-to", "return-receipt-requested",
  48. "registered-mail-reply-requested-by", "mail-followup-to", "mail-reply-to",
  49. "abuse-reports-to", "x-complaints-to", "x-report-abuse-to",
  50. // Undocumented
  51. "x-beenthere",
  52. );
  53. protected $notify_methods = array(
  54. 'mailto',
  55. // 'sms',
  56. // 'tel',
  57. );
  58. protected $notify_importance_options = array(
  59. 3 => 'notifyimportancelow',
  60. 2 => 'notifyimportancenormal',
  61. 1 => 'notifyimportancehigh'
  62. );
  63. const VERSION = '8.6';
  64. const PROGNAME = 'Roundcube (Managesieve)';
  65. const PORT = 4190;
  66. /**
  67. * Class constructor
  68. */
  69. function __construct($plugin)
  70. {
  71. $this->rc = rcube::get_instance();
  72. $this->plugin = $plugin;
  73. }
  74. /**
  75. * Loads configuration, initializes plugin (including sieve connection)
  76. */
  77. function start($mode = null)
  78. {
  79. // register UI objects
  80. $this->rc->output->add_handlers(array(
  81. 'filterslist' => array($this, 'filters_list'),
  82. 'filtersetslist' => array($this, 'filtersets_list'),
  83. 'filterframe' => array($this, 'filter_frame'),
  84. 'filterform' => array($this, 'filter_form'),
  85. 'filtersetform' => array($this, 'filterset_form'),
  86. ));
  87. // connect to managesieve server
  88. $error = $this->connect($_SESSION['username'], $this->rc->decrypt($_SESSION['password']));
  89. // load current/active script
  90. if (!$error) {
  91. // Get list of scripts
  92. $list = $this->list_scripts();
  93. // reset current script when entering filters UI (#1489412)
  94. if ($this->rc->action == 'plugin.managesieve') {
  95. $this->rc->session->remove('managesieve_current');
  96. }
  97. if ($mode != 'vacation') {
  98. if (!empty($_GET['_set']) || !empty($_POST['_set'])) {
  99. $script_name = rcube_utils::get_input_value('_set', rcube_utils::INPUT_GPC, true);
  100. }
  101. else if (!empty($_SESSION['managesieve_current'])) {
  102. $script_name = $_SESSION['managesieve_current'];
  103. }
  104. }
  105. $error = $this->load_script($script_name);
  106. }
  107. // finally set script objects
  108. if ($error) {
  109. switch ($error) {
  110. case rcube_sieve::ERROR_CONNECTION:
  111. case rcube_sieve::ERROR_LOGIN:
  112. $this->rc->output->show_message('managesieve.filterconnerror', 'error');
  113. break;
  114. default:
  115. $this->rc->output->show_message('managesieve.filterunknownerror', 'error');
  116. break;
  117. }
  118. // reload interface in case of possible error when specified script wasn't found (#1489412)
  119. if ($script_name !== null && !empty($list) && !in_array($script_name, $list)) {
  120. $this->rc->output->command('reload', 500);
  121. }
  122. // to disable 'Add filter' button set env variable
  123. $this->rc->output->set_env('filterconnerror', true);
  124. $this->script = array();
  125. }
  126. else {
  127. $this->exts = $this->sieve->get_extensions();
  128. $this->init_script();
  129. $this->rc->output->set_env('currentset', $this->sieve->current);
  130. $_SESSION['managesieve_current'] = $this->sieve->current;
  131. }
  132. return $error;
  133. }
  134. /**
  135. * Connect to configured managesieve server
  136. *
  137. * @param string $username User login
  138. * @param string $password User password
  139. *
  140. * @return int Connection status: 0 on success, >0 on failure
  141. */
  142. public function connect($username, $password)
  143. {
  144. // Get connection parameters
  145. $host = $this->rc->config->get('managesieve_host', 'localhost');
  146. $port = $this->rc->config->get('managesieve_port');
  147. $tls = $this->rc->config->get('managesieve_usetls', false);
  148. $host = rcube_utils::parse_host($host);
  149. $host = rcube_utils::idn_to_ascii($host);
  150. // remove tls:// prefix, set TLS flag
  151. if (($host = preg_replace('|^tls://|i', '', $host, 1, $cnt)) && $cnt) {
  152. $tls = true;
  153. }
  154. if (empty($port)) {
  155. $port = getservbyname('sieve', 'tcp');
  156. if (empty($port)) {
  157. $port = self::PORT;
  158. }
  159. }
  160. $plugin = $this->rc->plugins->exec_hook('managesieve_connect', array(
  161. 'user' => $username,
  162. 'password' => $password,
  163. 'host' => $host,
  164. 'port' => $port,
  165. 'usetls' => $tls,
  166. 'auth_type' => $this->rc->config->get('managesieve_auth_type'),
  167. 'disabled' => $this->rc->config->get('managesieve_disabled_extensions'),
  168. 'debug' => $this->rc->config->get('managesieve_debug', false),
  169. 'auth_cid' => $this->rc->config->get('managesieve_auth_cid'),
  170. 'auth_pw' => $this->rc->config->get('managesieve_auth_pw'),
  171. 'socket_options' => $this->rc->config->get('managesieve_conn_options'),
  172. ));
  173. // try to connect to managesieve server and to fetch the script
  174. $this->sieve = new rcube_sieve(
  175. $plugin['user'],
  176. $plugin['password'],
  177. $plugin['host'],
  178. $plugin['port'],
  179. $plugin['auth_type'],
  180. $plugin['usetls'],
  181. $plugin['disabled'],
  182. $plugin['debug'],
  183. $plugin['auth_cid'],
  184. $plugin['auth_pw'],
  185. $plugin['socket_options']
  186. );
  187. $error = $this->sieve->error();
  188. if ($error) {
  189. rcube::raise_error(array(
  190. 'code' => 403,
  191. 'file' => __FILE__,
  192. 'line' => __LINE__,
  193. 'message' => "Unable to connect to managesieve on $host:$port"
  194. ), true, false);
  195. }
  196. return $error;
  197. }
  198. /**
  199. * Load specified (or active) script
  200. *
  201. * @param string $script_name Optional script name
  202. *
  203. * @return int Connection status: 0 on success, >0 on failure
  204. */
  205. protected function load_script($script_name = null)
  206. {
  207. // Get list of scripts
  208. $list = $this->list_scripts();
  209. if ($script_name === null || $script_name === '') {
  210. // get (first) active script
  211. if (!empty($this->active)) {
  212. $script_name = $this->active[0];
  213. }
  214. else if ($list) {
  215. $script_name = $list[0];
  216. }
  217. // create a new (initial) script
  218. else {
  219. // if script not exists build default script contents
  220. $script_file = $this->rc->config->get('managesieve_default');
  221. $script_name = $this->rc->config->get('managesieve_script_name');
  222. if (empty($script_name)) {
  223. $script_name = 'roundcube';
  224. }
  225. if ($script_file && is_readable($script_file)) {
  226. $content = file_get_contents($script_file);
  227. }
  228. // add script and set it active
  229. if ($this->sieve->save_script($script_name, $content)) {
  230. $this->activate_script($script_name);
  231. $this->list[] = $script_name;
  232. }
  233. }
  234. }
  235. if ($script_name) {
  236. $this->sieve->load($script_name);
  237. }
  238. return $this->sieve->error();
  239. }
  240. /**
  241. * User interface actions handler
  242. */
  243. function actions()
  244. {
  245. $error = $this->start();
  246. // Handle user requests
  247. if ($action = rcube_utils::get_input_value('_act', rcube_utils::INPUT_GPC)) {
  248. $fid = (int) rcube_utils::get_input_value('_fid', rcube_utils::INPUT_POST);
  249. if ($action == 'delete' && !$error) {
  250. if (isset($this->script[$fid])) {
  251. if ($this->sieve->script->delete_rule($fid))
  252. $result = $this->save_script();
  253. if ($result === true) {
  254. $this->rc->output->show_message('managesieve.filterdeleted', 'confirmation');
  255. $this->rc->output->command('managesieve_updatelist', 'del', array('id' => $fid));
  256. } else {
  257. $this->rc->output->show_message('managesieve.filterdeleteerror', 'error');
  258. }
  259. }
  260. }
  261. else if ($action == 'move' && !$error) {
  262. if (isset($this->script[$fid])) {
  263. $to = (int) rcube_utils::get_input_value('_to', rcube_utils::INPUT_POST);
  264. $rule = $this->script[$fid];
  265. // remove rule
  266. unset($this->script[$fid]);
  267. $this->script = array_values($this->script);
  268. // add at target position
  269. if ($to >= count($this->script)) {
  270. $this->script[] = $rule;
  271. }
  272. else {
  273. $script = array();
  274. foreach ($this->script as $idx => $r) {
  275. if ($idx == $to)
  276. $script[] = $rule;
  277. $script[] = $r;
  278. }
  279. $this->script = $script;
  280. }
  281. $this->sieve->script->content = $this->script;
  282. $result = $this->save_script();
  283. if ($result === true) {
  284. $result = $this->list_rules();
  285. $this->rc->output->show_message('managesieve.moved', 'confirmation');
  286. $this->rc->output->command('managesieve_updatelist', 'list',
  287. array('list' => $result, 'clear' => true, 'set' => $to));
  288. } else {
  289. $this->rc->output->show_message('managesieve.moveerror', 'error');
  290. }
  291. }
  292. }
  293. else if ($action == 'act' && !$error) {
  294. if (isset($this->script[$fid])) {
  295. $rule = $this->script[$fid];
  296. $disabled = !empty($rule['disabled']);
  297. $rule['disabled'] = !$disabled;
  298. $result = $this->sieve->script->update_rule($fid, $rule);
  299. if ($result !== false)
  300. $result = $this->save_script();
  301. if ($result === true) {
  302. if ($rule['disabled'])
  303. $this->rc->output->show_message('managesieve.deactivated', 'confirmation');
  304. else
  305. $this->rc->output->show_message('managesieve.activated', 'confirmation');
  306. $this->rc->output->command('managesieve_updatelist', 'update',
  307. array('id' => $fid, 'disabled' => $rule['disabled']));
  308. } else {
  309. if ($rule['disabled'])
  310. $this->rc->output->show_message('managesieve.deactivateerror', 'error');
  311. else
  312. $this->rc->output->show_message('managesieve.activateerror', 'error');
  313. }
  314. }
  315. }
  316. else if ($action == 'setact' && !$error) {
  317. $script_name = rcube_utils::get_input_value('_set', rcube_utils::INPUT_POST, true);
  318. $result = $this->activate_script($script_name);
  319. $kep14 = $this->rc->config->get('managesieve_kolab_master');
  320. if ($result === true) {
  321. $this->rc->output->set_env('active_sets', $this->active);
  322. $this->rc->output->show_message('managesieve.setactivated', 'confirmation');
  323. $this->rc->output->command('managesieve_updatelist', 'setact',
  324. array('name' => $script_name, 'active' => true, 'all' => !$kep14));
  325. } else {
  326. $this->rc->output->show_message('managesieve.setactivateerror', 'error');
  327. }
  328. }
  329. else if ($action == 'deact' && !$error) {
  330. $script_name = rcube_utils::get_input_value('_set', rcube_utils::INPUT_POST, true);
  331. $result = $this->deactivate_script($script_name);
  332. if ($result === true) {
  333. $this->rc->output->set_env('active_sets', $this->active);
  334. $this->rc->output->show_message('managesieve.setdeactivated', 'confirmation');
  335. $this->rc->output->command('managesieve_updatelist', 'setact',
  336. array('name' => $script_name, 'active' => false));
  337. } else {
  338. $this->rc->output->show_message('managesieve.setdeactivateerror', 'error');
  339. }
  340. }
  341. else if ($action == 'setdel' && !$error) {
  342. $script_name = rcube_utils::get_input_value('_set', rcube_utils::INPUT_POST, true);
  343. $result = $this->remove_script($script_name);
  344. if ($result === true) {
  345. $this->rc->output->show_message('managesieve.setdeleted', 'confirmation');
  346. $this->rc->output->command('managesieve_updatelist', 'setdel',
  347. array('name' => $script_name));
  348. $this->rc->session->remove('managesieve_current');
  349. } else {
  350. $this->rc->output->show_message('managesieve.setdeleteerror', 'error');
  351. }
  352. }
  353. else if ($action == 'setget') {
  354. $this->rc->request_security_check(rcube_utils::INPUT_GET);
  355. $script_name = rcube_utils::get_input_value('_set', rcube_utils::INPUT_GPC, true);
  356. $script = $this->sieve->get_script($script_name);
  357. if (is_a($script, 'PEAR_Error')) {
  358. exit;
  359. }
  360. $browser = new rcube_browser;
  361. // send download headers
  362. header("Content-Type: application/octet-stream");
  363. header("Content-Length: ".strlen($script));
  364. if ($browser->ie) {
  365. header("Content-Type: application/force-download");
  366. $filename = rawurlencode($script_name);
  367. }
  368. else {
  369. $filename = addcslashes($script_name, '\\"');
  370. }
  371. header("Content-Disposition: attachment; filename=\"$filename.txt\"");
  372. echo $script;
  373. exit;
  374. }
  375. else if ($action == 'list') {
  376. $result = $this->list_rules();
  377. $this->rc->output->command('managesieve_updatelist', 'list', array('list' => $result));
  378. }
  379. else if ($action == 'ruleadd') {
  380. $rid = rcube_utils::get_input_value('_rid', rcube_utils::INPUT_POST);
  381. $id = $this->genid();
  382. $content = $this->rule_div($fid, $id, false);
  383. $this->rc->output->command('managesieve_rulefill', $content, $id, $rid);
  384. }
  385. else if ($action == 'actionadd') {
  386. $aid = rcube_utils::get_input_value('_aid', rcube_utils::INPUT_POST);
  387. $id = $this->genid();
  388. $content = $this->action_div($fid, $id, false);
  389. $this->rc->output->command('managesieve_actionfill', $content, $id, $aid);
  390. }
  391. else if ($action == 'addresses') {
  392. $aid = rcube_utils::get_input_value('_aid', rcube_utils::INPUT_POST);
  393. $this->rc->output->command('managesieve_vacation_addresses_update', $aid, $this->user_emails());
  394. }
  395. $this->rc->output->send();
  396. }
  397. else if ($this->rc->task == 'mail') {
  398. // Initialize the form
  399. $rules = rcube_utils::get_input_value('r', rcube_utils::INPUT_GET);
  400. if (!empty($rules)) {
  401. $i = 0;
  402. foreach ($rules as $rule) {
  403. list($header, $value) = explode(':', $rule, 2);
  404. $tests[$i] = array(
  405. 'type' => 'contains',
  406. 'test' => 'header',
  407. 'arg1' => $header,
  408. 'arg2' => $value,
  409. );
  410. $i++;
  411. }
  412. $this->form = array(
  413. 'join' => count($tests) > 1 ? 'allof' : 'anyof',
  414. 'name' => '',
  415. 'tests' => $tests,
  416. 'actions' => array(
  417. 0 => array('type' => 'fileinto'),
  418. 1 => array('type' => 'stop'),
  419. ),
  420. );
  421. }
  422. }
  423. $this->send();
  424. }
  425. function save()
  426. {
  427. // Init plugin and handle managesieve connection
  428. $error = $this->start();
  429. // get request size limits (#1488648)
  430. $max_post = max(array(
  431. ini_get('max_input_vars'),
  432. ini_get('suhosin.request.max_vars'),
  433. ini_get('suhosin.post.max_vars'),
  434. ));
  435. $max_depth = max(array(
  436. ini_get('suhosin.request.max_array_depth'),
  437. ini_get('suhosin.post.max_array_depth'),
  438. ));
  439. // check request size limit
  440. if ($max_post && count($_POST, COUNT_RECURSIVE) >= $max_post) {
  441. rcube::raise_error(array(
  442. 'code' => 500, 'type' => 'php',
  443. 'file' => __FILE__, 'line' => __LINE__,
  444. 'message' => "Request size limit exceeded (one of max_input_vars/suhosin.request.max_vars/suhosin.post.max_vars)"
  445. ), true, false);
  446. $this->rc->output->show_message('managesieve.filtersaveerror', 'error');
  447. }
  448. // check request depth limits
  449. else if ($max_depth && count($_POST['_header']) > $max_depth) {
  450. rcube::raise_error(array(
  451. 'code' => 500, 'type' => 'php',
  452. 'file' => __FILE__, 'line' => __LINE__,
  453. 'message' => "Request size limit exceeded (one of suhosin.request.max_array_depth/suhosin.post.max_array_depth)"
  454. ), true, false);
  455. $this->rc->output->show_message('managesieve.filtersaveerror', 'error');
  456. }
  457. // filters set add action
  458. else if (!empty($_POST['_newset'])) {
  459. $name = rcube_utils::get_input_value('_name', rcube_utils::INPUT_POST, true);
  460. $copy = rcube_utils::get_input_value('_copy', rcube_utils::INPUT_POST, true);
  461. $from = rcube_utils::get_input_value('_from', rcube_utils::INPUT_POST);
  462. $exceptions = $this->rc->config->get('managesieve_filename_exceptions');
  463. $kolab = $this->rc->config->get('managesieve_kolab_master');
  464. $name_uc = mb_strtolower($name);
  465. $list = $this->list_scripts();
  466. if (!$name) {
  467. $this->errors['name'] = $this->plugin->gettext('cannotbeempty');
  468. }
  469. else if (mb_strlen($name) > 128) {
  470. $this->errors['name'] = $this->plugin->gettext('nametoolong');
  471. }
  472. else if (!empty($exceptions) && in_array($name, (array)$exceptions)) {
  473. $this->errors['name'] = $this->plugin->gettext('namereserved');
  474. }
  475. else if (!empty($kolab) && in_array($name_uc, array('MASTER', 'USER', 'MANAGEMENT'))) {
  476. $this->errors['name'] = $this->plugin->gettext('namereserved');
  477. }
  478. else if (in_array($name, $list)) {
  479. $this->errors['name'] = $this->plugin->gettext('setexist');
  480. }
  481. else if ($from == 'file') {
  482. // from file
  483. if (is_uploaded_file($_FILES['_file']['tmp_name'])) {
  484. $file = file_get_contents($_FILES['_file']['tmp_name']);
  485. $file = preg_replace('/\r/', '', $file);
  486. // for security don't save script directly
  487. // check syntax before, like this...
  488. $this->sieve->load_script($file);
  489. if (!$this->save_script($name)) {
  490. $this->errors['file'] = $this->plugin->gettext('setcreateerror');
  491. }
  492. }
  493. else { // upload failed
  494. $err = $_FILES['_file']['error'];
  495. if ($err == UPLOAD_ERR_INI_SIZE || $err == UPLOAD_ERR_FORM_SIZE) {
  496. $msg = $this->rc->gettext(array('name' => 'filesizeerror',
  497. 'vars' => array('size' =>
  498. $this->rc->show_bytes(parse_bytes(ini_get('upload_max_filesize'))))));
  499. }
  500. else {
  501. $this->errors['file'] = $this->plugin->gettext('fileuploaderror');
  502. }
  503. }
  504. }
  505. else if (!$this->sieve->copy($name, $from == 'set' ? $copy : '')) {
  506. $error = 'managesieve.setcreateerror';
  507. }
  508. if (!$error && empty($this->errors)) {
  509. // Find position of the new script on the list
  510. $list[] = $name;
  511. asort($list, SORT_LOCALE_STRING);
  512. $list = array_values($list);
  513. $index = array_search($name, $list);
  514. $this->rc->output->show_message('managesieve.setcreated', 'confirmation');
  515. $this->rc->output->command('parent.managesieve_updatelist', 'setadd',
  516. array('name' => $name, 'index' => $index));
  517. } else if ($msg) {
  518. $this->rc->output->command('display_message', $msg, 'error');
  519. } else if ($error) {
  520. $this->rc->output->show_message($error, 'error');
  521. }
  522. }
  523. // filter add/edit action
  524. else if (isset($_POST['_name'])) {
  525. $name = trim(rcube_utils::get_input_value('_name', rcube_utils::INPUT_POST, true));
  526. $fid = trim(rcube_utils::get_input_value('_fid', rcube_utils::INPUT_POST));
  527. $join = trim(rcube_utils::get_input_value('_join', rcube_utils::INPUT_POST));
  528. // and arrays
  529. $headers = rcube_utils::get_input_value('_header', rcube_utils::INPUT_POST);
  530. $cust_headers = rcube_utils::get_input_value('_custom_header', rcube_utils::INPUT_POST);
  531. $ops = rcube_utils::get_input_value('_rule_op', rcube_utils::INPUT_POST);
  532. $sizeops = rcube_utils::get_input_value('_rule_size_op', rcube_utils::INPUT_POST);
  533. $sizeitems = rcube_utils::get_input_value('_rule_size_item', rcube_utils::INPUT_POST);
  534. $sizetargets = rcube_utils::get_input_value('_rule_size_target', rcube_utils::INPUT_POST);
  535. $targets = rcube_utils::get_input_value('_rule_target', rcube_utils::INPUT_POST, true);
  536. $mods = rcube_utils::get_input_value('_rule_mod', rcube_utils::INPUT_POST);
  537. $mod_types = rcube_utils::get_input_value('_rule_mod_type', rcube_utils::INPUT_POST);
  538. $body_trans = rcube_utils::get_input_value('_rule_trans', rcube_utils::INPUT_POST);
  539. $body_types = rcube_utils::get_input_value('_rule_trans_type', rcube_utils::INPUT_POST, true);
  540. $comparators = rcube_utils::get_input_value('_rule_comp', rcube_utils::INPUT_POST);
  541. $indexes = rcube_utils::get_input_value('_rule_index', rcube_utils::INPUT_POST);
  542. $lastindexes = rcube_utils::get_input_value('_rule_index_last', rcube_utils::INPUT_POST);
  543. $dateheaders = rcube_utils::get_input_value('_rule_date_header', rcube_utils::INPUT_POST);
  544. $dateparts = rcube_utils::get_input_value('_rule_date_part', rcube_utils::INPUT_POST);
  545. $act_types = rcube_utils::get_input_value('_action_type', rcube_utils::INPUT_POST, true);
  546. $mailboxes = rcube_utils::get_input_value('_action_mailbox', rcube_utils::INPUT_POST, true);
  547. $act_targets = rcube_utils::get_input_value('_action_target', rcube_utils::INPUT_POST, true);
  548. $domain_targets = rcube_utils::get_input_value('_action_target_domain', rcube_utils::INPUT_POST);
  549. $area_targets = rcube_utils::get_input_value('_action_target_area', rcube_utils::INPUT_POST, true);
  550. $reasons = rcube_utils::get_input_value('_action_reason', rcube_utils::INPUT_POST, true);
  551. $addresses = rcube_utils::get_input_value('_action_addresses', rcube_utils::INPUT_POST, true);
  552. $intervals = rcube_utils::get_input_value('_action_interval', rcube_utils::INPUT_POST);
  553. $interval_types = rcube_utils::get_input_value('_action_interval_type', rcube_utils::INPUT_POST);
  554. $from = rcube_utils::get_input_value('_action_from', rcube_utils::INPUT_POST);
  555. $subject = rcube_utils::get_input_value('_action_subject', rcube_utils::INPUT_POST, true);
  556. $flags = rcube_utils::get_input_value('_action_flags', rcube_utils::INPUT_POST);
  557. $varnames = rcube_utils::get_input_value('_action_varname', rcube_utils::INPUT_POST);
  558. $varvalues = rcube_utils::get_input_value('_action_varvalue', rcube_utils::INPUT_POST);
  559. $varmods = rcube_utils::get_input_value('_action_varmods', rcube_utils::INPUT_POST);
  560. $notifymethods = rcube_utils::get_input_value('_action_notifymethod', rcube_utils::INPUT_POST);
  561. $notifytargets = rcube_utils::get_input_value('_action_notifytarget', rcube_utils::INPUT_POST, true);
  562. $notifyoptions = rcube_utils::get_input_value('_action_notifyoption', rcube_utils::INPUT_POST, true);
  563. $notifymessages = rcube_utils::get_input_value('_action_notifymessage', rcube_utils::INPUT_POST, true);
  564. $notifyfrom = rcube_utils::get_input_value('_action_notifyfrom', rcube_utils::INPUT_POST);
  565. $notifyimp = rcube_utils::get_input_value('_action_notifyimportance', rcube_utils::INPUT_POST);
  566. // we need a "hack" for radiobuttons
  567. foreach ($sizeitems as $item)
  568. $items[] = $item;
  569. $this->form['disabled'] = !empty($_POST['_disabled']);
  570. $this->form['join'] = $join == 'allof';
  571. $this->form['name'] = $name;
  572. $this->form['tests'] = array();
  573. $this->form['actions'] = array();
  574. if ($name == '')
  575. $this->errors['name'] = $this->plugin->gettext('cannotbeempty');
  576. else {
  577. foreach($this->script as $idx => $rule)
  578. if($rule['name'] == $name && $idx != $fid) {
  579. $this->errors['name'] = $this->plugin->gettext('ruleexist');
  580. break;
  581. }
  582. }
  583. $i = 0;
  584. // rules
  585. if ($join == 'any') {
  586. $this->form['tests'][0]['test'] = 'true';
  587. }
  588. else {
  589. foreach ($headers as $idx => $header) {
  590. // targets are indexed differently (assume form order)
  591. $target = $this->strip_value(array_shift($targets), true);
  592. $header = $this->strip_value($header);
  593. $operator = $this->strip_value($ops[$idx]);
  594. $comparator = $this->strip_value($comparators[$idx]);
  595. if ($header == 'size') {
  596. $sizeop = $this->strip_value($sizeops[$idx]);
  597. $sizeitem = $this->strip_value($items[$idx]);
  598. $sizetarget = $this->strip_value($sizetargets[$idx]);
  599. $this->form['tests'][$i]['test'] = 'size';
  600. $this->form['tests'][$i]['type'] = $sizeop;
  601. $this->form['tests'][$i]['arg'] = $sizetarget;
  602. if ($sizetarget == '')
  603. $this->errors['tests'][$i]['sizetarget'] = $this->plugin->gettext('cannotbeempty');
  604. else if (!preg_match('/^[0-9]+(K|M|G)?$/i', $sizetarget.$sizeitem, $m)) {
  605. $this->errors['tests'][$i]['sizetarget'] = $this->plugin->gettext('forbiddenchars');
  606. $this->form['tests'][$i]['item'] = $sizeitem;
  607. }
  608. else
  609. $this->form['tests'][$i]['arg'] .= $m[1];
  610. }
  611. else if ($header == 'currentdate') {
  612. $datepart = $this->strip_value($dateparts[$idx]);
  613. if (preg_match('/^not/', $operator))
  614. $this->form['tests'][$i]['not'] = true;
  615. $type = preg_replace('/^not/', '', $operator);
  616. if ($type == 'exists') {
  617. $this->errors['tests'][$i]['op'] = true;
  618. }
  619. $this->form['tests'][$i]['test'] = 'currentdate';
  620. $this->form['tests'][$i]['type'] = $type;
  621. $this->form['tests'][$i]['part'] = $datepart;
  622. $this->form['tests'][$i]['arg'] = $target;
  623. if ($type != 'exists') {
  624. if (!count($target)) {
  625. $this->errors['tests'][$i]['target'] = $this->plugin->gettext('cannotbeempty');
  626. }
  627. else if (strpos($type, 'count-') === 0) {
  628. foreach ($target as $arg) {
  629. if (preg_match('/[^0-9]/', $arg)) {
  630. $this->errors['tests'][$i]['target'] = $this->plugin->gettext('forbiddenchars');
  631. }
  632. }
  633. }
  634. else if (strpos($type, 'value-') === 0) {
  635. // Some date/time formats do not support i;ascii-numeric comparator
  636. if ($comparator == 'i;ascii-numeric' && in_array($datepart, array('date', 'time', 'iso8601', 'std11'))) {
  637. $comparator = '';
  638. }
  639. }
  640. if (!preg_match('/^(regex|matches|count-)/', $type) && count($target)) {
  641. foreach ($target as $arg) {
  642. if (!$this->validate_date_part($datepart, $arg)) {
  643. $this->errors['tests'][$i]['target'] = $this->plugin->gettext('invaliddateformat');
  644. break;
  645. }
  646. }
  647. }
  648. }
  649. }
  650. else if ($header == 'date') {
  651. $datepart = $this->strip_value($dateparts[$idx]);
  652. $dateheader = $this->strip_value($dateheaders[$idx]);
  653. $index = $this->strip_value($indexes[$idx]);
  654. $indexlast = $this->strip_value($lastindexes[$idx]);
  655. if (preg_match('/^not/', $operator))
  656. $this->form['tests'][$i]['not'] = true;
  657. $type = preg_replace('/^not/', '', $operator);
  658. if ($type == 'exists') {
  659. $this->errors['tests'][$i]['op'] = true;
  660. }
  661. if (!empty($index) && $mod != 'envelope') {
  662. $this->form['tests'][$i]['index'] = intval($index);
  663. $this->form['tests'][$i]['last'] = !empty($indexlast);
  664. }
  665. if (empty($dateheader)) {
  666. $dateheader = 'Date';
  667. }
  668. else if (!preg_match('/^[\x21-\x39\x41-\x7E]+$/i', $dateheader)) {
  669. $this->errors['tests'][$i]['dateheader'] = $this->plugin->gettext('forbiddenchars');
  670. }
  671. $this->form['tests'][$i]['test'] = 'date';
  672. $this->form['tests'][$i]['type'] = $type;
  673. $this->form['tests'][$i]['part'] = $datepart;
  674. $this->form['tests'][$i]['arg'] = $target;
  675. $this->form['tests'][$i]['header'] = $dateheader;
  676. if ($type != 'exists') {
  677. if (!count($target)) {
  678. $this->errors['tests'][$i]['target'] = $this->plugin->gettext('cannotbeempty');
  679. }
  680. else if (strpos($type, 'count-') === 0) {
  681. foreach ($target as $arg) {
  682. if (preg_match('/[^0-9]/', $arg)) {
  683. $this->errors['tests'][$i]['target'] = $this->plugin->gettext('forbiddenchars');
  684. }
  685. }
  686. }
  687. else if (strpos($type, 'value-') === 0) {
  688. // Some date/time formats do not support i;ascii-numeric comparator
  689. if ($comparator == 'i;ascii-numeric' && in_array($datepart, array('date', 'time', 'iso8601', 'std11'))) {
  690. $comparator = '';
  691. }
  692. }
  693. if (count($target) && !preg_match('/^(regex|matches|count-)/', $type)) {
  694. foreach ($target as $arg) {
  695. if (!$this->validate_date_part($datepart, $arg)) {
  696. $this->errors['tests'][$i]['target'] = $this->plugin->gettext('invaliddateformat');
  697. break;
  698. }
  699. }
  700. }
  701. }
  702. }
  703. else if ($header == 'body') {
  704. $trans = $this->strip_value($body_trans[$idx]);
  705. $trans_type = $this->strip_value($body_types[$idx], true);
  706. if (preg_match('/^not/', $operator))
  707. $this->form['tests'][$i]['not'] = true;
  708. $type = preg_replace('/^not/', '', $operator);
  709. if ($type == 'exists') {
  710. $this->errors['tests'][$i]['op'] = true;
  711. }
  712. $this->form['tests'][$i]['test'] = 'body';
  713. $this->form['tests'][$i]['type'] = $type;
  714. $this->form['tests'][$i]['arg'] = $target;
  715. if (empty($target) && $type != 'exists') {
  716. $this->errors['tests'][$i]['target'] = $this->plugin->gettext('cannotbeempty');
  717. }
  718. else if (preg_match('/^(value|count)-/', $type)) {
  719. foreach ($target as $target_value) {
  720. if (preg_match('/[^0-9]/', $target_value)) {
  721. $this->errors['tests'][$i]['target'] = $this->plugin->gettext('forbiddenchars');
  722. }
  723. }
  724. }
  725. $this->form['tests'][$i]['part'] = $trans;
  726. if ($trans == 'content') {
  727. $this->form['tests'][$i]['content'] = $trans_type;
  728. }
  729. }
  730. else {
  731. $cust_header = $headers = $this->strip_value(array_shift($cust_headers));
  732. $mod = $this->strip_value($mods[$idx]);
  733. $mod_type = $this->strip_value($mod_types[$idx]);
  734. $index = $this->strip_value($indexes[$idx]);
  735. $indexlast = $this->strip_value($lastindexes[$idx]);
  736. if (preg_match('/^not/', $operator))
  737. $this->form['tests'][$i]['not'] = true;
  738. $type = preg_replace('/^not/', '', $operator);
  739. if (!empty($index) && $mod != 'envelope') {
  740. $this->form['tests'][$i]['index'] = intval($index);
  741. $this->form['tests'][$i]['last'] = !empty($indexlast);
  742. }
  743. if ($header == '...') {
  744. if (!count($headers))
  745. $this->errors['tests'][$i]['header'] = $this->plugin->gettext('cannotbeempty');
  746. else {
  747. foreach ($headers as $hr) {
  748. // RFC2822: printable ASCII except colon
  749. if (!preg_match('/^[\x21-\x39\x41-\x7E]+$/i', $hr)) {
  750. $this->errors['tests'][$i]['header'] = $this->plugin->gettext('forbiddenchars');
  751. }
  752. }
  753. }
  754. if (empty($this->errors['tests'][$i]['header']))
  755. $cust_header = (is_array($headers) && count($headers) == 1) ? $headers[0] : $headers;
  756. }
  757. $header = $header == '...' ? $cust_header : $header;
  758. if (is_array($header)) {
  759. foreach ($header as $h_index => $val) {
  760. if (isset($this->headers[$val])) {
  761. $header[$h_index] = $this->headers[$val];
  762. }
  763. }
  764. }
  765. if ($type == 'exists') {
  766. $this->form['tests'][$i]['test'] = 'exists';
  767. $this->form['tests'][$i]['arg'] = $header;
  768. }
  769. else {
  770. $test = 'header';
  771. if ($mod == 'address' || $mod == 'envelope') {
  772. $found = false;
  773. if (empty($this->errors['tests'][$i]['header'])) {
  774. foreach ((array)$header as $hdr) {
  775. if (!in_array(strtolower(trim($hdr)), $this->addr_headers))
  776. $found = true;
  777. }
  778. }
  779. if (!$found)
  780. $test = $mod;
  781. }
  782. $this->form['tests'][$i]['type'] = $type;
  783. $this->form['tests'][$i]['test'] = $test;
  784. $this->form['tests'][$i]['arg1'] = $header;
  785. $this->form['tests'][$i]['arg2'] = $target;
  786. if (empty($target)) {
  787. $this->errors['tests'][$i]['target'] = $this->plugin->gettext('cannotbeempty');
  788. }
  789. else if (preg_match('/^(value|count)-/', $type)) {
  790. foreach ($target as $target_value) {
  791. if (preg_match('/[^0-9]/', $target_value)) {
  792. $this->errors['tests'][$i]['target'] = $this->plugin->gettext('forbiddenchars');
  793. }
  794. }
  795. }
  796. if ($mod) {
  797. $this->form['tests'][$i]['part'] = $mod_type;
  798. }
  799. }
  800. }
  801. if ($header != 'size' && $comparator) {
  802. $this->form['tests'][$i]['comparator'] = $comparator;
  803. }
  804. $i++;
  805. }
  806. }
  807. $i = 0;
  808. // actions
  809. foreach ($act_types as $idx => $type) {
  810. $type = $this->strip_value($type);
  811. switch ($type) {
  812. case 'fileinto':
  813. case 'fileinto_copy':
  814. $mailbox = $this->strip_value($mailboxes[$idx], false, false);
  815. $this->form['actions'][$i]['target'] = $this->mod_mailbox($mailbox, 'in');
  816. if ($type == 'fileinto_copy') {
  817. $type = 'fileinto';
  818. $this->form['actions'][$i]['copy'] = true;
  819. }
  820. break;
  821. case 'reject':
  822. case 'ereject':
  823. $target = $this->strip_value($area_targets[$idx]);
  824. $this->form['actions'][$i]['target'] = str_replace("\r\n", "\n", $target);
  825. // if ($target == '')
  826. // $this->errors['actions'][$i]['targetarea'] = $this->plugin->gettext('cannotbeempty');
  827. break;
  828. case 'redirect':
  829. case 'redirect_copy':
  830. $target = $this->strip_value($act_targets[$idx]);
  831. $domain = $this->strip_value($domain_targets[$idx]);
  832. // force one of the configured domains
  833. $domains = (array) $this->rc->config->get('managesieve_domains');
  834. if (!empty($domains) && !empty($target)) {
  835. if (!$domain || !in_array($domain, $domains)) {
  836. $domain = $domains[0];
  837. }
  838. $target .= '@' . $domain;
  839. }
  840. $this->form['actions'][$i]['target'] = $target;
  841. if ($target == '')
  842. $this->errors['actions'][$i]['target'] = $this->plugin->gettext('cannotbeempty');
  843. else if (!rcube_utils::check_email($target))
  844. $this->errors['actions'][$i]['target'] = $this->plugin->gettext(!empty($domains) ? 'forbiddenchars' : 'noemailwarning');
  845. if ($type == 'redirect_copy') {
  846. $type = 'redirect';
  847. $this->form['actions'][$i]['copy'] = true;
  848. }
  849. break;
  850. case 'addflag':
  851. case 'setflag':
  852. case 'removeflag':
  853. $_target = array();
  854. if (empty($flags[$idx])) {
  855. $this->errors['actions'][$i]['target'] = $this->plugin->gettext('noflagset');
  856. }
  857. else {
  858. foreach ($flags[$idx] as $flag) {
  859. $_target[] = $this->strip_value($flag);
  860. }
  861. }
  862. $this->form['actions'][$i]['target'] = $_target;
  863. break;
  864. case 'vacation':
  865. $reason = $this->strip_value($reasons[$idx]);
  866. $interval_type = $interval_types[$idx] == 'seconds' ? 'seconds' : 'days';
  867. $this->form['actions'][$i]['reason'] = str_replace("\r\n", "\n", $reason);
  868. $this->form['actions'][$i]['from'] = $from[$idx];
  869. $this->form['actions'][$i]['subject'] = $subject[$idx];
  870. $this->form['actions'][$i]['addresses'] = array_shift($addresses);
  871. $this->form['actions'][$i][$interval_type] = $intervals[$idx];
  872. // @TODO: vacation :mime, :handle
  873. foreach ((array)$this->form['actions'][$i]['addresses'] as $aidx => $address) {
  874. $this->form['actions'][$i]['addresses'][$aidx] = $address = trim($address);
  875. if (empty($address)) {
  876. unset($this->form['actions'][$i]['addresses'][$aidx]);
  877. }
  878. else if (!rcube_utils::check_email($address)) {
  879. $this->errors['actions'][$i]['addresses'] = $this->plugin->gettext('noemailwarning');
  880. break;
  881. }
  882. }
  883. if (!empty($this->form['actions'][$i]['from']) && !rcube_utils::check_email($this->form['actions'][$i]['from'])) {
  884. $this->errors['actions'][$i]['from'] = $this->plugin->gettext('noemailwarning');
  885. }
  886. if ($this->form['actions'][$i]['reason'] == '')
  887. $this->errors['actions'][$i]['reason'] = $this->plugin->gettext('cannotbeempty');
  888. if ($this->form['actions'][$i][$interval_type] && !preg_match('/^[0-9]+$/', $this->form['actions'][$i][$interval_type]))
  889. $this->errors['actions'][$i]['interval'] = $this->plugin->gettext('forbiddenchars');
  890. break;
  891. case 'set':
  892. $this->form['actions'][$i]['name'] = $varnames[$idx];
  893. $this->form['actions'][$i]['value'] = $varvalues[$idx];
  894. foreach ((array)$varmods[$idx] as $v_m) {
  895. $this->form['actions'][$i][$v_m] = true;
  896. }
  897. if (empty($varnames[$idx])) {
  898. $this->errors['actions'][$i]['name'] = $this->plugin->gettext('cannotbeempty');
  899. }
  900. else if (!preg_match('/^[0-9a-z_]+$/i', $varnames[$idx])) {
  901. $this->errors['actions'][$i]['name'] = $this->plugin->gettext('forbiddenchars');
  902. }
  903. if (!isset($varvalues[$idx]) || $varvalues[$idx] === '') {
  904. $this->errors['actions'][$i]['value'] = $this->plugin->gettext('cannotbeempty');
  905. }
  906. break;
  907. case 'notify':
  908. if (empty($notifymethods[$idx])) {
  909. $this->errors['actions'][$i]['method'] = $this->plugin->gettext('cannotbeempty');
  910. }
  911. if (empty($notifytargets[$idx])) {
  912. $this->errors['actions'][$i]['target'] = $this->plugin->gettext('cannotbeempty');
  913. }
  914. if (!empty($notifyfrom[$idx]) && !rcube_utils::check_email($notifyfrom[$idx])) {
  915. $this->errors['actions'][$i]['from'] = $this->plugin->gettext('noemailwarning');
  916. }
  917. // skip empty options
  918. foreach ((array)$notifyoptions[$idx] as $opt_idx => $opt) {
  919. if (!strlen(trim($opt))) {
  920. unset($notifyoptions[$idx][$opt_idx]);
  921. }
  922. }
  923. $this->form['actions'][$i]['method'] = $notifymethods[$idx] . ':' . $notifytargets[$idx];
  924. $this->form['actions'][$i]['options'] = $notifyoptions[$idx];
  925. $this->form['actions'][$i]['message'] = $notifymessages[$idx];
  926. $this->form['actions'][$i]['from'] = $notifyfrom[$idx];
  927. $this->form['actions'][$i]['importance'] = $notifyimp[$idx];
  928. break;
  929. }
  930. $this->form['actions'][$i]['type'] = $type;
  931. $i++;
  932. }
  933. if (!$this->errors && !$error) {
  934. // save the script
  935. if (!isset($this->script[$fid])) {
  936. $fid = $this->sieve->script->add_rule($this->form);
  937. $new = true;
  938. }
  939. else {
  940. $fid = $this->sieve->script->update_rule($fid, $this->form);
  941. }
  942. if ($fid !== false)
  943. $save = $this->save_script();
  944. if ($save && $fid !== false) {
  945. $this->rc->output->show_message('managesieve.filtersaved', 'confirmation');
  946. if ($this->rc->task != 'mail') {
  947. $this->rc->output->command('parent.managesieve_updatelist',
  948. isset($new) ? 'add' : 'update',
  949. array(
  950. 'name' => $this->form['name'],
  951. 'id' => $fid,
  952. 'disabled' => $this->form['disabled']
  953. ));
  954. }
  955. else {
  956. $this->rc->output->command('managesieve_dialog_close');
  957. $this->rc->output->send('iframe');
  958. }
  959. }
  960. else {
  961. $this->rc->output->show_message('managesieve.filtersaveerror', 'error');
  962. // $this->rc->output->send();
  963. }
  964. }
  965. }
  966. $this->send();
  967. }
  968. protected function send()
  969. {
  970. // Handle form action
  971. if (isset($_GET['_framed']) || isset($_POST['_framed'])) {
  972. if (isset($_GET['_newset']) || isset($_POST['_newset'])) {
  973. $this->rc->output->send('managesieve.setedit');
  974. }
  975. else {
  976. $this->rc->output->send('managesieve.filteredit');
  977. }
  978. }
  979. else {
  980. $this->rc->output->set_pagetitle($this->plugin->gettext('filters'));
  981. $this->rc->output->send('managesieve.managesieve');
  982. }
  983. }
  984. // return the filters list as HTML table
  985. function filters_list($attrib)
  986. {
  987. // add id to message list table if not specified
  988. if (!strlen($attrib['id']))
  989. $attrib['id'] = 'rcmfilterslist';
  990. // define list of cols to be displayed
  991. $a_show_cols = array('name');
  992. $result = $this->list_rules();
  993. // create XHTML table
  994. $out = $this->rc->table_output($attrib, $result, $a_show_cols, 'id');
  995. // set client env
  996. $this->rc->output->add_gui_object('filterslist', $attrib['id']);
  997. $this->rc->output->include_script('list.js');
  998. // add some labels to client
  999. $this->rc->output->add_label('managesieve.filterdeleteconfirm');
  1000. return $out;
  1001. }
  1002. // return the filters list as <SELECT>
  1003. function filtersets_list($attrib, $no_env = false)
  1004. {
  1005. // add id to message list table if not specified
  1006. if (!strlen($attrib['id']))
  1007. $attrib['id'] = 'rcmfiltersetslist';
  1008. $list = $this->list_scripts();
  1009. if ($list) {
  1010. asort($list, SORT_LOCALE_STRING);
  1011. }
  1012. if (!empty($attrib['type']) && $attrib['type'] == 'list') {
  1013. // define list of cols to be displayed
  1014. $a_show_cols = array('name');
  1015. if ($list) {
  1016. foreach ($list as $idx => $set) {
  1017. $scripts['S'.$idx] = $set;
  1018. $result[] = array(
  1019. 'name' => $set,
  1020. 'id' => 'S'.$idx,
  1021. 'class' => !in_array($set, $this->active) ? 'disabled' : '',
  1022. );
  1023. }
  1024. }
  1025. // create XHTML table
  1026. $out = $this->rc->table_output($attrib, $result, $a_show_cols, 'id');
  1027. $this->rc->output->set_env('filtersets', $scripts);
  1028. $this->rc->output->include_script('list.js');
  1029. }
  1030. else {
  1031. $select = new html_select(array('name' => '_set', 'id' => $attrib['id'],
  1032. 'onchange' => $this->rc->task != 'mail' ? 'rcmail.managesieve_set()' : ''));
  1033. if ($list) {
  1034. foreach ($list as $set)
  1035. $select->add($set, $set);
  1036. }
  1037. $out = $select->show($this->sieve->current);
  1038. }
  1039. // set client env
  1040. if (!$no_env) {
  1041. $this->rc->output->add_gui_object('filtersetslist', $attrib['id']);
  1042. $this->rc->output->add_label('managesieve.setdeleteconfirm');
  1043. }
  1044. return $out;
  1045. }
  1046. function filter_frame($attrib)
  1047. {
  1048. return $this->rc->output->frame($attrib, true);
  1049. }
  1050. function filterset_form($attrib)
  1051. {
  1052. if (!$attrib['id'])
  1053. $attrib['id'] = 'rcmfiltersetform';
  1054. $out = '<form name="filtersetform" action="./" method="post" enctype="multipart/form-data">'."\n";
  1055. $hiddenfields = new html_hiddenfield(array('name' => '_task', 'value' => $this->rc->task));
  1056. $hiddenfields->add(array('name' => '_action', 'value' => 'plugin.managesieve-save'));
  1057. $hiddenfields->add(array('name' => '_framed', 'value' => ($_POST['_framed'] || $_GET['_framed'] ? 1 : 0)));
  1058. $hiddenfields->add(array('name' => '_newset', 'value' => 1));
  1059. $out .= $hiddenfields->show();
  1060. $name = rcube_utils::get_input_value('_name', rcube_utils::INPUT_POST);
  1061. $copy = rcube_utils::get_input_value('_copy', rcube_utils::INPUT_POST);
  1062. $selected = rcube_utils::get_input_value('_from', rcube_utils::INPUT_POST);
  1063. // filter set name input
  1064. $input_name = new html_inputfield(array('name' => '_name', 'id' => '_name', 'size' => 30,
  1065. 'class' => ($this->errors['name'] ? 'error' : '')));
  1066. $out .= sprintf('<label for="%s"><b>%s:</b></label> %s<br /><br />',
  1067. '_name', rcube::Q($this->plugin->gettext('filtersetname')), $input_name->show($name));
  1068. $out .="\n<fieldset class=\"itemlist\"><legend>" . $this->plugin->gettext('filters') . ":</legend>\n";
  1069. $out .= '<input type="radio" id="from_none" name="_from" value="none"'
  1070. .(!$selected || $selected=='none' ? ' checked="checked"' : '').'></input>';
  1071. $out .= sprintf('<label for="%s">%s</label> ', 'from_none', rcube::Q($this->plugin->gettext('none')));
  1072. // filters set list
  1073. $list = $this->list_scripts();
  1074. $select = new html_select(array('name' => '_copy', 'id' => '_copy'));
  1075. if (is_array($list)) {
  1076. asort($list, SORT_LOCALE_STRING);
  1077. if (!$copy)
  1078. $copy = $_SESSION['managesieve_current'];
  1079. foreach ($list as $set) {
  1080. $select->add($set, $set);
  1081. }
  1082. $out .= '<br /><input type="radio" id="from_set" name="_from" value="set"'
  1083. .($selected=='set' ? ' checked="checked"' : '').'></input>';
  1084. $out .= sprintf('<label for="%s">%s:</label> ', 'from_set', rcube::Q($this->plugin->gettext('fromset')));
  1085. $out .= $select->show($copy);
  1086. }
  1087. // script upload box
  1088. $upload = new html_inputfield(array('name' => '_file', 'id' => '_file', 'size' => 30,
  1089. 'type' => 'file', 'class' => ($this->errors['file'] ? 'error' : '')));
  1090. $out .= '<br /><input type="radio" id="from_file" name="_from" value="file"'
  1091. .($selected=='file' ? ' checked="checked"' : '').'></input>';
  1092. $out .= sprintf('<label for="%s">%s:</label> ', 'from_file', rcube::Q($this->plugin->gettext('fromfile')));
  1093. $out .= $upload->show();
  1094. $out .= '</fieldset>';
  1095. $this->rc->output->add_gui_object('sieveform', 'filtersetform');
  1096. if ($this->errors['name'])
  1097. $this->add_tip('_name', $this->errors['name'], true);
  1098. if ($this->errors['file'])
  1099. $this->add_tip('_file', $this->errors['file'], true);
  1100. $this->print_tips();
  1101. return $out;
  1102. }
  1103. function filter_form($attrib)
  1104. {
  1105. if (!$attrib['id'])
  1106. $attrib['id'] = 'rcmfilterform';
  1107. $fid = rcube_utils::get_input_value('_fid', rcube_utils::INPUT_GPC);
  1108. $scr = isset($this->form) ? $this->form : $this->script[$fid];
  1109. $hiddenfields = new html_hiddenfield(array('name' => '_task', 'value' => $this->rc->task));
  1110. $hiddenfields->add(array('name' => '_action', 'value' => 'plugin.managesieve-save'));
  1111. $hiddenfields->add(array('name' => '_framed', 'value' => ($_POST['_framed'] || $_GET['_framed'] ? 1 : 0)));
  1112. $hiddenfields->add(array('name' => '_fid', 'value' => $fid));
  1113. $out = '<form name="filterform" action="./" method="post">'."\n";
  1114. $out .= $hiddenfields->show();
  1115. // 'any' flag
  1116. if ((!isset($this->form) && empty($scr['tests']) && !empty($scr))
  1117. || (sizeof($scr['tests']) == 1 && $scr['tests'][0]['test'] == 'true' && !$scr['tests'][0]['not'])
  1118. ) {
  1119. $any = true;
  1120. }
  1121. // filter name input
  1122. $field_id = '_name';
  1123. $input_name = new html_inputfield(array('name' => '_name', 'id' => $field_id, 'size' => 30,
  1124. 'class' => ($this->errors['name'] ? 'error' : '')));
  1125. if ($this->errors['name'])
  1126. $this->add_tip($field_id, $this->errors['name'], true);
  1127. if (isset($scr))
  1128. $input_name = $input_name->show($scr['name']);
  1129. else
  1130. $input_name = $input_name->show();
  1131. $out .= sprintf("\n<label for=\"%s\"><b>%s:</b></label> %s\n",
  1132. $field_id, rcube::Q($this->plugin->gettext('filtername')), $input_name);
  1133. // filter set selector
  1134. if ($this->rc->task == 'mail') {
  1135. $out .= sprintf("\n&nbsp;<label for=\"%s\"><b>%s:</b></label> %s\n",
  1136. $field_id, rcube::Q($this->plugin->gettext('filterset')),
  1137. $this->filtersets_list(array('id' => 'sievescriptname'), true));
  1138. }
  1139. $out .= '<br /><br /><fieldset><legend>' . rcube::Q($this->plugin->gettext('messagesrules')) . "</legend>\n";
  1140. // any, allof, anyof radio buttons
  1141. $field_id = '_allof';
  1142. $input_join = new html_radiobutton(array('name' => '_join', 'id' => $field_id, 'value' => 'allof',
  1143. 'onclick' => 'rule_join_radio(\'allof\')', 'class' => 'radio'));
  1144. if (isset($scr) && !$any)
  1145. $input_join = $input_join->show($scr['join'] ? 'allof' : '');
  1146. else
  1147. $input_join = $input_join->show();
  1148. $out .= sprintf("%s<label for=\"%s\">%s</label>&nbsp;\n",
  1149. $input_join, $field_id, rcube::Q($this->plugin->gettext('filterallof')));
  1150. $field_id = '_anyof';
  1151. $input_join = new html_radiobutton(array('name' => '_join', 'id' => $field_id, 'value' => 'anyof',
  1152. 'onclick' => 'rule_join_radio(\'anyof\')', 'class' => 'radio'));
  1153. if (isset($scr) && !$any)
  1154. $input_join = $input_join->show($scr['join'] ? '' : 'anyof');
  1155. else
  1156. $input_join = $input_join->show('anyof'); // default
  1157. $out .= sprintf("%s<label for=\"%s\">%s</label>\n",
  1158. $input_join, $field_id, rcube::Q($this->plugin->gettext('filteranyof')));
  1159. $field_id = '_any';
  1160. $input_join = new html_radiobutton(array('name' => '_join', 'id' => $field_id, 'value' => 'any',
  1161. 'onclick' => 'rule_join_radio(\'any\')', 'class' => 'radio'));
  1162. $input_join = $input_join->show($any ? 'any' : '');
  1163. $out .= sprintf("%s<label for=\"%s\">%s</label>\n",
  1164. $input_join, $field_id, rcube::Q($this->plugin->gettext('filterany')));
  1165. $rows_num = !empty($scr['tests']) ? sizeof($scr['tests']) : 1;
  1166. $out .= '<div id="rules"'.($any ? ' style="display: none"' : '').'>';
  1167. for ($x=0; $x<$rows_num; $x++)
  1168. $out .= $this->rule_div($fid, $x);
  1169. $out .= "</div>\n";
  1170. $out .= "</fieldset>\n";
  1171. // actions
  1172. $out .= '<fieldset><legend>' . rcube::Q($this->plugin->gettext('messagesactions')) . "</legend>\n";
  1173. $rows_num = isset($scr) ? sizeof($scr['actions']) : 1;
  1174. $out .= '<div id="actions">';
  1175. for ($x=0; $x<$rows_num; $x++)
  1176. $out .= $this->action_div($fid, $x);
  1177. $out .= "</div>\n";
  1178. $out .= "</fieldset>\n";
  1179. $this->print_tips();
  1180. if ($scr['disabled']) {
  1181. $this->rc->output->set_env('rule_disabled', true);
  1182. }
  1183. $this->rc->output->add_label(
  1184. 'managesieve.ruledeleteconfirm',
  1185. 'managesieve.actiondeleteconfirm'
  1186. );
  1187. $this->rc->output->add_gui_object('sieveform', 'filterform');
  1188. return $out;
  1189. }
  1190. function rule_div($fid, $id, $div=true)
  1191. {
  1192. $rule = isset($this->form) ? $this->form['tests'][$id] : $this->script[$fid]['tests'][$id];
  1193. $rows_num = isset($this->form) ? sizeof($this->form['tests']) : sizeof($this->script[$fid]['tests']);
  1194. // headers select
  1195. $select_header = new html_select(array('name' => "_header[]", 'id' => 'header'.$id,
  1196. 'onchange' => 'rule_header_select(' .$id .')'));
  1197. foreach ($this->headers as $index => $header) {
  1198. $header = $this->rc->text_exists($index) ? $this->plugin->gettext($index) : $header;
  1199. $select_header->add($header, $index);
  1200. }
  1201. $select_header->add($this->plugin->gettext('...'), '...');
  1202. if (in_array('body', $this->exts))
  1203. $select_header->add($this->plugin->gettext('body'), 'body');
  1204. $select_header->add($this->plugin->gettext('size'), 'size');
  1205. if (in_array('date', $this->exts)) {
  1206. $select_header->add($this->plugin->gettext('datetest'), 'date');
  1207. $select_header->add($this->plugin->gettext('currdate'), 'currentdate');
  1208. }
  1209. if (isset($rule['test'])) {
  1210. if (in_array($rule['test'], array('header', 'address', 'envelope'))) {
  1211. if (is_array($rule['arg1']) && count($rule['arg1']) == 1) {
  1212. $rule['arg1'] = $rule['arg1'][0];
  1213. }
  1214. $matches = ($header = strtolower($rule['arg1'])) && isset($this->headers[$header]);
  1215. $test = $matches ? $header : '...';
  1216. }
  1217. else if ($rule['test'] == 'exists') {
  1218. if (is_array($rule['arg']) && count($rule['arg']) == 1) {
  1219. $rule['arg'] = $rule['arg'][0];
  1220. }
  1221. $matches = ($header = strtolower($rule['arg'])) && isset($this->headers[$header]);
  1222. $test = $matches ? $header : '...';
  1223. }
  1224. else if (in_array($rule['test'], array('size', 'body', 'date', 'currentdate'))) {
  1225. $test = $rule['test'];
  1226. }
  1227. else if ($rule['test'] != 'true') {
  1228. $test = '...';
  1229. }
  1230. }
  1231. $aout = $select_header->show($test);
  1232. // custom headers input
  1233. if (isset($rule['test']) && in_array($rule['test'], array('header', 'address', 'envelope'))) {
  1234. $custom = (array) $rule['arg1'];
  1235. if (count($custom) == 1 && isset($this->headers[strtolower($custom[0])])) {
  1236. unset($custom);
  1237. }
  1238. }
  1239. else if (isset($rule['test']) && $rule['test'] == 'exists') {
  1240. $custom = (array) $rule['arg'];
  1241. if (count($custom) == 1 && isset($this->headers[strtolower($custom[0])])) {
  1242. unset($custom);
  1243. }
  1244. }
  1245. $tout = $this->list_input($id, 'custom_header', $custom, isset($custom),
  1246. $this->error_class($id, 'test', 'header', 'custom_header'), 15) . "\n";
  1247. // matching type select (operator)
  1248. $select_op = new html_select(array('name' => "_rule_op[]", 'id' => 'rule_op'.$id,
  1249. 'style' => 'display:' .($rule['test']!='size' ? 'inline' : 'none'),
  1250. 'class' => 'operator_selector',
  1251. 'onchange' => 'rule_op_select(this, '.$id.')'));
  1252. $select_op->add(rcube::Q($this->plugin->gettext('filtercontains')), 'contains');
  1253. $select_op->add(rcube::Q($this->plugin->gettext('filternotcontains')), 'notcontains');
  1254. $select_op->add(rcube::Q($this->plugin->gettext('filteris')), 'is');
  1255. $select_op->add(rcube::Q($this->plugin->gettext('filterisnot')), 'notis');
  1256. $select_op->add(rcube::Q($this->plugin->gettext('filterexists')), 'exists');
  1257. $select_op->add(rcube::Q($this->plugin->gettext('filternotexists')), 'notexists');
  1258. $select_op->add(rcube::Q($this->plugin->gettext('filtermatches')), 'matches');
  1259. $select_op->add(rcube::Q($this->plugin->gettext('filternotmatches')), 'notmatches');
  1260. if (in_array('regex', $this->exts)) {
  1261. $select_op->add(rcube::Q($this->plugin->gettext('filterregex')), 'regex');
  1262. $select_op->add(rcube::Q($this->plugin->gettext('filternotregex')), 'notregex');
  1263. }
  1264. if (in_array('relational', $this->exts)) {
  1265. $select_op->add(rcube::Q($this->plugin->gettext('countisgreaterthan')), 'count-gt');
  1266. $select_op->add(rcube::Q($this->plugin->gettext('countisgreaterthanequal')), 'count-ge');
  1267. $select_op->add(rcube::Q($this->plugin->gettext('countislessthan')), 'count-lt');
  1268. $select_op->add(rcube::Q($this->plugin->gettext('countislessthanequal')), 'count-le');
  1269. $select_op->add(rcube::Q($this->plugin->gettext('countequals')), 'count-eq');
  1270. $select_op->add(rcube::Q($this->plugin->gettext('countnotequals')), 'count-ne');
  1271. $select_op->add(rcube::Q($this->plugin->gettext('valueisgreaterthan')), 'value-gt');
  1272. $select_op->add(rcube::Q($this->plugin->gettext('valueisgreaterthanequal')), 'value-ge');
  1273. $select_op->add(rcube::Q($this->plugin->gettext('valueislessthan')), 'value-lt');
  1274. $select_op->add(rcube::Q($this->plugin->gettext('valueislessthanequal')), 'value-le');
  1275. $select_op->add(rcube::Q($this->plugin->gettext('valueequals')), 'value-eq');
  1276. $select_op->add(rcube::Q($this->plugin->gettext('valuenotequals')), 'value-ne');
  1277. }
  1278. $test = self::rule_test($rule);
  1279. $target = '';
  1280. // target(s) input
  1281. if (in_array($rule['test'], array('header', 'address', 'envelope'))) {
  1282. $target = $rule['arg2'];
  1283. }
  1284. else if (in_array($rule['test'], array('body', 'date', 'currentdate'))) {
  1285. $target = $rule['arg'];
  1286. }
  1287. else if ($rule['test'] == 'size') {
  1288. if (preg_match('/^([0-9]+)(K|M|G)?$/', $rule['arg'], $matches)) {
  1289. $sizetarget = $matches[1];
  1290. $sizeitem = $matches[2];
  1291. }
  1292. else {
  1293. $sizetarget = $rule['arg'];
  1294. $sizeitem = $rule['item'];
  1295. }
  1296. }
  1297. // (current)date part select
  1298. if (in_array('date', $this->exts) || in_array('currentdate', $this->exts)) {
  1299. $date_parts = array('date', 'iso8601', 'std11', 'julian', 'time',
  1300. 'year', 'month', 'day', 'hour', 'minute', 'second', 'weekday', 'zone');
  1301. $select_dp = new html_select(array('name' => "_rule_date_part[]", 'id' => 'rule_date_part'.$id,
  1302. 'style' => in_array($rule['test'], array('currentdate', 'date')) && !preg_match('/^(notcount|count)-/', $test) ? '' : 'display:none',
  1303. 'class' => 'datepart_selector',
  1304. ));
  1305. foreach ($date_parts as $part) {
  1306. $select_dp->add(rcube::Q($this->plugin->gettext($part)), $part);
  1307. }
  1308. $tout .= $select_dp->show($rule['test'] == 'currentdate' || $rule['test'] == 'date' ? $rule['part'] : '');
  1309. }
  1310. $tout .= $select_op->show($test);
  1311. $tout .= $this->list_input($id, 'rule_target', $target,
  1312. $rule['test'] != 'size' && $rule['test'] != 'exists',
  1313. $this->error_class($id, 'test', 'target', 'rule_target')) . "\n";
  1314. $select_size_op = new html_select(array('name' => "_rule_size_op[]", 'id' => 'rule_size_op'.$id));
  1315. $select_size_op->add(rcube::Q($this->plugin->gettext('filterover')), 'over');
  1316. $select_size_op->add(rcube::Q($this->plugin->gettext('filterunder')), 'under');
  1317. $tout .= '<div id="rule_size' .$id. '" style="display:' . ($rule['test']=='size' ? 'inline' : 'none') .'">';
  1318. $tout .= $select_size_op->show($rule['test']=='size' ? $rule['type'] : '');
  1319. $tout .= '<input type="text" name="_rule_size_target[]" id="rule_size_i'.$id.'" value="'.$sizetarget.'" size="10" '
  1320. . $this->error_class($id, 'test', 'sizetarget', 'rule_size_i') .' />
  1321. <label><input type="radio" name="_rule_size_item['.$id.']" value=""'
  1322. . (!$sizeitem ? ' checked="checked"' : '') .' class="radio" />'.$this->rc->gettext('B').'</label>
  1323. <label><input type="radio" name="_rule_size_item['.$id.']" value="K"'
  1324. . ($sizeitem=='K' ? ' checked="checked"' : '') .' class="radio" />'.$this->rc->gettext('KB').'</label>
  1325. <label><input type="radio" name="_rule_size_item['.$id.']" value="M"'
  1326. . ($sizeitem=='M' ? ' checked="checked"' : '') .' class="radio" />'.$this->rc->gettext('MB').'</label>
  1327. <label><input type="radio" name="_rule_size_item['.$id.']" value="G"'
  1328. . ($sizeitem=='G' ? ' checked="checked"' : '') .' class="radio" />'.$this->rc->gettext('GB').'</label>';
  1329. $tout .= '</div>';
  1330. // Advanced modifiers (address, envelope)
  1331. $select_mod = new html_select(array('name' => "_rule_mod[]", 'id' => 'rule_mod_op'.$id,
  1332. 'onchange' => 'rule_mod_select(' .$id .')'));
  1333. $select_mod->add(rcube::Q($this->plugin->gettext('none')), '');
  1334. $select_mod->add(rcube::Q($this->plugin->gettext('address')), 'address');
  1335. if (in_array('envelope', $this->exts))
  1336. $select_mod->add(rcube::Q($this->plugin->gettext('envelope')), 'envelope');
  1337. $select_type = new html_select(array('name' => "_rule_mod_type[]", 'id' => 'rule_mod_type'.$id));
  1338. $select_type->add(rcube::Q($this->plugin->gettext('allparts')), 'all');
  1339. $select_type->add(rcube::Q($this->plugin->gettext('domain')), 'domain');
  1340. $select_type->add(rcube::Q($this->plugin->gettext('localpart')), 'localpart');
  1341. if (in_array('subaddress', $this->exts)) {
  1342. $select_type->add(rcube::Q($this->plugin->gettext('user')), 'user');
  1343. $select_type->add(rcube::Q($this->plugin->gettext('detail')), 'detail');
  1344. }
  1345. $need_mod = !in_array($rule['test'], array('size', 'body', 'date', 'currentdate'));
  1346. $mout = '<div id="rule_mod' .$id. '" class="adv"' . (!$need_mod ? ' style="display:none"' : '') . '>';
  1347. $mout .= ' <span class="label">' . rcube::Q($this->plugin->gettext('modifier')) . ' </span>';
  1348. $mout .= $select_mod->show($rule['test']);
  1349. $mout .= ' <span id="rule_mod_type' . $id . '"';
  1350. $mout .= ' style="display:' . (in_array($rule['test'], array('address', 'envelope')) ? 'inline' : 'none') .'">';
  1351. $mout .= rcube::Q($this->plugin->gettext('modtype')) . ' ';
  1352. $mout .= $select_type->show($rule['part']);
  1353. $mout .= '</span>';
  1354. $mout .= '</div>';
  1355. // Advanced modifiers (body transformations)
  1356. $select_mod = new html_select(array('name' => "_rule_trans[]", 'id' => 'rule_trans_op'.$id,
  1357. 'onchange' => 'rule_trans_select(' .$id .')'));
  1358. $select_mod->add(rcube::Q($this->plugin->gettext('text')), 'text');
  1359. $select_mod->add(rcube::Q($this->plugin->gettext('undecoded')), 'raw');
  1360. $select_mod->add(rcube::Q($this->plugin->gettext('contenttype')), 'content');
  1361. $mout .= '<div id="rule_trans' .$id. '" class="adv"' . ($rule['test'] != 'body' ? ' style="display:none"' : '') . '>';
  1362. $mout .= '<span class="label">' . rcube::Q($this->plugin->gettext('modifier')) . '</span>';
  1363. $mout .= $select_mod->show($rule['part']);
  1364. $mout .= '<input type="text" name="_rule_trans_type[]" id="rule_trans_type'.$id
  1365. . '" value="'.(is_array($rule['content']) ? implode(',', $rule['content']) : $rule['content'])
  1366. .'" size="20"' . ($rule['part'] != 'content' ? ' style="display:none"' : '')
  1367. . $this->error_class($id, 'test', 'part', 'rule_trans_type') .' />';
  1368. $mout .= '</div>';
  1369. // Advanced modifiers (body transformations)
  1370. $select_comp = new html_select(array('name' => "_rule_comp[]", 'id' => 'rule_comp_op'.$id));
  1371. $select_comp->add(rcube::Q($this->plugin->gettext('default')), '');
  1372. $select_comp->add(rcube::Q($this->plugin->gettext('octet')), 'i;octet');
  1373. $select_comp->add(rcube::Q($this->plugin->gettext('asciicasemap')), 'i;ascii-casemap');
  1374. if (in_array('comparator-i;ascii-numeric', $this->exts)) {
  1375. $select_comp->add(rcube::Q($this->plugin->gettext('asciinumeric')), 'i;ascii-numeric');
  1376. }
  1377. // Comparators
  1378. $mout .= '<div id="rule_comp' .$id. '" class="adv"' . ($rule['test'] == 'size' ? ' style="display:none"' : '') . '>';
  1379. $mout .= '<span class="label">' . rcube::Q($this->plugin->gettext('comparator')) . '</span>';
  1380. $mout .= $select_comp->show($rule['comparator']);
  1381. $mout .= '</div>';
  1382. // Date header
  1383. if (in_array('date', $this->exts)) {
  1384. $mout .= '<div id="rule_date_header_div' .$id. '" class="adv"'. ($rule['test'] != 'date' ? ' style="display:none"' : '') .'>';
  1385. $mout .= '<span class="label">' . rcube::Q($this->plugin->gettext('dateheader')) . '</span>';
  1386. $mout .= '<input type="text" name="_rule_date_header[]" id="rule_date_header'.$id
  1387. . '" value="'. rcube::Q($rule['test'] == 'date' ? $rule['header'] : '')
  1388. . '" size="15"' . $this->error_class($id, 'test', 'dateheader', 'rule_date_header') .' />';
  1389. $mout .= '</div>';
  1390. }
  1391. // Index
  1392. if (in_array('index', $this->exts)) {
  1393. $need_index = in_array($rule['test'], array('header', ', address', 'date'));
  1394. $mout .= '<div id="rule_index_div' .$id. '" class="adv"'. (!$need_index ? ' style="display:none"' : '') .'>';
  1395. $mout .= '<span class="label">' . rcube::Q($this->plugin->gettext('index')) . '</span>';
  1396. $mout .= '<input type="text" name="_rule_index[]" id="rule_index'.$id
  1397. . '" value="'. ($rule['index'] ? intval($rule['index']) : '')
  1398. . '" size="3"' . $this->error_class($id, 'test', 'index', 'rule_index') .' />';
  1399. $mout .= '&nbsp;<input type="checkbox" name="_rule_index_last[]" id="rule_index_last'.$id
  1400. . '" value="1"' . (!empty($rule['last']) ? ' checked="checked"' : '') . ' />'
  1401. . '<label for="rule_index_last'.$id.'">'.rcube::Q($this->plugin->gettext('indexlast')).'</label>';
  1402. $mout .= '</div>';
  1403. }
  1404. // Build output table
  1405. $out = $div ? '<div class="rulerow" id="rulerow' .$id .'">'."\n" : '';
  1406. $out .= '<table><tr>';
  1407. $out .= '<td class="advbutton">';
  1408. $out .= '<a href="#" id="ruleadv' . $id .'" title="'. rcube::Q($this->plugin->gettext('advancedopts')). '"
  1409. onclick="rule_adv_switch(' . $id .', this)" class="show">&nbsp;&nbsp;</a>';
  1410. $out .= '</td>';
  1411. $out .= '<td class="rowactions">' . $aout . '</td>';
  1412. $out .= '<td class="rowtargets">' . $tout . "\n";
  1413. $out .= '<div id="rule_advanced' .$id. '" style="display:none">' . $mout . '</div>';
  1414. $out .= '</td>';
  1415. // add/del buttons
  1416. $out .= '<td class="rowbuttons">';
  1417. $out .= '<a href="#" id="ruleadd' . $id .'" title="'. rcube::Q($this->plugin->gettext('add')). '"
  1418. onclick="rcmail.managesieve_ruleadd(' . $id .')" class="button add"></a>';
  1419. $out .= '<a href="#" id="ruledel' . $id .'" title="'. rcube::Q($this->plugin->gettext('del')). '"
  1420. onclick="rcmail.managesieve_ruledel(' . $id .')" class="button del' . ($rows_num<2 ? ' disabled' : '') .'"></a>';
  1421. $out .= '</td>';
  1422. $out .= '</tr></table>';
  1423. $out .= $div ? "</div>\n" : '';
  1424. return $out;
  1425. }
  1426. private static function rule_test(&$rule)
  1427. {
  1428. // first modify value/count tests with 'not' keyword
  1429. // we'll revert the meaning of operators
  1430. if ($rule['not'] && preg_match('/^(count|value)-([gteqnl]{2})/', $rule['type'], $m)) {
  1431. $rule['not'] = false;
  1432. switch ($m[2]) {
  1433. case 'gt': $rule['type'] = $m[1] . '-le'; break;
  1434. case 'ge': $rule['type'] = $m[1] . '-lt'; break;
  1435. case 'lt': $rule['type'] = $m[1] . '-ge'; break;
  1436. case 'le': $rule['type'] = $m[1] . '-gt'; break;
  1437. case 'eq': $rule['type'] = $m[1] . '-ne'; break;
  1438. case 'ne': $rule['type'] = $m[1] . '-eq'; break;
  1439. }
  1440. }
  1441. else if ($rule['not'] && $rule['test'] == 'size') {
  1442. $rule['not'] = false;
  1443. $rule['type'] = $rule['type'] == 'over' ? 'under' : 'over';
  1444. }
  1445. $set = array('header', 'address', 'envelope', 'body', 'date', 'currentdate');
  1446. // build test string supported by select element
  1447. if ($rule['size']) {
  1448. $test = $rule['type'];
  1449. }
  1450. else if (in_array($rule['test'], $set)) {
  1451. $test = ($rule['not'] ? 'not' : '') . ($rule['type'] ?: 'is');
  1452. }
  1453. else {
  1454. $test = ($rule['not'] ? 'not' : '') . $rule['test'];
  1455. }
  1456. return $test;
  1457. }
  1458. function action_div($fid, $id, $div=true)
  1459. {
  1460. $action = isset($this->form) ? $this->form['actions'][$id] : $this->script[$fid]['actions'][$id];
  1461. $rows_num = isset($this->form) ? sizeof($this->form['actions']) : sizeof($this->script[$fid]['actions']);
  1462. $out = $div ? '<div class="actionrow" id="actionrow' .$id .'">'."\n" : '';
  1463. $out .= '<table><tr><td class="rowactions">';
  1464. // action select
  1465. $select_action = new html_select(array('name' => "_action_type[$id]", 'id' => 'action_type'.$id,
  1466. 'onchange' => 'action_type_select(' .$id .')'));
  1467. if (in_array('fileinto', $this->exts))
  1468. $select_action->add(rcube::Q($this->plugin->gettext('messagemoveto')), 'fileinto');
  1469. if (in_array('fileinto', $this->exts) && in_array('copy', $this->exts))
  1470. $select_action->add(rcube::Q($this->plugin->gettext('messagecopyto')), 'fileinto_copy');
  1471. $select_action->add(rcube::Q($this->plugin->gettext('messageredirect')), 'redirect');
  1472. if (in_array('copy', $this->exts))
  1473. $select_action->add(rcube::Q($this->plugin->gettext('messagesendcopy')), 'redirect_copy');
  1474. if (in_array('reject', $this->exts))
  1475. $select_action->add(rcube::Q($this->plugin->gettext('messagediscard')), 'reject');
  1476. else if (in_array('ereject', $this->exts))
  1477. $select_action->add(rcube::Q($this->plugin->gettext('messagediscard')), 'ereject');
  1478. if (in_array('vacation', $this->exts))
  1479. $select_action->add(rcube::Q($this->plugin->gettext('messagereply')), 'vacation');
  1480. $select_action->add(rcube::Q($this->plugin->gettext('messagedelete')), 'discard');
  1481. if (in_array('imapflags', $this->exts) || in_array('imap4flags', $this->exts)) {
  1482. $select_action->add(rcube::Q($this->plugin->gettext('setflags')), 'setflag');
  1483. $select_action->add(rcube::Q($this->plugin->gettext('addflags')), 'addflag');
  1484. $select_action->add(rcube::Q($this->plugin->gettext('removeflags')), 'removeflag');
  1485. }
  1486. if (in_array('variables', $this->exts)) {
  1487. $select_action->add(rcube::Q($this->plugin->gettext('setvariable')), 'set');
  1488. }
  1489. if (in_array('enotify', $this->exts) || in_array('notify', $this->exts)) {
  1490. $select_action->add(rcube::Q($this->plugin->gettext('notify')), 'notify');
  1491. }
  1492. $select_action->add(rcube::Q($this->plugin->gettext('messagekeep')), 'keep');
  1493. $select_action->add(rcube::Q($this->plugin->gettext('rulestop')), 'stop');
  1494. $select_type = $action['type'];
  1495. if (in_array($action['type'], array('fileinto', 'redirect')) && $action['copy']) {
  1496. $select_type .= '_copy';
  1497. }
  1498. $out .= $select_action->show($select_type);
  1499. $out .= '</td>';
  1500. // actions target inputs
  1501. $out .= '<td class="rowtargets">';
  1502. // force domain selection in redirect email input
  1503. $domains = (array) $this->rc->config->get('managesieve_domains');
  1504. if (!empty($domains)) {
  1505. sort($domains);
  1506. $domain_select = new html_select(array('name' => "_action_target_domain[$id]", 'id' => 'action_target_domain'.$id));
  1507. $domain_select->add(array_combine($domains, $domains));
  1508. if ($action['type'] == 'redirect') {
  1509. $parts = explode('@', $action['target']);
  1510. if (!empty($parts)) {
  1511. $action['domain'] = array_pop($parts);
  1512. $action['target'] = implode('@', $parts);
  1513. }
  1514. }
  1515. }
  1516. // redirect target
  1517. $out .= '<span id="redirect_target' . $id . '" style="white-space:nowrap;'
  1518. . ' display:' . ($action['type'] == 'redirect' ? 'inline' : 'none') . '">'
  1519. . '<input type="text" name="_action_target['.$id.']" id="action_target' .$id. '"'
  1520. . ' value="' .($action['type'] == 'redirect' ? rcube::Q($action['target'], 'strict', false) : '') . '"'
  1521. . (!empty($domains) ? ' size="20"' : ' size="35"')
  1522. . $this->error_class($id, 'action', 'target', 'action_target') .' />'
  1523. . (!empty($domains) ? ' @ ' . $domain_select->show($action['domain']) : '')
  1524. . '</span>';
  1525. // (e)reject target
  1526. $out .= '<textarea name="_action_target_area['.$id.']" id="action_target_area' .$id. '" '
  1527. .'rows="3" cols="35" '. $this->error_class($id, 'action', 'targetarea', 'action_target_area')
  1528. .'style="display:' .(in_array($action['type'], array('reject', 'ereject')) ? 'inline' : 'none') .'">'
  1529. . (in_array($action['type'], array('reject', 'ereject')) ? rcube::Q($action['target'], 'strict', false) : '')
  1530. . "</textarea>\n";
  1531. // vacation
  1532. $vsec = in_array('vacation-seconds', $this->exts);
  1533. $auto_addr = $this->rc->config->get('managesieve_vacation_addresses_init');
  1534. $addresses = isset($action['addresses']) || !$auto_addr ? (array) $action['addresses'] : $this->user_emails();
  1535. $out .= '<div id="action_vacation' .$id.'" style="display:' .($action['type']=='vacation' ? 'inline' : 'none') .'">';
  1536. $out .= '<span class="label">'. rcube::Q($this->plugin->gettext('vacationreason')) .'</span><br />'
  1537. .'<textarea name="_action_reason['.$id.']" id="action_reason' .$id. '" '
  1538. .'rows="3" cols="35" '. $this->error_class($id, 'action', 'reason', 'action_reason') . '>'
  1539. . rcube::Q($action['reason'], 'strict', false) . "</textarea>\n";
  1540. $out .= '<br /><span class="label">' .rcube::Q($this->plugin->gettext('vacationsubject')) . '</span><br />'
  1541. .'<input type="text" name="_action_subject['.$id.']" id="action_subject'.$id.'" '
  1542. .'value="' . (is_array($action['subject']) ? rcube::Q(implode(', ', $action['subject']), 'strict', false) : $action['subject']) . '" size="35" '
  1543. . $this->error_class($id, 'action', 'subject', 'action_subject') .' />';
  1544. $out .= '<br /><span class="label">' .rcube::Q($this->plugin->gettext('vacationfrom')) . '</span><br />'
  1545. .'<input type="text" name="_action_from['.$id.']" id="action_from'.$id.'" '
  1546. .'value="' . $action['from'] . '" size="35" '
  1547. . $this->error_class($id, 'action', 'from', 'action_from') .' />';
  1548. $out .= '<br /><span class="label">' .rcube::Q($this->plugin->gettext('vacationaddr')) . '</span><br />'
  1549. . $this->list_input($id, 'action_addresses', $addresses, true,
  1550. $this->error_class($id, 'action', 'addresses', 'action_addresses'), 30)
  1551. . html::a(array('href' => '#', 'onclick' => rcmail_output::JS_OBJECT_NAME . ".managesieve_vacation_addresses($id)"),
  1552. rcube::Q($this->plugin->gettext('filladdresses')));
  1553. $out .= '<br /><span class="label">' . rcube::Q($this->plugin->gettext($vsec ? 'vacationinterval' : 'vacationdays')) . '</span><br />'
  1554. .'<input type="text" name="_action_interval['.$id.']" id="action_interval'.$id.'" '
  1555. .'value="' .rcube::Q(rcube_sieve_vacation::vacation_interval($action), 'strict', false) . '" size="2" '
  1556. . $this->error_class($id, 'action', 'interval', 'action_interval') .' />';
  1557. if ($vsec) {
  1558. $out .= '&nbsp;<label><input type="radio" name="_action_interval_type['.$id.']" value="days"'
  1559. . (!isset($action['seconds']) ? ' checked="checked"' : '') .' class="radio" />'.$this->plugin->gettext('days').'</label>'
  1560. . '&nbsp;<label><input type="radio" name="_action_interval_type['.$id.']" value="seconds"'
  1561. . (isset($action['seconds']) ? ' checked="checked"' : '') .' class="radio" />'.$this->plugin->gettext('seconds').'</label>';
  1562. }
  1563. $out .= '</div>';
  1564. // flags
  1565. $flags = array(
  1566. 'read' => '\\Seen',
  1567. 'answered' => '\\Answered',
  1568. 'flagged' => '\\Flagged',
  1569. 'deleted' => '\\Deleted',
  1570. 'draft' => '\\Draft',
  1571. );
  1572. $flags_target = (array)$action['target'];
  1573. $out .= '<div id="action_flags' .$id.'" style="display:'
  1574. . (preg_match('/^(set|add|remove)flag$/', $action['type']) ? 'inline' : 'none') . '"'
  1575. . $this->error_class($id, 'action', 'flags', 'action_flags') . '>';
  1576. foreach ($flags as $fidx => $flag) {
  1577. $out .= '<input type="checkbox" name="_action_flags[' .$id .'][]" value="' . $flag . '"'
  1578. . (in_array_nocase($flag, $flags_target) ? 'checked="checked"' : '') . ' />'
  1579. . rcube::Q($this->plugin->gettext('flag'.$fidx)) .'<br>';
  1580. }
  1581. $out .= '</div>';
  1582. // set variable
  1583. $set_modifiers = array(
  1584. 'lower',
  1585. 'upper',
  1586. 'lowerfirst',
  1587. 'upperfirst',
  1588. 'quotewildcard',
  1589. 'length'
  1590. );
  1591. $out .= '<div id="action_set' .$id.'" style="display:' .($action['type']=='set' ? 'inline' : 'none') .'">';
  1592. $out .= '<span class="label">' .rcube::Q($this->plugin->gettext('setvarname')) . '</span><br />'
  1593. .'<input type="text" name="_action_varname['.$id.']" id="action_varname'.$id.'" '
  1594. .'value="' . rcube::Q($action['name']) . '" size="35" '
  1595. . $this->error_class($id, 'action', 'name', 'action_varname') .' />';
  1596. $out .= '<br /><span class="label">' .rcube::Q($this->plugin->gettext('setvarvalue')) . '</span><br />'
  1597. .'<input type="text" name="_action_varvalue['.$id.']" id="action_varvalue'.$id.'" '
  1598. .'value="' . rcube::Q($action['value']) . '" size="35" '
  1599. . $this->error_class($id, 'action', 'value', 'action_varvalue') .' />';
  1600. $out .= '<br /><span class="label">' .rcube::Q($this->plugin->gettext('setvarmodifiers')) . '</span><br />';
  1601. foreach ($set_modifiers as $s_m) {
  1602. $s_m_id = 'action_varmods' . $id . $s_m;
  1603. $out .= sprintf('<input type="checkbox" name="_action_varmods[%s][]" value="%s" id="%s"%s />%s<br>',
  1604. $id, $s_m, $s_m_id,
  1605. (array_key_exists($s_m, (array)$action) && $action[$s_m] ? ' checked="checked"' : ''),
  1606. rcube::Q($this->plugin->gettext('var' . $s_m)));
  1607. }
  1608. $out .= '</div>';
  1609. // notify
  1610. $notify_methods = (array) $this->rc->config->get('managesieve_notify_methods');
  1611. $importance_options = $this->notify_importance_options;
  1612. if (empty($notify_methods)) {
  1613. $notify_methods = $this->notify_methods;
  1614. }
  1615. list($method, $target) = explode(':', $action['method'], 2);
  1616. $method = strtolower($method);
  1617. if ($method && !in_array($method, $notify_methods)) {
  1618. $notify_methods[] = $method;
  1619. }
  1620. $select_method = new html_select(array(
  1621. 'name' => "_action_notifymethod[$id]",
  1622. 'id' => "_action_notifymethod$id",
  1623. 'class' => $this->error_class($id, 'action', 'method', 'action_notifymethod'),
  1624. ));
  1625. foreach ($notify_methods as $m_n) {
  1626. $select_method->add(rcube::Q($this->rc->text_exists('managesieve.notifymethod'.$m_n) ? $this->plugin->gettext('managesieve.notifymethod'.$m_n) : $m_n), $m_n);
  1627. }
  1628. $select_importance = new html_select(array(
  1629. 'name' => "_action_notifyimportance[$id]",
  1630. 'id' => "_action_notifyimportance$id",
  1631. 'class' => $this->error_class($id, 'action', 'importance', 'action_notifyimportance')
  1632. ));
  1633. foreach ($importance_options as $io_v => $io_n) {
  1634. $select_importance->add(rcube::Q($this->plugin->gettext($io_n)), $io_v);
  1635. }
  1636. // @TODO: nice UI for mailto: (other methods too) URI parameters
  1637. $out .= '<div id="action_notify' .$id.'" style="display:' .($action['type'] == 'notify' ? 'inline' : 'none') .'">';
  1638. $out .= '<span class="label">' .rcube::Q($this->plugin->gettext('notifytarget')) . '</span><br />'
  1639. . $select_method->show($method)
  1640. .'<input type="text" name="_action_notifytarget['.$id.']" id="action_notifytarget'.$id.'" '
  1641. .'value="' . rcube::Q($target) . '" size="25" '
  1642. . $this->error_class($id, 'action', 'target', 'action_notifytarget') .' />';
  1643. $out .= '<br /><span class="label">'. rcube::Q($this->plugin->gettext('notifymessage')) .'</span><br />'
  1644. .'<textarea name="_action_notifymessage['.$id.']" id="action_notifymessage' .$id. '" '
  1645. .'rows="3" cols="35" '. $this->error_class($id, 'action', 'message', 'action_notifymessage') . '>'
  1646. . rcube::Q($action['message'], 'strict', false) . "</textarea>\n";
  1647. if (in_array('enotify', $this->exts)) {
  1648. $out .= '<br /><span class="label">' .rcube::Q($this->plugin->gettext('notifyfrom')) . '</span><br />'
  1649. .'<input type="text" name="_action_notifyfrom['.$id.']" id="action_notifyfrom'.$id.'" '
  1650. .'value="' . rcube::Q($action['from']) . '" size="35" '
  1651. . $this->error_class($id, 'action', 'from', 'action_notifyfrom') .' />';
  1652. }
  1653. $out .= '<br /><span class="label">' . rcube::Q($this->plugin->gettext('notifyimportance')) . '</span><br />';
  1654. $out .= $select_importance->show($action['importance'] ? (int) $action['importance'] : 2);
  1655. $out .= '<div id="action_notifyoption_div' . $id . '">'
  1656. .'<span class="label">' . rcube::Q($this->plugin->gettext('notifyoptions')) . '</span><br />'
  1657. .$this->list_input($id, 'action_notifyoption', (array)$action['options'], true,
  1658. $this->error_class($id, 'action', 'options', 'action_notifyoption'), 30) . '</div>';
  1659. $out .= '</div>';
  1660. // mailbox select
  1661. if ($action['type'] == 'fileinto') {
  1662. $mailbox = $this->mod_mailbox($action['target'], 'out');
  1663. // make sure non-existing (or unsubscribed) mailbox is listed (#1489956)
  1664. $additional = array($mailbox);
  1665. }
  1666. else {
  1667. $mailbox = '';
  1668. }
  1669. $select = $this->rc->folder_selector(array(
  1670. 'realnames' => false,
  1671. 'maxlength' => 100,
  1672. 'id' => 'action_mailbox' . $id,
  1673. 'name' => "_action_mailbox[$id]",
  1674. 'style' => 'display:'.(empty($action['type']) || $action['type'] == 'fileinto' ? 'inline' : 'none'),
  1675. 'additional' => $additional,
  1676. ));
  1677. $out .= $select->show($mailbox);
  1678. $out .= '</td>';
  1679. // add/del buttons
  1680. $out .= '<td class="rowbuttons">';
  1681. $out .= '<a href="#" id="actionadd' . $id .'" title="'. rcube::Q($this->plugin->gettext('add')). '"
  1682. onclick="rcmail.managesieve_actionadd(' . $id .')" class="button add"></a>';
  1683. $out .= '<a href="#" id="actiondel' . $id .'" title="'. rcube::Q($this->plugin->gettext('del')). '"
  1684. onclick="rcmail.managesieve_actiondel(' . $id .')" class="button del' . ($rows_num<2 ? ' disabled' : '') .'"></a>';
  1685. $out .= '</td>';
  1686. $out .= '</tr></table>';
  1687. $out .= $div ? "</div>\n" : '';
  1688. return $out;
  1689. }
  1690. protected function genid()
  1691. {
  1692. return preg_replace('/[^0-9]/', '', microtime(true));
  1693. }
  1694. protected function strip_value($str, $allow_html = false, $trim = true)
  1695. {
  1696. if (is_array($str)) {
  1697. foreach ($str as $idx => $val) {
  1698. $val = $this->strip_value($val, $allow_html, $trim);
  1699. if ($val === '') {
  1700. unset($str[$idx]);
  1701. }
  1702. }
  1703. return $str;
  1704. }
  1705. if (!$allow_html) {
  1706. $str = strip_tags($str);
  1707. }
  1708. return $trim ? trim($str) : $str;
  1709. }
  1710. protected function error_class($id, $type, $target, $elem_prefix='')
  1711. {
  1712. // TODO: tooltips
  1713. if (($type == 'test' && ($str = $this->errors['tests'][$id][$target])) ||
  1714. ($type == 'action' && ($str = $this->errors['actions'][$id][$target]))
  1715. ) {
  1716. $this->add_tip($elem_prefix.$id, $str, true);
  1717. return ' class="error"';
  1718. }
  1719. return '';
  1720. }
  1721. protected function add_tip($id, $str, $error=false)
  1722. {
  1723. if ($error)
  1724. $str = html::span('sieve error', $str);
  1725. $this->tips[] = array($id, $str);
  1726. }
  1727. protected function print_tips()
  1728. {
  1729. if (empty($this->tips))
  1730. return;
  1731. $script = rcmail_output::JS_OBJECT_NAME.'.managesieve_tip_register('.json_encode($this->tips).');';
  1732. $this->rc->output->add_script($script, 'foot');
  1733. }
  1734. protected function list_input($id, $name, $value, $enabled, $class, $size=null)
  1735. {
  1736. $value = (array) $value;
  1737. $value = array_map(array('rcube', 'Q'), $value);
  1738. $value = implode("\n", $value);
  1739. return '<textarea data-type="list" name="_' . $name . '['.$id.']" id="' . $name.$id . '"'
  1740. . ($enabled ? '' : ' disabled="disabled"')
  1741. . ($size ? ' data-size="'.$size.'"' : '')
  1742. . $class
  1743. . ' style="display:none">' . $value . '</textarea>';
  1744. }
  1745. /**
  1746. * Validate input for date part elements
  1747. */
  1748. protected function validate_date_part($type, $value)
  1749. {
  1750. // we do simple validation of date/part format
  1751. switch ($type) {
  1752. case 'date': // yyyy-mm-dd
  1753. return preg_match('/^[0-9]{4}-[0-9]{2}-[0-9]{2}$/', $value);
  1754. case 'iso8601':
  1755. return preg_match('/^[0-9: .,ZWT+-]+$/', $value);
  1756. case 'std11':
  1757. return preg_match('/^((Sun|Mon|Tue|Wed|Thu|Fri|Sat),\s+)?[0-9]{1,2}\s+'
  1758. . '(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s+[0-9]{2,4}\s+'
  1759. . '[0-9]{2}:[0-9]{2}(:[0-9]{2})?\s+([+-]*[0-9]{4}|[A-Z]{1,3})$', $value);
  1760. case 'julian':
  1761. return preg_match('/^[0-9]+$/', $value);
  1762. case 'time': // hh:mm:ss
  1763. return preg_match('/^[0-9]{2}:[0-9]{2}:[0-9]{2}$/', $value);
  1764. case 'year':
  1765. return preg_match('/^[0-9]{4}$/', $value);
  1766. case 'month':
  1767. return preg_match('/^[0-9]{2}$/', $value) && $value > 0 && $value < 13;
  1768. case 'day':
  1769. return preg_match('/^[0-9]{2}$/', $value) && $value > 0 && $value < 32;
  1770. case 'hour':
  1771. return preg_match('/^[0-9]{2}$/', $value) && $value < 24;
  1772. case 'minute':
  1773. return preg_match('/^[0-9]{2}$/', $value) && $value < 60;
  1774. case 'second':
  1775. // According to RFC5260, seconds can be from 00 to 60
  1776. return preg_match('/^[0-9]{2}$/', $value) && $value < 61;
  1777. case 'weekday':
  1778. return preg_match('/^[0-9]$/', $value) && $value < 7;
  1779. case 'zone':
  1780. return preg_match('/^[+-][0-9]{4}$/', $value);
  1781. }
  1782. }
  1783. /**
  1784. * Converts mailbox name from/to UTF7-IMAP from/to internal Sieve encoding
  1785. * with delimiter replacement.
  1786. *
  1787. * @param string $mailbox Mailbox name
  1788. * @param string $mode Conversion direction ('in'|'out')
  1789. *
  1790. * @return string Mailbox name
  1791. */
  1792. protected function mod_mailbox($mailbox, $mode = 'out')
  1793. {
  1794. $delimiter = $_SESSION['imap_delimiter'];
  1795. $replace_delimiter = $this->rc->config->get('managesieve_replace_delimiter');
  1796. $mbox_encoding = $this->rc->config->get('managesieve_mbox_encoding', 'UTF7-IMAP');
  1797. if ($mode == 'out') {
  1798. $mailbox = rcube_charset::convert($mailbox, $mbox_encoding, 'UTF7-IMAP');
  1799. if ($replace_delimiter && $replace_delimiter != $delimiter)
  1800. $mailbox = str_replace($replace_delimiter, $delimiter, $mailbox);
  1801. }
  1802. else {
  1803. $mailbox = rcube_charset::convert($mailbox, 'UTF7-IMAP', $mbox_encoding);
  1804. if ($replace_delimiter && $replace_delimiter != $delimiter)
  1805. $mailbox = str_replace($delimiter, $replace_delimiter, $mailbox);
  1806. }
  1807. return $mailbox;
  1808. }
  1809. /**
  1810. * List sieve scripts
  1811. *
  1812. * @return array Scripts list
  1813. */
  1814. public function list_scripts()
  1815. {
  1816. if ($this->list !== null) {
  1817. return $this->list;
  1818. }
  1819. $this->list = $this->sieve->get_scripts();
  1820. // Handle active script(s) and list of scripts according to Kolab's KEP:14
  1821. if ($this->rc->config->get('managesieve_kolab_master')) {
  1822. // Skip protected names
  1823. foreach ((array)$this->list as $idx => $name) {
  1824. $_name = strtoupper($name);
  1825. if ($_name == 'MASTER')
  1826. $master_script = $name;
  1827. else if ($_name == 'MANAGEMENT')
  1828. $management_script = $name;
  1829. else if($_name == 'USER')
  1830. $user_script = $name;
  1831. else
  1832. continue;
  1833. unset($this->list[$idx]);
  1834. }
  1835. // get active script(s), read USER script
  1836. if ($user_script) {
  1837. $extension = $this->rc->config->get('managesieve_filename_extension', '.sieve');
  1838. $filename_regex = '/'.preg_quote($extension, '/').'$/';
  1839. $_SESSION['managesieve_user_script'] = $user_script;
  1840. $this->sieve->load($user_script);
  1841. foreach ($this->sieve->script->as_array() as $rules) {
  1842. foreach ($rules['actions'] as $action) {
  1843. if ($action['type'] == 'include' && empty($action['global'])) {
  1844. $name = preg_replace($filename_regex, '', $action['target']);
  1845. // make sure the script exist
  1846. if (in_array($name, $this->list)) {
  1847. $this->active[] = $name;
  1848. }
  1849. }
  1850. }
  1851. }
  1852. }
  1853. // create USER script if it doesn't exist
  1854. else {
  1855. $content = "# USER Management Script\n"
  1856. ."#\n"
  1857. ."# This script includes the various active sieve scripts\n"
  1858. ."# it is AUTOMATICALLY GENERATED. DO NOT EDIT MANUALLY!\n"
  1859. ."#\n"
  1860. ."# For more information, see http://wiki.kolab.org/KEP:14#USER\n"
  1861. ."#\n";
  1862. if ($this->sieve->save_script('USER', $content)) {
  1863. $_SESSION['managesieve_user_script'] = 'USER';
  1864. if (empty($this->master_file))
  1865. $this->sieve->activate('USER');
  1866. }
  1867. }
  1868. }
  1869. else if (!empty($this->list)) {
  1870. // Get active script name
  1871. if ($active = $this->sieve->get_active()) {
  1872. $this->active = array($active);
  1873. }
  1874. // Hide scripts from config
  1875. $exceptions = $this->rc->config->get('managesieve_filename_exceptions');
  1876. if (!empty($exceptions)) {
  1877. $this->list = array_diff($this->list, (array)$exceptions);
  1878. }
  1879. }
  1880. // reindex
  1881. if (!empty($this->list)) {
  1882. $this->list = array_values($this->list);
  1883. }
  1884. return $this->list;
  1885. }
  1886. /**
  1887. * Removes sieve script
  1888. *
  1889. * @param string $name Script name
  1890. *
  1891. * @return bool True on success, False on failure
  1892. */
  1893. public function remove_script($name)
  1894. {
  1895. $result = $this->sieve->remove($name);
  1896. // Kolab's KEP:14
  1897. if ($result && $this->rc->config->get('managesieve_kolab_master')) {
  1898. $this->deactivate_script($name);
  1899. }
  1900. return $result;
  1901. }
  1902. /**
  1903. * Activates sieve script
  1904. *
  1905. * @param string $name Script name
  1906. *
  1907. * @return bool True on success, False on failure
  1908. */
  1909. public function activate_script($name)
  1910. {
  1911. // Kolab's KEP:14
  1912. if ($this->rc->config->get('managesieve_kolab_master')) {
  1913. $extension = $this->rc->config->get('managesieve_filename_extension', '.sieve');
  1914. $user_script = $_SESSION['managesieve_user_script'];
  1915. // if the script is not active...
  1916. if ($user_script && array_search($name, $this->active) === false) {
  1917. // ...rewrite USER file adding appropriate include command
  1918. if ($this->sieve->load($user_script)) {
  1919. $script = $this->sieve->script->as_array();
  1920. $list = array();
  1921. $regexp = '/' . preg_quote($extension, '/') . '$/';
  1922. // Create new include entry
  1923. $rule = array(
  1924. 'actions' => array(
  1925. 0 => array(
  1926. 'target' => $name.$extension,
  1927. 'type' => 'include',
  1928. 'personal' => true,
  1929. )));
  1930. // get all active scripts for sorting
  1931. foreach ($script as $rid => $rules) {
  1932. foreach ($rules['actions'] as $action) {
  1933. if ($action['type'] == 'include' && empty($action['global'])) {
  1934. $target = $extension ? preg_replace($regexp, '', $action['target']) : $action['target'];
  1935. $list[] = $target;
  1936. }
  1937. }
  1938. }
  1939. $list[] = $name;
  1940. // Sort and find current script position
  1941. asort($list, SORT_LOCALE_STRING);
  1942. $list = array_values($list);
  1943. $index = array_search($name, $list);
  1944. // add rule at the end of the script
  1945. if ($index === false || $index == count($list)-1) {
  1946. $this->sieve->script->add_rule($rule);
  1947. }
  1948. // add rule at index position
  1949. else {
  1950. $script2 = array();
  1951. foreach ($script as $rid => $rules) {
  1952. if ($rid == $index) {
  1953. $script2[] = $rule;
  1954. }
  1955. $script2[] = $rules;
  1956. }
  1957. $this->sieve->script->content = $script2;
  1958. }
  1959. $result = $this->sieve->save();
  1960. if ($result) {
  1961. $this->active[] = $name;
  1962. }
  1963. }
  1964. }
  1965. }
  1966. else {
  1967. $result = $this->sieve->activate($name);
  1968. if ($result)
  1969. $this->active = array($name);
  1970. }
  1971. return $result;
  1972. }
  1973. /**
  1974. * Deactivates sieve script
  1975. *
  1976. * @param string $name Script name
  1977. *
  1978. * @return bool True on success, False on failure
  1979. */
  1980. public function deactivate_script($name)
  1981. {
  1982. // Kolab's KEP:14
  1983. if ($this->rc->config->get('managesieve_kolab_master')) {
  1984. $extension = $this->rc->config->get('managesieve_filename_extension', '.sieve');
  1985. $user_script = $_SESSION['managesieve_user_script'];
  1986. // if the script is active...
  1987. if ($user_script && ($key = array_search($name, $this->active)) !== false) {
  1988. // ...rewrite USER file removing appropriate include command
  1989. if ($this->sieve->load($user_script)) {
  1990. $script = $this->sieve->script->as_array();
  1991. $name = $name.$extension;
  1992. foreach ($script as $rid => $rules) {
  1993. foreach ($rules['actions'] as $action) {
  1994. if ($action['type'] == 'include' && empty($action['global'])
  1995. && $action['target'] == $name
  1996. ) {
  1997. break 2;
  1998. }
  1999. }
  2000. }
  2001. // Entry found
  2002. if ($rid < count($script)) {
  2003. $this->sieve->script->delete_rule($rid);
  2004. $result = $this->sieve->save();
  2005. if ($result) {
  2006. unset($this->active[$key]);
  2007. }
  2008. }
  2009. }
  2010. }
  2011. }
  2012. else {
  2013. $result = $this->sieve->deactivate();
  2014. if ($result)
  2015. $this->active = array();
  2016. }
  2017. return $result;
  2018. }
  2019. /**
  2020. * Saves current script (adding some variables)
  2021. */
  2022. public function save_script($name = null)
  2023. {
  2024. // Kolab's KEP:14
  2025. if ($this->rc->config->get('managesieve_kolab_master')) {
  2026. $this->sieve->script->set_var('EDITOR', self::PROGNAME);
  2027. $this->sieve->script->set_var('EDITOR_VERSION', self::VERSION);
  2028. }
  2029. return $this->sieve->save($name);
  2030. }
  2031. /**
  2032. * Returns list of rules from the current script
  2033. *
  2034. * @return array List of rules
  2035. */
  2036. public function list_rules()
  2037. {
  2038. $result = array();
  2039. $i = 1;
  2040. foreach ($this->script as $idx => $filter) {
  2041. if (empty($filter['actions'])) {
  2042. continue;
  2043. }
  2044. $fname = $filter['name'] ?: "#$i";
  2045. $result[] = array(
  2046. 'id' => $idx,
  2047. 'name' => $fname,
  2048. 'class' => $filter['disabled'] ? 'disabled' : '',
  2049. );
  2050. $i++;
  2051. }
  2052. return $result;
  2053. }
  2054. /**
  2055. * Initializes internal script data
  2056. */
  2057. protected function init_script()
  2058. {
  2059. if (!$this->sieve->script) {
  2060. return;
  2061. }
  2062. $this->script = $this->sieve->script->as_array();
  2063. $headers = array();
  2064. $exceptions = array('date', 'currentdate', 'size', 'body');
  2065. // find common headers used in script, will be added to the list
  2066. // of available (predefined) headers (#1489271)
  2067. foreach ($this->script as $rule) {
  2068. foreach ((array) $rule['tests'] as $test) {
  2069. if ($test['test'] == 'header') {
  2070. foreach ((array) $test['arg1'] as $header) {
  2071. $lc_header = strtolower($header);
  2072. // skip special names to not confuse UI
  2073. if (in_array($lc_header, $exceptions)) {
  2074. continue;
  2075. }
  2076. if (!isset($this->headers[$lc_header]) && !isset($headers[$lc_header])) {
  2077. $headers[$lc_header] = $header;
  2078. }
  2079. }
  2080. }
  2081. }
  2082. }
  2083. ksort($headers);
  2084. $this->headers += $headers;
  2085. }
  2086. /**
  2087. * Get all e-mail addresses of the user
  2088. */
  2089. protected function user_emails()
  2090. {
  2091. $addresses = $this->rc->user->list_emails();
  2092. foreach ($addresses as $idx => $email) {
  2093. $addresses[$idx] = $email['email'];
  2094. }
  2095. $addresses = array_unique($addresses);
  2096. sort($addresses);
  2097. return $addresses;
  2098. }
  2099. }