Allow installation of packages via devtools
authorMatthias Schmidt <gravatronics@live.com>
Thu, 22 Nov 2018 18:50:55 +0000 (19:50 +0100)
committerMatthias Schmidt <gravatronics@live.com>
Thu, 22 Nov 2018 18:50:55 +0000 (19:50 +0100)
Close #2616

17 files changed:
wcfsetup/install/files/acp/js/WCF.ACP.js
wcfsetup/install/files/acp/templates/__devtoolsProjectInstallationJavaScript.tpl [new file with mode: 0644]
wcfsetup/install/files/acp/templates/devtoolsProjectPipList.tpl
wcfsetup/install/files/acp/templates/devtoolsProjectSync.tpl
wcfsetup/install/files/js/WoltLabSuite/Core/Acp/Ui/Devtools/Project/Installation/Confirmation.js [new file with mode: 0644]
wcfsetup/install/files/lib/acp/action/DevtoolsInstallPackageAction.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/acp/action/InstallPackageAction.class.php
wcfsetup/install/files/lib/acp/page/DevtoolsProjectSyncPage.class.php
wcfsetup/install/files/lib/data/devtools/project/DevtoolsProject.class.php
wcfsetup/install/files/lib/data/devtools/project/DevtoolsProjectAction.class.php
wcfsetup/install/files/lib/system/devtools/package/DevtoolsInstaller.class.php
wcfsetup/install/files/lib/system/devtools/package/DevtoolsPackageArchive.class.php
wcfsetup/install/files/lib/system/devtools/package/DevtoolsTar.class.php
wcfsetup/install/files/lib/system/devtools/pip/DevtoolsPackageInstallationDispatcher.class.php
wcfsetup/install/files/lib/system/package/PackageInstallationDispatcher.class.php
wcfsetup/install/lang/de.xml
wcfsetup/install/lang/en.xml

