Add EmailLogListPage
authorTim Düsterhus <duesterhus@woltlab.com>
Mon, 15 Feb 2021 13:21:27 +0000 (14:21 +0100)
committerTim Düsterhus <duesterhus@woltlab.com>
Thu, 18 Feb 2021 15:25:20 +0000 (16:25 +0100)
com.woltlab.wcf/acpMenu.xml
wcfsetup/install/files/acp/templates/emailLogList.tpl [new file with mode: 0644]
wcfsetup/install/files/lib/acp/page/EmailLogListPage.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/email/log/entry/EmailLogEntry.class.php
wcfsetup/install/lang/de.xml
wcfsetup/install/lang/en.xml

index 1781702140c1b8b7081283f2288d90627712862d..79bccab4278e27157c577bb9a43794b871023faa 100644 (file)
                        <parent>wcf.acp.menu.link.log</parent>
                        <permissions>admin.management.canManageCronjob</permissions>
                </acpmenuitem>
+               <acpmenuitem name="wcf.acp.menu.link.log.email">
+                       <controller>wcf\acp\page\EmailLogListPage</controller>
+                       <parent>wcf.acp.menu.link.log</parent>
+                       <permissions>admin.management.canViewLog</permissions>
+               </acpmenuitem>
                <acpmenuitem name="wcf.acp.menu.link.log.exception">
                        <controller>wcf\acp\page\ExceptionLogViewPage</controller>
                        <parent>wcf.acp.menu.link.log</parent>
