├── .gitignore ├── skins ├── larry │ ├── images │ │ ├── mail_toolbar.png │ │ └── messageactions.png │ └── markasjunk2.css ├── classic │ ├── images │ │ ├── mail_toolbar.png │ │ └── messageactions.png │ └── markasjunk2.css └── elastic │ ├── markasjunk2.min.css │ └── markasjunk2.less ├── localization ├── zh_TW.inc ├── zh_CN.inc ├── cs_CZ.inc ├── fa_IR.inc ├── nl_NL.inc ├── ja_JP.inc ├── ro_RO.inc ├── es_AR.inc ├── ca_ES.inc ├── es_ES.inc ├── sk_SK.inc ├── tr_TR.inc ├── ru_RU.inc ├── fr_FR.inc ├── lv_LV.inc ├── gl_ES.inc ├── it_IT.inc ├── pl_PL.inc ├── pt_BR.inc ├── de_CH.inc ├── de_DE.inc ├── bg_BG.inc ├── da_DK.inc ├── hu_HU.inc ├── en_GB.inc └── en_US.inc ├── composer.json ├── drivers ├── sa_detach.php ├── dir_learn.php ├── edit_headers.php ├── cmd_learn.php ├── amavis_blacklist.php ├── sa_blacklist.php └── email_learn.php ├── CHANGELOG ├── README.md ├── markasjunk2.js ├── config.inc.php.dist └── markasjunk2.php /.gitignore: -------------------------------------------------------------------------------- 1 | config.inc.php 2 | -------------------------------------------------------------------------------- /skins/larry/images/mail_toolbar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johndoh/roundcube-markasjunk2/HEAD/skins/larry/images/mail_toolbar.png -------------------------------------------------------------------------------- /skins/classic/images/mail_toolbar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johndoh/roundcube-markasjunk2/HEAD/skins/classic/images/mail_toolbar.png -------------------------------------------------------------------------------- /skins/classic/images/messageactions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johndoh/roundcube-markasjunk2/HEAD/skins/classic/images/messageactions.png -------------------------------------------------------------------------------- /skins/larry/images/messageactions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johndoh/roundcube-markasjunk2/HEAD/skins/larry/images/messageactions.png -------------------------------------------------------------------------------- /skins/elastic/markasjunk2.min.css: -------------------------------------------------------------------------------- 1 | .toolbar a.button.markasjunk2::before,.toolbarmenu li a.markasjunk2::before{content:"\f06d"}.toolbar a.button.markasnotjunk2::before,.toolbarmenu li a.markasnotjunk2::before{content:"\f01c"}#swipe-action.notjunk{background-color:#41b849;color:#fff}#swipe-action>div>span.notjunk::before{content:"\f01c"} -------------------------------------------------------------------------------- /localization/zh_TW.inc: -------------------------------------------------------------------------------- 1 | div > span.notjunk::before { 25 | content: @fa-var-inbox; 26 | } 27 | } -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "johndoh/markasjunk2", 3 | "description": "Learn messages as Junk/Not Junk", 4 | "keywords": ["junk","spam","lean","move"], 5 | "homepage": "https://github.com/johndoh/roundcube-markasjunk2/", 6 | "license": "GPL-3.0", 7 | "type": "roundcube-plugin", 8 | "version": "1.12", 9 | "authors": [ 10 | { 11 | "name": "Philip Weir", 12 | "email": "roundcube@tehinterweb.co.uk", 13 | "role": "Developer" 14 | } 15 | ], 16 | "repositories": [ 17 | { 18 | "type": "composer", 19 | "url": "https://plugins.roundcube.net" 20 | } 21 | ], 22 | "require": { 23 | "php": ">=5.2.1", 24 | "roundcube/plugin-installer": ">=0.1.2" 25 | }, 26 | "extra": { 27 | "roundcube": { 28 | "min-version": "1.4" 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /skins/larry/markasjunk2.css: -------------------------------------------------------------------------------- 1 | /** 2 | * MarkAsJunk2 plugin styles 3 | */ 4 | 5 | #messagetoolbar a.markasjunk2, 6 | #messagetoolbar a.markasnotjunk2 7 | { 8 | background-image: url(images/mail_toolbar.png); 9 | } 10 | 11 | #messagetoolbar a.markasjunk2 12 | { 13 | background-position: center -14px; 14 | } 15 | 16 | #messagetoolbar a.markasnotjunk2 17 | { 18 | background-position: center -61px; 19 | } 20 | 21 | ul.toolbarmenu li a.markasjunk2 span.icon, 22 | ul.toolbarmenu li a.markasnotjunk2 span.icon 23 | { 24 | background-image: url(images/messageactions.png) !important; 25 | } 26 | 27 | ul.toolbarmenu li a.markasjunk2 span.icon 28 | { 29 | background-position: 1px -17px !important; 30 | } 31 | 32 | ul.toolbarmenu li a.markasnotjunk2 span.icon 33 | { 34 | background-position: 1px 5px !important; 35 | } -------------------------------------------------------------------------------- /skins/classic/markasjunk2.css: -------------------------------------------------------------------------------- 1 | /** 2 | * MarkAsJunk2 plugin styles 3 | */ 4 | 5 | #messagetoolbar a.markasjunk2, 6 | #messagetoolbar a.markasnotjunk2 7 | { 8 | text-indent: -5000px; 9 | background-image: url(images/mail_toolbar.png); 10 | } 11 | 12 | #messagetoolbar a.markasjunk2 13 | { 14 | background-position: -32px 0; 15 | } 16 | 17 | #messagetoolbar a.markasjunk2.pressed 18 | { 19 | background-position: -32px -32px; 20 | } 21 | 22 | #messagetoolbar a.markasnotjunk2 23 | { 24 | background-position: 0 0; 25 | } 26 | 27 | #messagetoolbar a.markasnotjunk2.pressed 28 | { 29 | background-position: 0 -32px; 30 | } 31 | 32 | ul.toolbarmenu li a.markasjunk2, 33 | ul.toolbarmenu li a.markasnotjunk2 34 | { 35 | background-image: url(images/messageactions.png) !important; 36 | background-repeat: no-repeat; 37 | } 38 | 39 | ul.toolbarmenu li a.markasjunk2 40 | { 41 | background-position: 6px -17px !important; 42 | } 43 | 44 | ul.toolbarmenu li a.markasnotjunk2 45 | { 46 | background-position: 6px 1px !important; 47 | } -------------------------------------------------------------------------------- /drivers/sa_detach.php: -------------------------------------------------------------------------------- 1 | storage; 38 | 39 | $new_uids = array(); 40 | foreach ($uids as $uid) { 41 | $saved = false; 42 | $message = new rcube_message($uid); 43 | 44 | if (count($message->attachments) > 0) { 45 | foreach ($message->attachments as $part) { 46 | if ($part->ctype_primary == 'message' && $part->ctype_secondary == 'rfc822' && $part->ctype_parameters['x-spam-type'] == 'original') { 47 | $orig_message_raw = $message->get_part_body($part->mime_id); 48 | $saved = $storage->save_message($dst_mbox, $orig_message_raw); 49 | 50 | if ($saved !== false) { 51 | $rcube->output->command('rcmail_markasjunk2_move', null, $uid); 52 | array_push($new_uids, $saved); 53 | } 54 | } 55 | } 56 | } 57 | } 58 | 59 | if (count($new_uids) > 0) { 60 | $uids = $new_uids; 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /drivers/dir_learn.php: -------------------------------------------------------------------------------- 1 | _do_messagemove($uids, true); 32 | } 33 | 34 | public function ham($uids, $src_mbox, $dst_mbox) 35 | { 36 | $this->_do_messagemove($uids, false); 37 | } 38 | 39 | private function _do_messagemove($uids, $spam) 40 | { 41 | $rcube = rcube::get_instance(); 42 | $dest_dir = unslashify($rcube->config->get($spam ? 'markasjunk2_spam_dir' : 'markasjunk2_ham_dir')); 43 | 44 | if (!$dest_dir) { 45 | return; 46 | } 47 | 48 | $filename = $rcube->config->get('markasjunk2_filename'); 49 | $filename = str_replace('%u', $_SESSION['username'], $filename); 50 | $filename = str_replace('%t', ($spam) ? 'spam' : 'ham', $filename); 51 | $filename = str_replace('%l', $rcube->user->get_username('local'), $filename); 52 | $filename = str_replace('%d', $rcube->user->get_username('domain'), $filename); 53 | 54 | foreach ($uids as $uid) { 55 | $tmpfname = tempnam($dest_dir, $filename); 56 | file_put_contents($tmpfname, $rcube->storage->get_raw_body($uid)); 57 | 58 | if ($rcube->config->get('markasjunk2_debug')) { 59 | rcube::write_log('markasjunk2', $tmpfname); 60 | } 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /drivers/edit_headers.php: -------------------------------------------------------------------------------- 1 | _edit_headers($uids, true, $dst_mbox); 32 | } 33 | 34 | public function ham(&$uids, $src_mbox, $dst_mbox) 35 | { 36 | $this->_edit_headers($uids, false, $dst_mbox); 37 | } 38 | 39 | private function _edit_headers(&$uids, $spam, $dst_mbox) 40 | { 41 | $rcube = rcube::get_instance(); 42 | $args = $rcube->config->get($spam ? 'markasjunk2_spam_patterns' : 'markasjunk2_ham_patterns'); 43 | 44 | if (count($args['patterns']) == 0) { 45 | return; 46 | } 47 | 48 | $new_uids = array(); 49 | foreach ($uids as $uid) { 50 | $raw_message = $rcube->storage->get_raw_body($uid); 51 | $raw_headers = $rcube->storage->get_raw_headers($uid); 52 | 53 | $updated_headers = preg_replace($args['patterns'], $args['replacements'], $raw_headers); 54 | $raw_message = str_replace($raw_headers, $updated_headers, $raw_message); 55 | 56 | $saved = $rcube->storage->save_message($dst_mbox, $raw_message); 57 | 58 | if ($saved !== false) { 59 | $rcube->output->command('rcmail_markasjunk2_move', null, $uid); 60 | array_push($new_uids, $saved); 61 | } 62 | } 63 | 64 | if (count($new_uids) > 0) { 65 | $uids = $new_uids; 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /drivers/cmd_learn.php: -------------------------------------------------------------------------------- 1 | 11 | * 12 | * Copyright (C) 2009-2018 Philip Weir 13 | * 14 | * This driver is part of the MarkASJunk2 plugin for Roundcube. 15 | * 16 | * This program is free software: you can redistribute it and/or modify 17 | * it under the terms of the GNU General Public License as published by 18 | * the Free Software Foundation, either version 3 of the License, or 19 | * (at your option) any later version. 20 | * 21 | * This program is distributed in the hope that it will be useful, 22 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 23 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 24 | * GNU General Public License for more details. 25 | * 26 | * You should have received a copy of the GNU General Public License 27 | * along with Roundcube. If not, see https://www.gnu.org/licenses/. 28 | */ 29 | class markasjunk2_cmd_learn 30 | { 31 | public function spam($uids, $src_mbox, $dst_mbox) 32 | { 33 | $this->_do_salearn($uids, true, $src_mbox); 34 | } 35 | 36 | public function ham($uids, $src_mbox, $dst_mbox) 37 | { 38 | $this->_do_salearn($uids, false, $src_mbox); 39 | } 40 | 41 | private function _do_salearn($uids, $spam, $src_mbox) 42 | { 43 | $rcube = rcube::get_instance(); 44 | $temp_dir = realpath($rcube->config->get('temp_dir')); 45 | $command = $rcube->config->get($spam ? 'markasjunk2_spam_cmd' : 'markasjunk2_ham_cmd'); 46 | 47 | if (!$command) { 48 | return; 49 | } 50 | 51 | // backwards compatibility %xds removed in markasjunk2 v1.12 52 | $command = str_replace('%xds', '%h:x-dspam-signature', $command); 53 | 54 | $command = str_replace('%u', $_SESSION['username'], $command); 55 | $command = str_replace('%l', $rcube->user->get_username('local'), $command); 56 | $command = str_replace('%d', $rcube->user->get_username('domain'), $command); 57 | if (strpos($command, '%i') !== false) { 58 | $identity_arr = $rcube->user->get_identity(); 59 | $command = str_replace('%i', $identity_arr['email'], $command); 60 | } 61 | 62 | foreach ($uids as $uid) { 63 | // reset command for next message 64 | $tmp_command = $command; 65 | 66 | if (strpos($tmp_command, '%s') !== false) { 67 | $message = new rcube_message($uid); 68 | $tmp_command = str_replace('%s', escapeshellarg($message->sender['mailto']), $tmp_command); 69 | } 70 | 71 | if (strpos($command, '%h') !== false) { 72 | $storage = $rcube->get_storage(); 73 | $storage->check_connection(); 74 | $storage->conn->select($src_mbox); 75 | 76 | preg_match_all('/%h:([\w-_]+)/', $tmp_command, $header_names, PREG_SET_ORDER); 77 | foreach ($header_names as $header) { 78 | $val = null; 79 | if ($msg = $storage->conn->fetchHeader($src_mbox, $uid, true, false, array($header[1]))) { 80 | $val = $msg->{$header[1]} ?: $msg->others[$header[1]]; 81 | } 82 | 83 | if (!empty($val)) { 84 | $tmp_command = str_replace($header[0], escapeshellarg($val), $tmp_command); 85 | } 86 | else { 87 | if ($rcube->config->get('markasjunk2_debug')) { 88 | rcube::write_log('markasjunk2', 'header ' . $header[1] . ' not found in message ' . $src_mbox . '/' . $uid); 89 | } 90 | 91 | continue 2; 92 | } 93 | } 94 | } 95 | 96 | if (strpos($command, '%f') !== false) { 97 | $tmpfname = tempnam($temp_dir, 'rcmSALearn'); 98 | file_put_contents($tmpfname, $rcube->storage->get_raw_body($uid)); 99 | $tmp_command = str_replace('%f', escapeshellarg($tmpfname), $tmp_command); 100 | } 101 | 102 | $output = shell_exec($tmp_command); 103 | 104 | if ($rcube->config->get('markasjunk2_debug')) { 105 | rcube::write_log('markasjunk2', $tmp_command); 106 | rcube::write_log('markasjunk2', $output); 107 | } 108 | 109 | if (strpos($command, '%f') !== false) { 110 | unlink($tmpfname); 111 | } 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /CHANGELOG: -------------------------------------------------------------------------------- 1 | Roundcube Webmail MarkAsJunk2 2 | ============================= 3 | 4 | * email_learn driver: use new rcmail_resend_mail class (req RC 040a71e) 5 | * Replace classes .markasjunk2Sel/.markasnotjunk2Sel with .pressed 6 | * Add Elastic skin support 7 | * Add markasjunk2_permanently_remove option (#56) 8 | * Integration with Swipe plugin 9 | * %xds marco replaced with %h:
10 | * Fix check_request() bypass in places using get_uids() [CVE-2018-9846] (req RC 8e543f8) 11 | * Add markasjunk2_allowed_hosts config option 12 | * Add markasjunk2_host_config config option 13 | * Pass source and destination mbox vars to driver fix save problems in edit_headers and sa_detact drivers (#58) 14 | * Make flag defaults match markasjunk plugin from core (config change: to disable flagging set configs to false) (#59) 15 | * Add markasjunk2_spam_only option 16 | 17 | Version 1.11.1 (2017-07-29, rc-1.3) 18 | ================================================= 19 | * Fix icon display on message view screen 20 | 21 | Version 1.11 (2017-06-14, rc-1.3) 22 | ================================================= 23 | * "Flattened" the larry theme: fresher look by removing shadows and gradients 24 | 25 | Version 1.10 (2017-01-02, rc-1.1) 26 | ================================================= 27 | * Add JS event markasjunk2-update to allow other plugins to influence the spam/ham options show 28 | * Add init method to allow drivers to access markasjunk2-update JS event 29 | * Replace config options markasjunk2_mb_toolbar and markasjunk2_cp_toolbar with new markasjunk2_toolbar option 30 | 31 | Version 1.9 (2015-01-06, rc-1.1) 32 | ================================================= 33 | * Use get_part_body add in 48ba4414b3 34 | * Support multi-folder search results (66536974fe) 35 | * Drop IE6 support 36 | 37 | Version 1.8.2 (2014-03-30, rc-1.0) 38 | ================================================= 39 | * Add amavis_blacklist driver (by Der-Jan) 40 | 41 | Version 1.8.1 (2014-01-01, rc-1.0) 42 | ================================================= 43 | * Improve sa_detach driver, make sure only original message is detached 44 | * Correct skin folder structure for Roundcube packaging 45 | 46 | Version 1.8 (2013-12-01, rc-1.0) 47 | ================================================= 48 | * Use sauserprefs_userid config setting in sa_blacklist driver 49 | * Use plugin API to set message flags (05da157) 50 | * Update config file var names to match core 51 | * Add support for global sql_debug option 52 | * Improve handling of multiple marcos in cmd_learn driver 53 | 54 | Version 1.7 (2013-05-19, rc-1.0) 55 | ================================================= 56 | * add option to call markasjunk2 functions on message move 57 | * rcmail_deliver_message() > deliver_message() 58 | **** code branching/tagging no longer sync'd to roundcube versions **** 59 | 60 | Version 1.6 (2013-03-03, rc-0.9) 61 | ================================================= 62 | * merge PDO branch (de56ea1909) 63 | * rename default skin to classic (c40419bdfe) 64 | * add new edit_headers driver 65 | * rcube_ui > rcube_utils (r6091) 66 | * Update for Roundcube framework 67 | 68 | Version 1.5 (2012-06-19, rc-0.8) 69 | ================================================= 70 | * Fixed drivers namespace issues 71 | 72 | Version 1.4 (2012-01-21, rc-0.8) 73 | ================================================= 74 | * Improve spam/ham box configs 75 | 76 | Version 1.3 (2012-01-21, rc-0.8) 77 | ================================================= 78 | * Update after r5781 79 | * Add inital support for Larry 80 | * Remove the need to always move the message 81 | * Remove the requirement to have a spam mailbox set 82 | * Allow both buttons to be shown at once 83 | * Execute driver before standard actions so standard actions can be overridden 84 | * Move SA detach code to driver 85 | 86 | Version 1.2 (2010-07-02, rc-0.5) 87 | ================================================= 88 | * Imporve email_learn driver when not sending as attachemnt 89 | * Use better command names 90 | * Username parsing now in core (r3774) 91 | * Respect display_next setting 92 | * Fix mark as ham in message view 93 | * Fix ham detachment, broken after c585116e7759c94d19fe6713af72cb5b45f7fde2, 94 | also see RC ticket #1486584 95 | * Update to r3393 96 | * Better toolbar icons 97 | * Update after r3261 98 | * Another update after r3258 99 | * Update after r3258 100 | 101 | Version 1.1 (2010-02-07, rc-0.4) 102 | ================================================= 103 | * Move toolbar config to config file 104 | * CSS update after r3141 105 | * Use explode() rather than split() 106 | * Create driver based system for different learning methods 107 | * Rename plugin to markasjunk2 108 | 109 | Version 1.0 (2009-10-31, rc-0.3) 110 | ================================================= 111 | * Use local_skin_path() (rev 3076) 112 | * Added ability to mark ham as unread 113 | * Added ability to flag/unflag spam/ham 114 | * Added ability to trigger sa-learn 115 | * Added config file to control new options -------------------------------------------------------------------------------- /drivers/amavis_blacklist.php: -------------------------------------------------------------------------------- 1 | _do_list($uids, true); 42 | } 43 | 44 | public function ham($uids, $src_mbox, $dst_mbox) 45 | { 46 | $this->_do_list($uids, false); 47 | } 48 | 49 | private function _do_list($uids, $spam) 50 | { 51 | $rcube = rcube::get_instance(); 52 | $this->user_email = $rcube->user->data['username']; 53 | 54 | if (!$rcube->config->load_from_file($rcube->config->get('markasjunk2_amacube_config'))) { 55 | rcube::raise_error(array('code' => 527, 'type' => 'php', 56 | 'file' => __FILE__, 'line' => __LINE__, 57 | 'message' => "Failed to load config from " . $rcube->config->get('markasjunk2_amacube_config') 58 | ), true, false); 59 | 60 | return false; 61 | } 62 | 63 | $db = rcube_db::factory($rcube->config->get('amacube_db_dsn'), '', true); 64 | $db->set_debug((bool) $rcube->config->get('sql_debug')); 65 | $db->db_connect('w'); 66 | 67 | // check DB connections and exit on failure 68 | if ($err_str = $db->is_error()) { 69 | rcube::raise_error(array( 70 | 'code' => 603, 71 | 'type' => 'db', 72 | 'message' => $err_str 73 | ), false, true); 74 | } 75 | 76 | $sql_result = $db->query("SELECT `id` FROM `users` WHERE `email` = ?", $this->user_email); 77 | if ($sql_result && ($res_array = $db->fetch_assoc($sql_result))) { 78 | $rid = $res_array['id']; 79 | } 80 | else { 81 | if ($rcube->config->get('markasjunk2_debug')) { 82 | rcube::write_log('markasjunk2', $this->user_email . ' not found in users table'); 83 | } 84 | 85 | return false; 86 | } 87 | 88 | foreach ($uids as $uid) { 89 | $message = new rcube_message($uid); 90 | $email = $message->sender['mailto']; 91 | $sql_result = $db->query("SELECT `id` FROM `mailaddr` WHERE `email` = ? ORDER BY `priority` DESC", $email); 92 | 93 | if ($sql_result && ($res_array = $db->fetch_assoc($sql_result))) { 94 | $sid = $res_array['id']; 95 | } 96 | else { 97 | if ($rcube->config->get('markasjunk2_debug')) { 98 | rcube::write_log('markasjunk2', $email . ' not found in mailaddr table - add it'); 99 | } 100 | 101 | $sql_result = $db->query("INSERT INTO `mailaddr` ( `priority`, `email` ) VALUES ( 20, ? )", $email); 102 | if ($sql_result) { 103 | $sid = $db->insert_id(); 104 | } 105 | else { 106 | if ($rcube->config->get('markasjunk2_debug')) { 107 | rcube::write_log('markasjunk2', 'Cannot add ' . $email . ' to mailaddr table: ' . $db->is_error($sql_result)); 108 | } 109 | 110 | return false; 111 | } 112 | } 113 | 114 | $wb = ''; 115 | $sql_result = $db->query("SELECT `wb` FROM `wblist` WHERE `sid` = ? AND `rid` =?", $sid, $rid); 116 | if ($sql_result && ($res_array = $db->fetch_assoc($sql_result))) { 117 | $wb = $res_array['wb']; 118 | } 119 | 120 | if (!$wb || (!$spam && preg_match('/^([BbNnFf])[\s]*\z/', $wb)) || ($spam && preg_match('/^([WwYyTt])[\s]*\z/', $wb))) { 121 | $newwb = 'w'; 122 | 123 | if ($spam) { 124 | $newwb = 'b'; 125 | } 126 | 127 | if ($wb) { 128 | $sql_result = $db->query('UPDATE `wblist` SET `wb` = ? WHERE `sid` = ? AND `rid` = ?', 129 | $newwb, $sid, $rid); 130 | } 131 | else { 132 | $sql_result = $db->query('INSERT INTO `wblist` (`sid`, `rid`, `wb`) VALUES (?,?,?)', 133 | $sid, $rid, $newwb); 134 | } 135 | 136 | if (!$sql_result) { 137 | if ($rcube->config->get('markasjunk2_debug')) { 138 | rcube::write_log('markasjunk2', 'Cannot update wblist for user ' . $this->user_email . ' with ' . $email); 139 | } 140 | 141 | return false; 142 | } 143 | } 144 | } 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Roundcube Webmail MarkAsJunk2 2 | ============================= 3 | 4 | THIS PLUGIN IS NO LONGER MAINTAINED 5 | ----------------------------------- 6 | The features of this plugin have been integrated into the markasjunk plugin in 7 | the Roundcube core (v1.4+), via [pull request 6504][pr6504]. This plugin is now 8 | obsolete. 9 | 10 | This plugin adds "mark as spam" or "mark as not spam" button to the message 11 | menu. 12 | 13 | Inspiration for this plugin was taken from: 14 | [Thomas Bruederli][thomas] - original 15 | [Roundcube Mark As Junk plugin][rcmaj] 16 | 17 | When not in the Junk mailbox: 18 | Messages are moved into the Junk mailbox and marked as read 19 | 20 | When in the Junk mailbox: 21 | The buttons are changed to "mark as not spam" or "this message is not spam" 22 | and the message is moved to the Inbox 23 | 24 | ATTENTION 25 | --------- 26 | This is just a snapshot from the GIT repository and is **NOT A STABLE version 27 | of MarkAsJunk2**. It is Intended for use with the **GIT-master** version of 28 | Roundcube and it may not be compatible with older versions. Stable versions of 29 | MarkAsJunk2 are available from the [Roundcube plugin repository][rcplugrepo] 30 | (for 1.0 and above) or the [releases section][releases] of the GitHub 31 | repository. 32 | 33 | License 34 | ------- 35 | This plugin is released under the [GNU General Public License Version 3+][gpl]. 36 | 37 | Even if skins might contain some programming work, they are not considered 38 | as a linked part of the plugin and therefore skins DO NOT fall under the 39 | provisions of the GPL license. See the README file located in the core skins 40 | folder for details on the skin license. 41 | 42 | Install 43 | ------- 44 | * Place this plugin folder into plugins directory of Roundcube 45 | * Add markasjunk2 to $config['plugins'] in your Roundcube config 46 | 47 | **NB:** When downloading the plugin from GitHub you will need to create a 48 | directory called markasjunk2 and place the files in there, ignoring the root 49 | directory in the downloaded archive. 50 | 51 | Config 52 | ------ 53 | The default config file is plugins/markasjunk2/config.inc.php.dist 54 | Rename this to plugins/markasjunk2/config.inc.php 55 | All config parameters are optional 56 | 57 | The Learning Driver 58 | ------------------- 59 | The learning driver allows you to perform additional processing on each message 60 | marked as spam/ham. A driver must contain a class named markasjunk2_{driver 61 | file name}. The class must contain 3 functions: 62 | 63 | **spam:** This function should take 2 arguments: an array of UIDs of message(s) 64 | being marked as spam, the name of the mailbox containing those messages 65 | 66 | **ham:** This function should take 2 arguments: an array of UIDs of message(s) 67 | being marked as ham, the name of the mailbox containing those messages 68 | 69 | **init:** Optional, this function should take 0 arguments. eg: allows drivers 70 | to add JS to the page to control which of the spam/ham options are displayed 71 | An [example driver][jsevents] is available to show how to use the JS events 72 | 73 | Several drivers are provided by default they are: 74 | 75 | **cmd_learn:** This driver calls an external command (for example salearn) to 76 | process the message 77 | 78 | **dir_learn:** This driver places a copy of the message in a predefined folder, 79 | for example to allow for processing later 80 | 81 | **email_learn:** This driver emails the message either as an attachment or 82 | directly to a set address. This driver requires Roundcube 1.4 or above. 83 | 84 | **sa_blacklist:** This driver adds the sender address of a spam message to the 85 | users blacklist (or whitelist of ham messages) Requires SAUserPrefs plugin 86 | 87 | **amavis_blacklist:** This driver adds the sender address of a spam message to 88 | the users blacklist (or whitelist of ham messages) Requires Amacube plugin. 89 | Driver by Der-Jan 90 | 91 | **sa_detach:** If the message is a Spamassassin spam report with the original 92 | email attached then this is detached and saved in the Inbox, the spam report is 93 | deleted 94 | 95 | **edit_headers:** Edit the message headers. Headers are edited using 96 | preg_replace. 97 | 98 | **WARNING:** Be sure to match the entire header line, including the name of the 99 | header, and include the ^ and $ and test carefully before use on real messages. 100 | This driver alters the message source 101 | 102 | Running multiple drivers 103 | ------------------------ 104 | **WARNING:** This is very dangerous please always test carefully. Run multiple 105 | drivers at your own risk! It may be safer to create one driver that does 106 | everything you want. 107 | 108 | It is possible to run multiple drivers when marking a message as spam/ham. For 109 | example running sa_blacklist followed by cmd_learn or edit_headers and 110 | cmd_learn. An [example multi-driver][multidriver] is available. This is a 111 | starting point only, it requires modification for individual cases. 112 | 113 | Spam learning commands 114 | ---------------------- 115 | Spamassassin: 116 | 117 | ```sa-learn --spam --username=%u %f``` or 118 | ```sa-learn --spam --prefs-file=/var/mail/%d/%l/.spamassassin/user_prefs %f``` 119 | 120 | Ham learning commands 121 | --------------------- 122 | Spamassassin: 123 | 124 | ```sa-learn --ham --username=%u %f``` or 125 | ```sa-learn --ham --prefs-file=/var/mail/%d/%l/.spamassassin/user_prefs %f``` 126 | 127 | edit_headers example config 128 | --------------------------- 129 | **WARNING:** These are simple examples of how to configure the driver options, 130 | use at your own risk 131 | 132 | ```php 133 | $config['markasjunk2_spam_patterns'] = array( 134 | 'patterns' => array('/^(Subject:\s*)(.*)$/m'), 135 | 'replacements' => array('$1[SPAM] $2') 136 | ); 137 | ``` 138 | 139 | ```php 140 | $config['markasjunk2_ham_patterns'] = array( 141 | 'patterns' => array('/^(Subject:\s*)\[SPAM\](.*)$/m'), 142 | 'replacements' => array('$1$2') 143 | ); 144 | ``` 145 | 146 | [pr6504]: https://github.com/roundcube/roundcubemail/pull/6504 147 | [thomas]: mailto:roundcube@gmail.com 148 | [rcmaj]: https://github.com/roundcube/roundcubemail/tree/master/plugins/markasjunk 149 | [rcplugrepo]: https://plugins.roundcube.net/packages/johndoh/markasjunk2 150 | [releases]: https://github.com/johndoh/roundcube-markasjunk2/releases 151 | [gpl]: https://www.gnu.org/licenses/gpl.html 152 | [multidriver]: https://gist.github.com/johndoh/8173505 153 | [jsevents]: https://gist.github.com/johndoh/37ab8610f9fa63052197c89e5ef89266 -------------------------------------------------------------------------------- /drivers/sa_blacklist.php: -------------------------------------------------------------------------------- 1 | _do_list($uids, true); 39 | } 40 | 41 | public function ham($uids, $src_mbox, $dst_mbox) 42 | { 43 | $this->_do_list($uids, false); 44 | } 45 | 46 | private function _do_list($uids, $spam) 47 | { 48 | $rcube = rcube::get_instance(); 49 | $this->sa_user = $rcube->config->get('sauserprefs_userid', "%u"); 50 | $this->sa_table = $rcube->config->get('sauserprefs_sql_table_name'); 51 | $this->sa_username_field = $rcube->config->get('sauserprefs_sql_username_field'); 52 | $this->sa_preference_field = $rcube->config->get('sauserprefs_sql_preference_field'); 53 | $this->sa_value_field = $rcube->config->get('sauserprefs_sql_value_field'); 54 | 55 | $identity_arr = $rcube->user->get_identity(); 56 | $identity = $identity_arr['email']; 57 | $this->sa_user = str_replace('%u', $_SESSION['username'], $this->sa_user); 58 | $this->sa_user = str_replace('%l', $rcube->user->get_username('local'), $this->sa_user); 59 | $this->sa_user = str_replace('%d', $rcube->user->get_username('domain'), $this->sa_user); 60 | $this->sa_user = str_replace('%i', $identity, $this->sa_user); 61 | 62 | if (!$rcube->config->load_from_file($rcube->config->get('markasjunk2_sauserprefs_config'))) { 63 | rcube::raise_error(array('code' => 527, 'type' => 'php', 64 | 'file' => __FILE__, 'line' => __LINE__, 65 | 'message' => "Failed to load config from " . $rcube->config->get('markasjunk2_sauserprefs_config') 66 | ), true, false); 67 | 68 | return false; 69 | } 70 | 71 | $db = rcube_db::factory($rcube->config->get('sauserprefs_db_dsnw'), $rcube->config->get('sauserprefs_db_dsnr'), $rcube->config->get('sauserprefs_db_persistent')); 72 | $db->set_debug((bool) $rcube->config->get('sql_debug')); 73 | $db->db_connect('w'); 74 | 75 | // check DB connections and exit on failure 76 | if ($err_str = $db->is_error()) { 77 | rcube::raise_error(array( 78 | 'code' => 603, 79 | 'type' => 'db', 80 | 'message' => $err_str 81 | ), false, true); 82 | } 83 | 84 | foreach ($uids as $uid) { 85 | $message = new rcube_message($uid); 86 | $email = $message->sender['mailto']; 87 | 88 | if ($spam) { 89 | // delete any whitelisting for this address 90 | $db->query( 91 | "DELETE FROM `{$this->sa_table}` WHERE `{$this->sa_username_field}` = ? AND `{$this->sa_preference_field}` = ? AND `{$this->sa_value_field}` = ?;", 92 | $this->sa_user, 93 | 'whitelist_from', 94 | $email); 95 | 96 | // check address is not already blacklisted 97 | $sql_result = $db->query( 98 | "SELECT `value` FROM `{$this->sa_table}` WHERE `{$this->sa_username_field}` = ? AND `{$this->sa_preference_field}` = ? AND `{$this->sa_value_field}` = ?;", 99 | $this->sa_user, 100 | 'blacklist_from', 101 | $email); 102 | 103 | if (!$db->fetch_array($sql_result)) { 104 | $db->query( 105 | "INSERT INTO `{$this->sa_table}` (`{$this->sa_username_field}`, `{$this->sa_preference_field}`, `{$this->sa_value_field}`) VALUES (?, ?, ?);", 106 | $this->sa_user, 107 | 'blacklist_from', 108 | $email); 109 | 110 | if ($rcube->config->get('markasjunk2_debug')) { 111 | rcube::write_log('markasjunk2', $this->sa_user . ' blacklist ' . $email); 112 | } 113 | } 114 | } 115 | else { 116 | // delete any blacklisting for this address 117 | $db->query( 118 | "DELETE FROM `{$this->sa_table}` WHERE `{$this->sa_username_field}` = ? AND `{$this->sa_preference_field}` = ? AND `{$this->sa_value_field}` = ?;", 119 | $this->sa_user, 120 | 'blacklist_from', 121 | $email); 122 | 123 | // check address is not already whitelisted 124 | $sql_result = $db->query( 125 | "SELECT `value` FROM `{$this->sa_table}` WHERE `{$this->sa_username_field}` = ? AND `{$this->sa_preference_field}` = ? AND `{$this->sa_value_field}` = ?;", 126 | $this->sa_user, 127 | 'whitelist_from', 128 | $email); 129 | 130 | if (!$db->fetch_array($sql_result)) { 131 | $db->query( 132 | "INSERT INTO `{$this->sa_table}` (`{$this->sa_username_field}`, `{$this->sa_preference_field}`, `{$this->sa_value_field}`) VALUES (?, ?, ?);", 133 | $this->sa_user, 134 | 'whitelist_from', 135 | $email); 136 | 137 | if ($rcube->config->get('markasjunk2_debug')) { 138 | rcube::write_log('markasjunk2', $this->sa_user . ' whitelist ' . $email); 139 | } 140 | } 141 | } 142 | } 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /markasjunk2.js: -------------------------------------------------------------------------------- 1 | /** 2 | * MarkAsJunk2 plugin script 3 | * 4 | * @licstart The following is the entire license notice for the 5 | * JavaScript code in this file. 6 | * 7 | * Copyright (C) 2009-2018 Philip Weir 8 | * 9 | * The JavaScript code in this page is free software: you can redistribute it 10 | * and/or modify it under the terms of the GNU General Public License 11 | * as published by the Free Software Foundation, either version 3 of 12 | * the License, or (at your option) any later version. 13 | * 14 | * @licend The above is the entire license notice 15 | * for the JavaScript code in this file. 16 | */ 17 | 18 | rcube_webmail.prototype.markasjunk2_mark = function(is_spam) { 19 | var uids = this.env.uid ? [this.env.uid] : this.message_list.get_selection(); 20 | if (!uids) 21 | return; 22 | 23 | var lock = this.set_busy(true, 'loading'); 24 | this.http_post('plugin.markasjunk2.' + (is_spam ? 'junk' : 'not_junk'), this.selection_post_data({_uid: uids}), lock); 25 | } 26 | 27 | rcube_webmail.prototype.rcmail_markasjunk2_move = function(mbox, uids) { 28 | var prev_uid = this.env.uid, a_uids = $.isArray(uids) ? uids : uids.split(","); 29 | 30 | if (this.message_list && a_uids.length == 1 && !this.message_list.in_selection([a_uids[0]])) 31 | this.env.uid = a_uids[0]; 32 | 33 | if (mbox) 34 | this.move_messages(mbox); 35 | else if (this.env.markasjunk2_permanently_remove == true) 36 | this.permanently_remove_messages(); 37 | else 38 | this.delete_messages(); 39 | 40 | this.env.uid = prev_uid; 41 | } 42 | 43 | rcube_webmail.prototype.markasjunk2_toggle_button = function() { 44 | var spamobj = $('a.markasjunk2'); 45 | var hamobj = $('a.markasnotjunk2'); 46 | 47 | var disp = {'spam': true, 'ham': true}; 48 | if (this.env.markasjunk2_spam_only) { 49 | disp.ham = false; 50 | } 51 | else if (!this.is_multifolder_listing() && this.env.markasjunk2_spam_mailbox) { 52 | if (this.env.mailbox != this.env.markasjunk2_spam_mailbox) 53 | disp.ham = false; 54 | else 55 | disp.spam = false; 56 | } 57 | 58 | // if only 1 button is visible make sure its the last one (for styling) 59 | // allow for multiple instances of the buttons, eg toolbar and contextmenu 60 | $.each(spamobj, function(i) { 61 | var cur_spamobj = spamobj.eq(i), 62 | cur_hamobj = hamobj.eq(i), 63 | cur_index = spamobj.eq(i).index(); 64 | 65 | if (cur_spamobj.parent('li').length > 0) { 66 | cur_spamobj = cur_spamobj.parent(); 67 | cur_hamobj = cur_hamobj.parent(); 68 | } 69 | 70 | var evt_rtn = rcmail.triggerEvent('markasjunk2-update', {'objs': {'spamobj': cur_spamobj, 'hamobj': cur_hamobj}, 'disp': disp}); 71 | if (evt_rtn && evt_rtn.abort) 72 | return; 73 | 74 | disp = evt_rtn ? evt_rtn.disp : disp; 75 | 76 | disp.spam ? cur_spamobj.show() : cur_spamobj.hide(); 77 | disp.ham ? cur_hamobj.show() : cur_hamobj.hide(); 78 | 79 | if (disp.spam && !disp.ham) { 80 | if (cur_index < cur_hamobj.index()) { 81 | cur_spamobj.insertAfter(cur_hamobj); 82 | } 83 | } 84 | else if (cur_index > cur_hamobj.index()) { 85 | cur_hamobj.insertAfter(cur_spamobj); 86 | } 87 | }); 88 | } 89 | 90 | rcube_webmail.prototype.markasjunk2_is_spam_mbox = function() { 91 | return !this.is_multifolder_listing() && this.env.mailbox == this.env.markasjunk2_spam_mailbox; 92 | } 93 | 94 | $(document).ready(function() { 95 | if (window.rcmail) { 96 | rcmail.addEventListener('init', function() { 97 | // register command (directly enable in message view mode) 98 | rcmail.register_command('plugin.markasjunk2.junk', function() { rcmail.markasjunk2_mark(true); }, !rcmail.markasjunk2_is_spam_mbox() && rcmail.env.uid); 99 | rcmail.register_command('plugin.markasjunk2.not_junk', function() { rcmail.markasjunk2_mark(false); }, rcmail.env.uid); 100 | 101 | if (rcmail.message_list) { 102 | rcmail.message_list.addEventListener('select', function(list) { 103 | rcmail.enable_command('plugin.markasjunk2.junk', !rcmail.markasjunk2_is_spam_mbox() && list.get_selection(false).length > 0); 104 | rcmail.enable_command('plugin.markasjunk2.not_junk', list.get_selection(false).length > 0); 105 | }); 106 | } 107 | 108 | // make sure the correct icon is displayed even when there is no listupdate event 109 | rcmail.markasjunk2_toggle_button(); 110 | }); 111 | 112 | rcmail.addEventListener('listupdate', function() { rcmail.markasjunk2_toggle_button(); }); 113 | 114 | rcmail.addEventListener('beforemoveto', function(mbox) { 115 | if (mbox && typeof mbox === 'object') 116 | mbox = mbox.id; 117 | 118 | var is_spam = null; 119 | // check if destination mbox equals junk box (and we're not already in the junk box) 120 | if (rcmail.env.markasjunk2_move_spam && mbox && mbox == rcmail.env.markasjunk2_spam_mailbox && mbox != rcmail.env.mailbox) 121 | is_spam = true; 122 | // or if destination mbox equals ham box and we are in the junk box 123 | else if (rcmail.env.markasjunk2_move_ham && mbox && mbox == rcmail.env.markasjunk2_ham_mailbox && rcmail.env.mailbox == rcmail.env.markasjunk2_spam_mailbox) 124 | is_spam = false; 125 | 126 | if (is_spam !== null) { 127 | rcmail.markasjunk2_mark(is_spam); 128 | return false; 129 | } 130 | }); 131 | 132 | // integration with Swipe plugin 133 | rcmail.addEventListener('swipe-action', function(p) { 134 | if (rcmail.env.swipe_actions[p.direction] == 'markasjunk2' && rcmail.env.markasjunk2_spam_mailbox) { 135 | var action = {}; 136 | 137 | if (!rcmail.env.markasjunk2_spam_only && rcmail.env.mailbox == rcmail.env.markasjunk2_spam_mailbox) { 138 | action = { 139 | 'class': 'notjunk', 140 | 'text': 'markasjunk2.markasnotjunk', 141 | 'callback': function(p) { rcmail.swipe_action_callback('plugin.markasjunk2.not_junk', null, p); } 142 | }; 143 | } 144 | else { 145 | action = { 146 | 'class': 'junk', 147 | 'text': 'markasjunk2.markasjunk', 148 | 'callback': function(p) { rcmail.swipe_action_callback('plugin.markasjunk2.junk', null, p); } 149 | }; 150 | } 151 | 152 | return action; 153 | } 154 | }); 155 | } 156 | }); -------------------------------------------------------------------------------- /drivers/email_learn.php: -------------------------------------------------------------------------------- 1 | _do_emaillearn($uids, true); 36 | } 37 | 38 | public function ham($uids, $src_mbox, $dst_mbox) 39 | { 40 | $this->_do_emaillearn($uids, false); 41 | } 42 | 43 | private function _do_emaillearn($uids, $spam) 44 | { 45 | $this->rcube = rcube::get_instance(); 46 | $identity_arr = $this->rcube->user->get_identity(); 47 | $from = $identity_arr['email']; 48 | $from_string = format_email_recipient($identity_arr['email'], $identity_arr['name']); 49 | $attach = $this->rcube->config->get('markasjunk2_email_attach', false); 50 | $temp_dir = unslashify($this->rcube->config->get('temp_dir')); 51 | 52 | $mailto = $this->rcube->config->get($spam ? 'markasjunk2_email_spam' : 'markasjunk2_email_ham'); 53 | $mailto = $this->_parse_vars($mailto, $spam, $from); 54 | 55 | // no address to send to, exit 56 | if (!$mailto) { 57 | return; 58 | } 59 | 60 | $subject = $this->rcube->config->get('markasjunk2_email_subject'); 61 | $subject = $this->_parse_vars($subject, $spam, $from); 62 | 63 | foreach ($uids as $uid) { 64 | $MESSAGE = new rcube_message($uid); 65 | $message_file = null; 66 | 67 | // set message charset as default 68 | if (!empty($MESSAGE->headers->charset)) { 69 | $this->rcube->storage->set_charset($MESSAGE->headers->charset); 70 | } 71 | 72 | $OUTPUT = $this->rcube->output; 73 | $SENDMAIL = new rcmail_sendmail(null, array( 74 | 'sendmail' => true, 75 | 'from' => $from, 76 | 'mailto' => $mailto, 77 | 'dsn_enabled' => false, 78 | 'charset' => 'UTF-8', 79 | 'error_handler' => function() use ($OUTPUT) { 80 | call_user_func_array(array($OUTPUT, 'show_message'), func_get_args()); 81 | $OUTPUT->send(); 82 | } 83 | )); 84 | 85 | if ($attach) { 86 | $headers = array( 87 | 'Date' => $this->rcube->user_date(), 88 | 'From' => $from_string, 89 | 'To' => $mailto, 90 | 'Subject' => $subject, 91 | 'User-Agent' => $this->rcube->config->get('useragent'), 92 | 'Message-ID' => $this->rcube->gen_message_id($from), 93 | 'X-Sender' => $from 94 | ); 95 | 96 | $message_text = ($spam ? 'Spam' : 'Ham') . ' report from ' . $this->rcube->config->get('product_name'); 97 | 98 | // create attachment 99 | $orig_subject = $MESSAGE->get_header('subject'); 100 | $disp_name = (!empty($orig_subject) ? $orig_subject : 'message_rfc822') . '.eml'; 101 | $message_file = tempnam($temp_dir, 'rcmMarkAsJunk2'); 102 | 103 | $attachment = array(); 104 | if ($fp = fopen($message_file, 'w')) { 105 | $this->rcube->storage->get_raw_body($uid, $fp); 106 | fclose($fp); 107 | 108 | $attachment = array( 109 | 'name' => $disp_name, 110 | 'mimetype' => 'message/rfc822', 111 | 'path' => $message_file, 112 | 'size' => filesize($message_file), 113 | 'charset' => $MESSAGE->headers->charset 114 | ); 115 | } 116 | 117 | // create message 118 | $MAIL_MIME = $SENDMAIL->create_message($headers, $message_text, false, array($attachment)); 119 | 120 | if (count($attachment) > 0) { // sanity check incase creating the attachment failed 121 | $folding = (int) $this->rcube->config->get('mime_param_folding'); 122 | 123 | $MAIL_MIME->addAttachment($attachment['path'], 124 | $attachment['mimetype'], $attachment['name'], true, 125 | '8bit', 'attachment', $attachment['charset'], '', '', 126 | $folding ? 'quoted-printable' : null, 127 | $folding == 2 ? 'quoted-printable' : null, 128 | '', RCUBE_CHARSET 129 | ); 130 | } 131 | } 132 | else { 133 | $headers = array( 134 | 'Resent-From' => $from_string, 135 | 'Resent-To' => $mailto, 136 | 'Resent-Date' => $this->rcube->user_date(), 137 | 'Resent-Message-ID' => $this->rcube->gen_message_id($from) 138 | ); 139 | 140 | // create the bounce message 141 | $MAIL_MIME = new rcmail_resend_mail(array( 142 | 'bounce_message' => $MESSAGE, 143 | 'bounce_headers' => $headers, 144 | )); 145 | } 146 | 147 | $SENDMAIL->deliver_message($MAIL_MIME); 148 | $message_file = $message_file ?: $MAIL_MIME->mailbody_file; 149 | 150 | // clean up 151 | if ($message_file) { 152 | unlink($message_file); 153 | } 154 | 155 | if ($this->rcube->config->get('markasjunk2_debug')) { 156 | rcube::write_log('markasjunk2', $uid . ($spam ? ' SPAM ' : ' HAM ') . $mailto . ' (' . $subject . ')'); 157 | 158 | if ($smtp_error['vars']) { 159 | rcube::write_log('markasjunk2', $smtp_error['vars']); 160 | } 161 | } 162 | } 163 | } 164 | 165 | private function _parse_vars($data, $spam, $from) 166 | { 167 | $data = str_replace('%u', $_SESSION['username'], $data); 168 | $data = str_replace('%t', $spam ? 'spam' : 'ham', $data); 169 | $data = str_replace('%l', $this->rcube->user->get_username('local'), $data); 170 | $data = str_replace('%d', $this->rcube->user->get_username('domain'), $data); 171 | $data = str_replace('%i', $from, $data); 172 | 173 | return $data; 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /config.inc.php.dist: -------------------------------------------------------------------------------- 1 | 'mail1_config.inc.php', 68 | // 'mail2.domain.tld' => 'mail2_config.inc.php', 69 | // ); 70 | $config['markasjunk2_host_config'] = null; 71 | 72 | // cmd_learn Driver options 73 | // ------------------------ 74 | // The command used to learn that a message is spam 75 | // The command can contain the following macros that will be expanded as follows: 76 | // %u is replaced with the username (from the session info) 77 | // %l is replaced with the local part of the username (if the username is an email address) 78 | // %d is replaced with the domain part of the username (if the username is an email address or default mail domain if not) 79 | // %i is replaced with the email address from the user's default identity 80 | // %s is replaced with the email address the message is from 81 | // %f is replaced with the path to the message file 82 | // %h:
is replaced with the content of that header from the message (lower case) eg: %h:x-dspam-signature 83 | // If you do not want to run the command set this to null 84 | $config['markasjunk2_spam_cmd'] = null; 85 | 86 | // The command used to learn that a message is ham 87 | // The command can contain the following macros that will be expanded as follows: 88 | // %u is replaced with the username (from the session info) 89 | // %l is replaced with the local part of the username (if the username is an email address) 90 | // %d is replaced with the domain part of the username (if the username is an email address or default mail domain if not) 91 | // %i is replaced with the email address from the user's default identity 92 | // %s is replaced with the email address the message is from 93 | // %f is replaced with the path to the message file 94 | // %h:
is replaced with the content of that header from the message (lower case) eg: %h:x-dspam-signature 95 | // If you do not want to run the command set this to null 96 | $config['markasjunk2_ham_cmd'] = null; 97 | 98 | // dir_learn Driver options 99 | // ------------------------ 100 | // The full path of the directory used to store spam (must be writable by webserver) 101 | $config['markasjunk2_spam_dir'] = null; 102 | 103 | // The full path of the directory used to store ham (must be writable by webserver) 104 | $config['markasjunk2_ham_dir'] = null; 105 | 106 | // The filename prefix 107 | // The filename can contain the following macros that will be expanded as follows: 108 | // %u is replaced with the username (from the session info) 109 | // %l is replaced with the local part of the username (if the username is an email address) 110 | // %d is replaced with the domain part of the username (if the username is an email address or default mail domain if not) 111 | // %t is replaced with the type of message (spam/ham) 112 | $config['markasjunk2_filename'] = null; 113 | 114 | // email_learn Driver options 115 | // -------------------------- 116 | // The email address that spam messages will be sent to 117 | // The address can contain the following macros that will be expanded as follows: 118 | // %u is replaced with the username (from the session info) 119 | // %l is replaced with the local part of the username (if the username is an email address) 120 | // %d is replaced with the domain part of the username (if the username is an email address or default mail domain if not) 121 | // %i is replaced with the email address from the user's default identity 122 | // If you do not want to send an email set this to null 123 | $config['markasjunk2_email_spam'] = null; 124 | 125 | // The email address that ham messages will be sent to 126 | // The address can contain the following macros that will be expanded as follows: 127 | // %u is replaced with the username (from the session info) 128 | // %l is replaced with the local part of the username (if the username is an email address) 129 | // %d is replaced with the domain part of the username (if the username is an email address or default mail domain if not) 130 | // %i is replaced with the email address from the user's default identity 131 | // If you do not want to send an email set this to null 132 | $config['markasjunk2_email_ham'] = null; 133 | 134 | // Should the spam/ham message be sent as an attachment 135 | $config['markasjunk2_email_attach'] = true; 136 | 137 | // The email subject (when sending as attachment) 138 | // The subject can contain the following macros that will be expanded as follows: 139 | // %u is replaced with the username (from the session info) 140 | // %l is replaced with the local part of the username (if the username is an email address) 141 | // %d is replaced with the domain part of the username (if the username is an email address or default mail domain if not) 142 | // %t is replaced with the type of message (spam/ham) 143 | $config['markasjunk2_email_subject'] = 'learn this message as %t'; 144 | 145 | // sa_blacklist Driver options 146 | // --------------------------- 147 | // Path to SAUserPrefs config file 148 | $config['markasjunk2_sauserprefs_config'] = '../sauserprefs/config.inc.php'; 149 | 150 | // amavis_blacklist Driver options 151 | // --------------------------- 152 | // Path to amacube config file 153 | $config['markasjunk2_amacube_config'] = '../amacube/config.inc.php'; 154 | 155 | // edit_headers Driver options 156 | // --------------------------- 157 | // Patterns to match and replace headers for spam messages 158 | // Replacement method uses preg_replace - http://www.php.net/manual/function.preg-replace.php 159 | // WARNING: Be sure to match the entire header line, including the name of the header, also use ^ and $ and the 'm' flag 160 | // see the README for an example 161 | // TEST CAREFULLY BEFORE USE ON REAL MESSAGES 162 | $config['markasjunk2_spam_patterns'] = array( 163 | 'patterns' => array(), 164 | 'replacements' => array() 165 | ); 166 | 167 | // Patterns to match and replace headers for spam messages 168 | // Replacement method uses preg_replace - http://www.php.net/manual/function.preg-replace.php 169 | // WARNING: Be sure to match the entire header line, including the name of the header, also use ^ and $ and the 'm' flag 170 | // see the README for an example 171 | // TEST CAREFULLY BEFORE USE ON REAL MESSAGES 172 | $config['markasjunk2_ham_patterns'] = array( 173 | 'patterns' => array(), 174 | 'replacements' => array() 175 | ); 176 | -------------------------------------------------------------------------------- /markasjunk2.php: -------------------------------------------------------------------------------- 1 | 'Junk', 40 | 'NONJUNK' => 'NonJunk' 41 | ); 42 | 43 | private $toolbar = true; 44 | private $rcube; 45 | 46 | public function init() 47 | { 48 | $this->register_action('plugin.markasjunk2.junk', array($this, 'mark_message')); 49 | $this->register_action('plugin.markasjunk2.not_junk', array($this, 'mark_message')); 50 | 51 | $this->rcube = rcube::get_instance(); 52 | $this->load_config(); 53 | $this->_load_host_config(); 54 | 55 | // Host exceptions 56 | $hosts = $this->rcube->config->get('markasjunk2_allowed_hosts'); 57 | if (!empty($hosts) && !in_array($_SESSION['storage_host'], (array) $hosts)) { 58 | return; 59 | } 60 | 61 | $this->ham_mbox = $this->rcube->config->get('markasjunk2_ham_mbox', 'INBOX'); 62 | $this->spam_mbox = $this->rcube->config->get('markasjunk2_spam_mbox', $this->rcube->config->get('junk_mbox', null)); 63 | $this->toolbar = $this->_set_toolbar_display($this->rcube->config->get('markasjunk2_toolbar', -1), $this->rcube->action); 64 | $this->_init_flags(); 65 | 66 | // integration with Swipe plugin 67 | $this->add_hook('swipe_actions_list', array($this, 'swipe_action')); 68 | 69 | if ($this->rcube->action == '' || $this->rcube->action == 'show') { 70 | $this->include_script('markasjunk2.js'); 71 | $this->add_texts('localization', true); 72 | $this->include_stylesheet($this->local_skin_path() . '/markasjunk2.css'); 73 | 74 | if ($this->toolbar) { 75 | // add the buttons to the main toolbar 76 | $this->add_button(array('command' => 'plugin.markasjunk2.junk', 'type' => 'link', 'class' => 'button buttonPas markasjunk2 disabled', 'classact' => 'button markasjunk2', 'classsel' => 'button markasjunk2 pressed', 'title' => 'markasjunk2.buttonjunk', 'innerclass' => 'inner', 'label' => 'junk'), 'toolbar'); 77 | $this->add_button(array('command' => 'plugin.markasjunk2.not_junk', 'type' => 'link', 'class' => 'button buttonPas markasnotjunk2 disabled', 'classact' => 'button markasnotjunk2', 'classsel' => 'button markasnotjunk2 pressed', 'title' => 'markasjunk2.buttonnotjunk', 'innerclass' => 'inner', 'label' => 'markasjunk2.notjunk'), 'toolbar'); 78 | } 79 | else { 80 | // add the buttons to the mark message menu 81 | $this->add_button(array('command' => 'plugin.markasjunk2.junk', 'type' => 'link-menuitem', 'label' => 'markasjunk2.asjunk', 'id' => 'markasjunk2', 'class' => 'icon markasjunk2', 'classact' => 'icon markasjunk2 active', 'innerclass' => 'icon markasjunk2'), 'markmenu'); 82 | $this->add_button(array('command' => 'plugin.markasjunk2.not_junk', 'type' => 'link-menuitem', 'label' => 'markasjunk2.asnotjunk', 'id' => 'markasnotjunk2', 'class' => 'icon markasnotjunk2', 'classact' => 'icon markasnotjunk2 active', 'innerclass' => 'icon markasnotjunk2'), 'markmenu'); 83 | } 84 | 85 | // add markasjunk2 folder settings to the env for JS 86 | $this->rcube->output->set_env('markasjunk2_ham_mailbox', $this->ham_mbox); 87 | $this->rcube->output->set_env('markasjunk2_spam_mailbox', $this->spam_mbox); 88 | 89 | $this->rcube->output->set_env('markasjunk2_move_spam', $this->rcube->config->get('markasjunk2_move_spam', false)); 90 | $this->rcube->output->set_env('markasjunk2_move_ham', $this->rcube->config->get('markasjunk2_move_ham', false)); 91 | $this->rcube->output->set_env('markasjunk2_permanently_remove', $this->rcube->config->get('markasjunk2_permanently_remove', false)); 92 | $this->rcube->output->set_env('markasjunk2_spam_only', $this->rcube->config->get('markasjunk2_spam_only', false)); 93 | 94 | // check for init method from driver 95 | if ($this->rcube->config->get('markasjunk2_learning_driver', false)) { 96 | $this->_call_driver('init'); 97 | } 98 | } 99 | } 100 | 101 | public function mark_message() 102 | { 103 | $this->add_texts('localization'); 104 | 105 | $is_spam = $this->rcube->action == 'plugin.markasjunk2.junk' ? true : false; 106 | $messageset = rcmail::get_uids(null, null, $multifolder, rcube_utils::INPUT_POST); 107 | $mbox_name = rcube_utils::get_input_value('_mbox', rcube_utils::INPUT_POST); 108 | $dest_mbox = $is_spam ? $this->spam_mbox : $this->ham_mbox; 109 | $result = $is_spam ? $this->_spam($messageset, $dest_mbox) : $this->_ham($messageset, $dest_mbox); 110 | 111 | if ($result) { 112 | if ($dest_mbox && ($mbox_name !== $dest_mbox || $multifolder)) { 113 | $this->rcube->output->command('rcmail_markasjunk2_move', $dest_mbox, $this->_messageset_to_uids($messageset, $multifolder)); 114 | } 115 | else { 116 | $this->rcube->output->command('command', 'list', $mbox_name); 117 | } 118 | 119 | $this->rcube->output->command('display_message', $is_spam ? $this->gettext('reportedasjunk') : $this->gettext('reportedasnotjunk'), 'confirmation'); 120 | } 121 | 122 | $this->rcube->output->send(); 123 | } 124 | 125 | public function set_flags($p) 126 | { 127 | $p['message_flags'] = array_merge((array) $p['message_flags'], $this->flags); 128 | 129 | return $p; 130 | } 131 | 132 | public function swipe_action($p) 133 | { 134 | if ($this->spam_mbox && $p['source'] == 'messagelist' && $p['axis'] == 'horizontal') { 135 | $p['actions']['markasjunk2'] = 'markasjunk2.markasjunk'; 136 | 137 | return $p; 138 | } 139 | } 140 | 141 | private function _set_toolbar_display($display, $action) 142 | { 143 | $ret = true; 144 | 145 | // backwards compatibility for old config options (removed in 1.10) 146 | if ($display < 0) { 147 | $mb = $this->rcube->config->get('markasjunk2_mb_toolbar', true); 148 | $cp = $this->rcube->config->get('markasjunk2_cp_toolbar', true); 149 | 150 | if ($mb && $cp) { 151 | $display = 1; 152 | } 153 | elseif ($mb && !$cp) { 154 | $display = 2; 155 | } 156 | elseif (!$mb && $cp) { 157 | $display = 3; 158 | } 159 | else { 160 | $display = 0; 161 | } 162 | } 163 | 164 | switch ($display) { 165 | case 0: // always show in mark message menu 166 | $ret = false; 167 | break; 168 | case 1: // always show on toolbar 169 | $ret = true; 170 | break; 171 | case 2: // show in toolbar on mailbox screen, show in mark message menu message on screen 172 | $ret = ($action != 'show'); 173 | break; 174 | case 3: // show in mark message menu on mailbox screen, show in toolbar message on screen 175 | $ret = ($action == 'show'); 176 | break; 177 | } 178 | 179 | return $ret; 180 | } 181 | 182 | private function _spam(&$messageset, $dest_mbox = null) 183 | { 184 | $storage = $this->rcube->get_storage(); 185 | $result = true; 186 | 187 | foreach ($messageset as $source_mbox => &$uids) { 188 | $storage->set_folder($source_mbox); 189 | 190 | if ($this->rcube->config->get('markasjunk2_learning_driver', false)) { 191 | $result = $this->_call_driver('spam', $uids, $source_mbox, $dest_mbox); 192 | 193 | // abort function of the driver says so 194 | if (!$result) { 195 | break; 196 | } 197 | } 198 | 199 | if ($this->rcube->config->get('markasjunk2_read_spam', false)) { 200 | $storage->set_flag($uids, 'SEEN', $source_mbox); 201 | } 202 | 203 | if (array_key_exists('JUNK', $this->flags)) { 204 | $storage->set_flag($uids, 'JUNK', $source_mbox); 205 | } 206 | 207 | if (array_key_exists('NONJUNK', $this->flags)) { 208 | $storage->unset_flag($uids, 'NONJUNK', $source_mbox); 209 | } 210 | } 211 | 212 | return $result; 213 | } 214 | 215 | private function _ham(&$messageset, $dest_mbox = null) 216 | { 217 | $storage = $this->rcube->get_storage(); 218 | $result = true; 219 | 220 | foreach ($messageset as $source_mbox => &$uids) { 221 | $storage->set_folder($source_mbox); 222 | 223 | if ($this->rcube->config->get('markasjunk2_learning_driver', false)) { 224 | $result = $this->_call_driver('ham', $uids, $source_mbox, $dest_mbox); 225 | 226 | // abort function of the driver says so 227 | if (!$result) { 228 | break; 229 | } 230 | } 231 | 232 | if ($this->rcube->config->get('markasjunk2_unread_ham', false)) { 233 | $storage->unset_flag($uids, 'SEEN', $source_mbox); 234 | } 235 | 236 | if (array_key_exists('JUNK', $this->flags)) { 237 | $storage->unset_flag($uids, 'JUNK', $source_mbox); 238 | } 239 | 240 | if (array_key_exists('NONJUNK', $this->flags)) { 241 | $storage->set_flag($uids, 'NONJUNK', $source_mbox); 242 | } 243 | } 244 | 245 | return $result; 246 | } 247 | 248 | private function _call_driver($action, &$uids = null, $source_mbox = null, $dest_mbox = null) 249 | { 250 | $driver = $this->home . '/drivers/' . $this->rcube->config->get('markasjunk2_learning_driver', 'cmd_learn') . '.php'; 251 | $class = 'markasjunk2_' . $this->rcube->config->get('markasjunk2_learning_driver', 'cmd_learn'); 252 | 253 | if (!is_readable($driver)) { 254 | rcube::raise_error(array( 255 | 'code' => 600, 256 | 'type' => 'php', 257 | 'file' => __FILE__, 258 | 'line' => __LINE__, 259 | 'message' => "MarkasJunk2 plugin: Unable to open driver file $driver" 260 | ), true, false); 261 | } 262 | 263 | include_once $driver; 264 | 265 | if (!class_exists($class, false) || !method_exists($class, 'spam') || !method_exists($class, 'ham')) { 266 | rcube::raise_error(array( 267 | 'code' => 600, 268 | 'type' => 'php', 269 | 'file' => __FILE__, 270 | 'line' => __LINE__, 271 | 'message' => "MarkasJunk2 plugin: Broken driver: $driver" 272 | ), true, false); 273 | } 274 | 275 | // call the relevant function from the driver 276 | $object = new $class(); 277 | if ($action == 'spam') { 278 | $object->spam($uids, $source_mbox, $dest_mbox); 279 | } 280 | elseif ($action == 'ham') { 281 | $object->ham($uids, $source_mbox, $dest_mbox); 282 | } 283 | elseif ($action == 'init' && method_exists($object, 'init')) { // method_exists check here for backwards compatibility, init method added 20161127 284 | $object->init(); 285 | } 286 | 287 | return $object->is_error ? false : true; 288 | } 289 | 290 | private function _messageset_to_uids($messageset, $multifolder) 291 | { 292 | $a_uids = array(); 293 | 294 | foreach ($messageset as $mbox => $uids) { 295 | foreach ($uids as $uid) { 296 | $a_uids[] = $multifolder ? $uid . '-' . $mbox : $uid; 297 | } 298 | } 299 | 300 | return $a_uids; 301 | } 302 | 303 | private function _load_host_config() 304 | { 305 | $configs = $this->rcube->config->get('markasjunk2_host_config'); 306 | if (empty($configs) || !array_key_exists($_SESSION['storage_host'], (array) $configs)) { 307 | return; 308 | } 309 | 310 | $file = $configs[$_SESSION['storage_host']]; 311 | $this->load_config($file); 312 | } 313 | 314 | private function _init_flags() 315 | { 316 | $spam_flag = $this->rcube->config->get('markasjunk2_spam_flag'); 317 | $ham_flag = $this->rcube->config->get('markasjunk2_ham_flag'); 318 | 319 | // backwards compatibility 320 | // defaults for markasjunk2_spam_flag and markasjunk2_ham_flag changed in 1.12 321 | // from now on use false to disable rather than null 322 | if ($spam_flag === null || $ham_flag === null) { 323 | $all_configs = $this->rcube->config->all(); 324 | 325 | if ($spam_flag === null && array_key_exists('markasjunk2_spam_flag', $all_configs)) { 326 | $spam_flag = false; 327 | } 328 | 329 | if ($ham_flag === null && array_key_exists('markasjunk2_ham_flag', $all_configs)) { 330 | $ham_flag = false; 331 | } 332 | } 333 | 334 | if ($spam_flag === false) { 335 | unset($this->flags['JUNK']); 336 | } 337 | elseif (!empty($spam_flag)) { 338 | $this->flags['JUNK'] = $spam_flag; 339 | } 340 | 341 | if ($ham_flag === false) { 342 | unset($this->flags['NONJUNK']); 343 | } 344 | elseif (!empty($ham_flag)) { 345 | $this->flags['NONJUNK'] = $ham_flag; 346 | } 347 | 348 | if (count($this->flags) > 0) { 349 | // register the ham/spam flags with the core 350 | $this->add_hook('storage_init', array($this, 'set_flags')); 351 | } 352 | } 353 | } 354 | --------------------------------------------------------------------------------