Added a simple SMTP test
authorAlexander Ebert <ebert@woltlab.com>
Thu, 20 Jul 2017 17:23:22 +0000 (19:23 +0200)
committerAlexander Ebert <ebert@woltlab.com>
Thu, 20 Jul 2017 17:23:22 +0000 (19:23 +0200)
Closes #2342

wcfsetup/install/files/acp/templates/__optionEmailSmtpTest.tpl [new file with mode: 0644]
wcfsetup/install/files/acp/templates/option.tpl
wcfsetup/install/files/js/WoltLabSuite/Core/Acp/Ui/Option/EmailSmtpTest.js [new file with mode: 0644]
wcfsetup/install/files/js/WoltLabSuite/Core/Ajax.js
wcfsetup/install/files/lib/data/option/OptionAction.class.php
wcfsetup/install/files/lib/system/email/transport/SmtpEmailTransport.class.php
wcfsetup/install/lang/de.xml
wcfsetup/install/lang/en.xml

diff --git a/wcfsetup/install/files/acp/templates/__optionEmailSmtpTest.tpl b/wcfsetup/install/files/acp/templates/__optionEmailSmtpTest.tpl
new file mode 100644 (file)
index 0000000..6a22dc1
--- /dev/null
@@ -0,0 +1,17 @@
+{if $category->categoryName === 'general'}
+       <script data-relocate="true">
+               require(['Language', 'WoltLabSuite/Core/Acp/Ui/Option/EmailSmtpTest'], function (Language, AcpUiOptionEmailSmtpTest) {
+                       Language.addObject({
+                               'wcf.acp.email.smtp.test': '{lang}wcf.acp.email.smtp.test{/lang}',
+                               'wcf.acp.email.smtp.test.description': '{lang}wcf.acp.email.smtp.test.description{/lang}',
+                               'wcf.acp.email.smtp.test.error.empty.host': '{lang}wcf.acp.email.smtp.test.error.empty.host{/lang}',
+                               'wcf.acp.email.smtp.test.error.empty.password': '{lang}wcf.acp.email.smtp.test.error.empty.password{/lang}',
+                               'wcf.acp.email.smtp.test.error.empty.user': '{lang}wcf.acp.email.smtp.test.error.empty.user{/lang}',
+                               'wcf.acp.email.smtp.test.run': '{lang}wcf.acp.email.smtp.test.run{/lang}',
+                               'wcf.acp.email.smtp.test.run.success': '{lang}wcf.acp.email.smtp.test.run.success{/lang}'
+                       });
+                       
+                       AcpUiOptionEmailSmtpTest.init();
+               });
+       </script>
+{/if}
index d8bd8941b28140c9b0e2855c2b0db97c534e9612..c0dc383395f9b716856c55955020cc131a9020dc 100644 (file)
@@ -95,6 +95,7 @@
        </div>
 </form>
 
+{include file='__optionEmailSmtpTest'}
 {include file='__optionRewriteTest'}
 
 {include file='footer'}
diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Acp/Ui/Option/EmailSmtpTest.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Acp/Ui/Option/EmailSmtpTest.js
new file mode 100644 (file)
index 0000000..22dc160
--- /dev/null
@@ -0,0 +1,116 @@
+define(['Ajax', 'Core', 'Language'], function(Ajax, Core, Language) {
+       "use strict";
+       
+       var _buttonRunTest = null;
+       var _container = null;
+       
+       return {
+               init: function () {
+                       var smtpCheckbox = null;
+                       elBySelAll('input[name="values[mail_send_method]"]', undefined, (function (radioCheckbox) {
+                               radioCheckbox.addEventListener('change', this._onChange.bind(this));
+                               
+                               if (radioCheckbox.value === 'smtp') smtpCheckbox = radioCheckbox;
+                       }).bind(this));
+                       
+                       Core.triggerEvent(smtpCheckbox, 'change');
+               },
+               
+               _onChange: function (event) {
+                       var checkbox = event.currentTarget;
+                       
+                       if (checkbox.value === 'smtp' && checkbox.checked) {
+                               if (_container === null) this._initUi(checkbox);
+                               
+                               elShow(_container);
+                       }
+                       else if (_container !== null) {
+                               elHide(_container);
+                       }
+               },
+               
+               _initUi: function (checkbox) {
+                       var html = '<dt>' + Language.get('wcf.acp.email.smtp.test') + '</dt>';
+                       html += '<dd>';
+                       html += '<button>' + Language.get('wcf.acp.email.smtp.test.run') + '</button>';
+                       html += '<small>' + Language.get('wcf.acp.email.smtp.test.description') + '</small>';
+                       html += '</dd>';
+                       
+                       _container = elCreate('dl');
+                       _container.innerHTML = html;
+                       
+                       _buttonRunTest = elBySel('button', _container);
+                       _buttonRunTest.addEventListener(WCF_CLICK_EVENT, this._onClick.bind(this));
+                       
+                       var insertAfter = checkbox.closest('dl');
+                       insertAfter.parentNode.insertBefore(_container, insertAfter.nextSibling);
+               },
+               
+               _onClick: function (event) {
+                       event.preventDefault();
+                       
+                       _buttonRunTest.disabled = true;
+                       _buttonRunTest.innerHTML = '<span class="icon icon16 fa-spinner"></span> ' + Language.get('wcf.global.loading');
+                       
+                       var innerError = _buttonRunTest.nextElementSibling;
+                       if (innerError && innerError.classList.contains('innerError')) elRemove(innerError);
+                       
+                       window.setTimeout((function () {
+                               Ajax.api(this, {
+                                       parameters: {
+                                               host: elById('mail_smtp_host').value,
+                                               port: elById('mail_smtp_port').value,
+                                               startTls: elBySel('input[name="values[mail_smtp_starttls]"]:checked').value,
+                                               user: elById('mail_smtp_user').value,
+                                               password: elById('mail_smtp_password').value
+                                       }
+                               });
+                       }).bind(this), 100);
+               },
+               
+               _ajaxSuccess: function (data) {
+                       var result = data.returnValues.validationResult;
+                       if (result === '') {
+                               this._resetButton(true);
+                       }
+                       else {
+                               this._resetButton(false, result);
+                       }
+               },
+               
+               _ajaxFailure: function (data) {
+                       var result = '';
+                       if (data && data.returnValues && data.returnValues.fieldName) {
+                               result = Language.get('wcf.acp.email.smtp.test.error.empty.' + data.returnValues.fieldName);
+                       }
+                       
+                       this._resetButton(false, result);
+                       
+                       return (result === '');
+               },
+               
+               _resetButton: function (success, errorMessage) {
+                       _buttonRunTest.disabled = false;
+                       
+                       if (success) _buttonRunTest.innerHTML = '<span class="icon icon16 fa-check green"></span> ' + Language.get('wcf.acp.email.smtp.test.run.success');
+                       else _buttonRunTest.innerHTML = Language.get('wcf.acp.email.smtp.test.run');
+                       
+                       if (errorMessage) {
+                               var innerError = elCreate('small');
+                               innerError.className = 'innerError';
+                               innerError.textContent = errorMessage;
+                               _buttonRunTest.parentNode.insertBefore(innerError, _buttonRunTest.nextElementSibling);
+                       }
+               },
+               
+               _ajaxSetup: function () {
+                       return {
+                               data: {
+                                       actionName: 'emailSmtpTest',
+                                       className: 'wcf\\data\\option\\OptionAction'
+                               },
+                               silent: true
+                       };
+               }
+       };
+});
index 15a4947e9b169c6eff3b8722d348c3076e68567c..2f3b45f2e68f399b8809a69c8f507282ad333360 100644 (file)
@@ -26,6 +26,9 @@ define(['AjaxRequest', 'Core', 'ObjectMap'], function(AjaxRequest, Core, ObjectM
                 * @return      {AjaxRequest}
                 */
                api: function(callbackObject, data, success, failure) {
+                       // Fetch AjaxRequest, as it cannot be provided because of a circular dependency
+                       if (AjaxRequest === undefined) AjaxRequest = require('AjaxRequest');
+                       
                        if (typeof data !== 'object') data = {};
                        
                        var request = _requests.get(callbackObject);
index f3de428e98ee8805cb2fee18a0dda15f0e823cbb..2f00dc81bd9ad51ce09c635100b83aec66e04d78 100644 (file)
@@ -1,6 +1,9 @@
 <?php
 namespace wcf\data\option;
 use wcf\data\AbstractDatabaseObjectAction;
+use wcf\system\email\transport\SmtpEmailTransport;
+use wcf\system\exception\UserInputException;
+use wcf\system\WCF;
 
 /**
  * Executes option-related actions.
@@ -8,7 +11,8 @@ use wcf\data\AbstractDatabaseObjectAction;
  * @author     Alexander Ebert
  * @copyright  2001-2017 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @package    WoltLabSuite\Core\Data$2 * 
+ * @package    WoltLabSuite\Core\Data\Option
+ *  
  * @method     Option          create()
  * @method     OptionEditor[]  getObjects()
  * @method     OptionEditor    getSingleObject()
@@ -37,7 +41,7 @@ class OptionAction extends AbstractDatabaseObjectAction {
        /**
         * @inheritDoc
         */
-       protected $requireACP = ['create', 'delete', 'import', 'update', 'updateAll'];
+       protected $requireACP = ['create', 'delete', 'emailSmtpTest', 'import', 'update', 'updateAll'];
        
        /**
         * Validates permissions and parameters.
@@ -68,4 +72,43 @@ class OptionAction extends AbstractDatabaseObjectAction {
                // create data
                call_user_func([$this->className, 'updateAll'], $this->parameters['data']);
        }
+       
+       /**
+        * Validates the basic SMTP connection parameters.
+        * 
+        * @throws      UserInputException
+        */
+       public function validateEmailSmtpTest() {
+               WCF::getSession()->checkPermissions($this->permissionsUpdate);
+               
+               $this->readString('host');
+               $this->readInteger('port');
+               $this->readString('startTls');
+               
+               $this->readString('user', true);
+               $this->readString('password', true);
+               if (!empty($this->parameters['user']) && empty($this->parameters['password'])) {
+                       throw new UserInputException('password');
+               }
+               else if (empty($this->parameters['user']) && !empty($this->parameters['password'])) {
+                       throw new UserInputException('user');
+               }
+       }
+       
+       /**
+        * Runs a simple test of the SMTP connection.
+        * 
+        * @return      string[]
+        */
+       public function emailSmtpTest() {
+               $smtp = new SmtpEmailTransport(
+                       $this->parameters['host'],
+                       $this->parameters['port'],
+                       $this->parameters['user'],
+                       $this->parameters['password'],
+                       $this->parameters['startTls']
+               );
+               
+               return ['validationResult' => $smtp->testConnection()];
+       }
 }
index e19c2964b98821a2122387879fd1e516451fb0f2..717524a514080d36a44cabc56596d42ba5c72591 100644 (file)
@@ -6,6 +6,7 @@ use wcf\system\email\Email;
 use wcf\system\email\Mailbox;
 use wcf\system\exception\SystemException;
 use wcf\system\io\RemoteFile;
+use wcf\system\WCF;
 use wcf\util\StringUtil;
 
 /**
@@ -107,6 +108,51 @@ class SmtpEmailTransport implements IEmailTransport {
                $this->disconnect();
        }
        
+       /**
+        * Tests the connection by establishing a connection and optionally
+        * providing user credentials. Returns the error message or an empty
+        * string on success.
+        * 
+        * @return      string
+        */
+       public function testConnection() {
+               try {
+                       $this->connect(10);
+                       $this->auth();
+               }
+               catch (SystemException $e) {
+                       if (strpos($e->getMessage(), 'Can not connect to') === 0) {
+                               return WCF::getLanguage()->get('wcf.acp.email.smtp.test.error.hostUnknown');
+                       }
+                       
+                       return $e->getMessage();
+               }
+               catch (PermanentFailure $e) {
+                       if (strpos($e->getMessage(), 'Remote SMTP server does not support EHLO') === 0) {
+                               return WCF::getLanguage()->get('wcf.acp.email.smtp.test.error.notTlsSupport');
+                       }
+                       else if (strpos($e->getMessage(), 'Remote SMTP server does not advertise STARTTLS') === 0) {
+                               return WCF::getLanguage()->get('wcf.acp.email.smtp.test.error.notTlsSupport');
+                       }
+                       else if (strpos($e->getMessage(), "Remote SMTP server reported permanent error code: 535 (") === 0) {
+                               return WCF::getLanguage()->get('wcf.acp.email.smtp.test.error.badAuth');
+                       }
+                       
+                       return $e->getMessage();
+               }
+               catch (TransientFailure $e) {
+                       if (strpos($e->getMessage(), 'Enabling TLS failed') === 0) {
+                               return WCF::getLanguage()->get('wcf.acp.email.smtp.test.error.tlsFailed');
+                       }
+                       
+                       return $e->getMessage();
+               }
+               
+               $this->disconnect();
+               
+               return '';
+       }
+       
        /**
         * Reads a server reply and validates it against the given expected status codes.
         * Returns a tuple [ status code, reply text ].
@@ -177,10 +223,13 @@ class SmtpEmailTransport implements IEmailTransport {
         * Connects to the server and enables STARTTLS if available. Bails
         * out if STARTTLS is not available and connection is set to 'encrypt'.
         * 
+        * @param       integer         $overrideTimeout
         * @throws      PermanentFailure
         */
-       protected function connect() {
-               $this->connection = new RemoteFile($this->host, $this->port);
+       protected function connect($overrideTimeout = null) {
+               if ($overrideTimeout === null) $this->connection = new RemoteFile($this->host, $this->port);
+               else $this->connection = new RemoteFile($this->host, $this->port, $overrideTimeout);
+               
                $this->read([220]);
                
                try {
index aa23d4a6b839e694ea301d6c1899028416ee7a85..f3feabce91467920e6f76aa75b7e486e7785b90f 100644 (file)
                <item name="wcf.acp.devtools.sync.syncAll"><![CDATA[Alles Abgleichen]]></item>
        </category>
        
+       <category name="wcf.acp.email">
+               <item name="wcf.acp.email.smtp.test"><![CDATA[SMTP-Verbindungstest]]></item>
+               <item name="wcf.acp.email.smtp.test.description"><![CDATA[Testet die eingegebenen Verbindungsinformationen und Zugangsdaten, ob eine Anmeldung beim SMTP-Server möglich ist. Es wird keine E-Mail verschickt!<br><br><strong>Hinweis:</strong> Es handelt sich hierbei nur um einen sehr oberflächlichen Test. Ein erfolgreicher Versand von E-Mails kann auch beim Bestehen dieses Tests nicht abschließend garantiert werden.]]></item>
+               <item name="wcf.acp.email.smtp.test.run"><![CDATA[SMTP-Verbindung testen]]></item>
+               <item name="wcf.acp.email.smtp.test.run.success"><![CDATA[Verbindungstest erfolgreich]]></item>
+               <item name="wcf.acp.email.smtp.test.error.badAuth"><![CDATA[Benutzername und/oder Passwort wurde vom Server abgelehnt.]]></item>
+               <item name="wcf.acp.email.smtp.test.error.empty.host"><![CDATA[Kein SMTP-Server angegeben.]]></item>
+               <item name="wcf.acp.email.smtp.test.error.empty.password"><![CDATA[Das Passwort fehlt.]]></item>
+               <item name="wcf.acp.email.smtp.test.error.empty.user"><![CDATA[Der Benutzername fehlt.]]></item>
+               <item name="wcf.acp.email.smtp.test.error.hostUnknown"><![CDATA[Der Server antwortet nicht.]]></item>
+               <item name="wcf.acp.email.smtp.test.error.notTlsSupport"><![CDATA[Der Server unterstützt keine Verschlüsselung.]]></item>
+               <item name="wcf.acp.email.smtp.test.error.tlsFailed"><![CDATA[Der Aufbau einer verschlüselten Verbindung war nicht möglich.]]></item>
+       </category>
+       
        <category name="wcf.acp.exceptionLog">
                <item name="wcf.acp.exceptionLog"><![CDATA[Protokollierte Fehler]]></item>
                <item name="wcf.acp.exceptionLog.exception.message"><![CDATA[Fehlermeldung]]></item>
index 09a1bcb1bbbb64634d2595608086064b9ffed96f..212d3b4b20d2bbdaf0a6bb8cd8c347f7a1869be8 100644 (file)
                <item name="wcf.acp.devtools.sync.syncAll"><![CDATA[Sync All]]></item>
        </category>
        
+       <category name="wcf.acp.email">
+               <item name="wcf.acp.email.smtp.test"><![CDATA[SMTP Connection Test]]></item>
+               <item name="wcf.acp.email.smtp.test.description"><![CDATA[Validates the connection data and user credentials, for their basic validity. No email is sent during this process!<br><br><strong>Notice:</strong> This is only a very basic test. Passing it does not guarantee emails to be successfully delivered!]]></item>
+               <item name="wcf.acp.email.smtp.test.run"><![CDATA[Run SMTP Connection Test]]></item>
+               <item name="wcf.acp.email.smtp.test.run.success"><![CDATA[Test Passed]]></item>
+               <item name="wcf.acp.email.smtp.test.error.badAuth"><![CDATA[Username and/or password were rejected by the server.]]></item>
+               <item name="wcf.acp.email.smtp.test.error.empty.host"><![CDATA[Please enter a SMTP server.]]></item>
+               <item name="wcf.acp.email.smtp.test.error.empty.password"><![CDATA[Please enter a password.]]></item>
+               <item name="wcf.acp.email.smtp.test.error.empty.user"><![CDATA[Please enter a username.]]></item>
+               <item name="wcf.acp.email.smtp.test.error.hostUnknown"><![CDATA[The server is not responding.]]></item>
+               <item name="wcf.acp.email.smtp.test.error.notTlsSupport"><![CDATA[The server does not support encryption.]]></item>
+               <item name="wcf.acp.email.smtp.test.error.tlsFailed"><![CDATA[Unable to establish a secure connection.]]></item>
+       </category>
+       
        <category name="wcf.acp.exceptionLog">
                <item name="wcf.acp.exceptionLog"><![CDATA[Logged errors]]></item>
                <item name="wcf.acp.exceptionLog.exception.message"><![CDATA[Error Message]]></item>