2 namespace wcf\system\package\plugin
;
3 use wcf\data\
object\type\definition\ObjectTypeDefinitionList
;
4 use wcf\data\
object\type\ObjectTypeCache
;
5 use wcf\data\
object\type\ObjectTypeEditor
;
6 use wcf\data\DatabaseObjectList
;
7 use wcf\data\page\PageNode
;
8 use wcf\data\page\PageNodeTree
;
9 use wcf\system\application\ApplicationHandler
;
10 use wcf\system\condition\AbstractIntegerCondition
;
11 use wcf\system\condition\UserGroupCondition
;
12 use wcf\system\condition\UserIntegerPropertyCondition
;
13 use wcf\system\condition\UserTimestampPropertyCondition
;
14 use wcf\system\devtools\pip\DevtoolsPipEntryList
;
15 use wcf\system\devtools\pip\IDevtoolsPipEntryList
;
16 use wcf\system\devtools\pip\IGuiPackageInstallationPlugin
;
17 use wcf\system\devtools\pip\TXmlGuiPackageInstallationPlugin
;
18 use wcf\system\event\EventHandler
;
19 use wcf\system\exception\SystemException
;
20 use wcf\system\form\builder\container\FormContainer
;
21 use wcf\system\form\builder\container\IFormContainer
;
22 use wcf\system\form\builder\field\dependency\ValueFormFieldDependency
;
23 use wcf\system\form\builder\field\OptionFormField
;
24 use wcf\system\form\builder\field\UserGroupOptionFormField
;
25 use wcf\system\form\builder\field\validation\FormFieldValidationError
;
26 use wcf\system\form\builder\field\validation\FormFieldValidator
;
27 use wcf\system\form\builder\field\BooleanFormField
;
28 use wcf\system\form\builder\field\ClassNameFormField
;
29 use wcf\system\form\builder\field\FloatFormField
;
30 use wcf\system\form\builder\field\IntegerFormField
;
31 use wcf\system\form\builder\field\ItemListFormField
;
32 use wcf\system\form\builder\field\SingleSelectionFormField
;
33 use wcf\system\form\builder\field\TextFormField
;
34 use wcf\system\form\builder\field\validation\FormFieldValidatorUtil
;
35 use wcf\system\form\builder\IFormDocument
;
38 use wcf\util\DirectoryUtil
;
41 * Installs, updates and deletes object types.
43 * @author Alexander Ebert, Matthias Schmidt
44 * @copyright 2001-2018 WoltLab GmbH
45 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
46 * @package WoltLabSuite\Core\Acp\Package\Plugin
48 class ObjectTypePackageInstallationPlugin
extends AbstractXMLPackageInstallationPlugin
implements IGuiPackageInstallationPlugin
{
49 use TXmlGuiPackageInstallationPlugin
;
54 public $className = ObjectTypeEditor
::class;
59 public $tagName = 'type';
62 * list of names of tags which aren't considered as additional data
65 public static $reservedTags = ['classname', 'definitionname', 'name'];
70 public $definitionNames = [];
75 public $definitionInterfaces = [];
78 * data for object type definition-specific xml element children
81 public $definitionElementChildren = [];
84 * Returns the id of the object type definition with the given name.
86 * @param string $definitionName
88 * @throws SystemException
90 protected function getDefinitionID($definitionName) {
92 $sql = "SELECT definitionID
93 FROM wcf".WCF_N
."_object_type_definition
94 WHERE definitionName = ?";
95 $statement = WCF
::getDB()->prepareStatement($sql, 1);
96 $statement->execute([$definitionName]);
97 $row = $statement->fetchArray();
98 if (empty($row['definitionID'])) throw new SystemException("unknown object type definition '".$definitionName."' given");
99 return $row['definitionID'];
105 protected function handleDelete(array $items) {
106 $sql = "DELETE FROM wcf".WCF_N
."_".$this->tableName
."
110 $statement = WCF
::getDB()->prepareStatement($sql);
111 foreach ($items as $item) {
112 $statement->execute([
113 $item['attributes']['name'],
114 $this->getDefinitionID($item['elements']['definitionname']),
115 $this->installation
->getPackageID()
123 protected function prepareImport(array $data) {
124 $additionalData = [];
125 foreach ($data['elements'] as $tagName => $nodeValue) {
126 if (!in_array($tagName, self
::$reservedTags)) $additionalData[$tagName] = $nodeValue;
130 'definitionID' => $this->getDefinitionID($data['elements']['definitionname']),
131 'objectType' => $data['elements']['name'],
132 'className' => isset($data['elements']['classname']) ?
$data['elements']['classname'] : '',
133 'additionalData' => serialize($additionalData)
140 protected function findExistingItem(array $data) {
142 FROM wcf".WCF_N
."_".$this->tableName
."
148 $data['definitionID'],
149 $this->installation
->getPackageID()
154 'parameters' => $parameters
161 public static function getSyncDependencies() {
162 return ['objectTypeDefinition'];
169 public function getAdditionalTemplateCode() {
170 return WCF
::getTPL()->fetch('__objectTypePipGui', 'wcf', [
171 'definitionNames' => $this->definitionNames
,
172 'definitionInterfaces' => $this->definitionInterfaces
180 protected function doGetElementData(\DOMElement
$element, $saveData) {
182 'definitionID' => $this->getDefinitionID($element->getElementsByTagName('definitionname')->item(0)->nodeValue
),
183 'objectType' => $element->getElementsByTagName('name')->item(0)->nodeValue
,
184 'packageID' => $this->installation
->getPackage()->packageID
187 $className = $element->getElementsByTagName('classname')->item(0);
189 $data['className'] = $className->nodeValue
;
192 $additionalData = [];
194 /** @var \DOMElement $child */
195 foreach ($element->childNodes
as $child) {
196 if (!in_array($child->nodeName
, self
::$reservedTags)) {
197 $additionalData[$child->nodeName
] = $child->nodeValue
;
202 $data['additionalData'] = serialize($additionalData);
205 $data = array_merge($additionalData, $data);
215 protected function addFormFields(IFormDocument
$form) {
216 // read available object type definitions
217 $list = new ObjectTypeDefinitionList();
218 $list->sqlOrderBy
= 'definitionName';
219 $list->readObjects();
221 foreach ($list as $definition) {
222 $this->definitionNames
[$definition->definitionID
] = $definition->definitionName
;
224 if ($definition->interfaceName
) {
225 $this->definitionInterfaces
[$definition->definitionID
] = $definition->interfaceName
;
229 // add default form fields
230 /** @var FormContainer $dataContainer */
231 $dataContainer = $form->getNodeById('data');
233 $dataContainer->appendChildren([
234 SingleSelectionFormField
::create('definitionID')
235 ->label('wcf.acp.pip.objectType.definitionName')
236 ->description('<!-- will be replaced by JavaScript -->')
237 ->options($this->definitionNames
)
240 TextFormField
::create('objectType')
241 ->objectProperty('name')
242 ->label('wcf.acp.pip.objectType.objectType')
243 ->description('wcf.acp.pip.objectType.objectType.description')
245 ->addValidator(FormFieldValidatorUtil
::getDotSeparatedStringValidator('wcf.acp.pip.objectType.objectType', 4))
246 ->addValidator(new FormFieldValidator('uniqueness', function(TextFormField
$formField) {
247 /** @var SingleSelectionFormField $definitionIDField */
248 $definitionIDField = $formField->getDocument()->getNodeById('definitionID');
250 $definitionID = $definitionIDField->getSaveValue();
252 $definition = ObjectTypeCache
::getInstance()->getDefinition($definitionID);
254 $objectType = ObjectTypeCache
::getInstance()->getObjectTypeByName(
255 $definition->definitionName
,
256 $formField->getValue()
259 // the object type name is not unique if such an object type already exists
260 // and (a) a new object type is added or (b) the existing object type is
261 // different from the edited object type
262 if ($objectType !== null && (
263 $formField->getDocument()->getFormMode() === IFormDocument
::FORM_MODE_CREATE ||
264 $this->editedEntry
->getElementsByTagName('name')->item(0)->nodeValue
!== $formField->getValue() ||
265 $this->editedEntry
->getElementsByTagName('definitionname')->item(0)->nodeValue
!== $definition->definitionName
267 $formField->addValidationError(
268 new FormFieldValidationError(
270 'wcf.acp.pip.objectType.objectType.error.notUnique'
277 ClassNameFormField
::create()
278 ->objectProperty('classname')
279 ->description('<!-- will be replaced by JavaScript -->')
281 ->addValidator(new FormFieldValidator('implementsInterface', function(TextFormField
$formField) {
282 /** @var SingleSelectionFormField $definitionIDField */
283 $definitionIDField = $formField->getDocument()->getNodeById('definitionID');
285 $definitionID = $definitionIDField->getSaveValue();
287 $definition = ObjectTypeCache
::getInstance()->getDefinition($definitionID);
289 if (!is_subclass_of($formField->getValue(), $definition->interfaceName
)) {
290 $formField->addValidationError(
291 new FormFieldValidationError(
293 'wcf.form.field.className.error.interface',
294 ['interface' => $definition->interfaceName
]
302 /** @var SingleSelectionFormField $definitionName */
303 $definitionID = $form->getNodeById('definitionID');
305 // add general field dependencies
306 $form->getNodeById('className')->addDependency(
307 ValueFormFieldDependency
::create('definitionID')
308 ->field($definitionID)
309 ->values(array_keys($this->definitionInterfaces
))
312 // add object type-specific fields
314 // com.woltlab.wcf.adLocation
315 $this->getObjectTypeDefinitionDataContainer($form, 'com.woltlab.wcf.adLocation')
317 SingleSelectionFormField
::create('adLocationPage')
318 ->objectProperty('page')
319 ->label('wcf.acp.pip.objectType.com.woltlab.wcf.adLocation.page')
320 ->description('wcf.acp.pip.objectType.com.woltlab.wcf.adLocation.page.description')
321 ->options(function() {
325 'label' => 'wcf.global.noSelection',
330 $pageNodeTree = new PageNodeTree();
332 /** @var PageNode $pageNode */
333 foreach ($pageNodeTree->getNodeList() as $pageNode) {
335 'depth' => $pageNode->getDepth() - 1,
336 'label' => $pageNode->name
,
337 'value' => $pageNode->identifier
343 TextFormField
::create('adLocationCategoryName')
344 ->objectProperty('categoryname')
345 ->label('wcf.acp.pip.objectType.com.woltlab.wcf.adLocation.categoryName')
346 ->description('wcf.acp.pip.objectType.com.woltlab.wcf.adLocation.categoryName.description')
347 ->addValidator(FormFieldValidatorUtil
::getDotSeparatedStringValidator(
348 'wcf.acp.pip.objectType.com.woltlab.wcf.adLocation.categoryName',
351 ItemListFormField
::create('adLocationCssClassName')
352 ->objectProperty('cssclassname')
353 ->label('wcf.acp.pip.objectType.com.woltlab.wcf.adLocation.cssClassName')
354 ->description('wcf.acp.pip.objectType.com.woltlab.wcf.adLocation.cssClassName.description')
355 ->saveValueType(ItemListFormField
::SAVE_VALUE_TYPE_SSV
)
356 ->addValidator(new FormFieldValidator('format', function(ItemListFormField
$formField) {
357 if (!empty($formField->getValue())) {
358 $invalidClasses = [];
359 foreach ($formField->getValue() as $class) {
360 if (preg_match('~^-?[_A-z][_A-z0-9-]*$~', $class) !== 1) {
361 $invalidClasses[] = $class;
365 if (!empty($invalidClasses)) {
366 $formField->addValidationError(
367 new FormFieldValidationError(
369 'wcf.acp.pip.objectType.com.woltlab.wcf.adLocation.cssClassName.error.invalid',
370 ['invalidClasses' => $invalidClasses]
377 $this->definitionElementChildren
['com.woltlab.wcf.adLocation'] = [
379 'categoryname' => '',
383 // com.woltlab.wcf.attachment.objectType
384 $this->getObjectTypeDefinitionDataContainer($form, 'com.woltlab.wcf.attachment.objectType')
386 BooleanFormField
::create('attachmentPrivate')
387 ->objectProperty('private')
388 ->label('wcf.acp.pip.objectType.com.woltlab.wcf.attachment.objectType.private')
389 ->description('wcf.acp.pip.objectType.com.woltlab.wcf.attachment.objectType.private.description')
391 $this->definitionElementChildren
['com.woltlab.wcf.attachment.objectType'] = ['private' => 0];
393 // com.woltlab.wcf.bulkProcessing.user.action
394 $this->addBulkProcessingActionFields($form, 'com.woltlab.wcf.bulkProcessing.user.action');
396 // com.woltlab.wcf.bulkProcessing.user.condition
397 $bulkProcessingUserConditionContainer = $this->getObjectTypeDefinitionDataContainer($form, 'com.woltlab.wcf.bulkProcessing.user.condition');
398 $this->addConditionFields($bulkProcessingUserConditionContainer, 'com.woltlab.wcf.bulkProcessing.user.condition', false, true);
400 // com.woltlab.wcf.category
401 $this->getObjectTypeDefinitionDataContainer($form, 'com.woltlab.wcf.category')
403 BooleanFormField
::create('categoryDefaultPermission')
404 ->objectProperty('defaultpermission')
405 ->label('wcf.acp.pip.objectType.com.woltlab.wcf.category.defaultPermission')
406 ->description('wcf.acp.pip.objectType.com.woltlab.wcf.category.defaultPermission.description')
408 $this->definitionElementChildren
['com.woltlab.wcf.category'] = ['defaultpermission' => 0];
410 // com.woltlab.wcf.clipboardItem
411 $this->getObjectTypeDefinitionDataContainer($form, 'com.woltlab.wcf.clipboardItem')
413 ClassNameFormField
::create('clipboardItemListClassName')
414 ->objectProperty('listclassname')
415 ->label('wcf.acp.pip.objectType.com.woltlab.wcf.clipboardItem.listClassName')
416 ->description('wcf.acp.pip.objectType.com.woltlab.wcf.clipboardItem.listClassName.description')
418 ->parentClass(DatabaseObjectList
::class)
420 $this->definitionElementChildren
['com.woltlab.wcf.clipboardItem'] = ['listclassname'];
422 // com.woltlab.wcf.condition.ad
423 $conditionAdContainer = $this->getObjectTypeDefinitionDataContainer($form, 'com.woltlab.wcf.condition.ad');
424 $this->addConditionFields($conditionAdContainer, 'com.woltlab.wcf.condition.ad', true, true);
426 // com.woltlab.wcf.condition.notice
427 $conditionAdContainer = $this->getObjectTypeDefinitionDataContainer($form, 'com.woltlab.wcf.condition.notice');
428 $this->addConditionFields($conditionAdContainer, 'com.woltlab.wcf.condition.notice');
430 // com.woltlab.wcf.condition.trophy
431 $conditionAdContainer = $this->getObjectTypeDefinitionDataContainer($form, 'com.woltlab.wcf.condition.trophy');
432 $this->addConditionFields($conditionAdContainer, 'com.woltlab.wcf.condition.trophy', false, true);
434 // com.woltlab.wcf.condition.userGroupAssignment
435 $conditionAdContainer = $this->getObjectTypeDefinitionDataContainer($form, 'com.woltlab.wcf.condition.userGroupAssignment');
436 $this->addConditionFields($conditionAdContainer, 'com.woltlab.wcf.condition.userGroupAssignment', false, true);
438 // com.woltlab.wcf.condition.userSearch
439 $conditionAdContainer = $this->getObjectTypeDefinitionDataContainer($form, 'com.woltlab.wcf.condition.userSearch');
440 $this->addConditionFields($conditionAdContainer, 'com.woltlab.wcf.condition.userSearch', false, true);
442 // com.woltlab.wcf.message
443 $this->getObjectTypeDefinitionDataContainer($form, 'com.woltlab.wcf.message')
445 BooleanFormField
::create('enableToc')
446 ->label('wcf.acp.pip.objectType.com.woltlab.wcf.message.enableToc')
447 ->description('wcf.acp.pip.objectType.com.woltlab.wcf.message.enableToc.description')
449 $this->definitionElementChildren
['com.woltlab.wcf.message'] = ['enableToc' => 0];
451 // com.woltlab.wcf.notification.objectType
452 $this->getObjectTypeDefinitionDataContainer($form, 'com.woltlab.wcf.notification.objectType')
454 TextFormField
::create('notificationObjectTypeCategory')
455 ->objectProperty('category')
456 ->label('wcf.acp.pip.objectType.com.woltlab.wcf.notification.objectType.category')
457 ->description('wcf.acp.pip.objectType.com.woltlab.wcf.notification.objectType.category.description')
458 ->addValidator(FormFieldValidatorUtil
::getDotSeparatedStringValidator(
459 'wcf.acp.pip.objectType.com.woltlab.wcf.notification.objectType.category',
463 BooleanFormField
::create('notificationObjectTypeSupportsReactions')
464 ->objectProperty('supportsReactions')
465 ->label('wcf.acp.pip.objectType.com.woltlab.wcf.notification.objectType.supportsReactions')
466 ->description('wcf.acp.pip.objectType.com.woltlab.wcf.notification.objectType.supportsReactions.description')
468 $this->definitionElementChildren
['com.woltlab.wcf.notification.objectType'] = [
470 'supportsReactions' => 0
473 // com.woltlab.wcf.rebuildData
474 $this->getObjectTypeDefinitionDataContainer($form, 'com.woltlab.wcf.rebuildData')
476 IntegerFormField
::create('rebuildDataNiceValue')
477 ->objectProperty('nicevalue')
478 ->label('wcf.acp.pip.objectType.com.woltlab.wcf.rebuildData.niceValue')
479 ->description('wcf.acp.pip.objectType.com.woltlab.wcf.rebuildData.niceValue.description')
482 $this->definitionElementChildren
['com.woltlab.wcf.rebuildData'] = ['nicevalue' => null];
484 // com.woltlab.wcf.searchableObjectType
485 $this->getObjectTypeDefinitionDataContainer($form, 'com.woltlab.wcf.searchableObjectType')
487 TextFormField
::create('searchableObjectTypeSearchIndex')
488 ->objectProperty('searchindex')
489 ->label('wcf.acp.pip.objectType.com.woltlab.wcf.searchableObjectType.searchIndex')
490 ->description('wcf.acp.pip.objectType.com.woltlab.wcf.searchableObjectType.searchIndex.description')
492 ->addValidator(new FormFieldValidator('tableName', function(TextFormField
$formField) {
493 if ($formField->getValue()) {
494 if (preg_match('~^(?P<app>[A-z]+)1_[A-z_]+$~', $formField->getValue(), $match)) {
495 if (!ApplicationHandler
::getInstance()->getApplication($match['app'])) {
496 $formField->addValidationError(
497 new FormFieldValidationError(
499 'wcf.acp.pip.objectType.com.woltlab.wcf.searchableObjectType.searchIndex.error.unknownApp',
500 ['app' => $match['app']]
506 $formField->addValidationError(
507 new FormFieldValidationError(
509 'wcf.acp.pip.objectType.com.woltlab.wcf.searchableObjectType.searchIndex.error.invalid'
516 $this->definitionElementChildren
['com.woltlab.wcf.searchableObjectType'] = ['searchindex'];
518 // com.woltlab.wcf.sitemap.object
519 $this->getObjectTypeDefinitionDataContainer($form, 'com.woltlab.wcf.sitemap.object')
521 FloatFormField
::create('sitemapObjectPriority')
522 ->objectProperty('priority')
523 ->label('wcf.acp.pip.objectType.com.woltlab.wcf.sitemap.object.priority')
524 ->description('wcf.acp.pip.objectType.com.woltlab.wcf.sitemap.object.priority.description')
531 SingleSelectionFormField
::create('sitemapObjectchangeFreq')
532 ->objectProperty('changeFreq')
533 ->label('wcf.acp.pip.objectType.com.woltlab.wcf.sitemap.object.changeFreq')
534 ->description('wcf.acp.pip.objectType.com.woltlab.wcf.sitemap.object.changeFreq.description')
536 'always' => 'always',
537 'hourly' => 'hourly',
539 'weekly' => 'weekly',
540 'monthly' => 'monthly',
541 'yearly' => 'yearly',
546 IntegerFormField
::create('sitemapObjectRebuildTime')
547 ->objectProperty('rebuildTime')
548 ->label('wcf.acp.pip.objectType.com.woltlab.wcf.sitemap.object.rebuildTime')
549 ->description('wcf.acp.pip.objectType.com.woltlab.wcf.sitemap.object.rebuildTime.description')
550 ->suffix('wcf.acp.option.suffix.seconds')
554 $this->definitionElementChildren
['com.woltlab.wcf.sitemap.object'] = ['priority', 'changeFreq', 'rebuildTime'];
556 // com.woltlab.wcf.statDailyHandler
557 $this->getObjectTypeDefinitionDataContainer($form, 'com.woltlab.wcf.statDailyHandler')
559 TextFormField
::create('statDailyHandlerCategoryName')
560 ->objectProperty('categoryname')
561 ->label('wcf.acp.pip.objectType.com.woltlab.wcf.statDailyHandler.categoryName')
562 ->description('wcf.acp.pip.objectType.com.woltlab.wcf.statDailyHandler.categoryName.description')
563 ->addValidator(FormFieldValidatorUtil
::getDotSeparatedStringValidator(
564 'wcf.acp.pip.objectType.com.woltlab.wcf.statDailyHandler.categoryName'
567 BooleanFormField
::create('statDailyHandlerIsDefault')
568 ->objectProperty('default')
569 ->label('wcf.acp.pip.objectType.com.woltlab.wcf.statDailyHandler.isDefault')
570 ->description('wcf.acp.pip.objectType.com.woltlab.wcf.statDailyHandler.isDefault.description')
572 $this->definitionElementChildren
['com.woltlab.wcf.statDailyHandler'] = [
573 'categoryname' => '',
577 // com.woltlab.wcf.tagging.taggableObject
578 $this->getObjectTypeDefinitionDataContainer($form, 'com.woltlab.wcf.tagging.taggableObject')
580 OptionFormField
::create('taggingTaggableObjectOptions')
581 ->objectProperty('options')
582 ->description('wcf.acp.pip.objectType.com.woltlab.wcf.tagging.taggableObject.options.description')
583 ->packageIDs(array_merge(
584 [$this->installation
->getPackage()->packageID
],
585 array_keys($this->installation
->getPackage()->getAllRequiredPackages())
588 UserGroupOptionFormField
::create('taggingTaggableObjectPermissions')
589 ->objectProperty('permissions')
590 ->description('wcf.acp.pip.objectType.com.woltlab.wcf.tagging.taggableObject.permissions.description')
591 ->packageIDs(array_merge(
592 [$this->installation
->getPackage()->packageID
],
593 array_keys($this->installation
->getPackage()->getAllRequiredPackages())
596 $this->definitionElementChildren
['com.woltlab.wcf.tagging.taggableObject'] = [
601 // com.woltlab.wcf.user.activityPointEvent
602 $this->getObjectTypeDefinitionDataContainer($form, 'com.woltlab.wcf.user.activityPointEvent')
604 IntegerFormField
::create('userActivityPointEventPoints')
605 ->objectProperty('points')
606 ->label('wcf.acp.pip.objectType.com.woltlab.wcf.user.activityPointEvent.points')
607 ->description('wcf.acp.pip.objectType.com.woltlab.wcf.user.activityPointEvent.points.description')
611 $this->definitionElementChildren
['com.woltlab.wcf.user.activityPointEvent'] = ['points'];
613 // com.woltlab.wcf.user.recentActivityEvent
614 $this->getObjectTypeDefinitionDataContainer($form, 'com.woltlab.wcf.user.recentActivityEvent')
616 BooleanFormField
::create('userRecentActivityEventSupportsReactions')
617 ->objectProperty('supportsReactions')
618 ->label('wcf.acp.pip.objectType.com.woltlab.wcf.user.recentActivityEvent.supportsReactions')
619 ->description('wcf.acp.pip.objectType.com.woltlab.wcf.user.recentActivityEvent.supportsReactions.description')
621 $this->definitionElementChildren
['com.woltlab.wcf.user.recentActivityEvent'] = ['supportsReactions' => 0];
623 // com.woltlab.wcf.versionTracker.objectType
624 $this->getObjectTypeDefinitionDataContainer($form, 'com.woltlab.wcf.versionTracker.objectType')
626 TextFormField
::create('versionTrackerObjectTypeTableName')
627 ->objectProperty('tableName')
628 ->label('wcf.acp.pip.objectType.com.woltlab.wcf.versionTracker.objectType.tableName')
629 ->description('wcf.acp.pip.objectType.com.woltlab.wcf.versionTracker.objectType.tableName.description')
631 ->addValidator(new FormFieldValidator('tableExists', function(TextFormField
$formField) {
632 if ($formField->getValue()) {
633 $value = ApplicationHandler
::insertRealDatabaseTableNames($formField->getValue());
635 if (!in_array($value, WCF
::getDB()->getEditor()->getTableNames())) {
636 $formField->addValidationError(new FormFieldValidationError(
638 'wcf.acp.pip.objectType.com.woltlab.wcf.versionTracker.objectType.tableName.error.nonExistent',
639 ['tableName' => $value]
645 TextFormField
::create('versionTrackerObjectTypeTablePrimaryKey')
646 ->objectProperty('tablePrimaryKey')
647 ->label('wcf.acp.pip.objectType.com.woltlab.wcf.versionTracker.objectType.tablePrimaryKey')
648 ->description('wcf.acp.pip.objectType.com.woltlab.wcf.versionTracker.objectType.tablePrimaryKey.description')
650 ->addValidator(new FormFieldValidator('columnExists', function(TextFormField
$formField) {
651 if ($formField->getValue()) {
652 /** @var TextFormField $tableName */
653 $tableName = $formField->getDocument()->getNodeById('versionTrackerObjectTypeTableName');
655 if (empty($tableName->getValidationErrors())) {
656 // table name has already been validated and table exists
657 $columns = WCF
::getDB()->getEditor()->getColumns(ApplicationHandler
::insertRealDatabaseTableNames($tableName->getValue()));
659 foreach ($columns as $column) {
660 if ($column['name'] === $formField->getValue()) {
661 if ($column['data']['key'] !== 'PRIMARY') {
662 $formField->addValidationError(new FormFieldValidationError(
664 'wcf.acp.pip.objectType.com.woltlab.wcf.versionTracker.objectType.tablePrimaryKey.error.noPrimaryColumn'
672 $formField->addValidationError(new FormFieldValidationError(
674 'wcf.acp.pip.objectType.com.woltlab.wcf.versionTracker.objectType.tablePrimaryKey.error.nonExistent'
680 $this->definitionElementChildren
['com.woltlab.wcf.versionTracker.objectType'] = ['tableName', 'tablePrimaryKey'];
687 public function getElementIdentifier(\DOMElement
$element) {
689 $element->getElementsByTagName('name')->item(0)->nodeValue
. '/' .
690 $element->getElementsByTagName('definitionname')->item(0)->nodeValue
698 protected function getEmptyXml() {
700 <?xml version="1.0" encoding="UTF-8"?>
701 <data xmlns="http://www.woltlab.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.woltlab.com http://www.woltlab.com/XSD/vortex/objectType.xsd">
711 public function getEntryList() {
712 $xml = $this->getProjectXml();
713 $xpath = $xml->xpath();
715 $entryList = new DevtoolsPipEntryList();
716 $this->setEntryListKeys($entryList);
718 /** @var \DOMElement $element */
719 foreach ($this->getImportElements($xpath) as $element) {
720 $entryList->addEntry($this->getElementIdentifier($element), [
721 'name' => $element->getElementsByTagName('name')->item(0)->nodeValue
,
722 'definitionName' => $element->getElementsByTagName('definitionname')->item(0)->nodeValue
733 protected function setEntryListKeys(IDevtoolsPipEntryList
$entryList) {
734 $entryList->setKeys([
735 'name' => 'wcf.acp.pip.objectType.objectType',
736 'definitionName' => 'wcf.acp.pip.objectType.definitionName'
741 * Returns a form container for the object type definition-specific fields
742 * of the the object type definition with the given name.
744 * The returned form container is already appended to the given form and
745 * has a dependency on the `definitionName` field so that the form container
746 * is only shown for the relevant object type definition.
748 * @param IFormDocument $form
749 * @param string $definitionName
750 * @return FormContainer
753 public function getObjectTypeDefinitionDataContainer(IFormDocument
$form, $definitionName) {
754 /** @var SingleSelectionFormField $definitionNameField */
755 $definitionIDField = $form->getNodeById('definitionID');
757 $definitionPieces = explode('.', $definitionName);
759 $formContainer = FormContainer
::create(lcfirst(implode('', array_map('ucfirst', $definitionPieces))) . 'Fields')
760 ->label('wcf.acp.pip.objectType.' . $definitionName . '.data.title')
762 ValueFormFieldDependency
::create('definitionID')
763 ->field($definitionIDField)
764 ->values([ObjectTypeCache
::getInstance()->getDefinitionByName($definitionName)->definitionID
])
767 $form->appendChild($formContainer);
769 return $formContainer;
776 protected function doCreateXmlElement(\DOMDocument
$document, IFormDocument
$form) {
777 $data = $form->getData()['data'];
778 $definitionName = ObjectTypeCache
::getInstance()->getDefinition($data['definitionID'])->definitionName
;
780 $objectType = $document->createElement($this->tagName
);
781 $objectType->appendChild($document->createElement('name', $data['name']));
782 $objectType->appendChild($document->createElement('definitionname', $definitionName));
784 $this->appendElementChildren(
790 if (isset($this->definitionElementChildren
[$definitionName])) {
791 $this->appendElementChildren(
793 $this->definitionElementChildren
[$definitionName],
802 * Adds bulk processing action-related fields to the given form for the given bulk
803 * processing action object type definition.
805 * @param IFormDocument $form
806 * @param string $objectTypeDefinition
808 public function addBulkProcessingActionFields(IFormDocument
$form, $objectTypeDefinition) {
809 $definitionPieces = explode('.', $objectTypeDefinition);
810 $definitionIdString = implode('', array_map('ucfirst', $definitionPieces));
812 $this->getObjectTypeDefinitionDataContainer($form, $objectTypeDefinition)
814 TextFormField
::create('bulkProcessing' . $definitionIdString . 'Action')
815 ->objectProperty('action')
816 ->label('wcf.acp.pip.objectType.bulkProcessing.action')
817 ->description('wcf.acp.pip.objectType.bulkProcessing.action.description')
818 ->addValidator(new FormFieldValidator('format', function(TextFormField
$formField) {
819 if (!preg_match('~^[a-z][A-z]+$~', $formField->getValue())) {
820 $formField->addValidationError(
821 new FormFieldValidationError(
823 'wcf.acp.pip.objectType.bulkProcessing.action.error.format'
829 OptionFormField
::create('bulkProcessing' . $definitionIdString . 'Options')
830 ->objectProperty('options')
831 ->description('wcf.acp.pip.objectType.bulkProcessing.action.options.description')
832 ->packageIDs(array_merge(
833 [$this->installation
->getPackage()->packageID
],
834 array_keys($this->installation
->getPackage()->getAllRequiredPackages())
837 UserGroupOptionFormField
::create('bulkProcessing' . $definitionIdString . 'Permissions')
838 ->objectProperty('permissions')
839 ->description('wcf.acp.pip.objectType.bulkProcessing.action.permissions.description')
840 ->packageIDs(array_merge(
841 [$this->installation
->getPackage()->packageID
],
842 array_keys($this->installation
->getPackage()->getAllRequiredPackages())
845 $this->definitionElementChildren
[$objectTypeDefinition] = [
853 * Adds all condition specific fields to the given form container.
855 * @param IFormContainer $dataContainer
856 * @param string $objectTypeDefinition
857 * @param bool $addConditionObject
858 * @param bool $addConditionGroup
861 public function addConditionFields(IFormContainer
$dataContainer, $objectTypeDefinition, $addConditionObject = true, $addConditionGroup = true) {
862 $prefix = preg_replace('~Fields$~', '', $dataContainer->getId());
864 $this->definitionElementChildren
[$objectTypeDefinition] = [];
866 if ($addConditionObject) {
867 $dataContainer->appendChild(
868 TextFormField
::create($prefix . 'ConditionObject')
869 ->objectProperty('conditionobject')
870 ->label('wcf.acp.pip.objectType.condition.conditionObject')
871 ->description('wcf.acp.pip.objectType.condition.conditionObject.description')
873 ->addValidator(FormFieldValidatorUtil
::getDotSeparatedStringValidator(
874 'wcf.acp.pip.objectType.condition.conditionObject',
878 $this->definitionElementChildren
[$objectTypeDefinition][] = 'conditionobject';
881 if ($addConditionGroup) {
882 $dataContainer->appendChild(
883 TextFormField
::create($prefix . 'ConditionGroup')
884 ->objectProperty('conditiongroup')
885 ->label('wcf.acp.pip.objectType.condition.conditionGroup')
886 ->description('wcf.acp.pip.objectType.condition.conditionGroup.description')
887 ->addValidator(new FormFieldValidator('format', function(TextFormField
$formField) {
888 if ($formField->getValue() && !preg_match('~^[a-z][A-z]+$~', $formField->getValue())) {
889 $formField->addValidationError(
890 new FormFieldValidationError(
892 'wcf.acp.pip.objectType.condition.conditionGroup.error.format'
898 $this->definitionElementChildren
[$objectTypeDefinition]['conditiongroup'] = '';
901 // classes extending `AbstractIntegerCondition`
902 $integerConditions = [];
903 foreach (ApplicationHandler
::getInstance()->getApplications() as $application) {
904 $conditionDir = $application->getPackage()->getAbsolutePackageDir() . 'lib/system/condition/';
906 if (file_exists($conditionDir)) {
907 $directory = DirectoryUtil
::getInstance($conditionDir);
908 $conditionList = $directory->getFiles(SORT_ASC
, new Regex('Condition\.class\.php$'));
910 /** @var string $condition */
911 foreach ($conditionList as $condition) {
912 $pathPieces = explode('/', str_replace($conditionDir, '', $condition));
913 $filename = array_pop($pathPieces);
915 $className = $application->getAbbreviation() . '\system\condition\\';
916 if (!empty($pathPieces)) {
917 $className .= implode('\\', $pathPieces) . '\\';
919 $className .= basename($filename, '.class.php');
920 if (class_exists($className) && is_subclass_of($className, AbstractIntegerCondition
::class)) {
921 $reflection = new \
ReflectionClass($className);
922 if ($reflection->isInstantiable()) {
923 $integerConditions[] = $className;
930 /** @var TextFormField $className */
931 $className = $dataContainer->getDocument()->getNodeById('className');
933 // `UserGroupCondition`
934 $dataContainer->appendChild(
935 BooleanFormField
::create($prefix . 'UserGroupIncludeGuests')
936 ->objectProperty('includeguests')
937 ->label('wcf.acp.pip.objectType.condition.userGroup.includeGuests')
938 ->description('wcf.acp.pip.objectType.condition.userGroup.includeGuests.description')
940 ValueFormFieldDependency
::create('className')
942 ->values([UserGroupCondition
::class])
945 $this->definitionElementChildren
[$objectTypeDefinition]['includeguests'] = 0;
947 // `UserIntegerPropertyCondition`
948 $dataContainer->appendChild(
949 $this->getIntegerConditionPropertyNameField(
951 UserIntegerPropertyCondition
::class,
952 $prefix . 'UserIntegerPropertyName',
953 'wcf' . WCF_N
. '_user'
956 $this->definitionElementChildren
[$objectTypeDefinition]['propertyname'] = '';
958 // `UserTimestampPropertyCondition`
959 $dataContainer->appendChild(
960 $this->getIntegerConditionPropertyNameField(
962 UserTimestampPropertyCondition
::class,
963 $prefix . 'UserTimestampPropertyName',
964 'wcf' . WCF_N
. '_user'
967 // already added above:
968 // $this->definitionElementChildren[$objectTypeDefinition]['propertyname'] = '';
971 'dataContainer' => $dataContainer,
974 EventHandler
::getInstance()->fireAction($this, 'addConditionFields', $parameters);
976 // integer property fields should be shown last
977 $dataContainer->appendChildren([
978 IntegerFormField
::create($prefix . 'IntegerMinValue')
979 ->objectProperty('minvalue')
980 ->label('wcf.acp.pip.objectType.condition.integer.minValue')
981 ->description('wcf.acp.pip.objectType.condition.integer.minValue.description')
984 ValueFormFieldDependency
::create('className')
986 ->values($integerConditions)
988 IntegerFormField
::create($prefix . 'IntegerMaxValue')
989 ->objectProperty('maxvalue')
990 ->label('wcf.acp.pip.objectType.condition.integer.maxValue')
991 ->description('wcf.acp.pip.objectType.condition.integer.maxValue.description')
994 ValueFormFieldDependency
::create('className')
996 ->values($integerConditions)
999 $this->definitionElementChildren
[$objectTypeDefinition]['minvalue'] = null;
1000 $this->definitionElementChildren
[$objectTypeDefinition]['maxvalue'] = null;
1004 * Returns a form field to enter the name of an integer property for an
1005 * integer condition.
1007 * @param TextFormField $classNameField class name field on which the visibility of the created field depends
1008 * @param string $conditionClass name of the PHP class the field is created for
1009 * @param string $id id of the created field
1010 * @param string $databaseTableName name of the database table that stores the conditioned objects
1011 * @return TextFormField
1013 public function getIntegerConditionPropertyNameField(TextFormField
$classNameField, $conditionClass, $id, $databaseTableName) {
1014 return TextFormField
::create($id)
1015 ->objectProperty('propertyname')
1016 ->label('wcf.acp.pip.objectType.integerCondition.propertyName')
1018 'wcf.acp.pip.objectType.integerCondition.propertyName.description',
1019 ['tableName' => $databaseTableName]
1022 ValueFormFieldDependency
::create('className')
1023 ->field($classNameField)
1024 ->values([$conditionClass])
1026 ->addValidator(new FormFieldValidator('userTableIntegerColumn', function(TextFormField
$formField) use ($databaseTableName) {
1027 if ($formField->getSaveValue()) {
1028 $columns = WCF
::getDB()->getEditor()->getColumns($databaseTableName);
1030 foreach ($columns as $column) {
1031 if ($column['name'] === $formField->getValue()) {
1032 if ($column['data']['type'] !== 'int') {
1033 $formField->addValidationError(new FormFieldValidationError(
1035 'wcf.acp.pip.objectType.integerCondition.propertyName.error.noIntegerColumn',
1036 ['tableName' => $databaseTableName]
1044 $formField->addValidationError(new FormFieldValidationError(
1046 'wcf.acp.pip.objectType.integerCondition.propertyName.error.nonExistent',
1047 ['tableName' => $databaseTableName]