0013248: Notification to external email via sieve
authorPaul Mehrer <p.mehrer@metaways.de>
Tue, 20 Jun 2017 12:39:11 +0000 (14:39 +0200)
committerPhilipp Schüle <p.schuele@metaways.de>
Wed, 21 Jun 2017 14:48:09 +0000 (16:48 +0200)
* notify admin email about notification bounces
* adds ctx menu and edit dialog for notification email address

https://forge.tine20.org/view.php?id=13248

Change-Id: I116db2304d6da8913d452c414d201d4039257437
Reviewed-on: http://gerrit.tine20.com/customers/4912
Reviewed-by: Philipp Schüle <p.schuele@metaways.de>
Tested-by: Philipp Schüle <p.schuele@metaways.de>
15 files changed:
tests/tine20/Felamimail/Frontend/JsonTest.php
tine20/Felamimail/Config.php
tine20/Felamimail/Controller/Account.php
tine20/Felamimail/Controller/Sieve.php
tine20/Felamimail/Felamimail.jsb2
tine20/Felamimail/Frontend/Json.php
tine20/Felamimail/Model/Account.php
tine20/Felamimail/Setup/Initialize.php
tine20/Felamimail/Setup/Update/Release10.php
tine20/Felamimail/Setup/setup.xml
tine20/Felamimail/css/Felamimail.css
tine20/Felamimail/js/AccountEditDialog.js
tine20/Felamimail/js/Model.js
tine20/Felamimail/js/TreeContextMenu.js
tine20/Felamimail/js/sieve/NotificationDialog.js [new file with mode: 0644]

index a1160f7..24db69d 100644 (file)
@@ -1829,27 +1829,21 @@ IbVx8ZTO7dJRKrg72aFmWTf0uNla7vicAhpiLWobyNYcZbIjrAGDfg==
         $this->assertContains($sieveScriptRules, $sieveScriptVacation, 'rule order changed');
     }
 
-    /**
-     * testSieveRulesOrder
-     *
-     * @see 0007240: order of sieve rules changes when vacation message is saved
-     */
     public function testSieveEmailNotification()
     {
         $this->_setTestScriptname();
 
-        $result = $this->_json->setSieveNotificationEmail($this->_account->getId(), 'test@test.de');
-        static::assertTrue(is_array($result) && count($result) === 1 && isset($result['success']) &&
-            true === $result['success'], print_r($result, true));
+        $this->_account->sieve_notification_email = 'test@test.de';
+        Felamimail_Controller_Account::getInstance()->update($this->_account);
 
         $script = new Felamimail_Sieve_Backend_Sql($this->_account->getId());
         $scriptParts = $script->getScriptParts();
         static::assertEquals(1, $scriptParts->count());
         /** @var Felamimail_Model_Sieve_ScriptPart $scriptPart */
         $scriptPart = $scriptParts->getFirstRecord();
-        static::assertTrue(count(array_intersect(array('"enotify"', '"variables"', '"copy"'), $scriptPart->xprops(
-            Felamimail_Model_Sieve_ScriptPart::XPROPS_REQUIRES))) === 3, print_r($scriptPart->xprops(
-            Felamimail_Model_Sieve_ScriptPart::XPROPS_REQUIRES), true));
+        static::assertTrue(count(array_intersect(array('"enotify"', '"variables"', '"copy"', '"body"'),
+            $scriptPart->xprops(Felamimail_Model_Sieve_ScriptPart::XPROPS_REQUIRES))) === 4,
+            print_r($scriptPart->xprops(Felamimail_Model_Sieve_ScriptPart::XPROPS_REQUIRES), true));
         static::assertContains('test@test.de', $script->getSieve());
     }
     