diff --git a/wcfsetup/install/files/acp/templates/emailLogList.tpl b/wcfsetup/install/files/acp/templates/emailLogList.tpl
new file mode 100644 (file)
index 0000000..51cc014
--- /dev/null
@@ -0,0 +1,103 @@
+{include file='header' pageTitle='wcf.acp.email.log'}
+
+<header class="contentHeader">
+       <div class="contentHeaderTitle">
+               <h1 class="contentTitle">{lang}wcf.acp.email.log{/lang}{if $items} <span class="badge badgeInverse">{#$items}</span>{/if}</h1>
+       </div>
+       
+       {hascontent}
+               <nav class="contentHeaderNavigation">
+                       <ul>
+                               {content}
+                                       {event name='contentHeaderNavigation'}
+                               {/content}
+                       </ul>
+               </nav>
+       {/hascontent}
+</header>
+
+{hascontent}
+       <div class="paginationTop">
+               {content}{pages print=true assign=pagesLinks controller="EmailLogList" link="pageNo=%d&sortField=$sortField&sortOrder=$sortOrder"}{/content}
+       </div>
+{/hascontent}
+
+{if $objects|count}
+       <div class="section tabularBox">
+               <table class="table">
+                       <thead>
+                               <tr>
+                                       <th class="columnID columnEntryID{if $sortField == 'entryID'} active {@$sortOrder}{/if}"><a href="{link controller='EmailLogList'}pageNo={@$pageNo}&sortField=entryID&sortOrder={if $sortField == 'entryID' && $sortOrder == 'ASC'}DESC{else}ASC{/if}{/link}">{lang}wcf.global.objectID{/lang}</a></th>
+                                       <th class="columnTitle columnMessageID{if $sortField == 'messageID'} active {@$sortOrder}{/if}">{lang}wcf.acp.email.log.messageID{/lang}</th>
+                                       <th class="columnText columnRecipient{if $sortField == 'recipient'} active {@$sortOrder}{/if}">{lang}wcf.user.email{/lang}</th>
+                                       <th class="columnDate columnTime{if $sortField == 'time'} active {@$sortOrder}{/if}"><a href="{link controller='EmailLogList'}pageNo={@$pageNo}&sortField=time&sortOrder={if $sortField == 'execTime' && $sortOrder == 'ASC'}DESC{else}ASC{/if}{/link}">{lang}wcf.acp.email.log.time{/lang}</a></th>
+                                       <th class="columnText columnStatusMessage{if $sortField == 'status'} active {@$sortOrder}{/if}"><a href="{link controller='EmailLogList'}pageNo={@$pageNo}&sortField=status&sortOrder={if $sortField == 'success' && $sortOrder == 'ASC'}DESC{else}ASC{/if}{/link}">{lang}wcf.acp.email.log.status{/lang}</a></th>
+                                       
+                                       {event name='columnHeads'}
+                               </tr>
+                       </thead>
+                       
+                       <tbody>
+                               {foreach from=$objects item=entry}
+                                       <tr class="jsEmailLogEntry">
+                                               <td class="columnID columnEntryID">{@$entry->entryID}</td>
+                                               <td class="columnTitle columnMessageID">
+                                                       <kbd title="{$entry->messageID}">{$entry->getFormattedMessageId()|truncate:50}</kbd>
+                                               </td>
+                                               <td class="columnText columnRecipient">
+                                                       {if $__wcf->session->getPermission('admin.user.canEditMailAddress')}
+                                                               {$entry->recipient}
+                                                       {else}
+                                                               {$entry->getRedactedRecipientAddress()}
+                                                       {/if}
+                                                       {if $entry->getRecipient()}
+                                                               (<a href="{link controller='UserEdit' id=$entry->getRecipient()->getObjectID()}{/link}">{$entry->getRecipient()->getTitle()}</a>)
+                                                       {/if}
+                                               </td>
+                                               <td class="columnDate columnTime">{@$entry->time|time}</td>
+                                               
+                                               <td class="columnText columnStatusMessage">
+                                                       <span class="
+                                                               badge
+                                                               {if $entry->status === 'success'}green
+                                                               {elseif $entry->status === 'transient_failure'}yellow
+                                                               {elseif $entry->status === 'permanent_failure'}red
+                                                               {/if}
+                                                               {if $entry->message}pointer jsStaticDialog{/if}
+                                                       "{if $entry->message} data-dialog-id="statusMessage{$entry->entryID}"{/if}>{lang}wcf.acp.email.log.status.{$entry->status}{/lang}</span>
+                                                       {if $entry->message}
+                                                               <div id="statusMessage{$entry->entryID}" data-title="{lang}wcf.acp.email.log.statusMessage.title{/lang}" style="display: none">
+                                                                       {$entry->message}
+                                                               </div>
+                                                       {/if}
+                                               </td>
+                                               
+                                               {event name='columns'}
+                                       </tr>
+                               {/foreach}
+                       </tbody>
+               </table>
+       </div>
+       
+       <footer class="contentFooter">
+               {hascontent}
+                       <div class="paginationBottom">
+                               {content}{@$pagesLinks}{/content}
+                       </div>
+               {/hascontent}
+               
+               {hascontent}
+                       <nav class="contentFooterNavigation">
+                               <ul>
+                                       {content}
+                                               {event name='contentFooterNavigation'}
+                                       {/content}
+                               </ul>
+                       </nav>
+               {/hascontent}
+       </footer>
+{else}
+       <p class="info" role="status">{lang}wcf.global.noItems{/lang}</p>
+{/if}
+
+{include file='footer'}
diff --git a/wcfsetup/install/files/lib/acp/page/EmailLogListPage.class.php b/wcfsetup/install/files/lib/acp/page/EmailLogListPage.class.php
new file mode 100644 (file)
index 0000000..0f710c2
--- /dev/null
@@ -0,0 +1,66 @@
+<?php
+
+namespace wcf\acp\page;
+
+use wcf\data\email\log\entry\EmailLogEntryList;
+use wcf\page\SortablePage;
+use wcf\system\cache\runtime\UserRuntimeCache;
+
+/**
+ * Shows email logs.
+ *
+ * @author  Tim Duesterhus
+ * @copyright   2001-2021 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package WoltLabSuite\Core\Acp\Page
+ *
+ * @property    EmailLogEntryList $objectList
+ */
+class EmailLogListPage extends SortablePage
+{
+    /**
+     * @inheritDoc
+     */
+    public $activeMenuItem = 'wcf.acp.menu.link.log.email';
+
+    /**
+     * @inheritDoc
+     */
+    public $neededPermissions = ['admin.management.canViewLog'];
+
+    /**
+     * @inheritDoc
+     */
+    public $itemsPerPage = 100;
+
+    /**
+     * @inheritDoc
+     */
+    public $defaultSortField = 'time';
+
+    /**
+     * @inheritDoc
+     */
+    public $defaultSortOrder = 'DESC';
+
+    /**
+     * @inheritDoc
+     */
+    public $validSortFields = ['entryID', 'time', 'status'];
+
+    /**
+     * @inheritDoc
+     */
+    public $objectListClassName = EmailLogEntryList::class;
+
+    /**
+     * @inheritDoc
+     */
+    public function readData()
+    {
+        parent::readData();
+
+        $userIDs = \array_filter(\array_column($this->objectList->getObjects(), 'recipientID'));
+        UserRuntimeCache::getInstance()->cacheObjectIDs($userIDs);
+    }
+}
index e723646f0e5674d81b8264d7059732710b1909da..6d37ee37d01e74b703119da2e0699f67c2ae5761 100644 (file)
@@ -3,6 +3,9 @@
 namespace wcf\data\email\log\entry;
 
 use wcf\data\DatabaseObject;
+use wcf\data\user\User;
+use wcf\system\cache\runtime\UserRuntimeCache;
+use wcf\system\email\Email;
 
 /**
  * Represents an email log entry.
@@ -30,4 +33,48 @@ class EmailLogEntry extends DatabaseObject
     public const STATUS_TRANSIENT_FAILURE = 'transient_failure';
 
     public const STATUS_PERMANENT_FAILURE = 'permanent_failure';
+
+    /**
+     * Returns the formatted 'Message-ID', stripping useless information.
+     */
+    public function getFormattedMessageId(): string
+    {
+        return \preg_replace_callback(
+            '/^\<((.*)@(.*))\>$/',
+            static function ($matches) {
+                if ($matches[3] === Email::getHost()) {
+                    return $matches[2] . '@';
+                } else {
+                    return $matches[1];
+                }
+            },
+            $this->messageID
+        );
+    }
+
+    /**
+     * Returns the recipient.
+     *
+     * @see EmailLogEntry::$recipient
+     */
+    public function getRecipient(): ?User
+    {
+        if (!$this->recipientID) {
+            return null;
+        }
+
+        return UserRuntimeCache::getInstance()->getObject($this->recipientID);
+    }
+
+    /**
+     * Returns the redacted recipient address.
+     */
+    public function getRedactedRecipientAddress(): string
+    {
+        $atSign = \strrpos($this->recipient, '@');
+        $localpart = \substr($this->recipient, 0, $atSign);
+        $domain = \substr($this->recipient, $atSign + 1);
+
+        return \substr($localpart, 0, 1) . "\u{2022}\u{2022}\u{2022}\u{2022}@{$domain}";
+    }
 }
