From: Matthias Schmidt Date: Sun, 2 Jun 2019 07:31:02 +0000 (+0200) Subject: Add abstract object tree node used for selection form fields X-Git-Tag: 5.2.0_Alpha_1~45^2 X-Git-Url: https://git.stricted.de/?a=commitdiff_plain;h=755d49f06774b43fc6154a800eb132cd50a8b56c;p=GitHub%2FWoltLab%2FWCF.git Add abstract object tree node used for selection form fields --- diff --git a/wcfsetup/install/files/lib/data/DatabaseObject.class.php b/wcfsetup/install/files/lib/data/DatabaseObject.class.php index c7ea75db6f..99e9056411 100644 --- a/wcfsetup/install/files/lib/data/DatabaseObject.class.php +++ b/wcfsetup/install/files/lib/data/DatabaseObject.class.php @@ -10,7 +10,7 @@ use wcf\system\WCF; * @license GNU Lesser General Public License * @package WoltLabSuite\Core\Data */ -abstract class DatabaseObject implements IStorableObject { +abstract class DatabaseObject implements IIDObject, IStorableObject { /** * database table for this object * @var string diff --git a/wcfsetup/install/files/lib/data/IIDObject.class.php b/wcfsetup/install/files/lib/data/IIDObject.class.php new file mode 100644 index 0000000000..4ff7595da1 --- /dev/null +++ b/wcfsetup/install/files/lib/data/IIDObject.class.php @@ -0,0 +1,20 @@ + + * @package WoltLabSuite\Core\Data + * @since 5.2 + */ +interface IIDObject { + /** + * Returns the unique id of the object. + * + * @return integer + */ + public function getObjectID(); +} diff --git a/wcfsetup/install/files/lib/data/IObjectTreeNode.class.php b/wcfsetup/install/files/lib/data/IObjectTreeNode.class.php new file mode 100644 index 0000000000..4d6b15af98 --- /dev/null +++ b/wcfsetup/install/files/lib/data/IObjectTreeNode.class.php @@ -0,0 +1,52 @@ + + * @package WoltLabSuite\Core\Data + * @since 5.2 + */ +interface IObjectTreeNode extends \Countable, IIDObject, \RecursiveIterator { + /** + * Adds the given node as child node and sets the child node's parent node to this node. + * + * @param IObjectTreeNode $child added child node + * @throws \InvalidArgumentException if given object is no (deocrated) instance of this class + */ + public function addChild(IObjectTreeNode $child); + + /** + * Returns the depth of the node within the tree. + * + * The minimum depth is `1`. + * + * @return integer + */ + public function getDepth(); + + /** + * Returns the number of open parent nodes. + * + * @return integer + */ + public function getOpenParentNodes(); + + /** + * Returns `true` if this node is the last sibling and `false` otherwise. + * + * @return boolean + */ + public function isLastSibling(); + + /** + * Sets the parent node of this node. + * + * @param IObjectTreeNode $parentNode parent node + * @throws \InvalidArgumentException if given object is no (deocrated) instance of this class + */ + public function setParentNode(IObjectTreeNode $parentNode); +} diff --git a/wcfsetup/install/files/lib/data/IPollContainer.class.php b/wcfsetup/install/files/lib/data/IPollContainer.class.php index a898c69f63..a176cad398 100644 --- a/wcfsetup/install/files/lib/data/IPollContainer.class.php +++ b/wcfsetup/install/files/lib/data/IPollContainer.class.php @@ -10,14 +10,7 @@ namespace wcf\data; * @package WoltLabSuite\Core\Data * @since 5.2 */ -interface IPollContainer extends IPollObject { - /** - * Returns the id of the poll container. - * - * @return integer - */ - public function getObjectID(); - +interface IPollContainer extends IIDObject, IPollObject { /** * Returns the id of the poll that belongs to this object or `null` if there is no such poll. * diff --git a/wcfsetup/install/files/lib/data/IVersionTrackerObject.class.php b/wcfsetup/install/files/lib/data/IVersionTrackerObject.class.php index 1342a11ebe..ef29e38437 100644 --- a/wcfsetup/install/files/lib/data/IVersionTrackerObject.class.php +++ b/wcfsetup/install/files/lib/data/IVersionTrackerObject.class.php @@ -10,18 +10,11 @@ namespace wcf\data; * @package WoltLabSuite\Core\Data * @since 3.1 */ -interface IVersionTrackerObject extends IUserContent { +interface IVersionTrackerObject extends IIDObject, IUserContent { /** * Returns the link to the object's edit page. * * @return string */ public function getEditLink(); - - /** - * Returns the object's unique id. - * - * @return integer - */ - public function getObjectID(); } diff --git a/wcfsetup/install/files/lib/data/TObjectTreeNode.class.php b/wcfsetup/install/files/lib/data/TObjectTreeNode.class.php new file mode 100644 index 0000000000..3a05e1ece2 --- /dev/null +++ b/wcfsetup/install/files/lib/data/TObjectTreeNode.class.php @@ -0,0 +1,181 @@ + + * @package WoltLabSuite\Core\Data + * @since 5.2 + */ +trait TObjectTreeNode { + /** + * child nodes + * @var TObjectTreeNode[] + */ + protected $children = []; + + /** + * current iterator key + * @var integer + */ + protected $index = 0; + + /** + * parent node object + * @var TObjectTreeNode + */ + protected $parentNode = null; + + /** + * Adds the given node as child node and sets the child node's parent node to this node. + * + * @param IObjectTreeNode $child added child node + * @throws \InvalidArgumentException if given object is no (deocrated) instance of this class + */ + public function addChild(IObjectTreeNode $child) { + if (!($child instanceof $this) && !ClassUtil::isDecoratedInstanceOf($child, static::class)) { + throw new \InvalidArgumentException("Child has to be a (decorated) instance of '" . static::class . "', but instance of '" . get_class($child) . "' given."); + } + + $child->setParentNode($this); + + $this->children[] = $child; + } + + /** + * Returns the number of child nodes. + * + * @return integer + */ + public function count() { + return count($this->children); + } + + /** + * Return the currently iterated child node. + * + * @return IObjectTreeNode + */ + public function current() { + return $this->children[$this->index]; + } + + /** + * Returns an iterator for the currently iterated child node by returning the node itself. + * + * @return IObjectTreeNode + */ + public function getChildren() { + return $this->children[$this->index]; + } + + /** + * Returns the depth of the node within the tree. + * + * The minimum depth is `1`. + * + * @return integer + */ + public function getDepth() { + $element = $this; + $depth = 1; + + while ($element->parentNode->parentNode !== null) { + $depth++; + $element = $element->parentNode; + } + + return $depth; + } + + /** + * Returns the number of open parent nodes. + * + * @return integer + */ + public function getOpenParentNodes() { + $element = $this; + $i = 0; + + while ($element->parentNode->parentNode !== null && $element->isLastSibling()) { + $i++; + $element = $element->parentNode; + } + + return $i; + } + + /** + * Returns `true` if the node as any children and return `false` otherwise. + * + * @return boolean + */ + public function hasChildren() { + return !empty($this->children); + } + + /** + * Return the key of the currently iterated child node. + * + * @return integer + */ + public function key() { + return $this->index; + } + + /** + * Returns `true` if this node is the last sibling and `false` otherwise. + * + * @return boolean + */ + public function isLastSibling() { + foreach ($this->parentNode as $key => $child) { + if ($child === $this) { + return $key === count($this->parentNode) - 1; + } + } + + throw new \LogicException("Unreachable"); + } + + /** + * Moves the iteration forward to next child node. + */ + public function next() { + $this->index++; + } + + /** + * Rewind the iteration to the first child node. + */ + public function rewind() { + $this->index = 0; + } + + /** + * Sets the parent node of this node. + * + * @param IObjectTreeNode $parentNode parent node + * @throws \InvalidArgumentException if given object is no (deocrated) instance of this class + */ + public function setParentNode(IObjectTreeNode $parentNode) { + if (!($parentNode instanceof $this) && !ClassUtil::isDecoratedInstanceOf($parentNode, static::class)) { + throw new \InvalidArgumentException("Parent has to be a (decorated) instance of '" . static::class . "', but instance of '" . get_class($parentNode) . "' given."); + } + + $this->parentNode = $parentNode; + } + + /** + * Returns `true` if current iteration position is valid and `false` otherwise. + * + * @return boolean + */ + public function valid() { + return isset($this->children[$this->index]); + } +} diff --git a/wcfsetup/install/files/lib/data/category/CategoryNode.class.php b/wcfsetup/install/files/lib/data/category/CategoryNode.class.php index 4cadf7c0a7..113264ef87 100644 --- a/wcfsetup/install/files/lib/data/category/CategoryNode.class.php +++ b/wcfsetup/install/files/lib/data/category/CategoryNode.class.php @@ -1,6 +1,8 @@ setParentNode($this); - - $this->children[] = $categoryNode; - } - - /** - * Sets parent node object. - * - * @param CategoryNode $parentNode - */ - public function setParentNode(CategoryNode $parentNode) { - $this->parentNode = $parentNode; - } - - /** - * Returns true if this element is the last sibling. - * - * @return boolean - */ - public function isLastSibling() { - foreach ($this->parentNode as $key => $child) { - if ($child === $this) { - if ($key == count($this->parentNode) - 1) return true; - return false; - } - } - } - - /** - * Returns the number of open parent nodes. - * - * @return integer - */ - public function getOpenParentNodes() { - $element = $this; - $i = 0; - - while ($element->parentNode->parentNode != null && $element->isLastSibling()) { - $i++; - $element = $element->parentNode; - } - - return $i; - } - - /** - * Returns node depth. - * - * @return integer - */ - public function getDepth() { - $element = $this; - $depth = 1; - - while ($element->parentNode->parentNode != null) { - $depth++; - $element = $element->parentNode; - } - - return $depth; - } - - /** - * @inheritDoc - */ - public function count() { - return count($this->children); - } - - /** - * @inheritDoc - */ - public function current() { - return $this->children[$this->index]; - } - - /** - * @inheritDoc - */ - public function getChildren() { - return $this->children[$this->index]; - } - - /** - * @inheritDoc - */ - public function hasChildren() { - return !empty($this->children); - } - - /** - * @inheritDoc - */ - public function key() { - return $this->index; - } - - /** - * @inheritDoc - */ - public function next() { - $this->index++; - } - - /** - * @inheritDoc - */ - public function rewind() { - $this->index = 0; - } - - /** - * @inheritDoc - */ - public function valid() { - return isset($this->children[$this->index]); - } - /** * Returns true if this category is visible in a nested menu item list. * diff --git a/wcfsetup/install/files/lib/data/like/object/ILikeObject.class.php b/wcfsetup/install/files/lib/data/like/object/ILikeObject.class.php index d53b71a7c1..b692397c80 100644 --- a/wcfsetup/install/files/lib/data/like/object/ILikeObject.class.php +++ b/wcfsetup/install/files/lib/data/like/object/ILikeObject.class.php @@ -1,5 +1,6 @@ * @package WoltLabSuite\Core\Data\Like\Object */ -interface ILikeObject extends IDatabaseObjectProcessor, ITitledObject { +interface ILikeObject extends IDatabaseObjectProcessor, IIDObject, ITitledObject { /** * Returns the url to this likeable. * @@ -28,13 +29,6 @@ interface ILikeObject extends IDatabaseObjectProcessor, ITitledObject { */ public function getUserID(); - /** - * Returns the id of this object. - * - * @return integer - */ - public function getObjectID(); - /** * Returns the likeable object type previously set via `setObjectType()`. * diff --git a/wcfsetup/install/files/lib/system/form/builder/field/TSelectionFormField.class.php b/wcfsetup/install/files/lib/system/form/builder/field/TSelectionFormField.class.php index 257d15313e..78975c002b 100644 --- a/wcfsetup/install/files/lib/system/form/builder/field/TSelectionFormField.class.php +++ b/wcfsetup/install/files/lib/system/form/builder/field/TSelectionFormField.class.php @@ -1,6 +1,7 @@ options($options, $nestedOptions, $labelLanguageItems); } - else if ($options instanceof DatabaseObjectList) { + else if ($options instanceof \Traversable) { // automatically read objects - if ($options->objectIDs === null) { + if ($options instanceof DatabaseObjectList && $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."); + if ($nestedOptions) { + $collectedOptions = []; + foreach ($options as $key => $object) { + if (!($object instanceof IObjectTreeNode)) { + throw new \InvalidArgumentException("Nested traversable options must implement '" . IObjectTreeNode::class . "'."); + } + + $collectedOptions[] = [ + 'depth' => $object->getDepth() - 1, + 'label' => $object, + 'value' => $object->getObjectID() + ]; } - $dboOptions[$object->getObjectID()] = $object->getTitle(); + $options = $collectedOptions; + } + else { + $options = iterator_to_array($options); } - - $options = $dboOptions; } $this->options = []; @@ -161,8 +168,16 @@ trait TSelectionFormField { } // validate label - if (is_object($option['label']) && method_exists($option['label'], '__toString')) { - $option['label'] = (string) $option['label']; + if (is_object($option['label'])) { + if (method_exists($option['label'], '__toString')) { + $option['label'] = (string)$option['label']; + } + else if ($option['label'] instanceof ITitledObject || ClassUtil::isDecoratedInstanceOf($option['label'], ITitledObject::class)) { + $option['label'] = $option['label']->getTitle(); + } + else { + throw new \InvalidArgumentException("Nested option with key '{$key}' contain invalid label of type " . gettype($option['label']) . "."); + } } else if (!is_string($option['label']) && !is_numeric($option['label'])) { throw new \InvalidArgumentException("Nested option with key '{$key}' contain invalid label of type " . gettype($option['label']) . "."); @@ -202,8 +217,16 @@ trait TSelectionFormField { throw new \InvalidArgumentException("Non-nested options must not contain any array. Array given for value '{$value}'."); } - if (is_object($label) && method_exists($label, '__toString')) { - $label = (string) $label; + if (is_object($label)) { + if (method_exists($label, '__toString')) { + $label = (string)$label; + } + else if ($label instanceof ITitledObject || ClassUtil::isDecoratedInstanceOf($label, ITitledObject::class)) { + $label = $label->getTitle(); + } + else { + throw new \InvalidArgumentException("Options contain invalid label of type " . gettype($label) . "."); + } } else if (!is_string($label) && !is_numeric($label)) { throw new \InvalidArgumentException("Options contain invalid label of type " . gettype($label) . "."); diff --git a/wcfsetup/install/files/lib/system/request/IRouteController.class.php b/wcfsetup/install/files/lib/system/request/IRouteController.class.php index f4ef29b767..0f7b8e6bf5 100644 --- a/wcfsetup/install/files/lib/system/request/IRouteController.class.php +++ b/wcfsetup/install/files/lib/system/request/IRouteController.class.php @@ -1,5 +1,6 @@ * @package WoltLabSuite\Core\System\Request */ -interface IRouteController extends ITitledObject { - /** - * Returns the id of the object. - * - * @return integer - */ - public function getObjectID(); -} +interface IRouteController extends IIDObject, ITitledObject {} diff --git a/wcfsetup/install/files/lib/system/tagging/ITagged.class.php b/wcfsetup/install/files/lib/system/tagging/ITagged.class.php index a714671d8a..da10bda3fb 100644 --- a/wcfsetup/install/files/lib/system/tagging/ITagged.class.php +++ b/wcfsetup/install/files/lib/system/tagging/ITagged.class.php @@ -1,5 +1,6 @@ * @package WoltLabSuite\Core\System\Tagging */ -interface ITagged { - /** - * Returns the id of the tagged object. - * - * @return integer the id to get - */ - public function getObjectID(); - +interface ITagged extends IIDObject { /** * Returns the taggable type of this tagged object. * diff --git a/wcfsetup/install/files/lib/system/user/notification/object/IUserNotificationObject.class.php b/wcfsetup/install/files/lib/system/user/notification/object/IUserNotificationObject.class.php index d15ee276af..3d19ff8967 100644 --- a/wcfsetup/install/files/lib/system/user/notification/object/IUserNotificationObject.class.php +++ b/wcfsetup/install/files/lib/system/user/notification/object/IUserNotificationObject.class.php @@ -1,6 +1,7 @@ * @package WoltLabSuite\Core\System\User\Notification\Object */ -interface IUserNotificationObject extends IDatabaseObjectProcessor, ITitledObject { - /** - * Returns the ID of this object. - * - * @return integer - */ - public function getObjectID(); - +interface IUserNotificationObject extends IDatabaseObjectProcessor, IIDObject, ITitledObject { /** * Returns the url of this object. *