index d61edcc..f608d93 100644 (file)
@@ -66,6 +66,13 @@ class Felamimail_Config extends Tinebase_Config_Abstract
     const EMAIL_NOTIFICATION_TEMPLATES_CONTAINER_ID = 'emailNotificationTemplatesContainerId';
 
     /**
+     * the email address to notifify about notification bounces
+     *
+     * @var string
+     */
+    const SIEVE_ADMIN_BOUNCE_NOTIFICATION_EMAIL = 'sieveAdminBounceNotificationEmail';
+
+    /**
      * allow only sieve redirect rules to internal (primary/secondary) email addresses
      *
      * @var string
@@ -188,11 +195,22 @@ class Felamimail_Config extends Tinebase_Config_Abstract
             // _('Allow only sieve redirect rules to internal (primary/secondary) email addresses')
             'description'           => 'Allow only sieve redirect rules to internal (primary/secondary) email addresses',
             'type'                  => Tinebase_Config_Abstract::TYPE_BOOL,
-            'clientRegistryInclude' => TRUE,
-            'setByAdminModule'      => TRUE,
-            'setBySetupModule'      => FALSE,
+            'clientRegistryInclude' => true,
+            'setByAdminModule'      => true,
+            'setBySetupModule'      => false,
             'default'               => false,
         ),
+        self::SIEVE_ADMIN_BOUNCE_NOTIFICATION_EMAIL => array(
+            //_('Sieve Notification Bounces Reporting Email')
+            'label'                 => 'Sieve Notification Bounces Reporting Email',
+            // _('Sieve Notification Bounces Reporting Email')
+            'description'           => 'Sieve Notification Bounces Reporting Email',
+            'type'                  => Tinebase_Config_Abstract::TYPE_STRING,
+            'clientRegistryInclude' => false,
+            'setByAdminModule'      => true,
+            'setBySetupModule'      => false,
+            'default'               => null,
+        )
     );
     
     /**
index c6a6841..e140646 100644 (file)
@@ -325,6 +325,7 @@ class Felamimail_Controller_Account extends Tinebase_Controller_Record_Abstract
             'ns_shared',
             'last_modified_time',
             'last_modified_by',
+            'sieve_notification_email',
         );
         $diff = $_record->diff($_oldRecord)->diff;
         foreach ($diff as $key => $value) {
@@ -437,6 +438,22 @@ class Felamimail_Controller_Account extends Tinebase_Controller_Record_Abstract
             $_record->smtp_credentials_id = $_record->credentials_id;
         }
     }
+
+    /**
+     * inspect update of one record (after update)
+     *
+     * @param   Felamimail_Model_Account $updatedRecord   the just updated record
+     * @param   Felamimail_Model_Account $record          the update record
+     * @param   Felamimail_Model_Account $currentRecord   the current record (before update)
+     * @return  void
+     */
+    protected function _inspectAfterUpdate($updatedRecord, $record, $currentRecord)
+    {
+        if ($updatedRecord->sieve_notification_email !== $currentRecord->sieve_notification_email) {
+            Felamimail_Controller_Sieve::getInstance()->setNotificationEmail($updatedRecord->getId(),
+                $updatedRecord->sieve_notification_email);
+        }
+    }
     
     /**
      * check if user has the right to manage accounts
index 9a2f46b..c833e12 100644 (file)
@@ -655,22 +655,34 @@ class Felamimail_Controller_Sieve extends Tinebase_Controller_Abstract
      */
     public function setNotificationEmail($_accountId, $_email)
     {
-        $_email = trim($_email);
-        if (!preg_match(Tinebase_Mail::EMAIL_ADDRESS_REGEXP, $_email)) {
-            throw new Tinebase_Exception_UnexpectedValue($_email . ' is not a valid email address');
-        }
-
         // acl check
         if (!$_accountId instanceof Felamimail_Model_Account) {
             Felamimail_Controller_Account::getInstance()->get($_accountId);
         }
 
-        $fileSystem = Tinebase_FileSystem::getInstance();
         $scriptParts = new Tinebase_Record_RecordSet('Felamimail_Model_Sieve_ScriptPart', array());
+        $_email = trim($_email);
+        if (empty($_email)) {
+            $this->setNotificationScripts($_accountId, $scriptParts);
+            return;
+        }
 
+        if (!preg_match(Tinebase_Mail::EMAIL_ADDRESS_REGEXP, $_email)) {
+            throw new Tinebase_Exception_UnexpectedValue($_email . ' is not a valid email address');
+        }
+
+        $fileSystem = Tinebase_FileSystem::getInstance();
         $translate = Tinebase_Translation::getTranslation('Felamimail');
         $locale = Tinebase_Core::get(Tinebase_Core::LOCALE);
         $subject = $translate->_('You have new mail from ', $locale);
+        if (empty($adminBounceEmail = Felamimail_Config::getInstance()->
+                {Felamimail_Config::SIEVE_ADMIN_BOUNCE_NOTIFICATION_EMAIL}) ||
+                !preg_match(Tinebase_Mail::EMAIL_ADDRESS_REGEXP, $adminBounceEmail)) {
+            $defaultAdminGroup = Tinebase_Group::getInstance()->getDefaultAdminGroup();
+            $members = Tinebase_Group::getInstance()->getGroupMembers($defaultAdminGroup->getId());
+            $firstAdminUser = Tinebase_User::getInstance()->getFullUserById($members[0]);
+            $adminBounceEmail = $firstAdminUser->accountEmailAddress;
+        }
 
         /** @var Tinebase_Model_Tree_Node $sieveNode */
         foreach ($fileSystem->getTreeNodeChildren(Felamimail_Config::getInstance()->
@@ -682,6 +694,7 @@ class Felamimail_Controller_Sieve extends Tinebase_Controller_Abstract
             $sieveScript = file_get_contents($path->streamwrapperpath);
             $sieveScript = str_replace('USER_EXTERNAL_EMAIL', $_email, $sieveScript);
             $sieveScript = str_replace('TRANSLATE_SUBJECT', $subject, $sieveScript);
+            $sieveScript = str_replace('ADMIN_BOUNCE_EMAIL', $adminBounceEmail, $sieveScript);
             $scriptParts->addRecord(Felamimail_Model_Sieve_ScriptPart::createFromString(
                 Felamimail_Model_Sieve_ScriptPart::TYPE_NOTIFICATION, $sieveNode->name, $sieveScript));
         }
index 58a98ac..fde5331 100644 (file)
           "path": "js/sieve/"
         },
         {
+          "text": "NotificationDialog.js",
+          "path": "js/sieve/"
+        },
+        {
           "text": "TreeContextMenu.js",
           "path": "js/"
         },
index 8eed362..de157a2 100644 (file)
@@ -612,16 +612,4 @@ class Felamimail_Frontend_Json extends Tinebase_Frontend_Json_Abstract
         
         return $result;
     }
