<permissions>admin.configuration.package.canInstallPackage</permissions>
<icon>fa-plus</icon>
</acpmenuitem>
+ <acpmenuitem name="wcf.acp.menu.link.devtools.missingLanguageItem.list">
+ <controller>wcf\acp\page\DevtoolsMissingLanguageItemListPage</controller>
+ <parent>wcf.acp.menu.link.devtools</parent>
+ <permissions>admin.configuration.package.canInstallPackage</permissions>
+ </acpmenuitem>
<acpmenuitem name="wcf.acp.menu.link.devtools.notificationTest">
<controller>wcf\acp\page\DevtoolsNotificationTestPage</controller>
<parent>wcf.acp.menu.link.devtools</parent>
--- /dev/null
+<span class="icon icon16 fa-align-justify jsTooltip pointer jsOutputFormatToggle" title="{lang}wcf.acp.devtools.missingLanguageItem.stackTrace.toggleOutputFormat{/lang}" style="margin-bottom: 10px"></span>
+
+<pre>{*
+ *}{foreach from=$stackTrace item=stackEntry name=stackEntries}{*
+ *}#{$tpl[foreach][stackEntries][iteration]} {$stackEntry[file]} ({$stackEntry[line]}):
+{* *} {$stackEntry['class']}{$stackEntry['type']}{$stackEntry['function']}({*
+ *}{foreach from=$stackEntry['args'] item=stackEntryArg name=stackEntryArgs}{*
+ *}{assign var='argType' value=$stackEntryArg|gettype}{*
+ *}{if $argType === 'integer' || $argType === 'double'}{*
+ *}{$stackEntryArg}{*
+ *}{elseif $argType === 'NULL'}{*
+ *}null{*
+ *}{elseif $argType === 'string'}{*
+ *}'{$stackEntryArg}'{*
+ *}{elseif $argType === 'boolean'}{*
+ *}{if $stackEntryArg}true{else}false{/if}{*
+ *}{elseif $argType === 'array'}{*
+ *}[{if $stackEntryArg|count > 5}{$stackEntryArg|count} items{else}{implode from=$stackEntryArg|array_keys item=stackEntryKey}{$stackEntryKey} => {/implode}{/if}]{*
+ *}{elseif $argType === 'object'}{*
+ *}{$item|get_class}{*
+ *}{elseif $argType === 'resource'}{*
+ *}resource({$item|get_resource_type}){*
+ *}{elseif $argType === 'resource (closed)'}{*
+ *}resource (closed){*
+ *}{/if}{if !$tpl[foreach][stackEntryArgs][last]},{/if}{*
+ *}{/foreach}{*
+ *})
+{* *}{/foreach}{*
+*}</pre>
--- /dev/null
+{include file='header' pageTitle='wcf.acp.menu.link.devtools.missingLanguageItem.list'}
+
+<header class="contentHeader">
+ <div class="contentHeaderTitle">
+ <h1 class="contentTitle">{lang}wcf.acp.menu.link.devtools.missingLanguageItem.list{/lang}{if $items} <span class="badge badgeInverse">{#$items}</span>{/if}</h1>
+ </div>
+
+ {hascontent}
+ <nav class="contentHeaderNavigation">
+ <ul>
+ {content}
+ {if $items}
+ <li><a href="#" id="clearMissingLanguageItemLog" class="button"><span class="icon icon16 fa-times"></span> <span>{lang}wcf.acp.devtools.missingLanguageItem.clearLog{/lang}</span></a></li>
+ {/if}
+
+ {event name='contentHeaderNavigation'}
+ {/content}
+ </ul>
+ </nav>
+ {/hascontent}
+</header>
+
+{hascontent}
+ <div class="paginationTop">
+ {content}
+ {pages print=true assign=pagesLinks controller='DevtoolsMissingLanguageItemList' link="pageNo=%d&sortField=$sortField&sortOrder=$sortOrder"}
+ {/content}
+ </div>
+{/hascontent}
+
+{if $items}
+ <div class="section tabularBox">
+ <table class="table">
+ <thead>
+ <tr>
+ <th class="columnID{if $sortField === 'itemID'} active {@$sortOrder}{/if}" colspan="2"><a href="{link controller='DevtoolsMissingLanguageItemList'}sortField=itemID&sortOrder={if $sortField === 'itemID' && $sortOrder === 'ASC'}DESC{else}ASC{/if}{/link}">{lang}wcf.global.objectID{/lang}</a></th>
+ <th class="columnText{if $sortField === 'languageID'} active {@$sortOrder}{/if}"><a href="{link controller='DevtoolsMissingLanguageItemList'}sortField=languageID&sortOrder={if $sortField === 'languageID' && $sortOrder === 'ASC'}DESC{else}ASC{/if}{/link}">{lang}wcf.acp.devtools.missingLanguageItem.languageID{/lang}</a></th>
+ <th class="columnText{if $sortField === 'languageItem'} active {@$sortOrder}{/if}"><a href="{link controller='DevtoolsMissingLanguageItemList'}sortField=languageItem&sortOrder={if $sortField === 'languageItem' && $sortOrder === 'ASC'}DESC{else}ASC{/if}{/link}">{lang}wcf.acp.devtools.missingLanguageItem.languageItem{/lang}</a></th>
+ <th class="columnText{if $sortField === 'lastTime'} active {@$sortOrder}{/if}"><a href="{link controller='DevtoolsMissingLanguageItemList'}sortField=lastTime&sortOrder={if $sortField === 'lastTime' && $sortOrder === 'ASC'}DESC{else}ASC{/if}{/link}">{lang}wcf.acp.devtools.missingLanguageItem.lastTime{/lang}</a></th>
+
+ {event name='columnHeads'}
+ </tr>
+ </thead>
+
+ <tbody>
+ {foreach from=$objects item=logEntry}
+ <tr class="jsObjectRow">
+ <td class="columnIcon">
+ <span class="icon icon16 fa-times jsDeleteButton jsTooltip pointer" title="{lang}wcf.global.button.delete{/lang}" data-object-id="{@$logEntry->getObjectID()}" data-confirm-message-html="{lang __encode=true}wcf.acp.devtools.missingLanguageItem.delete.confirmMessage{/lang}"></span>
+ <span class="icon icon16 fa-align-justify jsStackTraceButton jsTooltip pointer" title="{lang}wcf.acp.devtools.missingLanguageItem.showStackTrace{/lang}" data-stack-trace="{$logEntry->getStackTrace()}"></span>
+ </td>
+ <td class="columnID">{@$logEntry->getObjectID()}</td>
+ <td class="columnText">{if $logEntry->getLanguage()}{$logEntry->getLanguage()}{else}{$logEntry->languageID}{/if}</td>
+ <td class="columnText">{$logEntry->languageItem}</td>
+ <td class="columnDate">{@$logEntry->lastTime|time}</td>
+ </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>
+
+ <script data-relocate="true">
+ require(['Ajax', 'Ui/Confirmation', 'Ui/Dialog'], function(Ajax, UiConfirmation, UiDialog) {
+ new WCF.Action.Delete('wcf\\data\\devtools\\missing\\language\\item\\DevtoolsMissingLanguageItemAction', '.jsObjectRow');
+
+ elBySelAll('.jsStackTraceButton', undefined, function(button) {
+ button.addEventListener('click', function(event) {
+ var dialog = UiDialog.openStatic(
+ 'logEntryStackTrace',
+ elData(event.currentTarget, 'stack-trace'),
+ {
+ title: '{lang}wcf.acp.devtools.missingLanguageItem.stackTrace{/lang}',
+ }
+ );
+
+ elBySel('.jsOutputFormatToggle', dialog.dialog).addEventListener('click', function(event) {
+ var pre = event.currentTarget.nextElementSibling;
+ if (pre.style.whiteSpace) {
+ pre.style.whiteSpace = '';
+ }
+ else {
+ pre.style.whiteSpace = 'pre-wrap';
+ }
+ });
+ });
+ });
+
+ elById('clearMissingLanguageItemLog').addEventListener('click', function() {
+ UiConfirmation.show({
+ 'confirm': function() {
+ Ajax.apiOnce({
+ data: {
+ actionName: 'clearLog',
+ className: 'wcf\\data\\devtools\\missing\\language\\item\\DevtoolsMissingLanguageItemAction',
+ },
+ success: function() {
+ window.location.reload();
+ }
+ });
+ },
+ 'message': '{lang}wcf.acp.devtools.missingLanguageItem.clearLog.confirmMessage{/lang}',
+ });
+ });
+ });
+ </script>
+{else}
+ <p class="info">{lang}wcf.global.noItems{/lang}</p>
+{/if}
+
+{include file='footer'}
--- /dev/null
+<?php
+namespace wcf\acp\page;
+use wcf\data\devtools\missing\language\item\DevtoolsMissingLanguageItemList;
+use wcf\page\SortablePage;
+
+/**
+ * Shows the list of missing language item log entries.
+ *
+ * @author Matthias Schmidt
+ * @copyright 2001-2020 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package WoltLabSuite\Core\Acp\Page
+ * @since 5.3
+ */
+class DevtoolsMissingLanguageItemListPage extends SortablePage {
+ /**
+ * @inheritDoc
+ */
+ public $activeMenuItem = 'wcf.acp.menu.link.devtools.missingLanguageItem.list';
+
+ /**
+ * @inheritDoc
+ */
+ public $defaultSortField = 'lastTime';
+
+ /**
+ * @inheritDoc
+ */
+ public $defaultSortOrder = 'DESC';
+
+ /**
+ * @inheritDoc
+ */
+ public $itemsPerPage = 50;
+
+ /**
+ * @inheritDoc
+ */
+ public $objectListClassName = DevtoolsMissingLanguageItemList::class;
+
+ /**
+ * @inheritDoc
+ */
+ public $neededModules = ['ENABLE_DEVELOPER_TOOLS'];
+
+ /**
+ * @inheritDoc
+ */
+ public $neededPermissions = ['admin.configuration.package.canInstallPackage'];
+
+ /**
+ * @inheritDoc
+ */
+ public $validSortFields = ['itemID', 'languageID', 'languageItem', 'lastTime'];
+}
<?php
namespace wcf\acp\page;
+use wcf\data\devtools\missing\language\item\DevtoolsMissingLanguageItemList;
use wcf\page\AbstractPage;
use wcf\system\application\ApplicationHandler;
use wcf\system\cache\builder\OptionCacheBuilder;
&& file_exists(WCF_DIR . 'log/missingLanguageItems.txt')
&& filesize(WCF_DIR . 'log/missingLanguageItems.txt') > 0
) {
- $missingLanguageItemsMTime = filemtime(WCF_DIR . 'log/missingLanguageItems.txt');
+ $logList = new DevtoolsMissingLanguageItemList();
+ $logList->sqlOrderBy = 'lastTime DESC';
+ $logList->sqlLimit = 1;
+ $logList->readObjects();
+ $logEntry = $logList->getSingleObject();
+
+ if ($logEntry !== null) {
+ $missingLanguageItemsMTime = $logEntry->lastTime;
+ }
}
WCF::getTPL()->assign([
--- /dev/null
+<?php
+namespace wcf\data\devtools\missing\language\item;
+use wcf\data\DatabaseObject;
+use wcf\data\language\Language;
+use wcf\system\language\LanguageFactory;
+use wcf\system\WCF;
+use wcf\util\JSON;
+
+/**
+ * Represents a missing language item log entry.
+ *
+ * @author Matthias Schmidt
+ * @copyright 2001-2020 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package WoltLabSuite\Core\Devtools\Missing\Language\Item
+ * @since 5.3
+ *
+ * @property-read integer $itemID unique id of the missing language item log entry
+ * @property-read integer $languageID id of the language the missing language item was requested for
+ * @property-read string $languageItem name of the missing language item
+ * @property-read integer $lastTime timestamp of the last time the missing language item was requested
+ * @property-read string $stackTrace stack trace of how the missing language item was requested for the last time
+ */
+class DevtoolsMissingLanguageItem extends DatabaseObject {
+ /**
+ * Returns the language the missing language item was requested for or `null` if the language
+ * does not exist anymore.
+ *
+ * @return null|Language
+ */
+ public function getLanguage() {
+ if ($this->languageID === null) {
+ return null;
+ }
+
+ return LanguageFactory::getInstance()->getLanguage($this->languageID);
+ }
+
+ /**
+ * Returns the formatted stack trace of how the missing language item was requested for the
+ * last time.
+ *
+ * @return string
+ */
+ public function getStackTrace() {
+ $stackTrace = JSON::decode($this->stackTrace);
+ foreach ($stackTrace as &$stackEntry) {
+ foreach ($stackEntry['args'] as &$stackEntryArg) {
+ if (gettype($stackEntryArg) === 'string') {
+ $stackEntryArg = str_replace(["\n", "\t"], ['\n', '\t'], $stackEntryArg);
+ }
+ }
+ unset($stackEntryArg);
+ }
+ unset($stackEntry);
+
+ return WCF::getTPL()->fetch('__devtoolsMissingLanguageItemStackTrace', 'wcf', [
+ 'stackTrace' => $stackTrace,
+ ]);
+ }
+}
--- /dev/null
+<?php
+namespace wcf\data\devtools\missing\language\item;
+use wcf\data\AbstractDatabaseObjectAction;
+use wcf\data\IDeleteAction;
+use wcf\system\exception\IllegalLinkException;
+use wcf\system\WCF;
+use wcf\util\JSON;
+use function wcf\functions\exception\sanitizeStacktrace;
+
+/**
+ * Executes missing language item log entry-related actions.
+ *
+ * @author Matthias Schmidt
+ * @copyright 2001-2020 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package WoltLabSuite\Core\Devtools\Missing\Language\Item
+ * @since 5.3
+ *
+ * @method DevtoolsMissingLanguageItemEditor[] getObjects()
+ * @method DevtoolsMissingLanguageItemEditor getSingleObject()
+ */
+class DevtoolsMissingLanguageItemAction extends AbstractDatabaseObjectAction implements IDeleteAction {
+ /**
+ * @inheritDoc
+ */
+ protected $permissionsDelete = ['admin.configuration.package.canInstallPackage'];
+
+ /**
+ * Logs a missing language item.
+ */
+ public function logLanguageItem() {
+ $stackTraceData = sanitizeStacktrace(new \Exception(), true);
+ // Remove stack entries related to logging.
+ array_shift($stackTraceData);
+ array_shift($stackTraceData);
+ array_shift($stackTraceData);
+ $stackTrace = JSON::encode($stackTraceData);
+
+ $sql = "INSERT INTO wcf" . WCF_N . "_devtools_missing_language_item
+ (languageID, languageItem, lastTime, stackTrace)
+ VALUES (?, ?, ?, ?)
+ ON DUPLICATE KEY
+ UPDATE lastTime = ?,
+ stackTrace = ?";
+ $statement = WCF::getDB()->prepareStatement($sql);
+ $statement->execute([
+ $this->parameters['language']->languageID,
+ $this->parameters['languageItem'],
+ TIME_NOW,
+ $stackTrace,
+
+ TIME_NOW,
+ $stackTrace,
+ ]);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function validateDelete() {
+ if (!ENABLE_DEVELOPER_TOOLS || !LOG_MISSING_LANGUAGE_ITEMS) {
+ throw new IllegalLinkException();
+ }
+
+ parent::validateDelete();
+ }
+
+ /**
+ * Validates the `clearLog` action.
+ */
+ public function validateClearLog() {
+ if (!ENABLE_DEVELOPER_TOOLS || !LOG_MISSING_LANGUAGE_ITEMS) {
+ throw new IllegalLinkException();
+ }
+
+ WCF::getSession()->checkPermissions(['admin.configuration.package.canInstallPackage']);
+ }
+
+ /**
+ * Removes all entries from the missing language item log.
+ */
+ public function clearLog() {
+ $sql = "DELETE FROM wcf" . WCF_N . "_devtools_missing_language_item";
+ $statement = WCF::getDB()->prepareStatement($sql);
+ $statement->execute();
+ }
+}
--- /dev/null
+<?php
+namespace wcf\data\devtools\missing\language\item;
+use wcf\data\DatabaseObjectEditor;
+
+/**
+ * Provides functions to edit missing language item log entry.
+ *
+ * @author Matthias Schmidt
+ * @copyright 2001-2020 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package WoltLabSuite\Core\Devtools\Missing\Language\Item
+ * @since 5.3
+ *
+ * @method static DevtoolsMissingLanguageItem create(array $parameters = [])
+ * @method DevtoolsMissingLanguageItem getDecoratedObject()
+ * @mixin DevtoolsMissingLanguageItem
+ */
+class DevtoolsMissingLanguageItemEditor extends DatabaseObjectEditor {
+ /**
+ * @inheritDoc
+ */
+ protected static $baseClass = DevtoolsMissingLanguageItem::class;
+}
--- /dev/null
+<?php
+namespace wcf\data\devtools\missing\language\item;
+use wcf\data\DatabaseObjectList;
+
+/**
+ * Represents a list of missing language item log entries.
+ *
+ * @author Matthias Schmidt
+ * @copyright 2001-2020 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package WoltLabSuite\Core\Devtools\Missing\Language\Item
+ * @since 5.3
+ *
+ * @method DevtoolsMissingLanguageItem current()
+ * @method DevtoolsMissingLanguageItem[] getObjects()
+ * @method DevtoolsMissingLanguageItem getSingleObject()
+ * @method DevtoolsMissingLanguageItem|null search($objectID)
+ * @property DevtoolsMissingLanguageItem[] $objects
+ */
+class DevtoolsMissingLanguageItemList extends DatabaseObjectList {}
<?php
namespace wcf\data\language;
use wcf\data\DatabaseObject;
+use wcf\data\devtools\missing\language\item\DevtoolsMissingLanguageItemAction;
use wcf\system\language\LanguageFactory;
use wcf\system\WCF;
use wcf\util\StringUtil;
LOG_MISSING_LANGUAGE_ITEMS &&
preg_match('~^([a-zA-Z0-9-_]+\.)+[a-zA-Z0-9-_]+$~', $item)
) {
- $logFile = WCF_DIR . 'log/missingLanguageItems.txt';
- \wcf\functions\exception\logThrowable(
- new \Exception("Missing language item '{$item}'."),
- $logFile
- );
+ (new DevtoolsMissingLanguageItemAction([], 'logLanguageItem', [
+ 'language' => $this,
+ 'languageItem' => $item,
+ ]))->executeAction();
}
// return plain input
$staticItem === $item &&
preg_match('~^([a-zA-Z0-9-_]+\.)+[a-zA-Z0-9-_]+$~', $item)
) {
- $logFile = WCF_DIR . 'log/missingLanguageItems.txt';
- \wcf\functions\exception\logThrowable(
- new \Exception("Missing language item '{$item}'."),
- $logFile
- );
+ (new DevtoolsMissingLanguageItemAction([], 'logLanguageItem', [
+ 'language' => $this,
+ 'languageItem' => $item,
+ ]))->executeAction();
}
return $staticItem;
<item name="wcf.acp.devtools.project.edit.error.brokenPath"><![CDATA[Es existiert keine <kbd>package.xml</kbd> im angegebenen Pfad. Die Paket-Dateien wurden entweder gelöscht oder verschoben.]]></item>
<item name="wcf.acp.devtools.project.edit.warning.missingElements"><![CDATA[Die folgenden Informationen fehlen in der <kbd>package.xml</kbd>-Datei: {implode from=$missingElements item=missingElement}<kbd>{$missingElement}</kbd>{/implode}.]]></item>
<item name="wcf.acp.devtools.project.license"><![CDATA[Lizenz]]></item>
+ <item name="wcf.acp.devtools.missingLanguageItem.languageID"><![CDATA[Sprache]]></item>
+ <item name="wcf.acp.devtools.missingLanguageItem.languageItem"><![CDATA[Name des Texts]]></item>
+ <item name="wcf.acp.devtools.missingLanguageItem.lastTime"><![CDATA[Zeitpunkt des letzten Auftretens]]></item>
+ <item name="wcf.acp.devtools.missingLanguageItem.delete.confirmMessage"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Willst du{else}Wollen Sie{/if} den Eintrag wirklich löschen?]]></item>
+ <item name="wcf.acp.devtools.missingLanguageItem.stackTrace"><![CDATA[Stacktrace]]></item>
+ <item name="wcf.acp.devtools.missingLanguageItem.showStackTrace"><![CDATA[Stacktrace anzeigen]]></item>
+ <item name="wcf.acp.devtools.missingLanguageItem.clearLog"><![CDATA[Alle Einträge löschen]]></item>
+ <item name="wcf.acp.devtools.missingLanguageItem.clearLog.confirmMessage"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Willst du{else}Wollen Sie{/if} wirklich alle Einträge löschen?]]></item>
+ <item name="wcf.acp.devtools.missingLanguageItem.stackTrace.toggleOutputFormat"><![CDATA[Zeilenumbrüche einschalten/ausschalten]]></item>
</category>
<category name="wcf.acp.email">
<item name="wcf.acp.email.smtp.test"><![CDATA[SMTP-Verbindungstest]]></item>
<item name="wcf.acp.index.woltlab.pluginStore"><![CDATA[Plugin-Store]]></item>
<item name="wcf.acp.index.tinyBuild"><![CDATA[Die Seitenbeschleunigung für Gäste verbessert die Ladezeiten für Besucher und Suchmaschinen, es wird empfohlen diese <a href="{link controller='Option' id=1 optionName="visitor_use_tiny_build"}#category_module.system{/link}">zu aktivieren</a>.]]></item>
<item name="wcf.acp.index.recaptchaWithoutKey"><![CDATA[Die Nutzung von reCAPTCHA ohne einen individuellen Website-Schlüssel wird von Google nicht mehr unterstützt.<br><br>Für eine weitere Nutzung {if LANGUAGE_USE_INFORMAL_VARIANT}musst du{else}müssen Sie{/if} <a href="{$recaptchaKeyLink}">einen Schlüssel in den Optionen hinterlegen</a>, unterhalb des Eingabefeldes befindet sich eine Anleitung zum Anfordern des Schlüssels.]]></item>
- <item name="wcf.acp.index.missingLanguageItems"><![CDATA[Es wurden fehlende Sprachvariablen protokolliert (zuletzt: {@$missingLanguageItemsMTime|time}). {if LANGUAGE_USE_INFORMAL_VARIANT}Überprüfe{else}Überprüfen Sie{/if} die Datei <kbd>{'WCF_DIR'|constant}log/missingLanguageItems.txt</kbd> für weitere Informationen.]]></item>
+ <item name="wcf.acp.index.missingLanguageItems"><![CDATA[Es wurden fehlende Sprachvariablen protokolliert (zuletzt: {@$missingLanguageItemsMTime|time}). {if LANGUAGE_USE_INFORMAL_VARIANT}Überprüfe{else}Überprüfen Sie{/if} die <a href="{link controller='DevtoolsMissingLanguageItemList'}{/link}">Liste der fehlenden Texten</a> für weitere Informationen.]]></item>
</category>
<category name="wcf.acp.label">
<item name="wcf.acp.label.add"><![CDATA[Label hinzufügen]]></item>
<item name="wcf.acp.menu.link.reactionType.edit"><![CDATA[Reaktions-Typ bearbeiten]]></item>
<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>
</category>
<category name="wcf.acp.modificationLog">
<item name="wcf.acp.modificationLog.list"><![CDATA[Globales Änderungsprotokoll]]></item>
<item name="wcf.acp.devtools.project.edit.error.brokenPath"><![CDATA[There is no <kbd>package.xml</kbd> in the given directory. The package files have either been deleted or moved.]]></item>
<item name="wcf.acp.devtools.project.edit.warning.missingElements"><![CDATA[The following information is missing from the <kbd>package.xml</kbd> file: {implode from=$missingElements item=missingElement}<kbd>{$missingElement}</kbd>{/implode}.]]></item>
<item name="wcf.acp.devtools.project.license"><![CDATA[License]]></item>
+ <item name="wcf.acp.devtools.missingLanguageItem.languageID"><![CDATA[Language]]></item>
+ <item name="wcf.acp.devtools.missingLanguageItem.languageItem"><![CDATA[Name of Phrase]]></item>
+ <item name="wcf.acp.devtools.missingLanguageItem.lastTime"><![CDATA[Time of Last Occurence]]></item>
+ <item name="wcf.acp.devtools.missingLanguageItem.delete.confirmMessage"><![CDATA[Do you really want to delete the entry?]]></item>
+ <item name="wcf.acp.devtools.missingLanguageItem.stackTrace"><![CDATA[Stacktrace]]></item>
+ <item name="wcf.acp.devtools.missingLanguageItem.showStackTrace"><![CDATA[Show Stacktrace]]></item>
+ <item name="wcf.acp.devtools.missingLanguageItem.clearLog"><![CDATA[Delete all entries]]></item>
+ <item name="wcf.acp.devtools.missingLanguageItem.clearLog.confirmMessage"><![CDATA[Do you really want to delete all entries?]]></item>
+ <item name="wcf.acp.devtools.missingLanguageItem.stackTrace.toggleOutputFormat"><![CDATA[Toggle line breaks]]></item>
</category>
<category name="wcf.acp.email">
<item name="wcf.acp.email.smtp.test"><![CDATA[SMTP Connection Test]]></item>
<item name="wcf.acp.index.woltlab.pluginStore"><![CDATA[Plugin Store]]></item>
<item name="wcf.acp.index.tinyBuild"><![CDATA[The accelerated guest view improves the page responsiveness and loading times for both visitors and search engines alike, please consider <a href="{link controller='Option' id=1 optionName="visitor_use_tiny_build"}#category_module.system{/link}">enabling it</a>.]]></item>
<item name="wcf.acp.index.recaptchaWithoutKey"><![CDATA[Using reCAPTCHA without an individual website key is no longer supported by Google.<br><br>For further use you need to <a href="{$recaptchaKeyLink}">provide a key in your options</a>, please follow the instructions below the input field to obtain a key.]]></item>
- <item name="wcf.acp.index.missingLanguageItems"><![CDATA[Missing language items have been detected (last time: {@$missingLanguageItemsMTime|time}). Check the file <kbd>{'WCF_DIR'|constant}log/missingLanguageItems.txt</kbd> for more information.]]></item>
+ <item name="wcf.acp.index.missingLanguageItems"><![CDATA[Missing language items have been detected (last time: {@$missingLanguageItemsMTime|time}). Check the <a href="{link controller='DevtoolsMissingLanguageItemList'}{/link}">list of missing phrases</a> for more information.]]></item>
</category>
<category name="wcf.acp.label">
<item name="wcf.acp.label.add"><![CDATA[Add Label]]></item>
<item name="wcf.acp.menu.link.reactionType.edit"><![CDATA[Edit Reaction Type]]></item>
<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>
</category>
<category name="wcf.acp.modificationLog">
<item name="wcf.acp.modificationLog.list"><![CDATA[Global Modification Log]]></item>
UNIQUE KEY name (name)
);
+DROP TABLE IF EXISTS wcf1_devtools_missing_language_item;
+CREATE TABLE wcf1_devtools_missing_language_item (
+ itemID INT(10) NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ languageID INT(10),
+ languageItem VARCHAR(191) NOT NULL,
+ lastTime INT(10) NOT NULL,
+ stackTrace MEDIUMTEXT NOT NULL,
+
+ UNIQUE KEY (languageID, languageItem)
+);
+
DROP TABLE IF EXISTS wcf1_edit_history_entry;
CREATE TABLE wcf1_edit_history_entry (
entryID INT(10) NOT NULL AUTO_INCREMENT PRIMARY KEY,
ALTER TABLE wcf1_cronjob_log ADD FOREIGN KEY (cronjobID) REFERENCES wcf1_cronjob (cronjobID) ON DELETE CASCADE;
+ALTER TABLE wcf1_devtools_missing_language_item ADD FOREIGN KEY (languageID) REFERENCES wcf1_language (languageID) ON DELETE SET NULL;
+
ALTER TABLE wcf1_edit_history_entry ADD FOREIGN KEY (objectTypeID) REFERENCES wcf1_object_type (objectTypeID) ON DELETE CASCADE;
ALTER TABLE wcf1_edit_history_entry ADD FOREIGN KEY (userID) REFERENCES wcf1_user (userID) ON DELETE SET NULL;
ALTER TABLE wcf1_edit_history_entry ADD FOREIGN KEY (obsoletedByUserID) REFERENCES wcf1_user (userID) ON DELETE SET NULL;