3 namespace wcf\acp\form
;
5 use wcf\data\AbstractDatabaseObjectAction
;
6 use wcf\data\devtools\project\DevtoolsProject
;
7 use wcf\data\devtools\project\DevtoolsProjectAction
;
8 use wcf\data\devtools\project\DevtoolsProjectList
;
9 use wcf\data\package\installation\plugin\PackageInstallationPlugin
;
10 use wcf\data\package\installation\plugin\PackageInstallationPluginList
;
11 use wcf\data\package\Package
;
12 use wcf\form\AbstractForm
;
13 use wcf\form\AbstractFormBuilderForm
;
14 use wcf\system\devtools\package\DevtoolsPackageXmlWriter
;
15 use wcf\system\form\builder\container\FormContainer
;
16 use wcf\system\form\builder\container\TabFormContainer
;
17 use wcf\system\form\builder\container\TabMenuFormContainer
;
18 use wcf\system\form\builder\field\BooleanFormField
;
19 use wcf\system\form\builder\field\DateFormField
;
20 use wcf\system\form\builder\field\dependency\NonEmptyFormFieldDependency
;
21 use wcf\system\form\builder\field\dependency\ValueFormFieldDependency
;
22 use wcf\system\form\builder\field\devtools\project\DevtoolsProjectExcludedPackagesFormField
;
23 use wcf\system\form\builder\field\devtools\project\DevtoolsProjectInstructionsFormField
;
24 use wcf\system\form\builder\field\devtools\project\DevtoolsProjectOptionalPackagesFormField
;
25 use wcf\system\form\builder\field\devtools\project\DevtoolsProjectRequiredPackagesFormField
;
26 use wcf\system\form\builder\field\MultipleSelectionFormField
;
27 use wcf\system\form\builder\field\RadioButtonFormField
;
28 use wcf\system\form\builder\field\TextFormField
;
29 use wcf\system\form\builder\field\UrlFormField
;
30 use wcf\system\form\builder\field\validation\FormFieldValidationError
;
31 use wcf\system\form\builder\field\validation\FormFieldValidator
;
32 use wcf\system\form\builder\field\validation\FormFieldValidatorUtil
;
33 use wcf\system\package\plugin\AbstractXMLPackageInstallationPlugin
;
36 use wcf\util\DirectoryUtil
;
37 use wcf\util\FileUtil
;
40 * Shows the devtools project add form.
42 * @author Alexander Ebert, Matthias Schmidt
43 * @copyright 2001-2019 WoltLab GmbH
44 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
45 * @package WoltLabSuite\Core\Acp\Form
48 * @property null|DevtoolsProject $formObject
50 class DevtoolsProjectAddForm
extends AbstractFormBuilderForm
55 public $activeMenuItem = 'wcf.acp.menu.link.devtools.project.add';
60 public $formAction = 'create';
65 public $neededPermissions = ['admin.configuration.package.canInstallPackage'];
70 public $objectActionClass = DevtoolsProjectAction
::class;
73 * newly added or edited project
74 * @var DevtoolsProject
79 * ids of the fields containing object data
82 public $projectFields = ['name', 'path'];
87 protected function createForm()
91 $tabMenu = TabMenuFormContainer
::create('project');
92 $this->form
->appendChild($tabMenu);
94 $dataTab = TabFormContainer
::create('dataTab');
95 $dataTab->label('wcf.global.form.data');
96 $tabMenu->appendChild($dataTab);
98 $mode = RadioButtonFormField
::create('mode')
99 ->label('wcf.acp.devtools.project.add.mode')
100 ->options(function () {
102 'import' => 'wcf.acp.devtools.project.add.mode.import',
103 'setup' => 'wcf.acp.devtools.project.add.mode.setup',
106 if ($this->formObject
!== null) {
107 $options['edit'] = 'wcf.acp.devtools.project.add.mode.edit';
112 ->immutable($this->formObject
!== null)
113 ->value($this->formObject ?
'edit' : 'import');
115 $dataContainer = FormContainer
::create('data')
116 ->label('wcf.global.form.data')
119 TextFormField
::create('name')
120 ->label('wcf.acp.devtools.project.name')
122 ->addValidator(new FormFieldValidator('uniqueness', function (TextFormField
$formField) {
123 $name = $formField->getSaveValue();
125 if ($this->formObject
=== null ||
$this->formObject
->name
!== $name) {
126 $projectList = new DevtoolsProjectList();
127 $projectList->getConditionBuilder()->add('name = ?', [$name]);
129 if ($projectList->countObjects() > 0) {
130 $formField->addValidationError(
131 new FormFieldValidationError(
133 'wcf.acp.devtools.project.name.error.notUnique'
140 TextFormField
::create('path')
141 ->label('wcf.acp.devtools.project.path')
143 ->addValidator(new FormFieldValidator('validPath', function (TextFormField
$formField) {
144 // ensure that unified directory separators are used
145 // and that there is a trailing slash
147 FileUtil
::addTrailingSlash(
148 FileUtil
::unifyDirSeparator($formField->getSaveValue() ??
'')
152 $path = $formField->getSaveValue();
154 /** @var RadioButtonFormField $modeField */
155 $modeField = $formField->getDocument()->getNodeById('mode');
157 switch ($modeField->getSaveValue()) {
160 if ($this->formObject
=== null ||
$this->formObject
->path
!== $path) {
161 $errorType = DevtoolsProject
::validatePath($path);
162 if ($errorType !== '') {
163 $formField->addValidationError(
164 new FormFieldValidationError(
166 'wcf.acp.devtools.project.path.error.' . $errorType
175 if (\
is_dir($path)) {
176 $formField->addValidationError(
177 new FormFieldValidationError(
179 'wcf.acp.devtools.project.path.error.pathExists'
182 } elseif (!\
is_dir(\
dirname($path))) {
183 $formField->addValidationError(
184 new FormFieldValidationError(
185 'parentDoesNotExist',
186 'wcf.acp.devtools.project.path.error.parentDoesNotExist'
190 if (!FileUtil
::makePath($path)) {
191 $formField->addValidationError(
192 new FormFieldValidationError(
193 'cannotMakeDirectory',
194 'wcf.acp.devtools.project.path.error.cannotMakeDirectory'
198 // remove directory for now again
206 throw new \
LogicException("Unknown mode '{$modeField->getSaveValue()}'.");
210 ->addValidator(new FormFieldValidator('uniqueness', function (TextFormField
$formField) {
211 $path = $formField->getSaveValue();
213 if ($this->formObject
=== null ||
$this->formObject
->path
!== $path) {
214 $projectList = new DevtoolsProjectList();
215 $projectList->getConditionBuilder()->add('path = ?', [$path]);
217 if ($projectList->countObjects() > 0) {
218 $formField->addValidationError(
219 new FormFieldValidationError(
221 'wcf.acp.devtools.project.path.error.notUnique'
228 $dataTab->appendChild($dataContainer);
230 $packageInformation = FormContainer
::create('packageInformation')
231 ->label('wcf.acp.devtools.project.packageInformation')
233 TextFormField
::create('packageIdentifier')
234 ->label('wcf.acp.devtools.project.packageIdentifier')
235 ->description('wcf.acp.devtools.project.packageIdentifier.description')
238 ->addValidator(new FormFieldValidator('format', static function (TextFormField
$formField) {
239 if (!Package
::isValidPackageName($formField->getSaveValue())) {
240 $formField->addValidationError(
241 new FormFieldValidationError(
243 'wcf.acp.devtools.project.packageIdentifier.error.format'
249 TextFormField
::create('packageName')
250 ->label('wcf.acp.devtools.project.packageName')
254 ->languageItemPattern('__NONE__'),
256 TextFormField
::create('packageDescription')
257 ->label('wcf.global.description')
261 ->languageItemPattern('__NONE__'),
263 BooleanFormField
::create('isApplication')
264 ->label('wcf.acp.devtools.project.isApplication')
265 ->description('wcf.acp.devtools.project.isApplication.description'),
267 TextFormField
::create('applicationDirectory')
268 ->label('wcf.acp.devtools.project.applicationDirectory')
269 ->description('wcf.acp.devtools.project.applicationDirectory.description')
270 ->available($this->formObject
=== null ||
!$this->formObject
->isCore())
271 ->addValidator(FormFieldValidatorUtil
::getRegularExpressionValidator(
273 'wcf.acp.devtools.project.applicationDirectory'
276 TextFormField
::create('version')
277 ->label('wcf.acp.devtools.project.packageVersion')
278 ->description('wcf.acp.devtools.project.packageVersion.description')
280 ->maximumLength(255),
282 DateFormField
::create('date')
283 ->label('wcf.acp.devtools.project.packageDate')
284 ->description('wcf.acp.devtools.project.packageDate.description')
286 ->saveValueFormat('Y-m-d'),
288 UrlFormField
::create('packageUrl')
289 ->label('wcf.acp.devtools.project.packageUrl')
290 ->description('wcf.acp.devtools.project.packageUrl.description')
291 ->maximumLength(255),
293 TextFormField
::create('license')
294 ->label('wcf.acp.devtools.project.license')
297 ->languageItemPattern('__NONE__'),
300 ValueFormFieldDependency
::create('mode')
302 ->values(['edit', 'setup'])
304 $dataTab->appendChild($packageInformation);
306 /** @var BooleanFormField $isApplication */
307 $isApplication = $packageInformation->getNodeById('isApplication');
308 $packageInformation->getNodeById('applicationDirectory')
310 NonEmptyFormFieldDependency
::create('isApplication')
311 ->field($isApplication)
314 $authorInformation = FormContainer
::create('authorInformation')
315 ->label('wcf.acp.devtools.project.authorInformation')
317 TextFormField
::create('author')
318 ->label('wcf.acp.devtools.project.author')
320 ->maximumLength(255),
322 UrlFormField
::create('authorUrl')
323 ->label('wcf.acp.devtools.project.authorUrl')
324 ->maximumLength(255),
327 ValueFormFieldDependency
::create('mode')
329 ->values(['edit', 'setup'])
331 $dataTab->appendChild($authorInformation);
333 $compatibility = FormContainer
::create('compatibility')
334 ->label('wcf.acp.devtools.project.compatibility')
336 MultipleSelectionFormField
::create('apiVersions')
337 ->label('wcf.acp.devtools.project.apiVersions')
338 ->description('wcf.acp.devtools.project.apiVersions.description')
339 ->options(static function () {
340 $apiVersions = \array_filter
(\array_merge
(
341 WCF
::getSupportedLegacyApiVersions(),
343 ), static function ($value) {
344 return $value !== 2017;
349 return \array_combine
($apiVersions, $apiVersions);
351 ->available($this->formObject
=== null ||
!$this->formObject
->isCore()),
354 ValueFormFieldDependency
::create('mode')
356 ->values(['edit', 'setup'])
358 $dataTab->appendChild($compatibility);
360 $requiredPackages = FormContainer
::create('requiredPackagesContainer')
361 ->label('wcf.acp.devtools.project.requiredPackages')
362 ->description('wcf.acp.devtools.project.requiredPackages.description')
364 DevtoolsProjectRequiredPackagesFormField
::create()
365 ->addValidator(new FormFieldValidator(
367 static function (DevtoolsProjectRequiredPackagesFormField
$formField) {
368 /** @var TextFormField $packageIdentifier */
369 $packageIdentifier = $formField->getDocument()->getNodeById('packageIdentifier');
371 // ensure that the package does not require itself
372 foreach ($formField->getSaveValue() as $requirement) {
373 if ($requirement['packageIdentifier'] === $packageIdentifier->getSaveValue()) {
374 $formField->addValidationError(
375 new FormFieldValidationError(
377 'wcf.acp.devtools.project.requiredPackage.error.selfRequirement'
384 ->addValidator(new FormFieldValidator(
386 function (DevtoolsProjectRequiredPackagesFormField
$formField) {
387 /** @var TextFormField $pathField */
388 $pathField = $this->form
->getNodeById('path');
389 $path = FileUtil
::addTrailingSlash($pathField->getSaveValue());
392 foreach ($formField->getSaveValue() as $requirement) {
393 if ($requirement['file'] && !\
is_file($path . "requirements/{$requirement['packageIdentifier']}.tar")) {
394 $missingFiles[] = "requirements/{$requirement['packageIdentifier']}.tar";
398 if (!empty($missingFiles)) {
399 $formField->addValidationError(
400 new FormFieldValidationError(
402 'wcf.acp.devtools.project.requiredPackage.error.missingFiles',
403 ['missingFiles' => $missingFiles]
410 $tabMenu->appendChild(
411 TabFormContainer
::create('requiredPackagesTab')
412 ->label('wcf.acp.devtools.project.requiredPackages.shortTitle')
413 ->appendChild($requiredPackages)
415 ValueFormFieldDependency
::create('mode')
417 ->values(['edit', 'setup'])
421 $optionalPackages = FormContainer
::create('optionalPackagesContainer')
422 ->label('wcf.acp.devtools.project.optionalPackages')
423 ->description('wcf.acp.devtools.project.optionalPackages.description')
425 DevtoolsProjectOptionalPackagesFormField
::create()
426 ->addValidator(new FormFieldValidator(
428 static function (DevtoolsProjectOptionalPackagesFormField
$formField) {
429 /** @var TextFormField $packageIdentifier */
430 $packageIdentifier = $formField->getDocument()->getNodeById('packageIdentifier');
432 // ensure that the package does not mark itself as optional
433 foreach ($formField->getSaveValue() as $requirement) {
434 if ($requirement['packageIdentifier'] === $packageIdentifier->getSaveValue()) {
435 $formField->addValidationError(
436 new FormFieldValidationError(
438 'wcf.acp.devtools.project.optionalPackage.error.selfOptional'
445 ->addValidator(new FormFieldValidator(
446 'requirementOptional',
447 static function (DevtoolsProjectOptionalPackagesFormField
$formField) {
448 /** @var DevtoolsProjectRequiredPackagesFormField $requiredPackagesField */
449 $requiredPackagesField = $formField->getDocument()->getNodeById('requiredPackages');
450 $requiredPackages = [];
451 foreach ($requiredPackagesField->getSaveValue() as $requiredPackage) {
452 $requiredPackages[$requiredPackage['packageIdentifier']] = $requiredPackage;
455 // ensure that the optionals and requirements do not conflict
456 foreach ($formField->getSaveValue() as $optional) {
457 if (isset($requiredPackages[$optional['packageIdentifier']])) {
458 $erroneousPackages[] = $optional['packageIdentifier'];
462 if (!empty($erroneousPackages)) {
463 $formField->addValidationError(
464 new FormFieldValidationError(
465 'requirementOptional',
466 'wcf.acp.devtools.project.optionalPackage.error.requirementOptional',
467 ['affectedPackages' => $erroneousPackages]
473 ->addValidator(new FormFieldValidator(
475 static function (DevtoolsProjectOptionalPackagesFormField
$formField) {
476 /** @var DevtoolsProjectExcludedPackagesFormField $excludedPackagesField */
477 $excludedPackagesField = $formField->getDocument()->getNodeById('excludedPackages');
478 $excludedPackages = [];
479 foreach ($excludedPackagesField->getSaveValue() as $requiredPackage) {
480 $excludedPackages[$requiredPackage['packageIdentifier']] = $requiredPackage;
483 // ensure that the exclusions and requirements do not conflict
484 foreach ($formField->getSaveValue() as $optional) {
485 if (isset($excludedPackages[$optional['packageIdentifier']])) {
486 $erroneousPackages[] = $optional['packageIdentifier'];
490 if (!empty($erroneousPackages)) {
491 $formField->addValidationError(
492 new FormFieldValidationError(
493 'requirementOptional',
494 'wcf.acp.devtools.project.optionalPackage.error.exclusionOptional',
495 ['affectedPackages' => $erroneousPackages]
501 ->addValidator(new FormFieldValidator(
503 function (DevtoolsProjectOptionalPackagesFormField
$formField) {
504 /** @var TextFormField $pathField */
505 $pathField = $this->form
->getNodeById('path');
506 $path = FileUtil
::addTrailingSlash($pathField->getSaveValue());
509 foreach ($formField->getSaveValue() as $optional) {
510 if (!\
is_file($path . "optionals/{$optional['packageIdentifier']}.tar")) {
511 $missingFiles[] = "optionals/{$optional['packageIdentifier']}.tar";
515 if (!empty($missingFiles)) {
516 $formField->addValidationError(
517 new FormFieldValidationError(
519 'wcf.acp.devtools.project.optionalPackage.error.missingFiles',
520 ['missingFiles' => $missingFiles]
527 $tabMenu->appendChild(
528 TabFormContainer
::create('optionalPackagesTab')
529 ->label('wcf.acp.devtools.project.optionalPackages.shortTitle')
530 ->appendChild($optionalPackages)
532 ValueFormFieldDependency
::create('mode')
538 $excludedPackages = FormContainer
::create('excludedPackagesContainer')
539 ->label('wcf.acp.devtools.project.excludedPackages')
540 ->description('wcf.acp.devtools.project.excludedPackages.description')
542 DevtoolsProjectExcludedPackagesFormField
::create()
543 ->addValidator(new FormFieldValidator(
545 static function (DevtoolsProjectExcludedPackagesFormField
$formField) {
546 /** @var TextFormField $packageIdentifier */
547 $packageIdentifier = $formField->getDocument()->getNodeById('packageIdentifier');
549 // ensure that the package does not exclude itself
550 foreach ($formField->getSaveValue() as $requirement) {
551 if ($requirement['packageIdentifier'] === $packageIdentifier->getSaveValue()) {
552 $formField->addValidationError(
553 new FormFieldValidationError(
555 'wcf.acp.devtools.project.excludedPackage.error.selfExclusion'
562 ->addValidator(new FormFieldValidator(
563 'requirementExclusion',
564 static function (DevtoolsProjectExcludedPackagesFormField
$formField) {
565 /** @var DevtoolsProjectRequiredPackagesFormField $requiredPackagesField */
566 $requiredPackagesField = $formField->getDocument()->getNodeById('requiredPackages');
567 $requiredPackageVersions = [];
568 foreach ($requiredPackagesField->getSaveValue() as $requiredPackage) {
569 $requiredPackageVersions[$requiredPackage['packageIdentifier']] = $requiredPackage['minVersion'];
572 // ensure that the exclusions and requirements do not conflict
573 $affectedPackages = [];
574 foreach ($formField->getSaveValue() as $exclusion) {
575 if (isset($requiredPackageVersions[$exclusion['packageIdentifier']])) {
576 $requiredVersion = $requiredPackageVersions[$exclusion['packageIdentifier']];
577 $excludedVersion = $exclusion['version'];
579 // we enfore a hard rule: if a package is both an exclusion
580 // and a requirement, both must specify a version
581 if ($requiredVersion === '' ||
$excludedVersion === '') {
582 $affectedPackages[] = $exclusion['packageIdentifier'];
583 } elseif (Package
::compareVersion($excludedVersion, $requiredVersion) <= 0) {
584 $affectedPackages[] = $exclusion['packageIdentifier'];
589 if (!empty($affectedPackages)) {
590 $formField->addValidationError(
591 new FormFieldValidationError(
592 'requirementExclusion',
593 'wcf.acp.devtools.project.excludedPackage.error.requirementExclusion',
594 ['affectedPackages' => $affectedPackages]
601 $tabMenu->appendChild(
602 TabFormContainer
::create('excludedPackagesTab')
603 ->label('wcf.acp.devtools.project.excludedPackages.shortTitle')
604 ->appendChild($excludedPackages)
606 ValueFormFieldDependency
::create('mode')
608 ->values(['edit', 'setup'])
612 $instructions = FormContainer
::create('instructionsContainer')
613 ->label('wcf.acp.devtools.project.instructions')
614 ->description('wcf.acp.devtools.project.instructions.description')
616 DevtoolsProjectInstructionsFormField
::create()
617 ->label('wcf.acp.devtools.project.instructions')
618 ->addValidator(new FormFieldValidator(
619 'updateFromPreviousVersion',
620 function (DevtoolsProjectInstructionsFormField
$formField) {
621 /** @var TextFormField $versionField */
622 $versionField = $this->form
->getNodeById('version');
623 $version = $versionField->getSaveValue();
625 foreach ($formField->getValue() as $key => $instructions) {
626 if ($instructions['type'] === 'install') {
630 $fromVersion = $instructions['fromVersion'];
631 if (\
strpos($fromVersion, '*') !== false) {
632 // assume the smallest version by replacing
633 // all wildcards with zeros
634 $checkedFromVersion = \
str_replace('*', '0', $fromVersion);
635 if (Package
::compareVersion($version, $checkedFromVersion) <= 0) {
636 $formField->addValidationError(
637 new FormFieldValidationError(
638 'updateForFutureVersion',
639 'wcf.acp.devtools.project.instructions.type.update.error.updateForFutureVersion',
641 'fromVersion' => $fromVersion,
642 'instructions' => $key,
643 'version' => $version,
648 } elseif (Package
::compareVersion($version, $fromVersion) <= 0) {
649 $formField->addValidationError(
650 new FormFieldValidationError(
651 'updateForFutureVersion',
652 'wcf.acp.devtools.project.instructions.type.update.error.updateForFutureVersion',
654 'fromVersion' => $fromVersion,
655 'instructions' => $key,
656 'version' => $version,
664 ->addValidator($this->getInstructionValuesValidator())
667 $tabMenu->appendChild(
668 TabFormContainer
::create('instructionsTab')
669 ->label('wcf.acp.devtools.project.instructions')
670 ->appendChild($instructions)
672 ValueFormFieldDependency
::create('mode')
680 * Returns the form field validator for the instructions form field to check
681 * the values of all instructions.
683 * @return FormFieldValidator
685 protected function getInstructionValuesValidator()
687 return new FormFieldValidator('instructionValues', function (DevtoolsProjectInstructionsFormField
$formField) {
688 /** @var TextFormField $pathField */
689 $pathField = $this->form
->getNodeById('path');
690 $path = FileUtil
::addTrailingSlash($pathField->getSaveValue());
692 /** @var TextFormField $packageIdentifierField */
693 $packageIdentifierField = $this->form
->getNodeById('packageIdentifier');
694 $packageIdentifier = $packageIdentifierField->getSaveValue();
696 /** @var BooleanFormField $isApplicationField */
697 $isApplicationField = $this->form
->getNodeById('isApplication');
698 $isApplication = $isApplicationField->getSaveValue();
700 $packageInstallationPluginList = new PackageInstallationPluginList();
701 $packageInstallationPluginList->readObjects();
703 /** @var PackageInstallationPlugin[] $packageInstallationPlugins */
704 $packageInstallationPlugins = [];
705 foreach ($packageInstallationPluginList as $packageInstallationPlugin) {
706 $packageInstallationPlugins[$packageInstallationPlugin->pluginName
] = $packageInstallationPlugin;
709 foreach ($formField->getValue() as $instructionsKey => $instructions) {
710 if (empty($instructions['instructions'])) {
711 $formField->addValidationError(
712 new FormFieldValidationError(
713 'missingInstructions',
714 'wcf.acp.devtools.project.instructions.error.missingInstructions',
715 ['instructions' => $instructionsKey]
722 foreach ($instructions['instructions'] as $instructionKey => $instruction) {
723 $value = $instruction['value'];
724 $packageInstallationPlugin = $packageInstallationPlugins[$instruction['pip']];
726 // explicity set value for valiation if instruction value
727 // is empty but supports default filename
728 if ($value === '' && $packageInstallationPlugin->getDefaultFilename() !== null) {
729 $value = $packageInstallationPlugin->getDefaultFilename();
732 switch ($instruction['pip']) {
736 // core is too special, ignore it
737 if ($this->formObject
!== null && $this->formObject
->isCore()) {
741 // only tar archives are supported for file-based pips
742 if (\
substr($value, -4) !== '.tar') {
743 $formField->addValidationError(
744 new FormFieldValidationError(
746 'wcf.acp.devtools.project.instruction.error.noArchive',
748 'instruction' => $instructionKey,
749 'instructions' => $instructionsKey,
754 // the associated directory with the source fles
756 elseif (!\
is_dir($path . \
substr($value, 0, -4))) {
757 // ... unless it is an update and an archive
758 // with updated files only
760 $instructions['type'] === 'update' && \
preg_match(
761 '~^(.+)_update\.tar$~',
766 if (!\
is_dir($path . $match[1])) {
767 $formField->addValidationError(
768 new FormFieldValidationError(
769 'missingDirectoryForUpdatedFiles',
770 'wcf.acp.devtools.project.instruction.error.missingDirectoryForUpdatedFiles',
772 'directory' => $path . $match[1] . '/',
773 'instruction' => $instructionKey,
774 'instructions' => $instructionsKey,
780 $formField->addValidationError(
781 new FormFieldValidationError(
783 'wcf.acp.devtools.project.instruction.error.missingDirectory',
785 'directory' => $path . \
substr($value, 0, -4) . '/',
786 'instruction' => $instructionKey,
787 'instructions' => $instructionsKey,
796 if ($value === 'language/*.xml') {
797 $directory = FileUtil
::addTrailingSlash(\
dirname($path . $value));
798 if ($this->formObject
!== null && $this->formObject
->isCore()) {
799 $directory = FileUtil
::addTrailingSlash(\
dirname($path . 'wcfsetup/install/lang/*.xml'));
802 $directoryUtil = DirectoryUtil
::getInstance($directory);
803 if (empty($directoryUtil->getFiles(\SORT_ASC
, Regex
::compile('.+\.xml')))) {
804 $formField->addValidationError(
805 new FormFieldValidationError(
807 'wcf.acp.devtools.project.instruction.language.error.missingFiles',
809 'directory' => $directory,
810 'instruction' => $instructionKey,
811 'instructions' => $instructionsKey,
816 } elseif (\
substr($value, -4) !== '.xml') {
817 $formField->addValidationError(
818 new FormFieldValidationError(
820 'wcf.acp.devtools.project.instruction.error.noXmlFile',
822 'instruction' => $instructionKey,
823 'instructions' => $instructionsKey,
833 // only PHP files are supported for file-based pips
834 if (\
substr($value, -4) !== '.php') {
835 $formField->addValidationError(
836 new FormFieldValidationError(
838 'wcf.acp.devtools.project.instruction.script.error.noPhpFile',
840 'instruction' => $instructionKey,
841 'instructions' => $instructionsKey,
846 $application = 'wcf';
847 if (!empty($instruction['application'])) {
848 $application = $instruction['application'];
849 } elseif ($isApplication) {
850 $application = Package
::getAbbreviation($packageIdentifier);
854 $checkedFileLocations = [];
855 if ($this->formObject
!== null && $this->formObject
->isCore()) {
856 $scriptLocation = $path . 'wcfsetup/install/files/' . $instruction['value'];
857 if (!\
is_file($scriptLocation)) {
858 $checkedFileLocations[] = $scriptLocation;
860 $missingFile = false;
863 // try to find matching `file` instruction
864 // for determined application
865 foreach ($instructions['instructions'] as $fileSearchInstruction) {
866 if ($fileSearchInstruction['pip'] === 'file') {
867 $fileSearchValue = $fileSearchInstruction['value'];
869 // ignore empty instructions with default filename
870 if ($fileSearchValue === '' && $packageInstallationPlugins['file']->getDefaultFilename() !== null) {
871 $fileSearchValue = $packageInstallationPlugins['file']->getDefaultFilename();
874 $fileApplication = 'wcf';
875 if (!empty($fileSearchInstruction['application'])) {
876 $fileApplication = $fileSearchInstruction['application'];
877 } elseif ($isApplication) {
878 $fileApplication = Package
::getAbbreviation($packageIdentifier);
881 if ($fileApplication === $application) {
882 $scriptLocation = $path . \
substr(
886 ) . '/' . $instruction['value'];
887 if (!\
is_file($scriptLocation)) {
888 $checkedFileLocations[] = $scriptLocation;
890 $missingFile = false;
899 $formField->addValidationError(
900 new FormFieldValidationError(
902 'wcf.acp.devtools.project.instruction.script.error.missingFile',
904 'checkedFileLocations' => $checkedFileLocations,
905 'instruction' => $instructionKey,
906 'instructions' => $instructionsKey,
916 $filePath = $path . $value;
917 if ($this->formObject
!== null && $this->formObject
->isCore()) {
918 $filePath = $path . 'com.woltlab.wcf/' . $value;
921 if (!\file_exists
($filePath)) {
922 $formField->addValidationError(
923 new FormFieldValidationError(
925 'wcf.acp.devtools.project.instruction.error.missingFile',
928 'instruction' => $instructionKey,
929 'instructions' => $instructionsKey,
935 $packageInstallationPlugin->className
,
936 AbstractXMLPackageInstallationPlugin
::class
938 && \
substr($value, -4) !== '.xml'
940 $formField->addValidationError(
941 new FormFieldValidationError(
943 'wcf.acp.devtools.project.instruction.error.noXmlFile',
945 'instruction' => $instructionKey,
946 'instructions' => $instructionsKey,
962 public function save()
964 AbstractForm
::save();
966 $data = $this->form
->getData();
968 foreach ($this->projectFields
as $projectField) {
969 if (isset($data['data'][$projectField])) {
970 $projectData[$projectField] = $data['data'][$projectField];
971 unset($data['data'][$projectField]);
975 $action = $this->formAction
;
976 if ($this->objectActionName
) {
977 $action = $this->objectActionName
;
978 } elseif ($this->formAction
=== 'edit') {
982 /** @var AbstractDatabaseObjectAction objectAction */
983 $this->objectAction
= new $this->objectActionClass(
984 \array_filter
([$this->formObject
]),
986 ['data' => $projectData]
988 $project = $this->objectAction
->executeAction()['returnValues'];
990 if (!($project instanceof DevtoolsProject
)) {
991 if ($this->formObject
instanceof DevtoolsProject
) {
992 $project = new DevtoolsProject($this->formObject
->projectID
);
994 throw new \
LogicException('Cannot determine project object.');
998 if ($data['data']['mode'] !== 'import') {
999 $this->writePackageXml($project, $data);
1004 WCF
::getTPL()->assign('success', true);
1008 * Writes the updated `package.xml` file for the given project using the given data.
1010 * @param DevtoolsProject $project
1011 * @param array $data
1013 protected function writePackageXml(DevtoolsProject
$project, array $data)
1015 $xmlData = \array_merge
($data, $data['data']);
1016 unset($xmlData['data'], $xmlData['mode']);
1017 $packageXmlWriter = new DevtoolsPackageXmlWriter($project, $xmlData);
1018 $packageXmlWriter->write();