-
-    /**
-     * @param string $_accountId
-     * @param string $_email
-     * @return array
-     */
-    public function setSieveNotificationEmail($_accountId, $_email)
-    {
-        Felamimail_Controller_Sieve::getInstance()->setNotificationEmail($_accountId, $_email);
-
-        return array('success' => true);
-    }
 }
index 2e47081..9c2fb52 100644 (file)
@@ -6,7 +6,7 @@
  * @subpackage  Model
  * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
  * @author      Philipp Schüle <p.schuele@metaways.de>
- * @copyright   Copyright (c) 2009-2015 Metaways Infosystems GmbH (http://www.metaways.de)
+ * @copyright   Copyright (c) 2009-2017 Metaways Infosystems GmbH (http://www.metaways.de)
  * 
  * @todo        update account credentials if user password changed
  * @todo        use generic (JSON encoded) field for 'other' settings like folder names
@@ -26,6 +26,8 @@
  * @property  string    signature_position
  * @property  string    email
  * @property  string    user_id
+ * @property  string    sieve_notification_email
+ *
  * @package   Felamimail
  * @subpackage    Model
  */
@@ -132,6 +134,7 @@ class Felamimail_Model_Account extends Tinebase_EmailUser_Model_Account
             array('InArray', array(self::SECURE_NONE, self::SECURE_TLS)),
         ),
         'sieve_vacation_active' => array(Zend_Filter_Input::ALLOW_EMPTY => true, Zend_Filter_Input::DEFAULT_VALUE => 0),
+        'sieve_notification_email' => array(Zend_Filter_Input::ALLOW_EMPTY => true),
         //'sieve_credentials_id'  => array(Zend_Filter_Input::ALLOW_EMPTY => true),
         //'sieve_user'            => array(Zend_Filter_Input::ALLOW_EMPTY => true),
         //'sieve_password'        => array(Zend_Filter_Input::ALLOW_EMPTY => true),
index a653418..3c3795a 100644 (file)
@@ -125,9 +125,13 @@ class Felamimail_Setup_Initialize extends Setup_Initialize
             }
 
             fwrite($fh, <<<'sieveFile'
-require ["enotify", "variables", "copy"];
+require ["enotify", "variables", "copy", "body"];
 
 if header :contains "Return-Path" "<>" {
+    if body :raw :contains "X-Tine20-Type: Notification" {
+        notify :message "there was a notification bounce"
+              "mailto:ADMIN_BOUNCE_EMAIL";
+    }
 } elsif header :contains "X-Tine20-Type" "Notification" {
     redirect :copy "USER_EXTERNAL_EMAIL"; 
 } else {
index 34cad96..a2efeb7 100644 (file)
@@ -130,4 +130,73 @@ class Felamimail_Setup_Update_Release10 extends Setup_Update_Abstract
 
         $this->setApplicationVersion('Felamimail', '10.4');
     }
+
+    /**
+     * update to 10.5
+     *
+     * add sieve_notification_email column
+     * update sieve script in FS
+     */
+    public function update_4()
+    {
+        if (! $this->_backend->columnExists('sieve_notification_email', 'felamimail_account')) {
+            $declaration = new Setup_Backend_Schema_Field_Xml(
+                '<field>
+                    <name>sieve_notification_email</name>
+                    <type>text</type>
+                    <length>255</length>
+                </field>');
+            $this->_backend->addCol('felamimail_account', $declaration);
+            $this->setTableVersion('felamimail_account', 24);
+        }
+
+        $basepath = Tinebase_FileSystem::getInstance()->getApplicationBasePath(
+            'Felamimail',
+            Tinebase_FileSystem::FOLDER_TYPE_SHARED
+        );
+        $templatePath = $basepath . '/Email Notification Templates';
+        if (! Tinebase_FileSystem::getInstance()->fileExists($templatePath)) {
+            $node = Tinebase_FileSystem::getInstance()->createAclNode($templatePath);
+        } else {
+            $node = Tinebase_FileSystem::getInstance()->stat($templatePath);
+        }
+        Felamimail_Config::getInstance()->set(Felamimail_Config::EMAIL_NOTIFICATION_TEMPLATES_CONTAINER_ID, $node->getId());
+
+        if (false === ($fh = Tinebase_FileSystem::getInstance()->fopen($basepath . '/Email Notification Templates/defaultForwarding.sieve', 'w'))) {
+            if (Tinebase_Core::isLogLevel(Zend_Log::ERR)) Tinebase_Core::getLogger()->err(__METHOD__ . '::' . __LINE__
+                . ' Could not create defaultForwarding.sieve file');
+            throw new Tinebase_Exception('could not update email notification tempalte');
+        }
+
+        fwrite($fh, <<<'sieveFile'
+require ["enotify", "variables", "copy", "body"];
+
+if header :contains "Return-Path" "<>" {
+    if body :raw :contains "X-Tine20-Type: Notification" {
+        notify :message "there was a notification bounce"
+              "mailto:ADMIN_BOUNCE_EMAIL";
+    }
+} elsif header :contains "X-Tine20-Type" "Notification" {
+    redirect :copy "USER_EXTERNAL_EMAIL"; 
+} else {
+    if header :matches "Subject" "*" {
+        set "subject" "${1}";
+    }
+    if header :matches "From" "*" {
+        set "from" "${1}";
+    }
+    notify :message "TRANSLATE_SUBJECT${from}: ${subject}"
+              "mailto:USER_EXTERNAL_EMAIL";
+}
+sieveFile
+        );
+
+        if (true !== Tinebase_FileSystem::getInstance()->fclose($fh)) {
+            if (Tinebase_Core::isLogLevel(Zend_Log::ERR)) Tinebase_Core::getLogger()->err(__METHOD__ . '::' . __LINE__
+                . ' Could not create defaultForwarding.sieve file');
+            throw new Tinebase_Exception('could not update email notification tempalte');
+        }
+
+        $this->setApplicationVersion('Felamimail', '10.5');
+    }
 }
index 32adb16..dfd0dd3 100644 (file)
@@ -1,13 +1,13 @@
 <?xml version="1.0" encoding="utf-8"?>
 <application>
     <name>Felamimail</name>
-    <version>10.4</version>
+    <version>10.5</version>
     <order>30</order>
     <status>enabled</status>
     <tables>
         <table>
             <name>felamimail_account</name>
-            <version>23</version>
+            <version>24</version>
             <declaration>
                 <field>
                     <name>id</name>
                     <default>false</default>
                 </field>
                 <field>
+                    <name>sieve_notification_email</name>
+                    <type>text</type>
+                    <length>255</length>
+                </field>
+                <field>
                     <name>sieve_ssl</name>
                     <type>text</type>
                     <length>32</length>
index 71f7376..50caa6b 100644 (file)
     background-image:url(../../images/oxygen/32x32/actions/arrow-down.png) !important;
 }
 