index c050f48f7c9e41fea51e0a420aec000fa86a5b83..f527b3f7e8a7d1edb4757eac17b6429f255c065d 100644 (file)
@@ -167,6 +167,12 @@ WCF.ACP.Package.Installation = Class.extend({
         */
        _actionName: 'InstallPackage',
        
+       /**
+        * additional parameters send in all requests
+        * @var object
+        */
+       _additionalRequestParameters: {},
+       
        /**
         * true, if rollbacks are supported
         * @var boolean
@@ -210,20 +216,17 @@ WCF.ACP.Package.Installation = Class.extend({
         * @param       string          actionName
         * @param       boolean         allowRollback
         * @param       boolean         isUpdate
+        * @param       object          additionalRequestParameters
         */
-       init: function(queueID, actionName, allowRollback, isUpdate) {
+       init: function(queueID, actionName, allowRollback, isUpdate, additionalRequestParameters) {
                this._actionName = (actionName) ? actionName : 'InstallPackage';
                this._allowRollback = (allowRollback === true);
                this._queueID = queueID;
+               this._additionalRequestParameters = additionalRequestParameters || {};
                
-               switch (this._actionName) {
-                       case 'InstallPackage':
-                               this._dialogTitle = 'wcf.acp.package.' + (isUpdate ? 'update' : 'install') + '.title';
-                       break;
-                       
-                       case 'UninstallPackage':
-                               this._dialogTitle = 'wcf.acp.package.uninstallation.title';
-                       break;
+               this._dialogTitle = 'wcf.acp.package.' + (isUpdate ? 'update' : 'install') + '.title';
+               if (this._actionName === 'UninstallPackage') {
+                       this._dialogTitle = 'wcf.acp.package.uninstallation.title';
                }
                
                this._initProxy();
@@ -317,10 +320,10 @@ WCF.ACP.Package.Installation = Class.extend({
         * @return      object
         */
        _getParameters: function() {
-               return {
+               return $.extend({}, this._additionalRequestParameters, {
                        queueID: this._queueID,
                        step: 'prepare'
-               };
+               });
        },
        
        /**
@@ -517,7 +520,7 @@ WCF.ACP.Package.Installation = Class.extend({
        _executeStep: function(step, node, additionalData) {
                if (!additionalData) additionalData = { };
                
-               var $data = $.extend({
+               var $data = $.extend({}, this._additionalRequestParameters, {
                        node: node,
                        queueID: this._queueID,
                        step: step
diff --git a/wcfsetup/install/files/acp/templates/__devtoolsProjectInstallationJavaScript.tpl b/wcfsetup/install/files/acp/templates/__devtoolsProjectInstallationJavaScript.tpl
new file mode 100644 (file)
index 0000000..7c93c45
--- /dev/null
@@ -0,0 +1,24 @@
+{if !$project->getPackage() && $project->getPackageArchive()->getOpenRequirements()|empty}
+       <script data-relocate="true">
+               require(['Language', 'WoltLabSuite/Core/Acp/Ui/Devtools/Project/Installation/Confirmation'], function(Language, DevtoolsProjectInstallationConfirmation) {
+                       Language.addObject({
+                               'wcf.acp.devtools.project.installPackage.confirmMessage': '{lang __literal=true}wcf.acp.devtools.project.installPackage.confirmMessage{/lang}',
+                               'wcf.acp.package.install.title': '{lang}wcf.acp.package.install.title{/lang}'
+                       });
+                       
+                       DevtoolsProjectInstallationConfirmation.init({@$project->projectID}, '{@$project->name|encodeJS}');
+               });
+       </script>
+{/if}
+
+{if !$project->getPackageArchive()->getOpenRequirements()|empty}
+       <div id="openPackageRequirements" class="jsStaticDialogContent" data-title="{lang}wcf.acp.devtools.project.installPackage.error.openRequirements.title{/lang}">
+               <p>{lang}wcf.acp.devtools.project.installPackage.error.openRequirements{/lang}</p>
+               
+               <ul class="nativeList">
+                       {foreach from=$project->getPackageArchive()->getOpenRequirements() key=openPackage item=openRequirement}
+                               <li>{lang}wcf.acp.devtools.project.installPackage.openRequirement{/lang}</li>
+                       {/foreach}
+               </ul>
+       </div>
+{/if}
index 8c77cf58616df722232bd1617c4c63ea8d6b692a..59c7edee9da11d552713202e502f3d2e10c67f39 100644 (file)
        updateDisplayedPips();
 </script>
 
+{include file='__devtoolsProjectInstallationJavaScript'}
 {include file='footer'}
index acb0ddfef3e644b430f6d0be2715909393d92c2e..ad5eaa2342575fecb6b01974e6ca500761bf005e 100644 (file)
        <p class="error">{@$object->validate()}</p>
 {/if}
 
+{include file='__devtoolsProjectInstallationJavaScript'}
 {include file='footer'}
diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Acp/Ui/Devtools/Project/Installation/Confirmation.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Acp/Ui/Devtools/Project/Installation/Confirmation.js
new file mode 100644 (file)
index 0000000..787fc49
--- /dev/null
@@ -0,0 +1,68 @@
+/**
+ * Handles installing a project as a package.
+ *
+ * @author     Matthias Schmidt
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module     WoltLabSuite/Core/Acp/Ui/Devtools/Project/Installation/Confirmation
+ */
+define(['Ajax', 'Language', 'Ui/Confirmation'], function(Ajax, Language, UiConfirmation) {
+       "use strict";
+       
+       var _projectId;
+       var _projectName;
+       
+       return {
+               /**
+                * Initializes the confirmation to install a project as a package.
+                * 
+                * @param       {int}           projectId       id of the installed project
+                * @param       {string}        projectName     name of the installed project
+                */
+               init: function(projectId, projectName) {
+                       _projectId = projectId;
+                       _projectName = projectName;
+                       
+                       [].forEach.call(elByClass('jsDevtoolsInstallPackage'), function(element) {
+                               element.addEventListener('click', this._showConfirmation.bind(this));
+                       }.bind(this));
+               },
+               
+               /**
+                * Starts the package installation.
+                */
+               _installPackage: function() {
+                       Ajax.apiOnce({
+                               data: {
+                                       actionName: 'installPackage',
+                                       className: 'wcf\\data\\devtools\\project\\DevtoolsProjectAction',
+                                       objectIDs: [ _projectId ]
+                               },
+                               success: function(data) {
+                                       var packageInstallation = new WCF.ACP.Package.Installation(
+                                               data.returnValues.queueID,
+                                               'DevtoolsInstallPackage',
+                                               data.returnValues.isApplication,
+                                               false,
+                                               {projectID: _projectId}
+                                       );
+                                       
+                                       packageInstallation.prepareInstallation();
+                               }
+                       });
+               },
+               
+               /**
+                * Shows the confirmation to start package installation.
+                */
+               _showConfirmation: function() {
+                       UiConfirmation.show({
+                               confirm: this._installPackage.bind(this),
+                               message: Language.get('wcf.acp.devtools.project.installPackage.confirmMessage', {
+                                       packageIdentifier: _projectName
+                               }),
+                               messageIsHtml: true
+                       });
+               }
+       };
+});
\ No newline at end of file
diff --git a/wcfsetup/install/files/lib/acp/action/DevtoolsInstallPackageAction.class.php b/wcfsetup/install/files/lib/acp/action/DevtoolsInstallPackageAction.class.php
new file mode 100644 (file)
index 0000000..089e6c6
--- /dev/null
@@ -0,0 +1,63 @@
+<?php
+namespace wcf\acp\action;
+use wcf\action\AbstractDialogAction;
+use wcf\data\devtools\project\DevtoolsProject;
+use wcf\data\package\installation\queue\PackageInstallationQueue;
+use wcf\system\devtools\pip\DevtoolsPackageInstallationDispatcher;
+use wcf\system\exception\IllegalLinkException;
+use wcf\system\request\LinkHandler;
+use wcf\util\StringUtil;
+
+/**
+ * Handles an AJAX-based package installation of devtools projects.
+ * 
+ * @author     Matthias Schmidt
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\Acp\Action
+ * @since      3.2
+ */
+class DevtoolsInstallPackageAction extends InstallPackageAction {
+       /**
+        * project whose source is installed as a package
+        * @var DevtoolsProject
+        */
+       public $project;
+       
+       /**
+        * id of the project whose source is installed as a package
+        * @var int
+        */
+       public $projectID;
+       
+       /**
+        * @inheritDoc
+        */
+       protected function getRedirectLink() {
+               return LinkHandler::getInstance()->getLink('DevtoolsProjectList');
+       }
+       
+       /**
+        * @inheritDoc
+        * @throws      IllegalLinkException
+        */
+       public function readParameters() {
+               AbstractDialogAction::readParameters();
+               
+               if (isset($_POST['projectID'])) $this->projectID = intval($_POST['projectID']);
+               $this->project = new DevtoolsProject($this->projectID);
+               if (!$this->project->projectID) {
+                       throw new IllegalLinkException();
+               }
+               
+               if (isset($_POST['node'])) $this->node = StringUtil::trim($_POST['node']);
+               
+               if (isset($_POST['queueID'])) $this->queueID = intval($_POST['queueID']);
+               $this->queue = new PackageInstallationQueue($this->queueID);
+               if (!$this->queue->queueID) {
+                       throw new IllegalLinkException();
+               }
+               
+               $this->installation = new DevtoolsPackageInstallationDispatcher($this->project, $this->queue);
+       }
+}
index 081d76a9ee622f77ff5e43e5db9d2befdb214a02..3634f278daecf3d693f74afba62fb718f6fdbef8 100755 (executable)
@@ -89,27 +89,13 @@ class InstallPackageAction extends AbstractDialogAction {
                                $this->installation->completeSetup();
                                $this->finalize();
                                
-                               // get domain path
-                               $sql = "SELECT  *
-                                       FROM    wcf".WCF_N."_application
-                                       WHERE   packageID = ?";
-                               $statement = WCF::getDB()->prepareStatement($sql);
-                               $statement->execute([1]);
-                               
-                               /** @var Application $application */
-                               $application = $statement->fetchObject(Application::class);
-                               
-                               // build redirect location
-                               // do not use the LinkHandler here as it is sort of unreliable during WCFSetup
-                               $location = $application->getPageURL() . 'acp/index.php?package-list/';
-                               
                                WCF::resetZendOpcache();
                                
                                // show success
                                $this->data = [
                                        'currentAction' => $this->getCurrentAction(null),
                                        'progress' => 100,
-                                       'redirectLocation' => $location,
+                                       'redirectLocation' => $this->getRedirectLink(),
                                        'step' => 'success'
                                ];
                                return;
@@ -128,6 +114,28 @@ class InstallPackageAction extends AbstractDialogAction {
                }
        }
        
+       /**
+        * Returns the link to the page to which the user is redirected after
+        * the installation finished.
+        * 
+        * @return      string
+        * @since       3.2
+        */
+       protected function getRedirectLink() {
+               // get domain path
+               $sql = "SELECT  *
+                       FROM    wcf".WCF_N."_application
+                       WHERE   packageID = ?";
+               $statement = WCF::getDB()->prepareStatement($sql);
+               $statement->execute([1]);
+               
+               /** @var Application $application */
+               $application = $statement->fetchObject(Application::class);
+               
+               // do not use the LinkHandler here as it is sort of unreliable during WCFSetup
+               return $application->getPageURL() . 'acp/index.php?package-list/';
+       }
+       
        /**
         * Prepares the installation process.
         */
@@ -159,9 +167,7 @@ class InstallPackageAction extends AbstractDialogAction {
        }
        
        /**
-        * Returns parameters required to perform a rollback.
-        * 
-        * @return      array
+        * Sets the parameters required to perform a rollback.
         */
        protected function stepRollback() {
                $this->data = [
index 37925787b283339f76222159ad94b7ccd1038912..1eceaf9ca8b8d3ececb0db4769be43bca4f34057 100644 (file)
@@ -63,7 +63,8 @@ class DevtoolsProjectSyncPage extends AbstractPage {
                
                WCF::getTPL()->assign([
                        'objectID' => $this->objectID,
-                       'object' => $this->object
+                       'object' => $this->object,
+                       'project' => $this->object
                ]);
        }
 }
index 6142352e9629338e7a5568c4f9457eae8959fd2c..eece7ad5c433e994c0fad2c6d9febdc8bafd47af 100644 (file)
@@ -2,8 +2,8 @@
 namespace wcf\data\devtools\project;
 use wcf\data\package\installation\plugin\PackageInstallationPluginList;
 use wcf\data\package\Package;
-use wcf\data\package\PackageCache;
 use wcf\data\DatabaseObject;
+use wcf\data\package\PackageList;
 use wcf\system\devtools\package\DevtoolsPackageArchive;
 use wcf\system\devtools\pip\DevtoolsPip;
 use wcf\system\package\validation\PackageValidationException;
@@ -25,6 +25,13 @@ use wcf\util\DirectoryUtil;
  * @property-read      string          $path           file system path
  */
 class DevtoolsProject extends DatabaseObject {
+       /**
+        * is `true` if it has already been attempted to fetch a package
+        * @var         bool
+        * @since       3.2
+        */
+       protected $didFetchPackage = false;
+       
        /**
         * @var boolean
         */
@@ -104,8 +111,7 @@ class DevtoolsProject extends DatabaseObject {
                        return $e->getErrorMessage();
                }
                
-               $this->package = PackageCache::getInstance()->getPackageByIdentifier($this->packageArchive->getPackageInfo('name'));
-               if ($this->package === null) {
+               if ($this->getPackage() === null) {
                        return WCF::getLanguage()->getDynamicVariable('wcf.acp.devtools.project.path.error.notInstalled', [
                                'package' => $this->packageArchive->getPackageInfo('name')
                        ]);
@@ -150,6 +156,18 @@ class DevtoolsProject extends DatabaseObject {
         * @return      Package
         */
        public function getPackage() {
+               if ($this->package === null) {
+                       $packageList = new PackageList();
+                       $packageList->getConditionBuilder()->add('package = ?', [$this->getPackageArchive()->getPackageInfo('name')]);
+                       $packageList->readObjects();
+                       
+                       if (count($packageList)) {
+                               $this->package = $packageList->current();
+                       }
+                       
+                       $this->didFetchPackage = true;
+               }
+               
                return $this->package;
        }
        
@@ -157,6 +175,18 @@ class DevtoolsProject extends DatabaseObject {
         * @return      DevtoolsPackageArchive
         */
        public function getPackageArchive() {
+               if ($this->packageArchive === null) {
+                       $this->packageArchive = new DevtoolsPackageArchive($this->path . ($this->isCore() ? 'com.woltlab.wcf/' : '') . 'package.xml');
+                       
+                       try {
+                               $this->packageArchive->openArchive();
+                       }
+                       catch (PackageValidationException $e) {
+                               // we do not care for errors here, `validatePackageXml()`
+                               // takes care of that
+                       }
+               }
+               
                return $this->packageArchive;
        }
        
@@ -171,6 +201,21 @@ class DevtoolsProject extends DatabaseObject {
                return array_values(DirectoryUtil::getInstance($languageDirectory)->getFiles(SORT_ASC, Regex::compile('\w+\.xml')));
        }
        
+       /**
+        * Sets the package that belongs to this project.
+        * 
+        * @param       Package         $package
+        * @throws      \InvalidArgumentException       if the identifier of the given package does not match
+        * @since       3.2
+        */
+       public function setPackage(Package $package) {
+               if ($package->package !== $this->getPackageArchive()->getPackageInfo('name')) {
+                       throw new \InvalidArgumentException("Package identifier of given package ('{$package->package}') does not match ('{$this->packageArchive->getPackageInfo('name')}')");
+               }
+               
+               $this->package = $package;
+       }
+       
        /**
         * Validates the provided path and returns an error code
         * if the path does not exist (`notFound`) or if there is
index 9c7ee4c1aa8051fe7d1521a8bbdc248072cbebad..af975350ff9e918c90563a0c1e113f0a94955da6 100644 (file)
@@ -1,6 +1,8 @@
 <?php
 namespace wcf\data\devtools\project;
 use wcf\data\AbstractDatabaseObjectAction;
+use wcf\data\package\installation\queue\PackageInstallationQueue;
+use wcf\data\package\installation\queue\PackageInstallationQueueEditor;
 use wcf\system\exception\IllegalLinkException;
 use wcf\system\WCF;
 use wcf\util\DirectoryUtil;
@@ -9,7 +11,7 @@ use wcf\util\FileUtil;
 /**
  * Executes devtools project related actions.
  * 
- * @author     Alexander Ebert
+ * @author     Alexander Ebert, Matthias Schmidt
  * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Devtools\Project
@@ -27,13 +29,20 @@ class DevtoolsProjectAction extends AbstractDatabaseObjectAction {
        /**
         * @inheritDoc
         */
-       protected $requireACP = ['delete'];
+       protected $requireACP = ['delete', 'installPackage'];
        
        /**
         * @inheritDoc
         */
        protected $permissionsDelete = ['admin.configuration.package.canInstallPackage'];
        
+       /**
+        * package installation queue for project to be installed from source
+        * @var         PackageInstallationQueue
+        * @since       3.2
+        */
+       public $queue;
+       
        /**
         * @inheritDoc
         */
@@ -130,4 +139,47 @@ class DevtoolsProjectAction extends AbstractDatabaseObjectAction {
                        ])
                ];
        }
+       
+       /**
+        * Checks if the `installPackage` action can be executed.
+        * 
+        * @throws      IllegalLinkException
+        * @since       3.2
+        */
+       public function validateInstallPackage() {
+               if (!ENABLE_DEVELOPER_TOOLS) {
+                       throw new IllegalLinkException();
+               }
+               
+               WCF::getSession()->checkPermissions(['admin.configuration.package.canInstallPackage']);
+               
+               $this->getSingleObject();
+       }
+       
+       /**
+        * Installs a package that is currently only available as a project.
+        * 
+        * @return      int[]           id of the package installation queue for the
+        * @since       3.2
+        */
+       public function installPackage() {
+               $packageArchive = $this->getSingleObject()->getPackageArchive();
+               $packageArchive->openArchive();
+               
+               $this->queue = PackageInstallationQueueEditor::create([
+                       'processNo' => PackageInstallationQueue::getNewProcessNo(),
+                       'userID' => WCF::getUser()->userID,
+                       'package' => $packageArchive->getPackageInfo('name'),
+                       'packageName' => $packageArchive->getLocalizedPackageInfo('packageName'),
+                       'packageID' => null,
+                       'archive' => '',
+                       'action' => 'install',
+                       'isApplication' => $packageArchive->getPackageInfo('isApplication') ? 1 : 0
+               ]);
+               
+               return [
+                       'isApplication' => $this->queue->isApplication,
+                       'queueID' => $this->queue->queueID
+               ];
+       }
 }
index d3b65d25bd071823bda027858dabc045192a8159..534740d42eae87fa3c493f4c9f4dd15c04da0001 100644 (file)
@@ -1,7 +1,12 @@
 <?php
 namespace wcf\system\devtools\package;
 use wcf\data\devtools\project\DevtoolsProject;
+use wcf\system\package\plugin\ACPTemplatePackageInstallationPlugin;
+use wcf\system\package\plugin\FilePackageInstallationPlugin;
+use wcf\system\package\plugin\TemplatePackageInstallationPlugin;
 use wcf\system\setup\Installer;
+use wcf\util\DirectoryUtil;
+use wcf\util\FileUtil;
 
 /**
  * Specialized implementation to emulate a regular package installation.
@@ -31,6 +36,45 @@ class DevtoolsInstaller extends Installer {
         * @inheritDoc
         */
        public function getTar($source) {
-               return $this->project->getPackageArchive()->getTar();
+               $directory = null;
+               
+               foreach ($this->project->getPackageArchive()->getInstallInstructions() as $instruction) {
+                       $archive = null;
+                       switch ($instruction['pip']) {
+                               case 'acpTemplate':
+                                       $archive = $instruction['value'] ?: ACPTemplatePackageInstallationPlugin::getDefaultFilename();
+                                       break;
+                               
+                               case 'file':
+                                       $archive = $instruction['value'] ?: FilePackageInstallationPlugin::getDefaultFilename();
+                                       break;
+                               
+                               case 'template':
+                                       $archive = $instruction['value'] ?: TemplatePackageInstallationPlugin::getDefaultFilename();
+                                       break;
+                       }
+                       
+                       if ($archive !== null) {
+                               $directory = FileUtil::addTrailingSlash($this->project->path . pathinfo($archive, PATHINFO_FILENAME));
+                               if ($source == $archive && is_dir($directory)) {
+                                       $files = $this->project->getPackageArchive()->getTar()->getFiles();
+                                       
+                                       foreach ($this->project->getPips() as $pip) {
+                                               if ($pip->pluginName === $instruction['pip']) {
+                                                       $pip->getInstructions($this->project, $source);
+                                                       
+                                                       $tar = new DevtoolsTar($this->project->getPackageArchive()->getTar()->getFiles());
+                                                       
+                                                       $this->project->getPackageArchive()->getTar()->setFiles($files);
+                                                       
+                                                       return $tar;
+                                               }
+                                       }
+                               }
+                       }
+                       
+               }
+               
+               throw new \InvalidArgumentException("Unknown file '{$source}'");
        }
 }
index 4f49ec94cea90f8b282d726ab6df62d9f404d11d..641465a8c3e1f8f3ba59d583658d1ffc7765d63c 100644 (file)
@@ -1,6 +1,12 @@
 <?php
 namespace wcf\system\devtools\package;
+use wcf\system\package\plugin\ACPTemplatePackageInstallationPlugin;
+use wcf\system\package\plugin\FilePackageInstallationPlugin;
+use wcf\system\package\plugin\TemplatePackageInstallationPlugin;
 use wcf\system\package\PackageArchive;
+use wcf\system\Regex;
+use wcf\util\DirectoryUtil;
+use wcf\util\FileUtil;
 
 /**
  * Specialized implementation to emulate a regular package installation.
@@ -26,15 +32,52 @@ class DevtoolsPackageArchive extends PackageArchive {
         * @inheritDoc
         */
        public function openArchive() {
-               $this->tar = new DevtoolsTar(['package.xml' => $this->packageXmlPath]);
+               $projectDir = FileUtil::addTrailingSlash(dirname($this->packageXmlPath));
+               
+               $readFiles = DirectoryUtil::getInstance($projectDir)->getFiles(
+                       SORT_ASC,
+                       // ignore folders whose contents are delivered as archives by default
+                       // and ignore dotfiles and dotdirectories
+                       Regex::compile('^' . preg_quote($projectDir) . '(acptemplates|files|templates|\.)'), 
+                       true
+               );
+               
+               $files = [];
+               foreach ($readFiles as $file) {
+                       if (is_file($file)) {
+                               $files[str_replace($projectDir, '', $file)] = $file;
+                       }
+               }
+               
+               $this->tar = new DevtoolsTar($files);
                
                $this->readPackageInfo();
+               foreach ($this->getInstallInstructions() as $instruction) {
+                       $archive = null;
+                       switch ($instruction['pip']) {
+                               case 'acpTemplate':
+                                       $archive = $instruction['value'] ?: ACPTemplatePackageInstallationPlugin::getDefaultFilename();
+                                       break;
+                               
+                               case 'file':
+                                       $archive = $instruction['value'] ?: FilePackageInstallationPlugin::getDefaultFilename();
+                                       break;
+                               
+                               case 'template':
+                                       $archive = $instruction['value'] ?: TemplatePackageInstallationPlugin::getDefaultFilename();
+                                       break;
+                       }
+                       
+                       if ($archive !== null) {
+                               $this->tar->registerFile($archive, $projectDir . $archive);
+                       }
+               }
        }
        
        /**
         * @inheritDoc
         */
        public function extractTar($filename, $tempPrefix = 'package_') {
-               return $tempPrefix . $filename . '_dummy';
+               return $filename;
        }
 }
index 781713ec8ffbf78051307d7944fa34ebb9ef6a18..54000cfc314992793e65f51cae930c722fab4ce2 100644 (file)
@@ -95,4 +95,22 @@ class DevtoolsTar extends Tar {
                
                return $this->contentList;
        }
+       
+       /**
+        * Returns all files in the virtual file list.
+        * 
+        * @return      string[]
+        */
+       public function getFiles() {
+               return $this->files;
+       }
+       
+       /**
+        * Sets all files in the virtual file list.
+        * 
+        * @param       string[]        $files
+        */
+       public function setFiles(array $files) {
+               $this->files = $files;
+       }
 }
index 61dfcb84bedc5bc3a476f44b7cae8aa1a1db2c8e..5ad14f732d803b456b362eb7a9fab034610e9c9b 100644 (file)
@@ -1,8 +1,10 @@
 <?php
 namespace wcf\system\devtools\pip;
 use wcf\data\devtools\project\DevtoolsProject;
+use wcf\data\package\installation\queue\PackageInstallationQueue;
 use wcf\system\devtools\package\DevtoolsInstaller;
 use wcf\system\package\PackageInstallationDispatcher;
+use wcf\system\package\PackageInstallationNodeBuilder;
 
 /**
  * Specialized implementation to emulate a regular package installation.
@@ -22,12 +24,31 @@ class DevtoolsPackageInstallationDispatcher extends PackageInstallationDispatche
        /**
         * @inheritDoc
         */
-       public function __construct(DevtoolsProject $project) {
-               parent::__construct(new DevtoolsPackageInstallationQueue($project));
+       public function __construct(DevtoolsProject $project, PackageInstallationQueue $queue = null) {
+               $this->queue = $queue;
+               $this->nodeBuilder = new class($this) extends PackageInstallationNodeBuilder {
+                       protected function buildOptionalNodes() {
+                               // does nothing; optional packages are not supported
+                       }
+               };
+               
+               $this->action = $this->queue->action;
                
                $this->project = $project;
        }
        
+       /**
+        * @inheritDoc
+        * @since       3.2
+        */
+       protected function createPackage(array $packageData) {
+               $package = parent::createPackage($packageData);
+               
+               $this->project->setPackage($package);
+               
+               return $package;
+       }
+       
        /**
         * @inheritDoc
         */
index 20951dac4f27200481df84605f0b8e6bf66840d6..007a3ff1f10342d7850e6626a992247647aa386f 100644 (file)
@@ -446,7 +446,7 @@ class PackageInstallationDispatcher {
                }
                else {
                        // create package entry
-                       $package = PackageEditor::create($nodeData);
+                       $package = $this->createPackage($nodeData);
                        
                        // update package id for current queue
                        $queueEditor = new PackageInstallationQueueEditor($this->queue);
@@ -533,6 +533,17 @@ class PackageInstallationDispatcher {
                return $installationStep;
        }
        
+       /**
+        * Creates a new package based on the given data and returns it.
+        * 
+        * @param       array   $packageData
+        * @return      Package
+        * @since       3.2
+        */
+       protected function createPackage(array $packageData) {
+               return PackageEditor::create($packageData);
+       }
+       
        /**
         * Saves the localized package info.
         */
index d9a091097d87243141fdc6e23729a2c0a5b7ce96..b8e5934aba5a9182530f7deaa40bb20dac84b216 100644 (file)
                <item name="wcf.acp.devtools.project.name.error.notUnique"><![CDATA[Der Name wird bereits von einem anderen Projekt verwendet.]]></item>
                <item name="wcf.acp.devtools.project.path"><![CDATA[Pfad]]></item>
                <item name="wcf.acp.devtools.project.path.error.missingCompatibility"><![CDATA[Das Paket verfügt über keine Angaben zur API-Kompatibilität.]]></item>
-               <item name="wcf.acp.devtools.project.path.error.notInstalled"><![CDATA[Das Paket muss bereits installiert sein.]]></item>
+               <item name="wcf.acp.devtools.project.path.error.notInstalled"><![CDATA[Das Paket wurde noch nicht installiert. <a href="#" {if $project->getPackageArchive()->getOpenRequirements()|empty}class="jsDevtoolsInstallPackage"{else}class="jsStaticDialog" data-dialog-id="openPackageRequirements"{/if}>{if LANGUAGE_USE_INFORMAL_VARIANT}Willst du{else}Wollen Sie{/if} das Paket installieren?</a>]]></item>
                <item name="wcf.acp.devtools.project.path.error.notFound"><![CDATA[Der Pfad ist ungültig.]]></item>
                <item name="wcf.acp.devtools.project.path.error.notUnique"><![CDATA[Der Pfad wird bereits von einem anderen Projekt verwendet.]]></item>
                <item name="wcf.acp.devtools.project.path.error.packageXml"><![CDATA[Unter dem angegebenen Pfad konnte keine gültige <kbd>package.xml</kbd> gefunden werden.]]></item>
                <item name="wcf.acp.devtools.project.pips"><![CDATA[PIPs]]></item>
                <item name="wcf.acp.devtools.pip.showGuiSupportingPipsOnly"><![CDATA[Zeige nur PIPs mit GUI-Unterstützung an]]></item>
                <item name="wcf.acp.devtools.pip.showGuiSupportingPipsOnly.description"><![CDATA[Es werden nur PIPs angeboten, die die Verwaltung von Einträgen mittels einer grafischen Benutzeroberfläche unterstützen.]]></item>
+               <item name="wcf.acp.devtools.project.installPackage.confirmMessage"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Willst du{else}Wollen Sie{/if} das Paket <span class="confirmationObject">{@$packageIdentifier}</span> wirklich installieren?]]></item>
+               <item name="wcf.acp.devtools.project.installPackage.error.openRequirements"><![CDATA[Das Projekt kann nicht installiert werden, weil die folgenden Paketen fehlen:]]></item>
+               <item name="wcf.acp.devtools.project.installPackage.openRequirement"><![CDATA[{$openPackage} (Version {$openRequirement[minversion]})]]></item>
+               <item name="wcf.acp.devtools.project.installPackage.error.openRequirements.title"><![CDATA[Fehlende Pakete]]></item>
        </category>
        <category name="wcf.acp.email">
                <item name="wcf.acp.email.smtp.test"><![CDATA[SMTP-Verbindungstest]]></item>
index d2b08aae0c511b504de4becd9354855517452d4a..b19bfa1b7abcda954b729f1c5e9e748907be7a6f 100644 (file)
                <item name="wcf.acp.devtools.project.name.error.notUnique"><![CDATA[The name is already used by another project.]]></item>
                <item name="wcf.acp.devtools.project.path"><![CDATA[Path]]></item>
                <item name="wcf.acp.devtools.project.path.error.missingCompatibility"><![CDATA[This package does not contain any data on API compatibility.]]></item>
-               <item name="wcf.acp.devtools.project.path.error.notInstalled"><![CDATA[The package must be installed already.]]></item>
+               <item name="wcf.acp.devtools.project.path.error.notInstalled"><![CDATA[The package has not been installed yet. <a href="#" {if $project->getPackageArchive()->getOpenRequirements()|empty}class="jsDevtoolsInstallPackage"{else}class="jsStaticDialog" data-dialog-id="openPackageRequirements"{/if}>Do you want to install the package?</a>]]></item>
                <item name="wcf.acp.devtools.project.path.error.notFound"><![CDATA[The path is invalid.]]></item>
                <item name="wcf.acp.devtools.project.path.error.notUnique"><![CDATA[The path is already used by another project.]]></item>
                <item name="wcf.acp.devtools.project.path.error.packageXml"><![CDATA[The path does not contain a valid <kbd>package.xml</kbd>.]]></item>
                <item name="wcf.acp.devtools.project.pips"><![CDATA[PIPs]]></item>
                <item name="wcf.acp.devtools.pip.showGuiSupportingPipsOnly"><![CDATA[Show PIPs supporting GUI only]]></item>
                <item name="wcf.acp.devtools.pip.showGuiSupportingPipsOnly.description"><![CDATA[Show only PIPs that support managing entries via a graphical user interface.]]></item>
+               <item name="wcf.acp.devtools.project.installPackage.confirmMessage"><![CDATA[Do you really want to install the package <span class="confirmationObject">{@$packageIdentifier}</span>?]]></item>
+               <item name="wcf.acp.devtools.project.installPackage.error.openRequirements"><![CDATA[The project cannot be installed because the following packages are missing:]]></item>
+               <item name="wcf.acp.devtools.project.installPackage.openRequirement"><![CDATA[{$openPackage} (Version {$openRequirement[minversion]})]]></item>
+               <item name="wcf.acp.devtools.project.installPackage.error.openRequirements.title"><![CDATA[Missing Packages]]></item>
        </category>
        <category name="wcf.acp.email">
                <item name="wcf.acp.email.smtp.test"><![CDATA[SMTP Connection Test]]></item>