Added basic developer tools (wip)
authorAlexander Ebert <ebert@woltlab.com>
Tue, 11 Jul 2017 17:18:14 +0000 (19:18 +0200)
committerAlexander Ebert <ebert@woltlab.com>
Tue, 11 Jul 2017 17:18:14 +0000 (19:18 +0200)
See #2331

52 files changed:
com.woltlab.wcf/acpMenu.xml
com.woltlab.wcf/option.xml
constants.php
wcfsetup/install/files/acp/templates/devtoolsProjectAdd.tpl [new file with mode: 0644]
wcfsetup/install/files/acp/templates/devtoolsProjectList.tpl [new file with mode: 0644]
wcfsetup/install/files/acp/templates/devtoolsProjectSync.tpl [new file with mode: 0644]
wcfsetup/install/files/lib/acp/form/DevtoolsProjectAddForm.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/acp/form/DevtoolsProjectEditForm.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/acp/form/DevtoolsProjectSyncForm.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/acp/page/DevtoolsProjectListPage.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/devtools/project/DevtoolsProject.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/devtools/project/DevtoolsProjectAction.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/devtools/project/DevtoolsProjectEditor.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/devtools/project/DevtoolsProjectList.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/package/installation/plugin/PackageInstallationPluginAction.class.php
wcfsetup/install/files/lib/system/application/ApplicationHandler.class.php
wcfsetup/install/files/lib/system/devtools/package/DevtoolsPackageArchive.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/devtools/package/DevtoolsTar.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/devtools/pip/DevtoolsPackageInstallationDispatcher.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/devtools/pip/DevtoolsPackageInstallationQueue.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/devtools/pip/DevtoolsPip.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/devtools/pip/IIdempotentPackageInstallationPlugin.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/package/PackageInstallationDispatcher.class.php
wcfsetup/install/files/lib/system/package/plugin/ACLOptionPackageInstallationPlugin.class.php
wcfsetup/install/files/lib/system/package/plugin/ACPSearchProviderPackageInstallationPlugin.class.php
wcfsetup/install/files/lib/system/package/plugin/ACPTemplatePackageInstallationPlugin.class.php
wcfsetup/install/files/lib/system/package/plugin/AbstractMenuPackageInstallationPlugin.class.php
wcfsetup/install/files/lib/system/package/plugin/AbstractOptionPackageInstallationPlugin.class.php
wcfsetup/install/files/lib/system/package/plugin/BBCodePackageInstallationPlugin.class.php
wcfsetup/install/files/lib/system/package/plugin/BoxPackageInstallationPlugin.class.php
wcfsetup/install/files/lib/system/package/plugin/ClipboardActionPackageInstallationPlugin.class.php
wcfsetup/install/files/lib/system/package/plugin/CoreObjectPackageInstallationPlugin.class.php
wcfsetup/install/files/lib/system/package/plugin/CronjobPackageInstallationPlugin.class.php
wcfsetup/install/files/lib/system/package/plugin/EventListenerPackageInstallationPlugin.class.php
wcfsetup/install/files/lib/system/package/plugin/FilePackageInstallationPlugin.class.php
wcfsetup/install/files/lib/system/package/plugin/LanguagePackageInstallationPlugin.class.php
wcfsetup/install/files/lib/system/package/plugin/MenuItemPackageInstallationPlugin.class.php
wcfsetup/install/files/lib/system/package/plugin/MenuPackageInstallationPlugin.class.php
wcfsetup/install/files/lib/system/package/plugin/ObjectTypeDefinitionPackageInstallationPlugin.class.php
wcfsetup/install/files/lib/system/package/plugin/ObjectTypePackageInstallationPlugin.class.php
wcfsetup/install/files/lib/system/package/plugin/OptionPackageInstallationPlugin.class.php
wcfsetup/install/files/lib/system/package/plugin/PIPPackageInstallationPlugin.class.php
wcfsetup/install/files/lib/system/package/plugin/PagePackageInstallationPlugin.class.php
wcfsetup/install/files/lib/system/package/plugin/SmileyPackageInstallationPlugin.class.php
wcfsetup/install/files/lib/system/package/plugin/TemplateListenerPackageInstallationPlugin.class.php
wcfsetup/install/files/lib/system/package/plugin/TemplatePackageInstallationPlugin.class.php
wcfsetup/install/files/lib/system/package/plugin/UserGroupOptionPackageInstallationPlugin.class.php
wcfsetup/install/files/lib/system/package/plugin/UserNotificationEventPackageInstallationPlugin.class.php
wcfsetup/install/files/lib/system/package/plugin/UserOptionPackageInstallationPlugin.class.php
wcfsetup/install/files/lib/system/package/plugin/UserProfileMenuPackageInstallationPlugin.class.php
wcfsetup/install/lang/de.xml
wcfsetup/setup/db/install.sql

index a4e2365f7ecbcd86dfbcc73b87bccf3a72476dbf..13ba392f2e1054b9d21b85c21bc0d4ebc3657e65 100644 (file)
                                <icon>fa-plus</icon>
                        </acpmenuitem>
                        <!-- /other -->
+                       
+                       <!-- devtools -->
+                       <acpmenuitem name="wcf.acp.menu.link.devtools">
+                               <parent>wcf.acp.menu.link.configuration</parent>
+                               <showorder>99</showorder>
+                               <options>enable_developer_tools</options>
+                       </acpmenuitem>
+                       
+                       <acpmenuitem name="wcf.acp.menu.link.devtools.project.list">
+                               <parent>wcf.acp.menu.link.devtools</parent>
+                               <controller>wcf\acp\page\DevtoolsProjectListPage</controller>
+                               <permissions>admin.configuration.package.canInstallPackage</permissions>
+                       </acpmenuitem>
+                       <acpmenuitem name="wcf.acp.menu.link.devtools.project.add">
+                               <parent>wcf.acp.menu.link.devtools.project.list</parent>
+                               <controller>wcf\acp\form\DevtoolsProjectAddForm</controller>
+                               <permissions>admin.configuration.package.canInstallPackage</permissions>
+                               <icon>fa-plus</icon>
+                       </acpmenuitem>
+                       <!-- /devtools -->
                
                <!-- /configuration -->
                
index cd34b70350079ec8237d140c49aca98e7d5535cb..a3aaf39649f0311c0de448d6a922d59c1a0b2e6e 100644 (file)
@@ -21,6 +21,9 @@
                                <category name="module.community">
                                        <parent>module</parent>
                                </category>
+                               <category name="module.developer">
+                                       <parent>module</parent>
+                               </category>
                        <!-- /modules -->
                        
                        <!-- general -->
                                <defaultvalue>0</defaultvalue>
                        </option>
                        
-                       <option name="enable_debug_mode">
-                               <categoryname>module.system</categoryname>
-                               <optiontype>boolean</optiontype>
-                               <defaultvalue>0</defaultvalue>
-                       </option>
-                       
-                       <option name="enable_benchmark">
-                               <categoryname>module.system</categoryname>
-                               <optiontype>boolean</optiontype>
-                               <defaultvalue>0</defaultvalue>
-                       </option>
-                       
                        <option name="visitor_use_tiny_build">
                                <categoryname>module.system</categoryname>
                                <optiontype>boolean</optiontype>
                                <defaultvalue>1</defaultvalue>
                        </option>
                        
-                       <!--
-                       <option name="enable_pluginstore_widget">
-                               <categoryname>module.system</categoryname>
-                               <optiontype>boolean</optiontype>
-                               <defaultvalue>0</defaultvalue>
-                       </option>
-                        -->
-                       
                        <option name="module_system_recaptcha"><!-- @deprecated -->
                                <categoryname>module.system</categoryname>
                                <optiontype>boolean</optiontype>
                                <defaultvalue>0</defaultvalue>
                        </option>
                        
+                       <option name="enable_debug_mode">
+                               <categoryname>module.developer</categoryname>
+                               <optiontype>boolean</optiontype>
+                               <defaultvalue>0</defaultvalue>
+                       </option>
+                       <option name="enable_benchmark">
+                               <categoryname>module.developer</categoryname>
+                               <optiontype>boolean</optiontype>
+                               <defaultvalue>0</defaultvalue>
+                       </option>
+                       <option name="enable_developer_tools">
+                               <categoryname>module.developer</categoryname>
+                               <optiontype>boolean</optiontype>
+                               <defaultvalue>0</defaultvalue>
+                       </option>
+                       
                        <!-- general.page -->
                        <option name="page_title">
                                <categoryname>general.page</categoryname>
index e801547f1fe38ccd54e5538149b2346cee0bb4e2..72aad40baf9313edf21284cbd91fedb5c3512f02 100644 (file)
@@ -227,3 +227,4 @@ define('FB_SHARE_APP_ID', '');
 define('MODULE_CONTACT_FORM', 0);
 define('SITEMAP_INDEX_TIME_FRAME', 365);
 define('MODULE_TROPHY', 1);