+/* other actions */
+
+.felamimail-action-reading-confirmation {
+    background-image:url(../../images/oxygen/16x16/actions/mail-mark-task.png) !important;
+}
+.x-btn-medium .felamimail-action-reading-confirmation {
+    background-image:url(../../images/oxygen/22x22/actions/mail-mark-task.png) !important;
+}
+.x-btn-large .felamimail-action-reading-confirmation {
+    background-image:url(../../images/oxygen/32x32/actions/mail-mark-task.png) !important;
+}
+
+
+.felamimail-action-sieve-notification {
+    background-image:url(../../images/oxygen/16x16/actions/bell.png) !important;
+}
+.x-btn-medium .felamimail-action-sieve-notification {
+    background-image:url(../../images/oxygen/22x22/actions/bell.png) !important;
+}
+.x-btn-large .felamimail-action-sieve-notification {
+    background-image:url(../../images/oxygen/32x32/actions/bell.png) !important;
+}
 /***** flag classes ******/
 
 .flag_unread td{
     background-repeat: no-repeat;
 }
 
-.felamimail-action-reading-confirmation {
-    background-image:url(../../images/oxygen/16x16/actions/mail-mark-task.png) !important;
-}
-.x-btn-medium .felamimail-action-reading-confirmation {
-    background-image:url(../../images/oxygen/22x22/actions/mail-mark-task.png) !important;
-}
-.x-btn-large .felamimail-action-reading-confirmation {
-    background-image:url(../../images/oxygen/32x32/actions/mail-mark-task.png) !important;
-}
-
 /***** sieve rules ******/
 
 .felamimail-sieverule-disabled {
index 7dcfbcc..117ff40 100644 (file)
@@ -67,6 +67,7 @@ Tine.Felamimail.AccountEditDialog = Ext.extend(Tine.widgets.dialog.EditDialog, {
                     case 'display_format':
                     case 'compose_format':
                     case 'preserve_format':
+                    case 'sieve_notification_email':
                     case 'reply_to':
                         break;
                     default:
@@ -282,6 +283,10 @@ Tine.Felamimail.AccountEditDialog = Ext.extend(Tine.widgets.dialog.EditDialog, {
                         ['none', this.app.i18n._('None')],
                         ['tls',  this.app.i18n._('TLS')]
                     ]
+                }, {
+                    fieldLabel: this.app.i18n._('Notification Email'),
+                    name: 'sieve_notification_email',
+                    vtype: 'email'
                 }]]
             }, {
                 title: this.app.i18n._('Other Settings'),
index 9ecdd7f..de2c62e 100644 (file)
@@ -396,6 +396,7 @@ Tine.Felamimail.Model.Account = Tine.Tinebase.data.Record.create(Tine.Tinebase.M
     { name: 'sieve_port' },
     { name: 'sieve_ssl' },
     { name: 'sieve_vacation_active', type: 'bool' },
+    { name: 'sieve_notification_email' },
     { name: 'all_folders_fetched', type: 'bool', defaultValue: false } // client only
 ]), {
     appName: 'Felamimail',
index b142acf..70fefc0 100644 (file)
@@ -154,6 +154,22 @@ Tine.Felamimail.setTreeContextMenus = function() {
         }
     };
 
