Add support for database object lists as selection form fields' options
authorMatthias Schmidt <gravatronics@live.com>
Tue, 29 May 2018 05:12:17 +0000 (07:12 +0200)
committerMatthias Schmidt <gravatronics@live.com>
Tue, 29 May 2018 05:12:17 +0000 (07:12 +0200)
See #2509

wcfsetup/install/files/lib/acp/form/DevtoolsFormBuilderTestForm.class.php
wcfsetup/install/files/lib/system/form/builder/field/ISelectionFormField.class.php
wcfsetup/install/files/lib/system/form/builder/field/TSelectionFormField.class.php
wcfsetup/install/files/lib/util/ClassUtil.class.php

index 973146853b0e6d3ed633081d38b8c963dabe7f74..926cf60166b97dda302e0a32e7ee70768e3d897c 100644 (file)
@@ -1,6 +1,7 @@
 <?php
 declare(strict_types=1);
 namespace wcf\acp\form;
+use wcf\data\user\UserList;
 use wcf\form\AbstractForm;
 use wcf\system\exception\UserInputException;
 use wcf\system\form\builder\container\FormContainer;
@@ -90,13 +91,15 @@ class DevtoolsFormBuilderTestForm extends AbstractForm {
                                ->description('wcf.global.description')
                                ->addClass('someSection')
                                ->appendChildren([
+                                       SingleSelectionFormField::create('dboTest')
+                                               ->label('Users')
+                                               ->options(new UserList()),
                                        ShowOrderFormField::create()
                                                ->options([
                                                        2 => 'Object with id 2',
                                                        5 => 'Object with id 5',
                                                        4 => 'Object with id 4'
-                                               ])
-                                               ->value(2),
+                                               ]),
                                        TextFormField::create('name')
                                                ->label('wcf.global.name'),
                                        TitleFormField::create()
index c29bda24a3ba0f3a71b43ad036858b388af42e97..6f2ea4692d8a49b2560d9518c592e85c24032c51 100644 (file)
@@ -1,6 +1,7 @@
 <?php
 declare(strict_types=1);
 namespace wcf\system\form\builder\field;
+use wcf\data\DatabaseObjectList;
 
 /**
  * Represents a form field that consists of a predefined set of possible values.
@@ -28,7 +29,14 @@ interface ISelectionFormField {
         * and the this field is nullable, then the save value of that key is `null`
         * instead of the given empty value.
         * 
-        * @param       array|callable          $options        selectable options or callable returning the options
+        * If a `callable` is passed, it is expected that it either returns an array
+        * or a `DatabaseObjectList` object.
+        * 
+        * If a `DatabaseObjectList` object is passed and `$options->objectIDs === null`,
+        * `$options->readObjects()` is called so that the `readObjects()` does not have
+        * to be called by the API user.
+        * 
+        * @param       array|callable|DatabaseObjectList       $options        selectable options or callable returning the options
         * @return      static                                  this field
         * 
         * @throws      \InvalidArgumentException               if given options are no array or callable or otherwise invalid
index 46320758f299ddf6c15f4ffed6abbfa11ac4c3ad..c566f4f7bf8be117404024ebe5ec6a8c5046143d 100644 (file)
@@ -1,8 +1,11 @@
 <?php
 declare(strict_types=1);
 namespace wcf\system\form\builder\field;
+use wcf\data\DatabaseObjectList;
+use wcf\data\ITitledObject;
 use wcf\system\form\builder\field\validation\FormFieldValidationError;
 use wcf\system\WCF;
+use wcf\util\ClassUtil;
 
 /**
  * Provides default implementations of `ISelectionFormField` methods.
@@ -84,16 +87,38 @@ trait TSelectionFormField {
         * @throws      \UnexpectedValueException               if callable does not return an array
         */
        public function options($options): ISelectionFormField {
-               if (!is_array($options) && !is_callable($options)) {
+               if (!is_array($options) && !is_callable($options) && !($options instanceof DatabaseObjectList)) {
                        throw new \InvalidArgumentException("Given options are neither an array nor a callable, " . gettype($options) . " given.");
                }
                
                if (is_callable($options)) {
                        $options = $options();
                        
-                       if (!is_array($options)) {
-                               throw new \UnexpectedValueException("The options callable is expected to return an array, " . gettype($options) . " returned.");
+                       if (!is_array($options) && !($options instanceof DatabaseObjectList)) {
+                               throw new \UnexpectedValueException("The options callable is expected to return an array or database object list, " . gettype($options) . " returned.");
                        }
+                       
+                       return $this->options($options);
+               }
+               else if ($options instanceof DatabaseObjectList) {
+                       // automatically read objects
+                       if ($options->objectIDs === null) {
+                               $options->readObjects();
+                       }
+                       
+                       $dboOptions = [];
+                       foreach ($options as $object) {
+                               if (!ClassUtil::isDecoratedInstanceOf($object, ITitledObject::class)) {
+                                       throw new \InvalidArgumentException("The database objects in the passed list must implement '" . ITitledObject::class . "'.");
+                               }
+                               if (!$object::getDatabaseTableIndexIsIdentity()) {
+                                       throw new \InvalidArgumentException("The database objects in the passed list must must have an index that identifies the objects.");
+                               }
+                               
+                               $dboOptions[$object->getObjectID()] = $object->getTitle();
+                       }
+                       
+                       $options = $dboOptions;
                }
                
                // validate options and read possible values
index c0f21f76daf48b1e8738330b027e6bcab1365713..5440212b40cf4069566a5c15efcbdb5b2f487fee 100644 (file)
@@ -1,6 +1,7 @@
 <?php
 declare(strict_types=1);
 namespace wcf\util;
+use wcf\data\DatabaseObjectDecorator;
 use wcf\system\exception\SystemException;
 
 /**
@@ -52,6 +53,39 @@ final class ClassUtil {
                return is_subclass_of($className, $targetClass);
        }
        
+       /**
+        * Returns `true` if the given class extends or implements the target class
+        * or interface or if the given class is database object decorator and the
+        * decorated class extends or implements the target class.
+        * 
+        * This method also supports decorated decorators.
+        * 
+        * @param       string          $className              checked class
+        * @param       string          $targetClass            target class or interface
+        * @return      bool
+        */
+       public static function isDecoratedInstanceOf($className, $targetClass) {
+               if (is_subclass_of($className, $targetClass)) {
+                       return true;
+               }
+               
+               $parentClass = new \ReflectionClass($className);
+               do {
+                       $className = $parentClass->name;
+                       
+                       if (!is_subclass_of($className, DatabaseObjectDecorator::class)) {
+                               return false;
+                       }
+                       
+                       if (is_subclass_of($className::getBaseClass(), $targetClass)) {
+                               return true;
+                       }
+               }
+               while (($parentClass = $parentClass->getParentClass()));
+               
+               return false;
+       }
+       
        /**
         * Forbid creation of ClassUtil objects.
         */