index 97736d4b0da703e19bb73c95c433480839a93190..6d3fb2a99bf234f6f08ec099a5460b0a0520b0b7 100644 (file)
                <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üsselten Verbindung war nicht möglich.]]></item>
+               <item name="wcf.acp.email.log"><![CDATA[Versendete E-Mails]]></item>
+               <item name="wcf.acp.email.log.messageID"><![CDATA[Message-ID]]></item>
+               <item name="wcf.acp.email.log.time"><![CDATA[Erzeugt]]></item>
+               <item name="wcf.acp.email.log.status"><![CDATA[Status]]></item>
+               <item name="wcf.acp.email.log.status.success"><![CDATA[Erfolgreich versendet]]></item>
+               <item name="wcf.acp.email.log.status.transient_failure"><![CDATA[Vorübergehendes Problem]]></item>
+               <item name="wcf.acp.email.log.status.permanent_failure"><![CDATA[Endgültig fehlgeschlagen]]></item>
+               <item name="wcf.acp.email.log.status.new"><![CDATA[Wartend]]></item>
+               <item name="wcf.acp.email.log.statusMessage.title"><![CDATA[Status-Nachricht]]></item>
        </category>
        <category name="wcf.acp.exceptionLog">
                <item name="wcf.acp.exceptionLog"><![CDATA[Protokollierte Fehler]]></item>
@@ -1212,6 +1221,7 @@ ACHTUNG: Die oben genannten Meldungen sind stark gekürzt. Sie können Details z
                <item name="wcf.acp.menu.link.language.item.add"><![CDATA[Text hinzufügen]]></item>
                <item name="wcf.acp.menu.link.systemCheck"><![CDATA[Systemüberprüfung]]></item>
                <item name="wcf.acp.menu.link.devtools.missingLanguageItem.list"><![CDATA[Fehlende Texte]]></item>
+               <item name="wcf.acp.menu.link.log.email"><![CDATA[E-Mails]]></item>
        </category>
        <category name="wcf.acp.modificationLog">
                <item name="wcf.acp.modificationLog.list"><![CDATA[Globales Änderungsprotokoll]]></item>
index a0b4ab333ddaecc19e7e4f1647a21d968c64a1ac..ae9b8b19beef15f4526e0ea439fcc909457eb10a 100644 (file)
                <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>
+               <item name="wcf.acp.email.log"><![CDATA[Emails Sent]]></item>
+               <item name="wcf.acp.email.log.messageID"><![CDATA[Message-ID]]></item>
+               <item name="wcf.acp.email.log.time"><![CDATA[Created]]></item>
+               <item name="wcf.acp.email.log.status"><![CDATA[Status]]></item>
+               <item name="wcf.acp.email.log.status.success"><![CDATA[Successfully Sent]]></item>
+               <item name="wcf.acp.email.log.status.transient_failure"><![CDATA[Transient Failure]]></item>
+               <item name="wcf.acp.email.log.status.permanent_failure"><![CDATA[Ultimately Failed]]></item>
+               <item name="wcf.acp.email.log.status.new"><![CDATA[Pending]]></item>
+               <item name="wcf.acp.email.log.statusMessage.title"><![CDATA[Status Message]]></item>
        </category>
        <category name="wcf.acp.exceptionLog">
                <item name="wcf.acp.exceptionLog"><![CDATA[Logged errors]]></item>
@@ -1189,6 +1198,7 @@ ATTENTION: The messages listed above are greatly shortened. You can view details
                <item name="wcf.acp.menu.link.language.item.add"><![CDATA[Add Phrase]]></item>
                <item name="wcf.acp.menu.link.systemCheck"><![CDATA[System Check]]></item>
                <item name="wcf.acp.menu.link.devtools.missingLanguageItem.list"><![CDATA[Missing Phrases]]></item>
+               <item name="wcf.acp.menu.link.log.email"><![CDATA[Emails]]></item>
        </category>
        <category name="wcf.acp.modificationLog">
                <item name="wcf.acp.modificationLog.list"><![CDATA[Global Modification Log]]></item>