*/
_actionName: 'InstallPackage',
+ /**
+ * additional parameters send in all requests
+ * @var object
+ */
+ _additionalRequestParameters: {},
+
/**
* true, if rollbacks are supported
* @var boolean
* @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();
* @return object
*/
_getParameters: function() {
- return {
+ return $.extend({}, this._additionalRequestParameters, {
queueID: this._queueID,
step: 'prepare'
- };
+ });
},
/**
_executeStep: function(step, node, additionalData) {
if (!additionalData) additionalData = { };
- var $data = $.extend({
+ var $data = $.extend({}, this._additionalRequestParameters, {
node: node,
queueID: this._queueID,
step: step
--- /dev/null
+{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}
updateDisplayedPips();
</script>
+{include file='__devtoolsProjectInstallationJavaScript'}
{include file='footer'}
<p class="error">{@$object->validate()}</p>
{/if}
+{include file='__devtoolsProjectInstallationJavaScript'}
{include file='footer'}
--- /dev/null
+/**
+ * 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
--- /dev/null
+<?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);
+ }
+}
$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;
}
}
+ /**
+ * 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.
*/
}
/**
- * Returns parameters required to perform a rollback.
- *
- * @return array
+ * Sets the parameters required to perform a rollback.
*/
protected function stepRollback() {
$this->data = [
WCF::getTPL()->assign([
'objectID' => $this->objectID,
- 'object' => $this->object
+ 'object' => $this->object,
+ 'project' => $this->object
]);
}
}
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;
* @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
*/
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')
]);
* @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;
}
* @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;
}
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
<?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;
/**
* 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
/**
* @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
*/
])
];
}
+
+ /**
+ * 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
+ ];
+ }
}
<?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.
* @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}'");
}
}
<?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.
* @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;
}
}
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;
+ }
}
<?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.
/**
* @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
*/
}
else {
// create package entry
- $package = PackageEditor::create($nodeData);
+ $package = $this->createPackage($nodeData);
// update package id for current queue
$queueEditor = new PackageInstallationQueueEditor($this->queue);
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.
*/
<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>
<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>