* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
* @package WoltLabSuite\Core\Data
*/
-abstract class DatabaseObject implements IStorableObject {
+abstract class DatabaseObject implements IIDObject, IStorableObject {
/**
* database table for this object
* @var string
--- /dev/null
+<?php
+namespace wcf\data;
+
+/**
+ * Provides a method to access the unique id of an object.
+ *
+ * @author Matthias Schmidt
+ * @copyright 2001-2019 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package WoltLabSuite\Core\Data
+ * @since 5.2
+ */
+interface IIDObject {
+ /**
+ * Returns the unique id of the object.
+ *
+ * @return integer
+ */
+ public function getObjectID();
+}
--- /dev/null
+<?php
+namespace wcf\data;
+
+/**
+ * Every node of a database object tree has to implement this interface.
+ *
+ * @author Matthias Schmidt
+ * @copyright 2001-2019 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @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);
+}
* @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.
*
* @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();
}
--- /dev/null
+<?php
+namespace wcf\data;
+use wcf\util\ClassUtil;
+
+/**
+ * Default implementation of `IObjectTreeNode`.
+ *
+ * @author Matthias Schmidt
+ * @copyright 2001-2019 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @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]);
+ }
+}
<?php
namespace wcf\data\category;
use wcf\data\DatabaseObjectDecorator;
+use wcf\data\IObjectTreeNode;
+use wcf\data\TObjectTreeNode;
/**
* Represents a category node.
* @method Category getDecoratedObject()
* @mixin Category
*/
-class CategoryNode extends DatabaseObjectDecorator implements \RecursiveIterator, \Countable {
- /**
- * child category nodes
- * @var CategoryNode[]
- */
- protected $children = [];
-
- /**
- * current iterator key
- * @var integer
- */
- protected $index = 0;
-
- /**
- * parent node object
- * @var CategoryNode
- */
- protected $parentNode = null;
+class CategoryNode extends DatabaseObjectDecorator implements IObjectTreeNode {
+ use TObjectTreeNode;
/**
* @inheritDoc
*/
protected static $baseClass = Category::class;
- /**
- * Adds the given category node as child node.
- *
- * @param CategoryNode $categoryNode
- */
- public function addChild(CategoryNode $categoryNode) {
- $categoryNode->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.
*
<?php
namespace wcf\data\like\object;
+use wcf\data\IIDObject;
use wcf\data\like\Like;
use wcf\data\object\type\ObjectType;
use wcf\data\IDatabaseObjectProcessor;
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
* @package WoltLabSuite\Core\Data\Like\Object
*/
-interface ILikeObject extends IDatabaseObjectProcessor, ITitledObject {
+interface ILikeObject extends IDatabaseObjectProcessor, IIDObject, ITitledObject {
/**
* Returns the url to this likeable.
*
*/
public function getUserID();
- /**
- * Returns the id of this object.
- *
- * @return integer
- */
- public function getObjectID();
-
/**
* Returns the likeable object type previously set via `setObjectType()`.
*
<?php
namespace wcf\system\form\builder\field;
use wcf\data\DatabaseObjectList;
+use wcf\data\IObjectTreeNode;
use wcf\data\ITitledObject;
use wcf\system\WCF;
use wcf\util\ClassUtil;
*/
public function options($options, $nestedOptions = false, $labelLanguageItems = true) {
if ($nestedOptions) {
- if (!is_array($options) && !is_callable($options)) {
- throw new \InvalidArgumentException("The given nested options are neither an array nor a callable, " . gettype($options) . " given.");
+ if (!is_array($options) && !($options instanceof \Traversable) && !is_callable($options)) {
+ throw new \InvalidArgumentException("The given nested options are neither iterable nor a callable, " . gettype($options) . " given.");
}
}
- else if (!is_array($options) && !is_callable($options) && !($options instanceof DatabaseObjectList)) {
- throw new \InvalidArgumentException("The given options are neither an array, a callable nor an instance of '" . DatabaseObjectList::class . "', " . gettype($options) . " given.");
+ else if (!is_array($options) && !($options instanceof \Traversable) && !is_callable($options)) {
+ throw new \InvalidArgumentException("The given options are neither iterable nor a callable, " . gettype($options) . " given.");
}
if (is_callable($options)) {
$options = $options();
if ($nestedOptions) {
- if (!is_array($options) && !($options instanceof DatabaseObjectList)) {
- throw new \UnexpectedValueException("The nested options callable is expected to return an array, " . gettype($options) . " returned.");
+ if (!is_array($options) && !($options instanceof \Traversable)) {
+ throw new \UnexpectedValueException("The nested options callable is expected to return an iterable value, " . gettype($options) . " returned.");
}
}
- else if (!is_array($options) && !($options instanceof DatabaseObjectList)) {
- throw new \UnexpectedValueException("The options callable is expected to return an array or an instance of '" . DatabaseObjectList::class . "', " . gettype($options) . " returned.");
+ else if (!is_array($options) && !($options instanceof \Traversable)) {
+ throw new \UnexpectedValueException("The options callable is expected to return an iterable value, " . gettype($options) . " returned.");
}
return $this->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 = [];
}
// 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']) . ".");
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) . ".");
<?php
namespace wcf\system\request;
+use wcf\data\IIDObject;
use wcf\data\ITitledObject;
/**
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
* @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 {}
<?php
namespace wcf\system\tagging;
+use wcf\data\IIDObject;
/**
* Any tagged object has to implement this interface.
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
* @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.
*
<?php
namespace wcf\system\user\notification\object;
use wcf\data\IDatabaseObjectProcessor;
+use wcf\data\IIDObject;
use wcf\data\ITitledObject;
/**
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
* @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.
*