Add GUI support for abstract menu package installation plugin
[GitHub/WoltLab/WCF.git] / wcfsetup / install / files / lib / system / package / plugin / AbstractMenuPackageInstallationPlugin.class.php
CommitLineData
11ade432 1<?php
308c880f 2declare(strict_types=1);
11ade432 3namespace wcf\system\package\plugin;
9847bda9
MS
4use wcf\page\IPage;
5use wcf\system\devtools\pip\IDevtoolsPipEntryList;
d7424422 6use wcf\system\devtools\pip\IIdempotentPackageInstallationPlugin;
9847bda9 7use wcf\system\devtools\pip\TXmlGuiPackageInstallationPlugin;
11ade432 8use wcf\system\exception\SystemException;
9847bda9
MS
9use wcf\system\form\builder\container\IFormContainer;
10use wcf\system\form\builder\field\ClassNameFormField;
11use wcf\system\form\builder\field\IntegerFormField;
12use wcf\system\form\builder\field\OptionFormField;
13use wcf\system\form\builder\field\SingleSelectionFormField;
14use wcf\system\form\builder\field\TextFormField;
15use wcf\system\form\builder\field\UserGroupOptionFormField;
16use wcf\system\form\builder\field\validation\FormFieldValidationError;
17use wcf\system\form\builder\field\validation\FormFieldValidator;
18use wcf\system\form\builder\IFormDocument;
11ade432 19use wcf\system\WCF;
4f2d3f58 20use wcf\util\StringUtil;
9847bda9 21use wcf\util\Url;
11ade432
AE
22
23/**
a17de04e
MS
24 * Abstract implementation of a package installation plugin for menu items.
25 *
9f959ced 26 * @author Alexander Ebert
c839bd49 27 * @copyright 2001-2018 WoltLab GmbH
11ade432 28 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
e71525e4 29 * @package WoltLabSuite\Core\System\Package\Plugin
11ade432 30 */
d7424422 31abstract class AbstractMenuPackageInstallationPlugin extends AbstractXMLPackageInstallationPlugin implements IIdempotentPackageInstallationPlugin {
9847bda9
MS
32 // we do no implement `IGuiPackageInstallationPlugin` but instead just
33 // provide the default implementation to ensure backwards compatibility
34 // with third-party packages containing classes that extend this abstract
35 // class
36 use TXmlGuiPackageInstallationPlugin;
37
11ade432 38 /**
0fcfe5f6 39 * @inheritDoc
11ade432
AE
40 */
41 protected function handleDelete(array $items) {
09b066e5 42 $sql = "DELETE FROM ".$this->application.WCF_N."_".$this->tableName."
11ade432
AE
43 WHERE menuItem = ?
44 AND packageID = ?";
45 $statement = WCF::getDB()->prepareStatement($sql);
46 foreach ($items as $item) {
058cbd6a 47 $statement->execute([
11ade432
AE
48 $item['attributes']['name'],
49 $this->installation->getPackageID()
058cbd6a 50 ]);
11ade432
AE
51 }
52 }
53
54 /**
0fcfe5f6 55 * @inheritDoc
11ade432
AE
56 */
57 protected function prepareImport(array $data) {
58 // adjust show order
63b9817b
MS
59 $showOrder = isset($data['elements']['showorder']) ? $data['elements']['showorder'] : null;
60 $parent = isset($data['elements']['parent']) ? $data['elements']['parent'] : '';
11ade432
AE
61 $showOrder = $this->getShowOrder($showOrder, $parent, 'parentMenuItem');
62
63 // merge values and default values
058cbd6a 64 return [
11ade432 65 'menuItem' => $data['attributes']['name'],
ab9f8211 66 'menuItemController' => isset($data['elements']['controller']) ? $data['elements']['controller'] : '',
63b9817b 67 'menuItemLink' => isset($data['elements']['link']) ? $data['elements']['link'] : '',
4f2d3f58 68 'options' => isset($data['elements']['options']) ? StringUtil::normalizeCsv($data['elements']['options']) : '',
63b9817b 69 'parentMenuItem' => isset($data['elements']['parent']) ? $data['elements']['parent'] : '',
4f2d3f58 70 'permissions' => isset($data['elements']['permissions']) ? StringUtil::normalizeCsv($data['elements']['permissions']) : '',
11ade432 71 'showOrder' => $showOrder
058cbd6a 72 ];
11ade432
AE
73 }
74
75 /**
0fcfe5f6 76 * @inheritDoc
11ade432
AE
77 */
78 protected function validateImport(array $data) {
79 if (empty($data['parentMenuItem'])) {
80 return;
81 }
82
5c6ddd85 83 $sql = "SELECT COUNT(menuItemID)
09b066e5 84 FROM ".$this->application.WCF_N."_".$this->tableName."
11ade432
AE
85 WHERE menuItem = ?";
86 $statement = WCF::getDB()->prepareStatement($sql);
058cbd6a 87 $statement->execute([$data['parentMenuItem']]);
11ade432 88
5c6ddd85 89 if (!$statement->fetchSingleColumn()) {
4fe0b42b 90 throw new SystemException("Unable to find parent 'menu item' with name '".$data['parentMenuItem']."' for 'menu item' with name '".$data['menuItem']."'.");
11ade432
AE
91 }
92 }
93
94 /**
0fcfe5f6 95 * @inheritDoc
11ade432
AE
96 */
97 protected function findExistingItem(array $data) {
98 $sql = "SELECT *
09b066e5 99 FROM ".$this->application.WCF_N."_".$this->tableName."
11ade432
AE
100 WHERE menuItem = ?
101 AND packageID = ?";
058cbd6a 102 $parameters = [
11ade432
AE
103 $data['menuItem'],
104 $this->installation->getPackageID()
058cbd6a 105 ];
11ade432 106
058cbd6a 107 return [
11ade432
AE
108 'sql' => $sql,
109 'parameters' => $parameters
058cbd6a 110 ];
11ade432 111 }
d4955651
AE
112
113 /**
114 * @inheritDoc
9847bda9 115 * @since 3.1
d4955651
AE
116 */
117 public static function getSyncDependencies() {
118 return [];
119 }
9847bda9
MS
120
121 /**
122 * @inheritDoc
123 * @since 3.2
124 */
125 public function addFormFields(IFormDocument $form) {
126 /** @var IFormContainer $dataContainer */
127 $dataContainer = $form->getNodeById('data');
128
129 $dataContainer->appendChildren([
130 TextFormField::create('menuItem')
131 ->objectProperty('name')
132 ->label('wcf.acp.pip.abstractMenu.menuItem'),
133
134 SingleSelectionFormField::create('parentMenuItem')
135 ->objectProperty('parent')
136 ->label('wcf.acp.pip.abstractForm.parentMenuItem')
137 ->filterable(),
138
139 ClassNameFormField::create('menuItemController')
140 ->objectProperty('controller')
141 ->label('wcf.acp.pip.abstractForm.menuItemController')
142 ->implementedInterface(IPage::class),
143
144 TextFormField::create('menuItemLink')
145 ->objectProperty('link')
146 ->label('wcf.acp.pip.abstractMenu.menuItemLink')
147 ->description('wcf.acp.pip.abstractMenu.menuItemLink.description')
148 ->objectProperty('link')
149 ->addValidator(new FormFieldValidator('linkSpecified', function(TextFormField $formField) {
150 /** @var ClassNameFormField $menuItemController */
151 $menuItemController = $formField->getDocument()->getNodeById('menuItemController');
152
153 // ensure that either a menu item controller is specified or a link
154 if ($formField->getSaveValue() === '' && $menuItemController->getSaveValue() === '') {
155 $formField->addValidationError(
156 new FormFieldValidationError(
157 'noLinkSpecified',
158 'wcf.acp.pip.abstractMenu.menuItemLink.error.noLinkSpecified'
159 )
160 );
161 }
162 }))
163 ->addValidator(new FormFieldValidator('format', function(TextFormField $formField) {
164 if ($formField->getSaveValue() !== '') {
165 /** @var ClassNameFormField $menuItemController */
166 $menuItemController = $formField->getDocument()->getNodeById('menuItemController');
167
168 if (!$menuItemController->getSaveValue() && !Url::is($formField->getSaveValue())) {
169 $formField->addValidationError(
170 new FormFieldValidationError(
171 'noLink',
172 'wcf.acp.pip.abstractMenu.menuItemLink.error.noLink'
173 )
174 );
175 }
176 else if ($menuItemController->getSaveValue() && Url::is($formField->getSaveValue())) {
177 $formField->addValidationError(
178 new FormFieldValidationError(
179 'isLink',
180 'wcf.acp.pip.abstractMenu.menuItemLink.error.isLink'
181 )
182 );
183 }
184 }
185 })),
186
187 OptionFormField::create()
188 ->description('wcf.acp.pip.abstractMenu.options.description')
189 ->saveValueType(OptionFormField::SAVE_VALUE_TYPE_CSV)
190 ->packageIDs(array_merge(
191 [$this->installation->getPackage()->packageID],
192 array_keys($this->installation->getPackage()->getAllRequiredPackages())
193 )),
194
195 UserGroupOptionFormField::create()
196 ->description('wcf.acp.pip.abstractMenu.options.description')
197 ->saveValueType(OptionFormField::SAVE_VALUE_TYPE_CSV)
198 ->packageIDs(array_merge(
199 [$this->installation->getPackage()->packageID],
200 array_keys($this->installation->getPackage()->getAllRequiredPackages())
201 )),
202
203 IntegerFormField::create('showOrder')
204 ->objectProperty('showorder')
205 ->label('wcf.acp.pip.abstractMenu.showOrder')
206 ->description('wcf.acp.pip.abstractMenu.showOrder.description')
207 ->objectProperty('showorder')
208 ->minimum(1)
209 ]);
210 }
211
212 /**
213 * @inheritDoc
214 * @since 3.2
215 */
216 protected function getElementData(\DOMElement $element): array {
217 $data = [
218 'menuItem' => $element->getAttribute('name'),
219 'packageID' => $this->installation->getPackage()->packageID
220 ];
221
222 $parentMenuItem = $element->getElementsByTagName('parent')->item(0);
223 if ($parentMenuItem !== null) {
224 $data['parentMenuItem'] = $parentMenuItem->nodeValue;
225 }
226
227 $controller = $element->getElementsByTagName('controller')->item(0);
228 if ($controller !== null) {
229 $data['menuItemController'] = $controller->nodeValue;
230 }
231
232 $link = $element->getElementsByTagName('link')->item(0);
233 if ($link !== null) {
234 $data['menuItemLink'] = $link->nodeValue;
235 }
236
237 $options = $element->getElementsByTagName('options')->item(0);
238 if ($options !== null) {
239 $data['options'] = $options->nodeValue;
240 }
241
242 $permissions = $element->getElementsByTagName('permissions')->item(0);
243 if ($permissions !== null) {
244 $data['permissions'] = $permissions->nodeValue;
245 }
246
247 $showOrder = $element->getElementsByTagName('showOrder')->item(0);
248 if ($showOrder !== null) {
249 $data['showOrder'] = $showOrder->nodeValue;
250 }
251
252 return $data;
253 }
254
255 /**
256 * @inheritDoc
257 * @since 3.2
258 */
259 public function getElementIdentifier(\DOMElement $element): string {
260 return $element->getAttribute('name');
261 }
262
263 /**
264 * @inheritDoc
265 * @since 3.2
266 */
267 protected function setEntryListKeys(IDevtoolsPipEntryList $entryList) {
268 $entryList->setKeys([
269 'menuItem' => 'wcf.acp.pip.abstractMenu.menuItem',
270 'parentMenuItem' => 'wcf.acp.pip.abstractMenu.parentMenuItem'
271 ]);
272 }
273
274 /**
275 * @inheritDoc
276 * @since 3.2
277 */
278 protected function sortDocument(\DOMDocument $document) {
279 $this->sortImportDelete($document);
280
281 $compareFunction = function(\DOMElement $element1, \DOMElement $element2) {
282 return strcmp(
283 $element1->getAttribute('name'),
284 $element2->getAttribute('name')
285 );
286 };
287
288 $this->sortChildNodes($document->getElementsByTagName('import'), $compareFunction);
289 $this->sortChildNodes($document->getElementsByTagName('delete'), $compareFunction);
290 }
291
292 /**
293 * @inheritDoc
294 * @since 3.2
295 */
296 protected function writeEntry(\DOMDocument $document, IFormDocument $form): \DOMElement {
297 $formData = $form->getData()['data'];
298
299 $menuItem = $document->createElement($this->tagName);
300 $menuItem->setAttribute('name', $formData['name']);
301
302 foreach (['parent', 'controller', 'link', 'options', 'permissions', 'showorder'] as $field) {
303 if (isset($formData[$field]) && $formData[$field] !== '') {
304 $menuItem->appendChild($document->createElement($field, (string) $formData[$field]));
305 }
306 }
307
308 $document->getElementsByTagName('import')->item(0)->appendChild($menuItem);
309
310 return $menuItem;
311 }
11ade432 312}