52e022c9da403845dc3d870db0e69023fd281abe
[GitHub/WoltLab/WCF.git] /
1 <?php
2
3 namespace wcf\system\form\builder\field\devtools\project;
4
5 use wcf\data\application\Application;
6 use wcf\data\package\installation\plugin\PackageInstallationPlugin;
7 use wcf\data\package\installation\plugin\PackageInstallationPluginList;
8 use wcf\system\application\ApplicationHandler;
9 use wcf\system\form\builder\field\AbstractFormField;
10 use wcf\system\form\builder\field\TDefaultIdFormField;
11 use wcf\system\form\builder\field\validation\FormFieldValidationError;
12 use wcf\system\WCF;
13
14 /**
15 * Form field implementation for the instructions of a devtools project.
16 *
17 * @author Matthias Schmidt
18 * @copyright 2001-2019 WoltLab GmbH
19 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
20 * @since 5.2
21 */
22 final class DevtoolsProjectInstructionsFormField extends AbstractFormField
23 {
24 use TDefaultIdFormField;
25
26 /**
27 * list of available applications
28 * @var null|Application[]
29 */
30 protected $applications;
31
32 /**
33 * list of available package installation plugins
34 * @var null|PackageInstallationPlugin[]
35 */
36 protected $packageInstallationPlugins;
37
38 /**
39 * @inheritDoc
40 */
41 protected $templateName = 'shared_devtoolsProjectInstructionsFormField';
42
43 /**
44 * @inheritDoc
45 */
46 protected $value = [];
47
48 /**
49 * names of package installation plugins that support the `application`
50 * attribute
51 * @var string[]
52 */
53 protected static $applicationPips = [
54 'acpTemplate',
55 'file',
56 'script',
57 'template',
58 ];
59
60 /**
61 * Returns the applications for which file-based package installation plugins
62 * can deliver files.
63 *
64 * @return Application[]
65 */
66 public function getApplications()
67 {
68 if ($this->applications === null) {
69 $this->applications = [];
70 foreach (ApplicationHandler::getInstance()->getApplications() as $application) {
71 $this->applications[$application->getAbbreviation()] = $application;
72 }
73
74 \uasort($this->applications, static function (Application $app1, Application $app2) {
75 return $app1->getAbbreviation() <=> $app2->getAbbreviation();
76 });
77 }
78
79 return $this->applications;
80 }
81
82 /**
83 * @inheritDoc
84 */
85 public function getHtml()
86 {
87 if ($this->requiresLabel() && $this->getLabel() === null) {
88 throw new \UnexpectedValueException("Form field '{$this->getPrefixedId()}' requires a label.");
89 }
90
91 $this->removeClass('formError');
92
93 return WCF::getTPL()->fetch(
94 $this->templateName,
95 'wcf',
96 [
97 'apps' => $this->getApplications(),
98 'field' => $this,
99 'packageInstallationPlugins' => $this->getPackageInstallationPlugins(),
100 ],
101 true
102 );
103 }
104
105 /**
106 * Returns the package installation plugins that can be used for instructions.
107 *
108 * @return PackageInstallationPlugin[]
109 */
110 public function getPackageInstallationPlugins()
111 {
112 if ($this->packageInstallationPlugins === null) {
113 $packageInstallationPluginList = new PackageInstallationPluginList();
114 $packageInstallationPluginList->sqlOrderBy = 'pluginName ASC';
115 $packageInstallationPluginList->readObjects();
116
117 foreach ($packageInstallationPluginList as $packageInstallationPlugin) {
118 $this->packageInstallationPlugins[$packageInstallationPlugin->pluginName] = $packageInstallationPlugin;
119 }
120 }
121
122 return $this->packageInstallationPlugins;
123 }
124
125 /**
126 * @inheritDoc
127 */
128 public function readValue()
129 {
130 if (
131 $this->getDocument()->hasRequestData($this->getPrefixedId())
132 && \is_array($this->getDocument()->getRequestData($this->getPrefixedId()))
133 ) {
134 $this->value = $this->getDocument()->getRequestData($this->getPrefixedId());
135 } else {
136 $this->value = [];
137 }
138
139 return $this;
140 }
141
142 /**
143 * @inheritDoc
144 */
145 public function validate()
146 {
147 // everything is already validated by JavaScript thus we skip
148 // reporting specific errors and simply remove manipulated values
149 $this->value(\array_filter($this->getValue(), function ($instructions) {
150 if (!\is_array($instructions)) {
151 return false;
152 }
153
154 // ensure that all relevant elements are present
155 if (!isset($instructions['type'])) {
156 return false;
157 }
158 if (!isset($instructions['instructions'])) {
159 $instructions['instructions'] = [];
160 }
161 if (!\is_array($instructions['instructions'])) {
162 return false;
163 }
164
165 if ($instructions['type'] !== 'install' && $instructions['type'] !== 'update') {
166 return false;
167 }
168
169 if ($instructions['type'] === 'update') {
170 if (!isset($instructions['fromVersion'])) {
171 return false;
172 }
173
174 // Do not validate the actual version. The actual installation process
175 // does not validate it either and we should not *silently* drop uncommon
176 // (but valid) formats.
177 // The JavaScript validation should be sufficient to nudge the user
178 // into the correct direction.
179 }
180
181 foreach ($instructions['instructions'] as $instruction) {
182 if (!isset($instruction['pip']) || !isset($this->getPackageInstallationPlugins()[$instruction['pip']])) {
183 return false;
184 }
185
186 if (!empty($instruction['application'])) {
187 if (!isset($this->getApplications()[$instruction['application']])) {
188 return false;
189 }
190
191 if (!\in_array($instruction['pip'], static::$applicationPips)) {
192 return false;
193 }
194 }
195
196 $instruction['runStandalone'] = $instruction['runStandalone'] ?? 0;
197 $instruction['value'] = $instruction['value'] ?? '';
198 }
199
200 return true;
201 }));
202
203 // the only thing left to validate is to ensure that there are
204 // installation instructions
205 $hasInstallInstructions = false;
206 foreach ($this->getValue() as $instructions) {
207 if ($instructions['type'] === 'install') {
208 $hasInstallInstructions = true;
209 break;
210 }
211 }
212
213 if (!$hasInstallInstructions) {
214 $this->addValidationError(
215 new FormFieldValidationError(
216 'noInstallationInstructions',
217 'wcf.acp.devtools.project.instructions.error.noInstallationInstructions'
218 )
219 );
220 }
221 }
222
223 /**
224 * @inheritDoc
225 */
226 protected static function getDefaultId()
227 {
228 return 'instructions';
229 }
230 }