+define('ENABLE_DEVELOPER_TOOLS', 0);
diff --git a/wcfsetup/install/files/acp/templates/devtoolsProjectAdd.tpl b/wcfsetup/install/files/acp/templates/devtoolsProjectAdd.tpl
new file mode 100644 (file)
index 0000000..3cf36cd
--- /dev/null
@@ -0,0 +1,68 @@
+{include file='header' pageTitle='wcf.acp.devtools.project.'|concat:$action}
+
+<header class="contentHeader">
+       <div class="contentHeaderTitle">
+               <h1 class="contentTitle">{lang}wcf.acp.devtools.project.{$action}{/lang}</h1>
+       </div>
+       
+       <nav class="contentHeaderNavigation">
+               <ul>
+                       <li><a href="{link controller='DevtoolsProjectList'}{/link}" class="button"><span class="icon icon16 fa-list"></span> <span>{lang}wcf.acp.menu.link.devtools.project.list{/lang}</span></a></li>
+                       
+                       {event name='contentHeaderNavigation'}
+               </ul>
+       </nav>
+</header>
+
+{include file='formError'}
+
+{if $success|isset}
+       <p class="success">{lang}wcf.global.success.{$action}{/lang}</p>
+{/if}
+
+<form method="post" action="{if $action == 'add'}{link controller='DevtoolsProjectAdd'}{/link}{else}{link controller='DevtoolsProjectEdit' id=$objectID}{/link}{/if}">
+       <div class="section">
+               <dl{if $errorField == 'name'} class="formError"{/if}>
+                       <dt><label for="name">{lang}wcf.acp.devtools.project.name{/lang}</label></dt>
+                       <dd>
+                               <input type="text" id="name" name="name" value="{$name}" required autofocus class="long">
+                               {if $errorField == 'name'}
+                                       <small class="innerError">
+                                               {if $errorType == 'empty'}
+                                                       {lang}wcf.global.form.error.empty{/lang}
+                                               {else}
+                                                       {lang}wcf.acp.devtools.project.name.error.{@$errorType}{/lang}
+                                               {/if}
+                                       </small>
+                               {/if}
+                       </dd>
+               </dl>
+               
+               <dl{if $errorField == 'path'} class="formError"{/if}>
+                       <dt><label for="path">{lang}wcf.acp.devtools.project.path{/lang}</label></dt>
+                       <dd>
+                               <input type="text" id="path" name="path" value="{$path}" class="long">
+                               {if $errorField == 'path'}
+                                       <small class="innerError">
+                                               {if $errorType == 'empty'}
+                                                       {lang}wcf.global.form.error.{@$errorType}{/lang}
+                                               {else}
+                                                       {lang}wcf.acp.devtools.project.path.error.{@$errorType}{/lang}
+                                               {/if}
+                                       </small>
+                               {/if}
+                       </dd>
+               </dl>
+               
+               {event name='dataFields'}
+       </div>
+       
+       {event name='sections'}
+       
+       <div class="formSubmit">
+               <input type="submit" value="{lang}wcf.global.button.submit{/lang}" accesskey="s">
+               {@SECURITY_TOKEN_INPUT_TAG}
+       </div>
+</form>
+
+{include file='footer'}
diff --git a/wcfsetup/install/files/acp/templates/devtoolsProjectList.tpl b/wcfsetup/install/files/acp/templates/devtoolsProjectList.tpl
new file mode 100644 (file)
index 0000000..c763f85
--- /dev/null
@@ -0,0 +1,71 @@
+{include file='header' pageTitle='wcf.acp.devtools.project.list'}
+
+<script data-relocate="true">
+       $(function() {
+               new WCF.Action.Delete('wcf\\data\\cronjob\\CronjobAction', '.jsObjectRow');
+       });
+</script>
+
+<header class="contentHeader">
+       <div class="contentHeaderTitle">
+               <h1 class="contentTitle">{lang}wcf.acp.devtools.project.list{/lang}</h1>
+       </div>
+       
+       <nav class="contentHeaderNavigation">
+               <ul>
+                       <li><a href="{link controller='DevtoolsProjectAdd'}{/link}" class="button"><span class="icon icon16 fa-plus"></span> <span>{lang}wcf.acp.devtools.project.add{/lang}</span></a></li>
+                       
+                       {event name='contentHeaderNavigation'}
+               </ul>
+       </nav>
+</header>
+
+{hascontent}
+       <div class="section tabularBox">
+               <table class="table">
+                       <thead>
+                               <tr>
+                                       <th class="columnID" colspan="2">{lang}wcf.global.objectID{/lang}</th>
+                                       <th class="columnText">{lang}wcf.acp.devtools.project.name{/lang}</th>
+                                       <th class="columnText">{lang}wcf.acp.devtools.project.path{/lang}</th>
+                                       <th class="columnIcon">{lang}wcf.acp.devtools.project.action{/lang}</th>
+                                       
+                                       {event name='columnHeads'}
+                               </tr>
+                       </thead>
+                       
+                       <tbody>
+                               {content}
+                                       {foreach from=$objects item=object}
+                                               <tr class="jsObjectRow">
+                                                       <td class="columnIcon">
+                                                               <a href="{link controller='DevtoolsProjectEdit' id=$object->getObjectID()}{/link}" title="{lang}wcf.global.button.edit{/lang}" class="jsTooltip"><span class="icon icon16 fa-pencil"></span></a>
+                                                               <span class="icon icon16 fa-times jsDeleteButton jsTooltip pointer" title="{lang}wcf.global.button.delete{/lang}" data-object-id="{@$object->getObjectID()}" data-confirm-message-html="{lang __encode=true}wcf.acp.devtools.project.delete.confirmMessage{/lang}"></span>
+                                                       </td>
+                                                       <td class="columnID">{@$object->getObjectID()}</td>
+                                                       <td class="columnText"><a href="{link controller='DevtoolsProjectEdit' id=$object->getObjectID()}{/link}">{$object->name}</a></td>
+                                                       <td class="columnText"><small>{$object->path}</small></td>
+                                                       <td class="columnIcon">
+                                                               <a href="{link controller='DevtoolsProjectSync' id=$object->getObjectID()}{/link}" class="button small">{lang}wcf.acp.devtools.project.sync{/lang}</a>
+                                                       </td>
+                                               </tr>
+                                       {/foreach}
+                               {/content}
+                       </tbody>
+               </table>
+       </div>
+{hascontentelse}
+       <p class="info">{lang}wcf.global.noItems{/lang}</p>
+{/hascontent}
+
+<footer class="contentFooter">
+       <nav class="contentFooterNavigation">
+               <ul>
+                       <li><a href="{link controller='DevtoolsProjectAdd'}{/link}" class="button"><span class="icon icon16 fa-plus"></span> <span>{lang}wcf.acp.devtools.project.add{/lang}</span></a></li>
+                       
+                       {event name='contentFooterNavigation'}
+               </ul>
+       </nav>
+</footer>
+
+{include file='footer'}
diff --git a/wcfsetup/install/files/acp/templates/devtoolsProjectSync.tpl b/wcfsetup/install/files/acp/templates/devtoolsProjectSync.tpl
new file mode 100644 (file)
index 0000000..441289b
--- /dev/null
@@ -0,0 +1,127 @@
+{include file='header' pageTitle='wcf.acp.devtools.project.sync'}
+
+<header class="contentHeader">
+       <div class="contentHeaderTitle">
+               <h1 class="contentTitle">{lang}wcf.acp.devtools.project.sync{/lang}</h1>
+               <p class="contentHeaderDescription">{$object->name}</p>
+       </div>
+       
+       <nav class="contentHeaderNavigation">
+               <ul>
+                       <li><a href="{link controller='DevtoolsProjectList'}{/link}" class="button"><span class="icon icon16 fa-list"></span> <span>{lang}wcf.acp.menu.link.devtools.project.list{/lang}</span></a></li>
+                       
+                       {event name='contentHeaderNavigation'}
+               </ul>
+       </nav>
+</header>
+
+{include file='formError'}
+
+{if $object->validate() === ''}
+       <p class="info">{lang}wcf.acp.devtools.pip.notice{/lang}</p>
+       
+       <form method="post" action="{link controller='DevtoolsProjectSync' id=$objectID}{/link}">
+               <div class="section">
+                       <dl>
+                               <dt></dt>
+                               <dd>
+                                       <label><input type="checkbox" id="syncShowOnlyMatches" checked> {lang}wcf.acp.devtools.pip.showOnlyMatches{/lang}</label>
+                                       <small>{lang}wcf.acp.devtools.pip.showOnlyMatches.description{/lang}</small>
+                               </dd>
+                       </dl>
+               </div>
+               <div class="section tabularBox jsShowOnlyMatches" id="syncPipMatches">
+                       <table class="table">
+                               <thead>
+                                       <tr>
+                                               <th class="columnText">{lang}wcf.acp.devtools.pip.pluginName{/lang}</th>
+                                               <th class="columnText">{lang}wcf.acp.devtools.pip.defaultFilename{/lang}</th>
+                                               <th class="columnIcon">{lang}wcf.acp.devtools.pip.target{/lang}</th>
+                                       </tr>
+                               </thead>
+                               
+                               <tbody>
+                                       {foreach from=$object->getPips() item=pip}
+                                               {assign var=_isSupported value=$pip->isSupported()}
+                                               {assign var=_targets value=$pip->getTargets($object)}
+                                               
+                                               <tr data-is-supported="{if $_isSupported}true{else}false{/if}" {if !$_targets|empty} class="jsHasPipTargets"{/if}>
+                                                       <td class="columnText">{$pip->pluginName}</td>
+                                                       {if $_isSupported}
+                                                               <td class="columnText"><small>{$pip->getDefaultFilename()}</small></td>
+                                                               <td class="columnIcon">
+                                                                       {hascontent}
+                                                                               <ul class="buttonGroup">
+                                                                                       {content}
+                                                                                               {foreach from=$_targets item=target}
+                                                                                                       <li><button class="small jsInvokePip" data-plugin-name="{$pip->pluginName}" data-target="{$target}">{$target}</button></li>
+                                                                                               {/foreach}
+                                                                                       {/content}
+                                                                               </ul>
+                                                                       {hascontentelse}
+                                                                               <small>{lang}wcf.acp.devtools.pip.target.noMatches{/lang}</small>
+                                                                       {/hascontent}
+                                                               </td>
+                                                       {else}
+                                                               <td class="columnText" colspan="2">{$pip->getFirstError()}</td>
+                                                       {/if}
+                                               </tr>
+                                       {/foreach}
+                               </tbody>
+                       </table>
+               </div>
+               
+               {event name='sections'}
+               
+               <div class="formSubmit">
+                       <input type="submit" value="{lang}wcf.global.button.submit{/lang}" accesskey="s">
+                       {@SECURITY_TOKEN_INPUT_TAG}
+               </div>
+       </form>
+       
+       <script data-relocate="true">
+               var container = elById('syncPipMatches');
+               elById('syncShowOnlyMatches').addEventListener('change', function() {
+                       container.classList.toggle('jsShowOnlyMatches');
+               });
+               
+               require(['Ajax'], function(Ajax) {
+                       var that = {
+                               _ajaxSetup: function() {
+                                       return {
+                                               data: {
+                                                       actionName: 'invoke',
+                                                       className: 'wcf\\data\\package\\installation\\plugin\\PackageInstallationPluginAction',
+                                                       parameters: {
+                                                               projectID: {@$object->projectID}
+                                                       }
+                                               }
+                                       }
+                               }
+                       };
+                       
+                       elBySelAll('.jsInvokePip', container, function(button) {
+                               button.addEventListener(WCF_CLICK_EVENT, function(event) {
+                                       event.preventDefault();
+                                       
+                                       Ajax.api(that, {
+                                               parameters: {
+                                                       pluginName: elData(button, 'plugin-name'),
+                                                       target: elData(button, 'target')
+                                               }
+                                       });
+                               });
+                       });
+               });
+       </script>
+       
+       <style>
+               #syncPipMatches.jsShowOnlyMatches tbody > tr:not(.jsHasPipTargets) {
+                       display: none;
+               }
+       </style>
+{else}
+       <p class="error">{$object->validate()}</p>
+{/if}
+
+{include file='footer'}
diff --git a/wcfsetup/install/files/lib/acp/form/DevtoolsProjectAddForm.class.php b/wcfsetup/install/files/lib/acp/form/DevtoolsProjectAddForm.class.php
new file mode 100644 (file)
index 0000000..ccf77c0
--- /dev/null
@@ -0,0 +1,131 @@
+<?php
+namespace wcf\acp\form;
+use wcf\data\devtools\project\DevtoolsProject;
+use wcf\data\devtools\project\DevtoolsProjectAction;
+use wcf\form\AbstractForm;
+use wcf\system\exception\UserInputException;
+use wcf\system\WCF;
+use wcf\util\FileUtil;
+use wcf\util\StringUtil;
+
+/**
+ * Shows the devtools project add form.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2017 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\Acp\Form
+ * @since       3.1
+ */
+class DevtoolsProjectAddForm extends AbstractForm {
+       /**
+        * @inheritDoc
+        */
+       public $activeMenuItem = 'wcf.acp.menu.link.devtools.project.add';
+       
+       /**
+        * @inheritDoc
+        */
+       public $neededModules = ['ENABLE_DEVELOPER_TOOLS'];
+       
+       /**
+        * @inheritDoc
+        */
+       public $neededPermissions = ['admin.configuration.package.canInstallPackage'];
+       
+       /**
+        * cronjob class name
+        * @var string
+        */
+       public $name = '';
+       
+       /**
+        * cronjob path
+        * @var string
+        */
+       public $path = '';
+       
+       /**
+        * @inheritDoc
+        */
+       public function readFormParameters() {
+               parent::readFormParameters();
+               
+               if (isset($_POST['name'])) $this->name = StringUtil::trim($_POST['name']);
+               if (isset($_POST['path'])) $this->path = StringUtil::trim($_POST['path']);
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function validate() {
+               parent::validate();
+               
+               // validate name
+               if (empty($this->name)) {
+                       throw new UserInputException('name');
+               }
+               else {
+                       $sql = "SELECT  COUNT(*) AS count
+                               FROM    wcf".WCF_N."_devtools_project
+                               WHERE   name = ?";
+                       $statement = WCF::getDB()->prepareStatement($sql);
+                       $statement->execute([$this->name]);
+                       $count = $statement->fetchColumn();
+                       if ($count > 0) {
+                               throw new UserInputException('name', 'notUnique');
+                       }
+               }
+               
+               // validate path
+               if (empty($this->path)) {
+                       throw new UserInputException('path');
+               }
+               else {
+                       $path = FileUtil::addTrailingSlash(FileUtil::unifyDirSeparator($this->path));
+                       $errorType = DevtoolsProject::validatePath($path);
+                       if ($errorType !== '') {
+                               throw new UserInputException('path', $errorType);
+                       }
+                       
+                       $this->path = $path;
+               }
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function save() {
+               parent::save();
+               
+               // save cronjob
+               $data = array_merge($this->additionalFields, [
+                       'name' => $this->name,
+                       'path' => $this->path
+               ]);
+               
+               $this->objectAction = new DevtoolsProjectAction([], 'create', ['data' => $data]);
+               $this->objectAction->executeAction();
+               
+               $this->saved();
+               
+               // reset values
+               $this->name = $this->path = '';
+               
+               // show success message
+               WCF::getTPL()->assign('success', true);
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function assignVariables() {
+               parent::assignVariables();
+               
+               WCF::getTPL()->assign([
+                       'name' => $this->name,
+                       'path' => $this->path,
+                       'action' => 'add'
+               ]);
+       }
+}
diff --git a/wcfsetup/install/files/lib/acp/form/DevtoolsProjectEditForm.class.php b/wcfsetup/install/files/lib/acp/form/DevtoolsProjectEditForm.class.php
new file mode 100644 (file)
index 0000000..3c60a3a
--- /dev/null
@@ -0,0 +1,93 @@
+<?php
+namespace wcf\acp\form;
+use wcf\data\devtools\project\DevtoolsProject;
+use wcf\data\devtools\project\DevtoolsProjectAction;
+use wcf\form\AbstractForm;
+use wcf\system\exception\IllegalLinkException;
+use wcf\system\WCF;
+
+/**
+ * Shows the devtools project edit form.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2017 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\Acp\Form
+ * @since       3.1
+ */
+class DevtoolsProjectEditForm extends DevtoolsProjectAddForm {
+       /**
+        * @inheritDoc
+        */
+       public $activeMenuItem = 'wcf.acp.menu.link.devtools.project.list';
+       
+       /**
+        * project id
+        * @var integer
+        */
+       public $objectID = 0;
+       
+       /**
+        * devtools project
+        * @var DevtoolsProject
+        */
+       public $object;
+       
+       /**
+        * @inheritDoc
+        */
+       public function readParameters() {
+               parent::readParameters();
+               
+               if (isset($_REQUEST['id'])) $this->objectID = intval($_REQUEST['id']);
+               $this->object = new DevtoolsProject($this->objectID);
+               if (!$this->object->projectID) {
+                       throw new IllegalLinkException();
+               }
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function readData() {
+               parent::readData();
+               
+               if (empty($_POST)) {
+                       $this->name = $this->object->name;
+                       $this->path = $this->object->path;
+               }
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function save() {
+               AbstractForm::save();
+               
+               // update cronjob
+               $data = array_merge($this->additionalFields, [
+                       'name' => $this->name,
+                       'path' => $this->path
+               ]);
+               
+               $this->objectAction = new DevtoolsProjectAction([$this->objectID], 'update', ['data' => $data]);
+               $this->objectAction->executeAction();
+               
+               $this->saved();
+               
+               // show success message
+               WCF::getTPL()->assign('success', true);
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function assignVariables() {
+               parent::assignVariables();
+               
+               WCF::getTPL()->assign([
+                       'objectID' => $this->objectID,
+                       'action' => 'edit'
+               ]);
+       }
+}
diff --git a/wcfsetup/install/files/lib/acp/form/DevtoolsProjectSyncForm.class.php b/wcfsetup/install/files/lib/acp/form/DevtoolsProjectSyncForm.class.php
new file mode 100644 (file)
index 0000000..67149be
--- /dev/null
@@ -0,0 +1,69 @@
+<?php
+namespace wcf\acp\form;
+use wcf\data\devtools\project\DevtoolsProject;
+use wcf\form\AbstractForm;
+use wcf\system\exception\IllegalLinkException;
+use wcf\system\WCF;
+
+/**
+ * Shows the devtools project sync form.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2017 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\Acp\Form
+ * @since       3.1
+ */
+class DevtoolsProjectSyncForm extends AbstractForm {
+       /**
+        * @inheritDoc
+        */
+       public $activeMenuItem = 'wcf.acp.menu.link.devtools.project.list';
+       
+       /**
+        * @inheritDoc
+        */
+       public $neededModules = ['ENABLE_DEVELOPER_TOOLS'];
+       
+       /**
+        * @inheritDoc
+        */
+       public $neededPermissions = ['admin.configuration.package.canInstallPackage'];
+       
+       /**
+        * project id
+        * @var integer
+        */
+       public $objectID = 0;
+       
+       /**
+        * devtools project
+        * @var DevtoolsProject
+        */
+       public $object;
+       
+       /**
+        * @inheritDoc
+        */
+       public function readParameters() {
+               parent::readParameters();
+               
+               if (isset($_REQUEST['id'])) $this->objectID = intval($_REQUEST['id']);
+               $this->object = new DevtoolsProject($this->objectID);
+               if (!$this->object->projectID) {
+                       throw new IllegalLinkException();
+               }
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function assignVariables() {
+               parent::assignVariables();
+               
+               WCF::getTPL()->assign([
+                       'objectID' => $this->objectID,
+                       'object' => $this->object
+               ]);
+       }
+}
diff --git a/wcfsetup/install/files/lib/acp/page/DevtoolsProjectListPage.class.php b/wcfsetup/install/files/lib/acp/page/DevtoolsProjectListPage.class.php
new file mode 100644 (file)
index 0000000..855866b
--- /dev/null
@@ -0,0 +1,57 @@
+<?php
+namespace wcf\acp\page;
+use wcf\data\devtools\project\DevtoolsProjectList;
+use wcf\page\AbstractPage;
+use wcf\system\WCF;
+
+/**
+ * Shows a list of devtools projects.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2017 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\Acp\Page
+ * @since      3.1
+ */
+class DevtoolsProjectListPage extends AbstractPage {
+       /**
+        * @inheritDoc
+        */
+       public $activeMenuItem = 'wcf.acp.menu.link.devtools.project.list';
+       
+       /**
+        * @inheritDoc
+        */
+       public $neededModules = ['ENABLE_DEVELOPER_TOOLS'];
+       
+       /**
+        * @inheritDoc
+        */
+       public $neededPermissions = ['admin.configuration.package.canInstallPackage'];
+       
+       /**
+        * @var DevtoolsProjectList
+        */
+       public $objectList;
+       
+       /**
+        * @inheritDoc
+        */
+       public function readData() {
+               parent::readData();
+               
+               $this->objectList = new DevtoolsProjectList();
+               $this->objectList->readObjects();
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function assignVariables() {
+               parent::assignVariables();
+               
+               WCF::getTPL()->assign([
+                       'objects' => $this->objectList
+               ]);
+       }
+}
diff --git a/wcfsetup/install/files/lib/data/devtools/project/DevtoolsProject.class.php b/wcfsetup/install/files/lib/data/devtools/project/DevtoolsProject.class.php
new file mode 100644 (file)
index 0000000..f8e5afc
--- /dev/null
@@ -0,0 +1,130 @@
+<?php
+namespace wcf\data\devtools\project;
+use wcf\data\DatabaseObject;
+use wcf\data\package\installation\plugin\PackageInstallationPluginList;
+use wcf\data\package\Package;
+use wcf\data\package\PackageCache;
+use wcf\system\devtools\package\DevtoolsPackageArchive;
+use wcf\system\devtools\pip\DevtoolsPip;
+use wcf\system\package\validation\PackageValidationException;
+use wcf\system\WCF;
+
+/**
+ * Represents a devtools project.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2017 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\Devtools\Project
+ * @since      3.1
+ * 
+ * @property-read      integer         $projectID      unique id of the project
+ * @property-read      string          $name           internal name for display inside the ACP
+ * @property-read      string          $path           file system path
+ */
+class DevtoolsProject extends DatabaseObject {
+       /**
+        * @var boolean
+        */
+       protected $isCore;
+       
+       /**
+        * @var Package
+        */
+       protected $package;
+       
+       /**
+        * @var DevtoolsPackageArchive
+        */
+       protected $packageArchive;
+       
+       public function getPips() {
+               $pipList = new PackageInstallationPluginList();
+               $pipList->sqlOrderBy = 'pluginName';
+               $pipList->readObjects();
+               
+               $pips = [];
+               foreach ($pipList as $pip) {
+                       $pips[] = new DevtoolsPip($pip);
+               }
+               
+               return $pips;
+       }
+       
+       public function validate() {
+               $errorType = self::validatePath($this->path);
+               if ($errorType !== '') {
+                       return WCF::getLanguage()->get('wcf.acp.devtools.project.path.error.' . $errorType);
+               }
+               
+               return $this->validatePackageXml();
+       }
+       
+       public function isCore() {
+               if ($this->isCore === null) {
+                       $this->isCore = self::pathIsCore($this->path);
+               }
+               
+               return $this->isCore;
+       }
+       
+       public function validatePackageXml() {
+               $packageXml = $this->path . ($this->isCore() ? 'com.woltlab.wcf/' : '') . 'package.xml';
+               $this->packageArchive = new DevtoolsPackageArchive($packageXml);
+               try {
+                       $this->packageArchive->openArchive();
+               }
+               catch (PackageValidationException $e) {
+                       return $e->getErrorMessage();
+               }
+               
+               $this->package = PackageCache::getInstance()->getPackageByIdentifier($this->packageArchive->getPackageInfo('name'));
+               if ($this->package === null) {
+                       return WCF::getLanguage()->getDynamicVariable('wcf.acp.devtools.project.path.error.notInstalled', [
+                               'package' => $this->packageArchive->getPackageInfo('name')
+                       ]);
+               }
+               
+               return '';
+       }
+       
+       /**
+        * @return      Package
+        */
+       public function getPackage() {
+               return $this->package;
+       }
+       
+       /**
+        * @return      DevtoolsPackageArchive
+        */
+       public function getPackageArchive() {
+               return $this->packageArchive;
+       }
+       
+       /**
+        * Validates the provided path and returns an error code
+        * if the path does not exist (`notFound`) or if there is
+        * no package.xml (`packageXml`).
+        * 
+        * @param       string          $path
+        * @return      string
+        */
+       public static function validatePath($path) {
+               if (!is_dir($path)) {
+                       return 'notFound';
+               }
+               else if (!file_exists($path . 'package.xml')) {
+                       // check if this is `com.woltlab.wcf`
+                       if (!self::pathIsCore($path)) {
+                               return 'packageXml';
+                       }
+               }
+               
+               return '';
+       }
+       
+       public static function pathIsCore($path) {
+               return (is_dir($path . 'com.woltlab.wcf') && file_exists($path . 'com.woltlab.wcf/package.xml'));
+       }
+}
diff --git a/wcfsetup/install/files/lib/data/devtools/project/DevtoolsProjectAction.class.php b/wcfsetup/install/files/lib/data/devtools/project/DevtoolsProjectAction.class.php
new file mode 100644 (file)
index 0000000..0ff5cc0
--- /dev/null
@@ -0,0 +1,22 @@
+<?php
+namespace wcf\data\devtools\project;
+use wcf\data\AbstractDatabaseObjectAction;
+
+/**
+ * Executes devtools project related actions.
+ * 
+ * @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\Devtools\Project
+ * @since      3.1
+ * 
+ * @method     DevtoolsProjectEditor[] getObjects()
+ * @method     DevtoolsProjectEditor   getSingleObject()
+ */
+class DevtoolsProjectAction extends AbstractDatabaseObjectAction {
+       /**
+        * @inheritDoc
+        */
+       protected $className = DevtoolsProjectEditor::class;
+}
diff --git a/wcfsetup/install/files/lib/data/devtools/project/DevtoolsProjectEditor.class.php b/wcfsetup/install/files/lib/data/devtools/project/DevtoolsProjectEditor.class.php
new file mode 100644 (file)
index 0000000..3d37476
--- /dev/null
@@ -0,0 +1,23 @@
+<?php
+namespace wcf\data\devtools\project;
+use wcf\data\DatabaseObjectEditor;
+
+/**
+ * Provides functions to edit devtool projects.
+ * 
+ * @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\Devtools\Project
+ * @since      3.1
+ * 
+ * @method static      DevtoolsProject create(array $parameters = [])
+ * @method             DevtoolsProject getDecoratedObject()
+ * @mixin              DevtoolsProject
+ */
+class DevtoolsProjectEditor extends DatabaseObjectEditor {
+       /**
+        * @inheritDoc
+        */
+       protected static $baseClass = DevtoolsProject::class;
+}
diff --git a/wcfsetup/install/files/lib/data/devtools/project/DevtoolsProjectList.class.php b/wcfsetup/install/files/lib/data/devtools/project/DevtoolsProjectList.class.php
new file mode 100644 (file)
index 0000000..01f22fc
--- /dev/null
@@ -0,0 +1,24 @@
+<?php
+namespace wcf\data\devtools\project;
+use wcf\data\DatabaseObjectList;
+
+/**
+ * Represents a list of devtools projects.
+ * 
+ * @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\Devtools\Project
+ * @since      3.1
+ *
+ * @method     DevtoolsProject         current()
+ * @method     DevtoolsProject[]       getObjects()
+ * @method     DevtoolsProject|null    search($objectID)
+ * @property   DevtoolsProject[]       $objects
+ */
+class DevtoolsProjectList extends DatabaseObjectList {
+       /**
+        * @inheritDoc
+        */
+       public $className = DevtoolsProject::class;
+}
index c5b58defb4966910e46e0722ac94af7b4cfa18db..7c7beabb5188a8dc3ae0363ddbaa3fb6120cbdce 100644 (file)
@@ -1,6 +1,18 @@
 <?php
 namespace wcf\data\package\installation\plugin;
 use wcf\data\AbstractDatabaseObjectAction;
+use wcf\data\devtools\project\DevtoolsProject;
+use wcf\system\cache\CacheHandler;
+use wcf\system\devtools\pip\DevtoolsPackageInstallationDispatcher;
+use wcf\system\devtools\pip\DevtoolsPip;
+use wcf\system\devtools\pip\IIdempotentPackageInstallationPlugin;
+use wcf\system\exception\PermissionDeniedException;
+use wcf\system\exception\UserInputException;
+use wcf\system\package\plugin\IPackageInstallationPlugin;
+use wcf\system\package\SplitNodeException;
+use wcf\system\search\SearchIndexManager;
+use wcf\system\version\VersionTracker;
+use wcf\system\WCF;
 
 /**
  * Executes package installation plugin-related actions.
@@ -19,4 +31,76 @@ class PackageInstallationPluginAction extends AbstractDatabaseObjectAction {
         * @inheritDoc
         */
        protected $className = PackageInstallationPluginEditor::class;
+       
+       /**
+        * @inheritDoc
+        */
+       protected $requireACP = ['invoke'];
+       
+       /**
+        * @var DevtoolsPip
+        */
+       public $devtoolsPip;
+       
+       /**
+        * @var PackageInstallationPlugin
+        */
+       public $packageInstallationPlugin;
+       
+       /**
+        * @var DevtoolsProject
+        */
+       public $project;
+       
+       public function validateInvoke() {
+               if (!ENABLE_DEVELOPER_TOOLS || !WCF::getSession()->getPermission('admin.configuration.package.canInstallPackage')) {
+                       throw new PermissionDeniedException();
+               }
+               
+               $this->readString('pluginName');
+               $this->readInteger('projectID');
+               $this->readString('target');
+               
+               $this->project = new DevtoolsProject($this->parameters['projectID']);
+               if (!$this->project->projectID || $this->project->validate() !== '') {
+                       throw new UserInputException('projectID');
+               }
+               
+               $this->packageInstallationPlugin = new PackageInstallationPlugin($this->parameters['pluginName']);
+               if (!$this->packageInstallationPlugin->pluginName) {
+                       throw new UserInputException('pluginName');
+               }
+               
+               $this->devtoolsPip = new DevtoolsPip($this->packageInstallationPlugin);
+               $targets = $this->devtoolsPip->getTargets($this->project);
+               if (!in_array($this->parameters['target'], $targets)) {
+                       throw new UserInputException('target');
+               }
+       }
+       
+       public function invoke() {
+               $dispatcher = new DevtoolsPackageInstallationDispatcher($this->project);
+               /** @var IIdempotentPackageInstallationPlugin $pip */
+               $pip = new $this->packageInstallationPlugin->className($dispatcher, [
+                       'value' => $this->devtoolsPip->getInstructionValue($this->project, $this->parameters['target'])
+               ]);
+               
+               try {
+                       $pip->update();
+               }
+               catch (SplitNodeException $e) {
+                       throw new \RuntimeException("PIP '{$this->packageInstallationPlugin->pluginName}' is not allowed to throw a 'SplitNodeException'.");
+               }
+               
+               // clear cache
+               
+               // TODO: use a central method instead!
+               
+               // create search index tables
+               SearchIndexManager::getInstance()->createSearchIndices();
+               
+               VersionTracker::getInstance()->createStorageTables();
+               
+               CacheHandler::getInstance()->flushAll();
+       }
 }
index 1ac668db6d80cc6d7d9474c04d5d61d7ebc3d9c7..5ad414e490ad32511a1700fa3336f8b325cc8bd6 100644 (file)
@@ -20,7 +20,7 @@ use wcf\util\FileUtil;
 class ApplicationHandler extends SingletonFactory {
        /**
         * application cache
-        * @var Application[]
+        * @var mixed[][]
         */
        protected $cache;
        
@@ -162,6 +162,16 @@ class ApplicationHandler extends SingletonFactory {
                return null;
        }
        
+       /**
+        * Returns the list of application abbreviations.
+        * 
+        * @return      string[]
+        * @since       3.1
+        */
+       public function getAbbreviations() {
+               return array_keys($this->cache['abbreviation']);
+       }
+       
        /**
         * Returns true if given $url is an internal URL.
         * 
diff --git a/wcfsetup/install/files/lib/system/devtools/package/DevtoolsPackageArchive.class.php b/wcfsetup/install/files/lib/system/devtools/package/DevtoolsPackageArchive.class.php
new file mode 100644 (file)
index 0000000..1494bec
--- /dev/null
@@ -0,0 +1,21 @@
+<?php
+namespace wcf\system\devtools\package;
+use wcf\system\package\PackageArchive;
+
+/**
+ * @method             DevtoolsTar     getTar()
+ */
+class DevtoolsPackageArchive extends PackageArchive {
+       protected $packageXmlPath = '';
+       
+       /** @noinspection PhpMissingParentConstructorInspection */
+       public function __construct($packageXmlPath) {
+               $this->packageXmlPath = $packageXmlPath;
+       }
+       
+       public function openArchive() {
+               $this->tar = new DevtoolsTar(['package.xml' => $this->packageXmlPath]);
+               
+               $this->readPackageInfo();
+       }
+}
diff --git a/wcfsetup/install/files/lib/system/devtools/package/DevtoolsTar.class.php b/wcfsetup/install/files/lib/system/devtools/package/DevtoolsTar.class.php
new file mode 100644 (file)
index 0000000..31fe021
--- /dev/null
@@ -0,0 +1,32 @@
+<?php
+namespace wcf\system\devtools\package;
+use wcf\system\io\Tar;
+
+class DevtoolsTar extends Tar {
+       protected $files = '';
+       
+       /** @noinspection PhpMissingParentConstructorInspection */
+       public function __construct(array $files) {
+               $this->files = $files;
+       }
+       
+       public function registerFile($filename, $fullPath) {
+               $this->files[$filename] = $fullPath;
+       }
+       
+       public function getIndexByFilename($filename) {
+               return (isset($this->files[$filename]) ? $filename : false);
+       }
+       
+       public function extractToString($index) {
+               if (!isset($this->files[$index])) {
+                       throw new \RuntimeException("DevtoolsTar does not permit reading any files except for the explicitly registered ones.");
+               }
+               
+               return file_get_contents($this->files[$index]);
+       }
+       
+       public function extract($index, $destination) {
+               copy($this->files[$index], $destination);
+       }
+}
diff --git a/wcfsetup/install/files/lib/system/devtools/pip/DevtoolsPackageInstallationDispatcher.class.php b/wcfsetup/install/files/lib/system/devtools/pip/DevtoolsPackageInstallationDispatcher.class.php
new file mode 100644 (file)
index 0000000..484f78e
--- /dev/null
@@ -0,0 +1,30 @@
+<?php
+namespace wcf\system\devtools\pip;
+use wcf\data\devtools\project\DevtoolsProject;
+use wcf\system\devtools\package\DevtoolsPackageArchive;
+use wcf\system\package\PackageInstallationDispatcher;
+
+class DevtoolsPackageInstallationDispatcher extends PackageInstallationDispatcher {
+       /**
+        * @var DevtoolsPackageArchive
+        */
+       protected $project;
+       
+       public function __construct(DevtoolsProject $project) {
+               parent::__construct(new DevtoolsPackageInstallationQueue($project));
+               
+               $this->project = $project;
+       }
+       
+       public function getArchive() {
+               return $this->project->getPackageArchive();
+       }
+       
+       public function getPackageID() {
+               return $this->project->getPackage()->packageID;
+       }
+       
+       public function getPackageName() {
+               return $this->project->getPackage()->getName();
+       }
+}
diff --git a/wcfsetup/install/files/lib/system/devtools/pip/DevtoolsPackageInstallationQueue.class.php b/wcfsetup/install/files/lib/system/devtools/pip/DevtoolsPackageInstallationQueue.class.php
new file mode 100644 (file)
index 0000000..6845c88
--- /dev/null
@@ -0,0 +1,22 @@
+<?php
+namespace wcf\system\devtools\pip;
+use wcf\data\devtools\project\DevtoolsProject;
+use wcf\data\package\installation\queue\PackageInstallationQueue;
+use wcf\system\WCF;
+
+class DevtoolsPackageInstallationQueue extends PackageInstallationQueue {
+       public function __construct(DevtoolsProject $project) {
+               parent::__construct(null, [
+                       'queueID' => 0,
+                       'parentQueueID' => 0,
+                       'processNo' => 0,
+                       'userID' => WCF::getUser()->userID,
+                       'package' => $project->getPackage()->package,
+                       'packageName' => $project->getPackage()->getName(),
+                       'archive' => '',
+                       'action' => 'update',
+                       'done' => 0,
+                       'isApplication' => $project->getPackage()->isApplication
+               ]);
+       }
+}
\ No newline at end of file
diff --git a/wcfsetup/install/files/lib/system/devtools/pip/DevtoolsPip.class.php b/wcfsetup/install/files/lib/system/devtools/pip/DevtoolsPip.class.php
new file mode 100644 (file)
index 0000000..54e19a1
--- /dev/null
@@ -0,0 +1,198 @@
+<?php
+namespace wcf\system\devtools\pip;
+use wcf\data\devtools\project\DevtoolsProject;
+use wcf\data\package\installation\plugin\PackageInstallationPlugin;
+use wcf\data\DatabaseObjectDecorator;
+use wcf\system\application\ApplicationHandler;
+use wcf\system\exception\NotImplementedException;
+use wcf\system\WCF;
+
+/**
+ * Wrapper class for package installation plugins for use with the sync feature.
+ *
+ * @author     Alexander Ebert
+ * @copyright  2001-2017 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\System\Devtools\Pip
+ * @since       3.1
+ * 
+ * @method     PackageInstallationPlugin       getDecoratedObject()
+ * @mixin      PackageInstallationPlugin
+ */
+class DevtoolsPip extends DatabaseObjectDecorator {
+       /**
+        * @inheritDoc
+        */
+       protected static $baseClass = PackageInstallationPlugin::class;
+       
+       /**
+        * Returns true if the PIP class can be found.
+        * 
+        * @return      boolean
+        */
+       public function classExists() {
+               return class_exists($this->getDecoratedObject()->className);
+       }
+       
+       /**
+        * Returns true if the PIP is expected to be idempotent.
+        * 
+        * @return      boolean
+        */
+       public function isIdempotent() {
+               return is_subclass_of($this->getDecoratedObject()->className, IIdempotentPackageInstallationPlugin::class);
+       }
+       
+       /**
+        * Returns the default filename of this PIP.
+        * 
+        * @return      string
+        */
+       public function getDefaultFilename() {
+               return call_user_func([$this->getDecoratedObject()->className, 'getDefaultFilename']);
+       }
+       
+       /**
+        * Returns true if the PIP exists, has a default filename and is idempotent.
+        * 
+        * @return      boolean
+        */
+       public function isSupported() {
+               return $this->classExists() && $this->getDefaultFilename() && $this->isIdempotent();
+       }
+       
+       /**
+        * Returns the first validation error.
+        * 
+        * @return      string
+        */
+       public function getFirstError() {
+               if (!$this->classExists()) {
+                       return WCF::getLanguage()->getDynamicVariable('wcf.acp.devtools.pip.error.className', ['className' => $this->getDecoratedObject()->className]);
+               }
+               else if (!$this->isIdempotent()) {
+                       return WCF::getLanguage()->get('wcf.acp.devtools.pip.error.notIdempotent');
+               }
+               else if (!$this->getDefaultFilename()) {
+                       return WCF::getLanguage()->get('wcf.acp.devtools.pip.error.defaultFilename');
+               }
+               
+               throw new \LogicException("Please call `isSupported()` to check for potential errors.");
+       }
+       
+       /**
+        * Returns the list of valid targets for this pip.
+        * 
+        * @param       DevtoolsProject         $project
+        * @return      string[]
+        */
+       public function getTargets(DevtoolsProject $project) {
+               if (!$this->isSupported()) {
+                       return [];
+               }
+               
+               $path = $project->path;
+               $defaultFilename = $this->getDefaultFilename();
+               $targets = [];
+               
+               // the core uses a significantly different file layout
+               if ($project->isCore()) {
+                       switch ($this->getDecoratedObject()->pluginName) {
+                               case 'acpTemplate':
+                               case 'file':
+                               case 'template':
+                                       // these pips are satisfied by definition
+                                       return [$defaultFilename];
+                                       
+                               case 'language':
+                                       foreach (glob($path . 'wcfsetup/install/lang/*.xml') as $file) {
+                                               $targets[] = basename($file);
+                                       }
+                                       
+                                       // `glob()` returns files in an arbitrary order
+                                       sort($targets, SORT_NATURAL);
+                                       
+                                       return $targets;
+                       }
+                       
+                       if (strpos($defaultFilename, '*') !== false) {
+                               foreach (glob($path . 'com.woltlab.wcf/' . $defaultFilename) as $file) {
+                                       $targets[] = basename($file);
+                               }
+                               
+                               // `glob()` returns files in an arbitrary order
+                               sort($targets, SORT_NATURAL);
+                       }
+                       else {
+                               if (file_exists($path . 'com.woltlab.wcf/' . $defaultFilename)) {
+                                       $targets[] = $defaultFilename;
+                               }
+                       }
+               }
+               else {
+                       if (preg_match('~^(?<filename>.*)\.tar$~', $defaultFilename, $match)) {
+                               if (is_dir($path . $match['filename'])) {
+                                       $targets[] = $defaultFilename;
+                               }
+                               
+                               // check for application-specific pips too
+                               foreach (ApplicationHandler::getInstance()->getAbbreviations() as $abbreviation) {
+                                       if (is_dir($path . $match['filename'] . '_' . $abbreviation)) {
+                                               $targets[] = $match['filename'] . "_{$abbreviation}.tar";
+                                       }
+                               }
+                       }
+                       else {
+                               if (strpos($defaultFilename, '*') !== false) {
+                                       foreach (glob($path . $defaultFilename) as $file) {
+                                               $targets[] = basename($file);
+                                       }
+                                       
+                                       // `glob()` returns files in an arbitrary order
+                                       sort($targets, SORT_NATURAL);
+                               }
+                               else {
+                                       if (file_exists($path . $defaultFilename)) {
+                                               $targets[] = $defaultFilename;
+                                       }
+                               }
+                       }
+               }
+               
+               return $targets;
+       }
+       
+       public function getInstructionValue(DevtoolsProject $project, $target) {
+               $defaultFilename = $this->getDefaultFilename();
+               
+               if ($project->isCore()) {
+                       switch ($this->getDecoratedObject()->pluginName) {
+                               case 'acpTemplate':
+                               case 'file':
+                               case 'template':
+                                       throw new NotImplementedException();
+                                       break;
+                               
+                               case 'language':
+                                       $filename = "wcfsetup/install/lang/{$target}";
+                                       $project->getPackageArchive()->getTar()->registerFile($filename, $project->path . $filename);
+                                       
+                                       return $filename;
+                                       
+                               default:
+                                       $filename = "com.woltlab.wcf/{$target}";
+                                       $project->getPackageArchive()->getTar()->registerFile($filename, $project->path . $filename);
+                                       
+                                       return $filename;
+                       }
+               }
+               else {
+                       if (strpos($defaultFilename, '*') !== false) {
+                               $filename = str_replace('*', $target, $defaultFilename);
+                               $project->getPackageArchive()->getTar()->registerFile($filename, $project->path . $filename);
+                       }
+               }
+               
+               throw new NotImplementedException();
+       }
+}
\ No newline at end of file
diff --git a/wcfsetup/install/files/lib/system/devtools/pip/IIdempotentPackageInstallationPlugin.class.php b/wcfsetup/install/files/lib/system/devtools/pip/IIdempotentPackageInstallationPlugin.class.php
new file mode 100644 (file)
index 0000000..9022c09
--- /dev/null
@@ -0,0 +1,23 @@
+<?php
+namespace wcf\system\devtools\pip;
+use wcf\system\package\plugin\IPackageInstallationPlugin;
+
+/**
+ * Default interface for package installation plugins that indicate to have
+ * no side-effects when run more than once. Furthermore such plugins may not
+ * throw a `SplitNodeException` during its normal operation.
+ * 
+ * This is especially important for the developer tools that rely on PIPs
+ * being invokable at any point, regardless if they have been ran before.
+ * 
+ * Classic examples include the menu-PIPs that will at most update existing
+ * data, but not cause items to be duplicated when evaluated once more. The
+ * opposite is the sql-PIP that (usually) contains non-repeatable instructions.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2017 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\System\Devtools\Pip
+ * @since       3.1
+ */
+interface IIdempotentPackageInstallationPlugin extends IPackageInstallationPlugin {}
index c934b050a6d50253e12a7102d65e0bd9f14ad7c6..9d3298ecf9f4a794cd43916ceeb65ef4a7fb5995 100644 (file)
@@ -234,6 +234,10 @@ class PackageInstallationDispatcher {
                                                        'debug',
                                                        'mail_send_method'
                                                ]);
+                                               $statement->execute([
+                                                       1,
+                                                       'enable_developer_tools'
+                                               ]);
                                        }
                                        
                                        // update options.inc.php
index 6e794a2a5292649e2cc02c1be2ac7c050c3ee111..a9026f6040262a879027b814bbf1a8a9cb306ec6 100644 (file)
@@ -1,6 +1,7 @@
 <?php
 namespace wcf\system\package\plugin;
 use wcf\data\acl\option\ACLOptionEditor;
+use wcf\system\devtools\pip\IIdempotentPackageInstallationPlugin;
 use wcf\system\exception\SystemException;
 use wcf\system\WCF;
 
@@ -12,7 +13,7 @@ use wcf\system\WCF;
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Package\Plugin
  */
-class ACLOptionPackageInstallationPlugin extends AbstractOptionPackageInstallationPlugin {
+class ACLOptionPackageInstallationPlugin extends AbstractOptionPackageInstallationPlugin implements IIdempotentPackageInstallationPlugin {
        /**
         * @inheritDoc
         */
index af1422c5c2c0241c787720931ab4dd4eb456cc20..c6b61b31f4a19ea6e42cd5a96a0d3da15b0e4d71 100644 (file)
@@ -2,6 +2,7 @@
 namespace wcf\system\package\plugin;
 use wcf\data\acp\search\provider\ACPSearchProviderEditor;
 use wcf\system\cache\builder\ACPSearchProviderCacheBuilder;
+use wcf\system\devtools\pip\IIdempotentPackageInstallationPlugin;
 use wcf\system\WCF;
 
 /**
@@ -12,7 +13,7 @@ use wcf\system\WCF;
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Package\Plugin
  */
-class ACPSearchProviderPackageInstallationPlugin extends AbstractXMLPackageInstallationPlugin {
+class ACPSearchProviderPackageInstallationPlugin extends AbstractXMLPackageInstallationPlugin implements IIdempotentPackageInstallationPlugin {
        /**
         * @inheritDoc
         */
index e70a901e728d54683093bf35a7f59589d0ff79bd..34871b578925ee9e92675f33a1c6e5a30d744193 100644 (file)
@@ -2,6 +2,7 @@
 namespace wcf\system\package\plugin;
 use wcf\data\application\Application;
 use wcf\data\package\Package;
+use wcf\system\devtools\pip\IIdempotentPackageInstallationPlugin;
 use wcf\system\exception\SystemException;
 use wcf\system\package\ACPTemplatesFileHandler;
 use wcf\system\package\PackageArchive;
@@ -15,7 +16,7 @@ use wcf\system\WCF;
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Package\Plugin
  */
-class ACPTemplatePackageInstallationPlugin extends AbstractPackageInstallationPlugin {
+class ACPTemplatePackageInstallationPlugin extends AbstractPackageInstallationPlugin implements IIdempotentPackageInstallationPlugin {
        /**
         * @inheritDoc
         */
index eb6a89e7ef0194d9eb4780c06ff700f65581c6ef..9cf09cda94fe8709e6cd5c7db2885ffd0eb2ea1f 100644 (file)
@@ -1,5 +1,6 @@
 <?php
 namespace wcf\system\package\plugin;
+use wcf\system\devtools\pip\IIdempotentPackageInstallationPlugin;
 use wcf\system\exception\SystemException;
 use wcf\system\WCF;
 use wcf\util\StringUtil;
@@ -12,7 +13,7 @@ use wcf\util\StringUtil;
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Package\Plugin
  */
-abstract class AbstractMenuPackageInstallationPlugin extends AbstractXMLPackageInstallationPlugin {
+abstract class AbstractMenuPackageInstallationPlugin extends AbstractXMLPackageInstallationPlugin implements IIdempotentPackageInstallationPlugin {
        /**
         * @inheritDoc
         */
index 578129ecc05663b0852bc9287d089bf17b9aff15..ad9959f7704220cf4829949a66a6f866cbb9977d 100644 (file)
@@ -1,6 +1,7 @@
 <?php
 namespace wcf\system\package\plugin;
 use wcf\data\package\Package;
+use wcf\system\devtools\pip\IIdempotentPackageInstallationPlugin;
 use wcf\system\exception\SystemException;
 use wcf\system\WCF;
 use wcf\util\StringUtil;
@@ -13,7 +14,7 @@ use wcf\util\StringUtil;
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Package\Plugin
  */
-abstract class AbstractOptionPackageInstallationPlugin extends AbstractXMLPackageInstallationPlugin {
+abstract class AbstractOptionPackageInstallationPlugin extends AbstractXMLPackageInstallationPlugin implements IIdempotentPackageInstallationPlugin {
        /**
         * @inheritDoc
         */
index 2735b3c9053968df8c9aa6db65281fa060ab27da..8f80ceaa62267f6174bfd58fc4f1a1f56b149615 100644 (file)
@@ -5,6 +5,7 @@ use wcf\data\bbcode\BBCode;
 use wcf\data\bbcode\BBCodeEditor;
 use wcf\data\package\PackageCache;
 use wcf\system\database\util\PreparedStatementConditionBuilder;
+use wcf\system\devtools\pip\IIdempotentPackageInstallationPlugin;
 use wcf\system\exception\SystemException;
 use wcf\system\WCF;
 use wcf\util\StringUtil;
@@ -17,7 +18,7 @@ use wcf\util\StringUtil;
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Package\Plugin
  */
-class BBCodePackageInstallationPlugin extends AbstractXMLPackageInstallationPlugin {
+class BBCodePackageInstallationPlugin extends AbstractXMLPackageInstallationPlugin implements IIdempotentPackageInstallationPlugin {
        /**
         * @inheritDoc
         */
index dc2aaeb64ca9b8b54e0d0a6d08da55bb8fa96e24..5266bc02d81af265307aedfbfca1548a1f065468 100644 (file)
@@ -3,6 +3,7 @@ namespace wcf\system\package\plugin;
 use wcf\data\box\Box;
 use wcf\data\box\BoxEditor;
 use wcf\system\database\util\PreparedStatementConditionBuilder;
+use wcf\system\devtools\pip\IIdempotentPackageInstallationPlugin;
 use wcf\system\exception\SystemException;
 use wcf\system\language\LanguageFactory;
 use wcf\system\WCF;
@@ -16,7 +17,7 @@ use wcf\system\WCF;
  * @package    WoltLabSuite\Core\Acp\Package\Plugin
  * @since      3.0
  */
-class BoxPackageInstallationPlugin extends AbstractXMLPackageInstallationPlugin {
+class BoxPackageInstallationPlugin extends AbstractXMLPackageInstallationPlugin implements IIdempotentPackageInstallationPlugin {
        /**
         * @inheritDoc
         */
index e6187480ae3d67dccd2dfc602708c34e7c617731..1f8bce012b24909d0c82982e58551bde7bcabeaa 100644 (file)
@@ -2,6 +2,7 @@
 namespace wcf\system\package\plugin;
 use wcf\data\clipboard\action\ClipboardAction;
 use wcf\data\clipboard\action\ClipboardActionEditor;
+use wcf\system\devtools\pip\IIdempotentPackageInstallationPlugin;
 use wcf\system\WCF;
 
 /**
@@ -12,7 +13,7 @@ use wcf\system\WCF;
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Package\Plugin
  */
-class ClipboardActionPackageInstallationPlugin extends AbstractXMLPackageInstallationPlugin {
+class ClipboardActionPackageInstallationPlugin extends AbstractXMLPackageInstallationPlugin implements IIdempotentPackageInstallationPlugin {
        /**
         * @inheritDoc
         */
index 39fda983d59dc72eeb0da8bb4d7f7f0f11f33833..1306e68363fc50d1941e965414d3f6d17a04de11 100644 (file)
@@ -2,6 +2,7 @@
 namespace wcf\system\package\plugin;
 use wcf\data\core\object\CoreObjectEditor;
 use wcf\system\cache\builder\CoreObjectCacheBuilder;
+use wcf\system\devtools\pip\IIdempotentPackageInstallationPlugin;
 use wcf\system\WCF;
 
 /**
@@ -12,7 +13,7 @@ use wcf\system\WCF;
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Package\Plugin
  */
-class CoreObjectPackageInstallationPlugin extends AbstractXMLPackageInstallationPlugin {
+class CoreObjectPackageInstallationPlugin extends AbstractXMLPackageInstallationPlugin implements IIdempotentPackageInstallationPlugin {
        /**
         * @inheritDoc
         */
index a21133432ddefd83a503befb13f6ec29ae56960a..b83aefd1b4caf66d86c73605ec8204963d7b5b39 100644 (file)
@@ -2,6 +2,7 @@
 namespace wcf\system\package\plugin;
 use wcf\data\cronjob\Cronjob;
 use wcf\data\cronjob\CronjobEditor;
+use wcf\system\devtools\pip\IIdempotentPackageInstallationPlugin;
 use wcf\system\WCF;
 use wcf\util\CronjobUtil;
 use wcf\util\StringUtil;
@@ -14,7 +15,7 @@ use wcf\util\StringUtil;
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Package\Plugin
  */
-class CronjobPackageInstallationPlugin extends AbstractXMLPackageInstallationPlugin {
+class CronjobPackageInstallationPlugin extends AbstractXMLPackageInstallationPlugin implements IIdempotentPackageInstallationPlugin {
        /**
         * @inheritDoc
         */
index 3a73d9a942f0a678953fa6b272bc1270d7c55904..ee1d778e05c7e76a09e8430b149da82a2e654bba 100644 (file)
@@ -3,6 +3,7 @@ namespace wcf\system\package\plugin;
 use wcf\data\event\listener\EventListener;
 use wcf\data\event\listener\EventListenerEditor;
 use wcf\system\cache\builder\EventListenerCacheBuilder;
+use wcf\system\devtools\pip\IIdempotentPackageInstallationPlugin;
 use wcf\system\WCF;
 use wcf\util\StringUtil;
 
@@ -14,7 +15,7 @@ use wcf\util\StringUtil;
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Package\Plugin
  */
-class EventListenerPackageInstallationPlugin extends AbstractXMLPackageInstallationPlugin {
+class EventListenerPackageInstallationPlugin extends AbstractXMLPackageInstallationPlugin implements IIdempotentPackageInstallationPlugin {
        /**
         * @inheritDoc
         */
index 762c69956a98c9455dbaf42be55bb7b5139545c7..a0501a74893955a35db61e45bf13ec706fbe9d0e 100644 (file)
@@ -2,6 +2,7 @@
 namespace wcf\system\package\plugin;
 use wcf\data\application\Application;
 use wcf\data\package\Package;
+use wcf\system\devtools\pip\IIdempotentPackageInstallationPlugin;
 use wcf\system\exception\SystemException;
 use wcf\system\package\FilesFileHandler;
 use wcf\system\package\PackageArchive;
@@ -17,7 +18,7 @@ use wcf\util\StyleUtil;
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Package\Plugin
  */
-class FilePackageInstallationPlugin extends AbstractPackageInstallationPlugin {
+class FilePackageInstallationPlugin extends AbstractPackageInstallationPlugin implements IIdempotentPackageInstallationPlugin {
        /**
         * @inheritDoc
         */
index 9e2045721e42b75af58d9f4a94f010fa42b48cc0..3813e55ed46f2425a08d59f59d05e07c13d60d57 100644 (file)
@@ -3,6 +3,7 @@ namespace wcf\system\package\plugin;
 use wcf\data\language\Language;
 use wcf\data\language\LanguageEditor;
 use wcf\system\database\util\PreparedStatementConditionBuilder;
+use wcf\system\devtools\pip\IIdempotentPackageInstallationPlugin;
 use wcf\system\exception\SystemException;
 use wcf\system\package\PackageArchive;
 use wcf\system\WCF;
@@ -16,7 +17,7 @@ use wcf\util\XML;
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Package\Plugin
  */
-class LanguagePackageInstallationPlugin extends AbstractXMLPackageInstallationPlugin {
+class LanguagePackageInstallationPlugin extends AbstractXMLPackageInstallationPlugin implements IIdempotentPackageInstallationPlugin {
        /**
         * @inheritDoc
         */
index 141602630ed6eeeeee5a48d1f7e8a42913f6547a..b2e5099c0e7a88ec9883dc90ac95331af1e0bf93 100644 (file)
@@ -2,6 +2,7 @@
 namespace wcf\system\package\plugin;
 use wcf\data\menu\item\MenuItem;
 use wcf\data\menu\item\MenuItemEditor;
+use wcf\system\devtools\pip\IIdempotentPackageInstallationPlugin;
 use wcf\system\exception\SystemException;
 use wcf\system\WCF;
 
@@ -14,7 +15,7 @@ use wcf\system\WCF;
  * @package    WoltLabSuite\Core\Acp\Package\Plugin
  * @since      3.0
  */
-class MenuItemPackageInstallationPlugin extends AbstractXMLPackageInstallationPlugin {
+class MenuItemPackageInstallationPlugin extends AbstractXMLPackageInstallationPlugin implements IIdempotentPackageInstallationPlugin {
        /**
         * @inheritDoc
         */
index fb572434dfa6175ecfd7d0b805486935217ca455..f2f1563a8ac3ed7d648d89d52494b7327923234e 100644 (file)
@@ -6,6 +6,7 @@ use wcf\data\menu\Menu;
 use wcf\data\menu\MenuEditor;
 use wcf\data\menu\MenuList;
 use wcf\system\database\util\PreparedStatementConditionBuilder;
+use wcf\system\devtools\pip\IIdempotentPackageInstallationPlugin;
 use wcf\system\exception\SystemException;
 use wcf\system\WCF;
 
@@ -18,7 +19,7 @@ use wcf\system\WCF;
  * @package    WoltLabSuite\Core\Acp\Package\Plugin
  * @since      3.0
  */
-class MenuPackageInstallationPlugin extends AbstractXMLPackageInstallationPlugin {
+class MenuPackageInstallationPlugin extends AbstractXMLPackageInstallationPlugin implements IIdempotentPackageInstallationPlugin {
        /**
         * box meta data per menu
         * @var string[]
index 47d2f0233ea0fc4ee39278548ce8a1053731f03b..6fa72676e1d950c8d5b3ed81d33ca7072a7d129e 100644 (file)
@@ -1,6 +1,7 @@
 <?php
 namespace wcf\system\package\plugin;
 use wcf\data\object\type\definition\ObjectTypeDefinitionEditor;
+use wcf\system\devtools\pip\IIdempotentPackageInstallationPlugin;
 use wcf\system\WCF;
 
 /**
@@ -11,7 +12,7 @@ use wcf\system\WCF;
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Package\Plugin
  */
-class ObjectTypeDefinitionPackageInstallationPlugin extends AbstractXMLPackageInstallationPlugin {
+class ObjectTypeDefinitionPackageInstallationPlugin extends AbstractXMLPackageInstallationPlugin implements IIdempotentPackageInstallationPlugin {
        /**
         * @inheritDoc
         */
index b594d710876fdfe24d972cc10586a1186bed57eb..f0597d50d6001a90c8e161fc1b1504b02226e3e9 100644 (file)
@@ -1,6 +1,7 @@
 <?php
 namespace wcf\system\package\plugin;
 use wcf\data\object\type\ObjectTypeEditor;
+use wcf\system\devtools\pip\IIdempotentPackageInstallationPlugin;
 use wcf\system\exception\SystemException;
 use wcf\system\WCF;
 
@@ -12,7 +13,7 @@ use wcf\system\WCF;
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Package\Plugin
  */
-class ObjectTypePackageInstallationPlugin extends AbstractXMLPackageInstallationPlugin {
+class ObjectTypePackageInstallationPlugin extends AbstractXMLPackageInstallationPlugin implements IIdempotentPackageInstallationPlugin {
        /**
         * @inheritDoc
         */
index 44feafe3aee7b3882c33bf418fbb50765223bde2..190106f4b9c33df10c16c89cd48f1f96c9726364 100644 (file)
@@ -3,6 +3,7 @@ namespace wcf\system\package\plugin;
 use wcf\data\option\Option;
 use wcf\data\option\OptionEditor;
 use wcf\data\package\Package;
+use wcf\system\devtools\pip\IIdempotentPackageInstallationPlugin;
 use wcf\system\exception\SystemException;
 use wcf\system\WCF;
 use wcf\util\StringUtil;
@@ -15,7 +16,7 @@ use wcf\util\StringUtil;
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Package\Plugin
  */
-class OptionPackageInstallationPlugin extends AbstractOptionPackageInstallationPlugin {
+class OptionPackageInstallationPlugin extends AbstractOptionPackageInstallationPlugin implements IIdempotentPackageInstallationPlugin {
        /**
         * @inheritDoc
         */
index d7aae82062fb0c1362acec235b76d16273f496e6..e033c3815906d88af6d4c9cd6aee5b0156ae6bdc 100644 (file)
@@ -1,6 +1,7 @@
 <?php
 namespace wcf\system\package\plugin;
 use wcf\data\package\installation\plugin\PackageInstallationPluginEditor;
+use wcf\system\devtools\pip\IIdempotentPackageInstallationPlugin;
 use wcf\system\WCF;
 
 /**
@@ -11,7 +12,7 @@ use wcf\system\WCF;
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Package\Plugin
  */
-class PIPPackageInstallationPlugin extends AbstractXMLPackageInstallationPlugin {
+class PIPPackageInstallationPlugin extends AbstractXMLPackageInstallationPlugin implements IIdempotentPackageInstallationPlugin {
        /**
         * @inheritDoc
         */
index 5174f79aa4895ef1e20021d16e8b07e15af1dc03..7daf6a3f327f4daf5997bf0f3bcfed4025317dfc 100644 (file)
@@ -3,6 +3,7 @@ namespace wcf\system\package\plugin;
 use wcf\data\package\PackageCache;
 use wcf\data\page\Page;
 use wcf\data\page\PageEditor;
+use wcf\system\devtools\pip\IIdempotentPackageInstallationPlugin;
 use wcf\system\exception\SystemException;
 use wcf\system\language\LanguageFactory;
 use wcf\system\request\RouteHandler;
@@ -19,7 +20,7 @@ use wcf\util\StringUtil;
  * @package    WoltLabSuite\Core\Acp\Package\Plugin
  * @since      3.0
  */
-class PagePackageInstallationPlugin extends AbstractXMLPackageInstallationPlugin {
+class PagePackageInstallationPlugin extends AbstractXMLPackageInstallationPlugin implements IIdempotentPackageInstallationPlugin {
        /**
         * @inheritDoc
         */
index 68d0f8112551f41165381e438a5d9fcd2a0737bd..596a6cf304ff8f5559d88d3e4eafb4570319d5e7 100644 (file)
@@ -1,6 +1,7 @@
 <?php
 namespace wcf\system\package\plugin;
 use wcf\data\smiley\SmileyEditor;
+use wcf\system\devtools\pip\IIdempotentPackageInstallationPlugin;
 use wcf\system\WCF;
 
 /**
@@ -11,7 +12,7 @@ use wcf\system\WCF;
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Package\Plugin
  */
-class SmileyPackageInstallationPlugin extends AbstractXMLPackageInstallationPlugin {
+class SmileyPackageInstallationPlugin extends AbstractXMLPackageInstallationPlugin implements IIdempotentPackageInstallationPlugin {
        /**
         * @inheritDoc
         */
index d424c53da5a9295c95c547b958814e7d3e6cb5ae..a3b3d9fcac62770546d04521177ebec60174b131 100644 (file)
@@ -2,6 +2,7 @@
 namespace wcf\system\package\plugin;
 use wcf\data\template\listener\TemplateListenerEditor;
 use wcf\system\cache\builder\TemplateListenerCodeCacheBuilder;
+use wcf\system\devtools\pip\IIdempotentPackageInstallationPlugin;
 use wcf\system\WCF;
 use wcf\util\StringUtil;
 
@@ -13,7 +14,7 @@ use wcf\util\StringUtil;
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Package\Plugin
  */
-class TemplateListenerPackageInstallationPlugin extends AbstractXMLPackageInstallationPlugin {
+class TemplateListenerPackageInstallationPlugin extends AbstractXMLPackageInstallationPlugin implements IIdempotentPackageInstallationPlugin {
        /**
         * @inheritDoc
         */
index 80ec131a704741b5eed51e77d7fabfd2e17ad22f..fcf275bfae2c6f4f9708f231183f9f5106ee0554 100644 (file)
@@ -2,6 +2,7 @@
 namespace wcf\system\package\plugin;
 use wcf\data\application\Application;
 use wcf\data\package\Package;
+use wcf\system\devtools\pip\IIdempotentPackageInstallationPlugin;
 use wcf\system\exception\SystemException;
 use wcf\system\package\PackageArchive;
 use wcf\system\package\TemplatesFileHandler;
@@ -15,7 +16,7 @@ use wcf\system\WCF;
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Package\Plugin
  */
-class TemplatePackageInstallationPlugin extends AbstractPackageInstallationPlugin {
+class TemplatePackageInstallationPlugin extends AbstractPackageInstallationPlugin implements IIdempotentPackageInstallationPlugin {
        /**
         * @inheritDoc
         */
index 3d173dd672e095319165d69a9ca9cabad7bd795a..05b52721ab9b734440b6e16d21f842cace2dbf9a 100644 (file)
@@ -3,6 +3,7 @@ namespace wcf\system\package\plugin;
 use wcf\data\user\group\option\UserGroupOption;
 use wcf\data\user\group\option\UserGroupOptionEditor;
 use wcf\data\user\group\UserGroup;
+use wcf\system\devtools\pip\IIdempotentPackageInstallationPlugin;
 use wcf\system\WCF;
 use wcf\util\StringUtil;
 
@@ -14,7 +15,7 @@ use wcf\util\StringUtil;
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Package\Plugin
  */
-class UserGroupOptionPackageInstallationPlugin extends AbstractOptionPackageInstallationPlugin {
+class UserGroupOptionPackageInstallationPlugin extends AbstractOptionPackageInstallationPlugin implements IIdempotentPackageInstallationPlugin {
        /**
         * list of group ids by type
         * @var integer[][]
index 9f130c8e4a6e9ad02b8adae67299775da71f8c74..ec90935e0021923a8e0757aa21842f2cb5e4770b 100644 (file)
@@ -2,6 +2,7 @@
 namespace wcf\system\package\plugin;
 use wcf\data\user\notification\event\UserNotificationEvent;
 use wcf\data\user\notification\event\UserNotificationEventEditor;
+use wcf\system\devtools\pip\IIdempotentPackageInstallationPlugin;
 use wcf\system\exception\SystemException;
 use wcf\system\WCF;
 use wcf\util\StringUtil;
@@ -14,7 +15,7 @@ use wcf\util\StringUtil;
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Package\Plugin
  */
-class UserNotificationEventPackageInstallationPlugin extends AbstractXMLPackageInstallationPlugin {
+class UserNotificationEventPackageInstallationPlugin extends AbstractXMLPackageInstallationPlugin implements IIdempotentPackageInstallationPlugin {
        /**
         * @inheritDoc
         */
index 588ce951e6f629bf1cef896aa95372b3db1965ec..3f509b99a8a8471d7261f41f1c6fc0f63352ec2b 100644 (file)
@@ -4,6 +4,7 @@ use wcf\data\user\option\category\UserOptionCategory;
 use wcf\data\user\option\category\UserOptionCategoryEditor;
 use wcf\data\user\option\UserOption;
 use wcf\data\user\option\UserOptionEditor;
+use wcf\system\devtools\pip\IIdempotentPackageInstallationPlugin;
 use wcf\system\exception\SystemException;
 use wcf\system\WCF;
 use wcf\util\StringUtil;
@@ -16,7 +17,7 @@ use wcf\util\StringUtil;
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Package\Plugin
  */
-class UserOptionPackageInstallationPlugin extends AbstractOptionPackageInstallationPlugin {
+class UserOptionPackageInstallationPlugin extends AbstractOptionPackageInstallationPlugin implements IIdempotentPackageInstallationPlugin {
        /**
         * @inheritDoc
         */
index e81c49c83c0ce8f21c1fb2a73e4f133f46c29b7b..23ba0ca631b6e8f289cda59290e8842864d2799f 100644 (file)
@@ -1,6 +1,7 @@
 <?php
 namespace wcf\system\package\plugin;
 use wcf\data\user\profile\menu\item\UserProfileMenuItemEditor;
+use wcf\system\devtools\pip\IIdempotentPackageInstallationPlugin;
 use wcf\system\WCF;
 use wcf\util\StringUtil;
 
@@ -12,7 +13,7 @@ use wcf\util\StringUtil;
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Package\Plugin
  */
-class UserProfileMenuPackageInstallationPlugin extends AbstractXMLPackageInstallationPlugin {
+class UserProfileMenuPackageInstallationPlugin extends AbstractXMLPackageInstallationPlugin implements IIdempotentPackageInstallationPlugin {
        /**
         * @inheritDoc
         */
index 34ac9e4638d774d439a456ddfa3f52fc91beae4d..dfeadd24f352ece89f1db674e281f946f6785b05 100644 (file)
                <item name="wcf.acp.dataImport.started"><![CDATA[Import begonnen.]]></item>
        </category>
        
+       <category name="wcf.acp.devtools">
+               <item name="wcf.acp.devtools.project.action"><![CDATA[Aktionen]]></item>
+               <item name="wcf.acp.devtools.project.add"><![CDATA[Projekt hinzufügen]]></item>
+               <item name="wcf.acp.devtools.project.edit"><![CDATA[Projekt bearbeiten]]></item>
+               <item name="wcf.acp.devtools.project.list"><![CDATA[Projekte]]></item>
+               <item name="wcf.acp.devtools.project.name"><![CDATA[Name]]></item>
+               <item name="wcf.acp.devtools.project.path"><![CDATA[Pfad]]></item>
+               <item name="wcf.acp.devtools.project.sync"><![CDATA[Daten-Abgleich]]></item>
+               <item name="wcf.acp.devtools.pip.defaultFilename"><![CDATA[Suchmuster]]></item>
+               <item name="wcf.acp.devtools.pip.error.notIdempotent"><![CDATA[Das PIP unterstützt keinen wiederholten Import und kann nur bei einem Update verarbeitet werden.]]></item>
+               <item name="wcf.acp.devtools.pip.notice"><![CDATA[Bestehende Anweisungen in der <kbd>package.xml</kbd> werden nicht berücksichtigt; es können somit auch PIPs importiert werden, für die noch keine Anweisungen hinterlegt worden sind. Es werden nur die Standardpfade bei der Suche verwendet, zusätzlich werden anwendungsbezogene Suffixe (z. B. <kbd>files_wcf.tar</kbd>) für <kbd>.tar</kbd>-basierte PIPs unterstützt.]]></item>
+               <item name="wcf.acp.devtools.pip.pluginName"><![CDATA[PIP-Bezeichner]]></item>
+               <item name="wcf.acp.devtools.pip.showOnlyMatches"><![CDATA[Zeige nur übereinstimmende PIPs an]]></item>
+               <item name="wcf.acp.devtools.pip.showOnlyMatches.description"><![CDATA[Es werden nur PIPs angeboten, die für den wiederholten Import geeignet sind und für die es eine Entsprechung auf Basis des Standard-Dateinamens existiert.]]></item>
+               <item name="wcf.acp.devtools.pip.target"><![CDATA[Übereinstimmungen]]></item>
+               <item name="wcf.acp.devtools.pip.target.noMatches"><![CDATA[(Keine Treffer)]]></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>
                <item name="wcf.acp.menu.link.trophy.list"><![CDATA[Trophäen]]></item>
                <item name="wcf.acp.menu.link.trophy.add"><![CDATA[Trophäe hinzufügen]]></item>
                <item name="wcf.acp.menu.link.trophy.edit"><![CDATA[Trophäe bearbeiten]]></item>
+               <item name="wcf.acp.menu.link.devtools"><![CDATA[Entwickler-Werkzeuge]]></item>
+               <item name="wcf.acp.menu.link.devtools.project.list"><![CDATA[Projekte auflisten]]></item>
        </category>
        
        <category name="wcf.acp.notice">
@@ -1313,6 +1333,9 @@ GmbH=Gesellschaft mit beschränkter Haftung]]></item>
                <item name="wcf.acp.option.module_contact_form"><![CDATA[Kontakt-Formular aktivieren]]></item>
                <item name="wcf.acp.option.module_contact_form.description"><![CDATA[Aktiviert das Kontakt-Formular, nach Aktivierung können Sie die <a href="{link controller='ContactSettings'}{/link}">Eingabefelder und Empfänger</a> individuell konfigurieren.]]></item>
                <item name="wcf.acp.option.module_trophy"><![CDATA[Trophäen]]></item>
+               <item name="wcf.acp.option.category.module.developer"><![CDATA[Entwickler]]></item>
+               <item name="wcf.acp.option.enable_developer_tools"><![CDATA[Entwickler-Werkzeuge aktivieren]]></item>
+               <item name="wcf.acp.option.enable_developer_tools.description"><![CDATA[Aktiviert spezielle Werkzeuge die für die Plugin-Entwicklung verwendet werden.]]></item>
        </category>
        
        <category name="wcf.acp.customOption">
index e3a9dc04f1af189c846c9e6537a1fb1318640af2..12984db6bcfc94a01f3a2de866b7bc89e89526c1 100644 (file)
@@ -481,6 +481,13 @@ CREATE TABLE wcf1_cronjob_log (
        error TEXT
 );
 
+DROP TABLE IF EXISTS wcf1_devtools_project;
+CREATE TABLE wcf1_devtools_project (
+       projectID INT(10) NOT NULL AUTO_INCREMENT PRIMARY KEY,
+       name VARCHAR(191) NOT NULL,
+       path TEXT
+);
+
 DROP TABLE IF EXISTS wcf1_edit_history_entry;
 CREATE TABLE wcf1_edit_history_entry (
        entryID INT(10) NOT NULL AUTO_INCREMENT PRIMARY KEY,