+    var editNotificationAction = {
+        text: this.app.i18n._('Notifications'),
+        iconCls: 'felamimail-action-sieve-notification',
+        scope: this,
+        handler: function() {
+            var accountId = this.ctxNode.attributes.account_id;
+            var account = this.accountStore.getById(accountId);
+
+            if (account.get('type') == 'system') {
+                var popupWindow = Tine.Felamimail.sieve.NotificationDialog.openWindow({
+                    record: account
+                });
+            }
+        }
+    };
+
     var markFolderSeenAction = {
         text: this.app.i18n._('Mark Folder as read'),
         iconCls: 'action_mark_read',
@@ -252,7 +268,15 @@ Tine.Felamimail.setTreeContextMenus = function() {
     // account ctx menu
     this.contextMenuAccount = Tine.widgets.tree.ContextMenu.getMenu({
         nodeName: this.app.i18n.n_('Account', 'Accounts', 1),
-        actions: [addFolderToRootAction, updateFolderCacheAction, editVacationAction, editRulesAction, editAccountAction, 'delete'],
+        actions: [
+            addFolderToRootAction,
+            updateFolderCacheAction,
+            editVacationAction,
+            editRulesAction,
+            editNotificationAction,
+            editAccountAction,
+            'delete'
+        ],
         scope: this,
         backend: 'Felamimail',
         backendModel: 'Account'
diff --git a/tine20/Felamimail/js/sieve/NotificationDialog.js b/tine20/Felamimail/js/sieve/NotificationDialog.js
new file mode 100644 (file)
index 0000000..b6d86b8
--- /dev/null
@@ -0,0 +1,109 @@
+/*
+ * Tine 2.0
+ * 
+ * @package     Felamimail
+ * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @author      Philipp Schüle <p.schuele@metaways.de>
+ * @copyright   Copyright (c) 2010-2017 Metaways Infosystems GmbH (http://www.metaways.de)
+ *
+ */
+Ext.namespace('Tine.Felamimail.sieve');
+
+ Tine.Felamimail.sieve.NotificationDialog = Ext.extend(Tine.widgets.dialog.EditDialog, {
+
+    /**
+     * @private
+     */
+    windowNamePrefix: 'NotificationWindow_',
+    appName: 'Felamimail',
+    recordClass: Tine.Felamimail.Model.Account,
+    recordProxy: Tine.Felamimail.accountBackend,
+    loadRecord: true,
+    tbarItems: [],
+    evalGrants: false,
+    readonlyReason: false,
+    
+    /**
+     * overwrite update toolbars function (we don't have record grants yet)
+     * 
+     * @private
+     */
+    updateToolbars: function() {
+    },
+    
+    /**
+     * executed after record got updated from proxy
+     * 
+     * @private
+     */
+    onRecordLoad: function() {
+        // interrupt process flow till dialog is rendered
+        if (! this.rendered) {
+            this.onRecordLoad.defer(250, this);
+            return;
+        }
+
+        this.getForm().loadRecord(this.record);
+
+        var title = String.format(this.app.i18n._('Notification for {0}'), this.record.get('name'));
+        this.window.setTitle(title);
+
+        this.loadMask.hide();
+    },
+        
+    /**
+     * returns dialog
+     * 
+     * NOTE: when this method gets called, all initalisation is done.
+     * 
+     * @return {Object}
+     * @private
+     * 
+     */
+    getFormItems: function() {
+
+        return {
+            xtype: 'tabpanel',
+            deferredRender: false,
+            border: false,
+            activeTab: 0,
+            items: [{
+                title: this.app.i18n._('Notifications'),
+                autoScroll: true,
+                border: false,
+                frame: true,
+                xtype: 'columnform',
+                formDefaults: {
+                    xtype: 'textfield',
+                    anchor: '100%',
+                    labelSeparator: '',
+                    columnWidth: 1,
+                },
+                items: [[{
+                    maxLength: 256,
+                    fieldLabel: this.app.i18n._('Notification Email'),
+                    name: 'sieve_notification_email',
+                    vtype: 'email'
+                }]]
+            }]
+        };
+    }
+});
+
+/**
+ * Felamimail Edit Popup
+ * 
+ * @param   {Object} config
+ * @return  {Ext.ux.Window}
+ */
+Tine.Felamimail.sieve.NotificationDialog.openWindow = function (config) {
+    var window = Tine.WindowFactory.getWindow({
+        width: 320,
+        height: 200,
+        name: Tine.Felamimail.sieve.NotificationDialog.prototype.windowNamePrefix + Ext.id(),
+        contentPanelConstructor: 'Tine.Felamimail.sieve.NotificationDialog',
+        contentPanelConstructorConfig: config
+    });
+    return window;
+};