$(function() {
new WCF.Action.Delete('wcf\\data\\devtools\\project\\DevtoolsProjectAction', '.jsObjectRow');
});
+
+ require(['WoltLabSuite/Core/Acp/Ui/Devtools/Project/QuickSetup', 'Language'], function(AcpUiDevtoolsProjectQuickSetup, Language) {
+ Language.add('wcf.acp.devtools.project.quickSetup', '{lang}wcf.acp.devtools.project.quickSetup{/lang}');
+
+ AcpUiDevtoolsProjectQuickSetup.init();
+ });
</script>
<header class="contentHeader">
<nav class="contentHeaderNavigation">
<ul>
+ <li><a href="#" class="button jsDevtoolsProjectQuickSetupButton"><span class="icon icon16 fa-search"></span> <span>{lang}wcf.acp.devtools.project.quickSetup{/lang}</span></a></li>
<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'}
</nav>
</footer>
+<div id="projectQuickSetup" style="display: none;">
+ <dl>
+ <dt>{lang}wcf.acp.devtools.project.quickSetup.path{/lang}</dt>
+ <dd>
+ <input type="text" name="projectQuickSetupPath" id="projectQuickSetupPath" class="long" />
+ <small>{lang}wcf.acp.devtools.project.quickSetup.path.description{/lang}</small>
+ </dd>
+ </dl>
+
+ <div class="formSubmit">
+ <button id="projectQuickSetupSubmit" class="buttonPrimary">{lang}wcf.global.button.submit{/lang}</button>
+ </div>
+</div>
+
{include file='footer'}
--- /dev/null
+/**
+ * Handles quick setup of all projects within a path.
+ *
+ * @author Matthias Schmidt
+ * @copyright 2001-2017 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module WoltLabSuite/Core/Acp/Ui/Devtools/Project/QuickSetup
+ */
+define([
+ 'Ajax',
+ 'Dom/Traverse',
+ 'Dom/Util',
+ 'EventKey',
+ 'Language',
+ 'Ui/Dialog',
+ 'Ui/Notification'
+], function (
+ Ajax,
+ DomTraverse,
+ DomUtil,
+ EventKey,
+ Language,
+ UiDialog,
+ UiNotification
+) {
+ "use strict";
+
+ var _setupButtons = elByClass('jsDevtoolsProjectQuickSetupButton');
+ var _submitButton = elById('projectQuickSetupSubmit');
+ var _pathInput = elById('projectQuickSetupPath');
+
+ return {
+ /**
+ * Initializes the project quick setup handler.
+ */
+ init: function() {
+ // add event listeners to open dialog
+ Array.prototype.forEach.call(_setupButtons, function(button) {
+ button.addEventListener('click', this._showDialog.bind(this));
+ }.bind(this));
+
+ // add event listener to submit dialog
+ _submitButton.addEventListener('click', this._submit.bind(this));
+
+ // add event listener to input field to submit dialog by pressing enter
+ _pathInput.addEventListener('keypress', this._keyPress.bind(this));
+ },
+
+ /**
+ * Returns the data used to setup the AJAX request object.
+ *
+ * @return {object} setup data
+ */
+ _ajaxSetup: function () {
+ return {
+ data: {
+ actionName: 'quickSetup',
+ className: 'wcf\\data\\devtools\\project\\DevtoolsProjectAction'
+ }
+ };
+ },
+
+ /**
+ * Handles successful AJAX request.
+ *
+ * @param {object} data response data
+ */
+ _ajaxSuccess: function(data) {
+ if (data.returnValues.errorMessage) {
+ this._showPathError(data.returnValues.errorMessage);
+
+ _submitButton.disabled = false;
+
+ return;
+ }
+
+ UiDialog.close(this);
+
+ UiNotification.show(data.returnValues.successMessage, function() {
+ window.location.reload();
+ });
+ },
+
+ /**
+ * Returns the data used to setup the dialog.
+ *
+ * @return {object} setup data
+ */
+ _dialogSetup: function() {
+ return {
+ id: 'projectQuickSetup',
+ options: {
+ onShow: this._onDialogShow.bind(this),
+ title: Language.get('wcf.acp.devtools.project.quickSetup')
+ }
+ };
+ },
+
+ /**
+ * Returns the error element for the path input in the dialog or
+ * `null` if no exists yet.
+ *
+ * @param {boolean} createPathError if `true` and inner error element does not exist, it will be created
+ * @return {HTMLElement?} path error element
+ */
+ _getPathError: function(createPathError) {
+ var innerError = DomTraverse.nextByClass(_pathInput, 'innerError');
+
+ if (createPathError && innerError === null) {
+ innerError = elCreate('small');
+ innerError.classList = 'innerError';
+
+ DomUtil.insertAfter(innerError, _pathInput);
+ }
+
+ return innerError;
+ },
+
+ /**
+ * Handles the `[ENTER]` key to submit the form.
+ *
+ * @param {object} event event object
+ */
+ _keyPress: function(event) {
+ if (EventKey.Enter(event)) {
+ this._submit();
+ }
+ },
+
+ /**
+ * Is called every time the dialog is shown.
+ */
+ _onDialogShow: function() {
+ // reset path input
+ _pathInput.value = '';
+ _pathInput.focus();
+
+ // hide error
+ var innerError = this._getPathError();
+ if (innerError) {
+ elHide(innerError);
+ }
+ },
+
+ /**
+ * Shows the dialog after clicking on the related button.
+ */
+ _showDialog: function() {
+ UiDialog.open(this);
+ },
+
+ /**
+ * Shows the path error message.
+ *
+ * @param {string} errorMessage path error emssage
+ */
+ _showPathError: function(errorMessage) {
+ var innerError = this._getPathError(true);
+ innerError.textContent = errorMessage;
+
+ elShow(innerError);
+ },
+
+ /**
+ * Is called if the dialog form is submitted.
+ */
+ _submit: function() {
+ // check if path is empty
+ if (_pathInput.value === '') {
+ this._showPathError(Language.get('wcf.global.form.error.empty'));
+
+ return;
+ }
+
+ Ajax.api(this, {
+ parameters: {
+ path: _pathInput.value
+ }
+ });
+
+ _submitButton.disabled = true;
+ }
+ };
+});
\ No newline at end of file
namespace wcf\data\devtools\project;
use wcf\data\AbstractDatabaseObjectAction;
use wcf\system\exception\IllegalLinkException;
+use wcf\system\WCF;
+use wcf\util\DirectoryUtil;
+use wcf\util\FileUtil;
/**
* Executes devtools project related actions.
parent::validateDelete();
}
+
+ /**
+ * Validates the 'quickSetup' action.
+ *
+ * @throws IllegalLinkException
+ */
+ public function validateQuickSetup() {
+ if (!ENABLE_DEVELOPER_TOOLS) {
+ throw new IllegalLinkException();
+ }
+
+ WCF::getSession()->checkPermissions(['admin.configuration.package.canInstallPackage']);
+
+ $this->readString('path');
+ }
+
+ /**
+ * Quickly setups multiple projects by scanning a directory.
+ *
+ * @return array
+ */
+ public function quickSetup() {
+ if (!is_dir($this->parameters['path'])) {
+ return [
+ 'errorMessage' => WCF::getLanguage()->get('wcf.acp.devtools.project.path.error.notFound'),
+ 'errorType' => 'notFound'
+ ];
+ }
+
+ $path = FileUtil::addTrailingSlash(FileUtil::unifyDirSeparator($this->parameters['path']));
+
+ // read all project names and paths
+ $projectList = new DevtoolsProjectList();
+ $projectList->readObjects();
+
+ $projectNames = $projectPaths = [];
+ foreach ($projectList as $project) {
+ $projectNames[] = $project->name;
+ $projectPaths[] = $project->path;
+ }
+
+ $projectCount = 0;
+
+ $directoryUtil = DirectoryUtil::getInstance($path, false);
+ $directoryUtil->executeCallback(function($directory) use ($path, $projectNames, $projectPaths, &$projectCount) {
+ $projectPath = $path . $directory . '/';
+
+ // validate path
+ if (DevtoolsProject::validatePath($projectPath) !== '') {
+ return;
+ }
+
+ // only consider paths that are not already used by a different project
+ if (in_array($projectPath, $projectPaths)) {
+ return;
+ }
+
+ // make sure that project name is unique
+ $name = $directory;
+
+ $iteration = 1;
+ while (in_array($name, $projectNames)) {
+ $name = $directory . ' (' . $iteration++ . ')';
+ }
+
+ (new DevtoolsProjectAction([], 'create', ['data' => [
+ 'name' => $name,
+ 'path' => $projectPath
+ ]]))->executeAction();
+
+ $projectCount++;
+ });
+
+ if (!$projectCount) {
+ return [
+ 'errorMessage' => WCF::getLanguage()->get('wcf.acp.devtools.project.quickSetup.path.error.noPackages'),
+ 'errorType' => 'noPackages'
+ ];
+ }
+
+ return [
+ 'successMessage' => WCF::getLanguage()->getDynamicVariable('wcf.acp.devtools.project.quickSetup.success', [
+ 'count' => $projectCount
+ ])
+ ];
+ }
}
<item name="wcf.acp.devtools.sync.status.idle"><![CDATA[Bereit.]]></item>
<item name="wcf.acp.devtools.sync.status.success"><![CDATA[{$timeElapsed}s ({@TIME_NOW|time})]]></item>
<item name="wcf.acp.devtools.sync.syncAll"><![CDATA[Alles Abgleichen]]></item>
+ <item name="wcf.acp.devtools.project.quickSetup"><![CDATA[Pfad durchsuchen]]></item>
+ <item name="wcf.acp.devtools.project.quickSetup.path"><![CDATA[Pfad]]></item>
+ <item name="wcf.acp.devtools.project.quickSetup.path.description"><![CDATA[Alle Ordner, die sich direkt in dem angegebenen Pfad befinden, werden überprüft, ob sie ein Paket enthalten.]]></item>
+ <item name="wcf.acp.devtools.project.quickSetup.path.error.noPackages"><![CDATA[Der Pfad enthält keine neuen Pakete.]]></item>
+ <item name="wcf.acp.devtools.project.quickSetup.success"><![CDATA[Es {if $count > 1}}wurden {#$count} neue Projekte{else}ein neues Projekt{/if} hinzugefügt.]]></item>
</category>
<category name="wcf.acp.email">
<item name="wcf.acp.devtools.sync.status.idle"><![CDATA[Ready.]]></item>
<item name="wcf.acp.devtools.sync.status.success"><![CDATA[{$timeElapsed}s ({@TIME_NOW|time})]]></item>
<item name="wcf.acp.devtools.sync.syncAll"><![CDATA[Sync All]]></item>
+ <item name="wcf.acp.devtools.project.quickSetup"><![CDATA[Search Path]]></item>
+ <item name="wcf.acp.devtools.project.quickSetup.path"><![CDATA[Path]]></item>
+ <item name="wcf.acp.devtools.project.quickSetup.path.description"><![CDATA[All folders directly contained in the path will be checked whether they contain a package.]]></item>
+ <item name="wcf.acp.devtools.project.quickSetup.path.error.noPackages"><![CDATA[The path contains no new packages.]]></item>
+ <item name="wcf.acp.devtools.project.quickSetup.success"><![CDATA[{#$count} new project{if $count > 1}s have{else} has{/if} been added.]]></item>
</category>
<category name="wcf.acp.email">