3 * Zend Framework (http://framework.zend.com/)
5 * @link http://github.com/zendframework/zf2 for the canonical source repository
6 * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
7 * @license http://framework.zend.com/license/new-bsd New BSD License
10 namespace Zend\Navigation\Page
;
13 use Zend\Navigation\AbstractContainer
;
14 use Zend\Navigation\Exception
;
15 use Zend\Permissions\Acl\
Resource\ResourceInterface
as AclResource
;
16 use Zend\Stdlib\ArrayUtils
;
19 * Base class for Zend\Navigation\Page pages
21 abstract class AbstractPage
extends AbstractContainer
31 * Fragment identifier (anchor identifier)
33 * The fragment identifier (anchor identifier) pointing to an anchor within
34 * a resource that is subordinate to another, primary resource.
35 * The fragment identifier introduced by a hash mark "#".
36 * Example: http://www.example.org/foo.html#bar ("bar" is the fragment identifier)
38 * @link http://www.w3.org/TR/html401/intro/intro.html#fragment-uri
52 * Style class for this page (CSS)
59 * A more descriptive title for this page
73 * Forward links to other pages
75 * @link http://www.w3.org/TR/html4/struct/links.html#h-12.3.1
79 protected $rel = array();
82 * Reverse links to other pages
84 * @link http://www.w3.org/TR/html4/struct/links.html#h-12.3.1
88 protected $rev = array();
91 * Page order used by parent container
98 * ACL resource associated with this page
100 * @var string|AclResource|null
105 * ACL privilege associated with this page
109 protected $privilege;
112 * Permission associated with this page
116 protected $permission;
119 * Text domain for Translator
123 protected $textDomain;
126 * Whether this page should be considered active
130 protected $active = false;
133 * Whether this page should be considered visible
137 protected $visible = true;
142 * @var \Zend\Navigation\AbstractContainer|null
147 * Custom page properties, used by __set(), __get() and __isset()
151 protected $properties = array();
154 * Static factories list for factory pages
158 protected static $factories = array();
163 * Factory for Zend\Navigation\Page classes
165 * A specific type to construct can be specified by specifying the key
166 * 'type' in $options. If type is 'uri' or 'mvc', the type will be resolved
167 * to Zend\Navigation\Page\Uri or Zend\Navigation\Page\Mvc. Any other value
168 * for 'type' will be considered the full name of the class to construct.
169 * A valid custom page class must extend Zend\Navigation\Page\AbstractPage.
171 * If 'type' is not given, the type of page to construct will be determined
172 * by the following rules:
173 * - If $options contains either of the keys 'action', 'controller',
174 * or 'route', a Zend\Navigation\Page\Mvc page will be created.
175 * - If $options contains the key 'uri', a Zend\Navigation\Page\Uri page
178 * @param array|Traversable $options options used for creating page
179 * @return AbstractPage a page instance
180 * @throws Exception\InvalidArgumentException if $options is not
182 * @throws Exception\InvalidArgumentException if 'type' is specified
183 * but class not found
184 * @throws Exception\InvalidArgumentException if something goes wrong
185 * during instantiation of
187 * @throws Exception\InvalidArgumentException if 'type' is given, and
188 * the specified type does
189 * not extend this class
190 * @throws Exception\InvalidArgumentException if unable to determine
191 * which class to instantiate
193 public static function factory($options)
195 if ($options instanceof Traversable
) {
196 $options = ArrayUtils
::iteratorToArray($options);
199 if (!is_array($options)) {
200 throw new Exception\
InvalidArgumentException(
201 'Invalid argument: $options must be an array or Traversable'
205 if (isset($options['type'])) {
206 $type = $options['type'];
207 if (is_string($type) && !empty($type)) {
208 switch (strtolower($type)) {
210 $type = 'Zend\Navigation\Page\Mvc';
213 $type = 'Zend\Navigation\Page\Uri';
217 if (!class_exists($type, true)) {
218 throw new Exception\
InvalidArgumentException(
219 'Cannot find class ' . $type
223 $page = new $type($options);
224 if (!$page instanceof self
) {
225 throw new Exception\
InvalidArgumentException(
227 'Invalid argument: Detected type "%s", which ' .
228 'is not an instance of Zend\Navigation\Page',
237 if (static::$factories) {
238 foreach (static::$factories as $factoryCallBack) {
239 if (($page = call_user_func($factoryCallBack, $options))) {
245 $hasUri = isset($options['uri']);
246 $hasMvc = isset($options['action']) ||
isset($options['controller'])
247 ||
isset($options['route']);
250 return new Mvc($options);
252 return new Uri($options);
254 throw new Exception\
InvalidArgumentException(
255 'Invalid argument: Unable to determine class to instantiate'
261 * Add static factory for self::factory function
263 * @param callable $callback Any callable variable
265 public static function addFactory($callback)
267 static::$factories[] = $callback;
273 * @param array|Traversable $options [optional] page options. Default is
274 * null, which should set defaults.
275 * @throws Exception\InvalidArgumentException if invalid options are given
277 public function __construct($options = null)
279 if ($options instanceof Traversable
) {
280 $options = ArrayUtils
::iteratorToArray($options);
282 if (is_array($options)) {
283 $this->setOptions($options);
286 // do custom initialization
291 * Initializes page (used by subclasses)
295 protected function init()
300 * Sets page properties using options from an associative array
302 * Each key in the array corresponds to the according set*() method, and
303 * each word is separated by underscores, e.g. the option 'target'
304 * corresponds to setTarget(), and the option 'reset_params' corresponds to
305 * the method setResetParams().
307 * @param array $options associative array of options to set
308 * @return AbstractPage fluent interface, returns self
309 * @throws Exception\InvalidArgumentException if invalid options are given
311 public function setOptions(array $options)
313 foreach ($options as $key => $value) {
314 $this->set($key, $value);
325 * @param string $label new page label
326 * @return AbstractPage fluent interface, returns self
327 * @throws Exception\InvalidArgumentException if empty/no string is given
329 public function setLabel($label)
331 if (null !== $label && !is_string($label)) {
332 throw new Exception\
InvalidArgumentException(
333 'Invalid argument: $label must be a string or null'
337 $this->label
= $label;
344 * @return string page label or null
346 public function getLabel()
352 * Sets a fragment identifier
354 * @param string $fragment new fragment identifier
355 * @return AbstractPage fluent interface, returns self
356 * @throws Exception\InvalidArgumentException if empty/no string is given
358 public function setFragment($fragment)
360 if (null !== $fragment && !is_string($fragment)) {
361 throw new Exception\
InvalidArgumentException(
362 'Invalid argument: $fragment must be a string or null'
366 $this->fragment
= $fragment;
371 * Returns fragment identifier
373 * @return string|null fragment identifier
375 public function getFragment()
377 return $this->fragment
;
383 * @param string|null $id [optional] id to set. Default is null,
385 * @return AbstractPage fluent interface, returns self
386 * @throws Exception\InvalidArgumentException if not given string or null
388 public function setId($id = null)
390 if (null !== $id && !is_string($id) && !is_numeric($id)) {
391 throw new Exception\
InvalidArgumentException(
392 'Invalid argument: $id must be a string, number or null'
396 $this->id
= null === $id ?
$id : (string) $id;
404 * @return string|null page id or null
406 public function getId()
412 * Sets page CSS class
414 * @param string|null $class [optional] CSS class to set. Default
415 * is null, which sets no CSS class.
416 * @return AbstractPage fluent interface, returns self
417 * @throws Exception\InvalidArgumentException if not given string or null
419 public function setClass($class = null)
421 if (null !== $class && !is_string($class)) {
422 throw new Exception\
InvalidArgumentException(
423 'Invalid argument: $class must be a string or null'
427 $this->class = $class;
432 * Returns page class (CSS)
434 * @return string|null page's CSS class or null
436 public function getClass()
444 * @param string $title [optional] page title. Default is
445 * null, which sets no title.
446 * @return AbstractPage fluent interface, returns self
447 * @throws Exception\InvalidArgumentException if not given string or null
449 public function setTitle($title = null)
451 if (null !== $title && !is_string($title)) {
452 throw new Exception\
InvalidArgumentException(
453 'Invalid argument: $title must be a non-empty string'
457 $this->title
= $title;
464 * @return string|null page title or null
466 public function getTitle()
474 * @param string|null $target [optional] target to set. Default is
475 * null, which sets no target.
477 * @return AbstractPage fluent interface, returns self
478 * @throws Exception\InvalidArgumentException if target is not string or null
480 public function setTarget($target = null)
482 if (null !== $target && !is_string($target)) {
483 throw new Exception\
InvalidArgumentException(
484 'Invalid argument: $target must be a string or null'
488 $this->target
= $target;
493 * Returns page target
495 * @return string|null page target or null
497 public function getTarget()
499 return $this->target
;
503 * Sets the page's forward links to other pages
505 * This method expects an associative array of forward links to other pages,
506 * where each element's key is the name of the relation (e.g. alternate,
507 * prev, next, help, etc), and the value is a mixed value that could somehow
508 * be considered a page.
510 * @param array|Traversable $relations [optional] an associative array of
511 * forward links to other pages
512 * @throws Exception\InvalidArgumentException if $relations is not an array
513 * or Traversable object
514 * @return AbstractPage fluent interface, returns self
516 public function setRel($relations = null)
518 $this->rel
= array();
520 if (null !== $relations) {
521 if ($relations instanceof Traversable
) {
522 $relations = ArrayUtils
::iteratorToArray($relations);
525 if (!is_array($relations)) {
526 throw new Exception\
InvalidArgumentException(
527 'Invalid argument: $relations must be an ' .
528 'array or an instance of Traversable'
532 foreach ($relations as $name => $relation) {
533 if (is_string($name)) {
534 $this->rel
[$name] = $relation;
543 * Returns the page's forward links to other pages
545 * This method returns an associative array of forward links to other pages,
546 * where each element's key is the name of the relation (e.g. alternate,
547 * prev, next, help, etc), and the value is a mixed value that could somehow
548 * be considered a page.
550 * @param string $relation [optional] name of relation to return. If not
551 * given, all relations will be returned.
552 * @return array an array of relations. If $relation is not
553 * specified, all relations will be returned in
554 * an associative array.
556 public function getRel($relation = null)
558 if (null !== $relation) {
559 return isset($this->rel
[$relation])
560 ?
$this->rel
[$relation]
568 * Sets the page's reverse links to other pages
570 * This method expects an associative array of reverse links to other pages,
571 * where each element's key is the name of the relation (e.g. alternate,
572 * prev, next, help, etc), and the value is a mixed value that could somehow
573 * be considered a page.
575 * @param array|Traversable $relations [optional] an associative array of
576 * reverse links to other pages
578 * @throws Exception\InvalidArgumentException if $relations it not an array
579 * or Traversable object
580 * @return AbstractPage fluent interface, returns self
582 public function setRev($relations = null)
584 $this->rev
= array();
586 if (null !== $relations) {
587 if ($relations instanceof Traversable
) {
588 $relations = ArrayUtils
::iteratorToArray($relations);
591 if (!is_array($relations)) {
592 throw new Exception\
InvalidArgumentException(
593 'Invalid argument: $relations must be an ' .
594 'array or an instance of Traversable'
598 foreach ($relations as $name => $relation) {
599 if (is_string($name)) {
600 $this->rev
[$name] = $relation;
609 * Returns the page's reverse links to other pages
611 * This method returns an associative array of forward links to other pages,
612 * where each element's key is the name of the relation (e.g. alternate,
613 * prev, next, help, etc), and the value is a mixed value that could somehow
614 * be considered a page.
616 * @param string $relation [optional] name of relation to return. If not
617 * given, all relations will be returned.
619 * @return array an array of relations. If $relation is not
620 * specified, all relations will be returned in
621 * an associative array.
623 public function getRev($relation = null)
625 if (null !== $relation) {
626 return isset($this->rev
[$relation])
628 $this->rev
[$relation]
637 * Sets page order to use in parent container
639 * @param int $order [optional] page order in container.
640 * Default is null, which sets no
642 * @return AbstractPage fluent interface, returns self
643 * @throws Exception\InvalidArgumentException if order is not integer or null
645 public function setOrder($order = null)
647 if (is_string($order)) {
648 $temp = (int) $order;
649 if ($temp < 0 ||
$temp > 0 ||
$order == '0') {
654 if (null !== $order && !is_int($order)) {
655 throw new Exception\
InvalidArgumentException(
656 'Invalid argument: $order must be an integer or null, ' .
657 'or a string that casts to an integer'
661 $this->order
= $order;
663 // notify parent, if any
664 if (isset($this->parent
)) {
665 $this->parent
->notifyOrderUpdated();
672 * Returns page order used in parent container
674 * @return int|null page order or null
676 public function getOrder()
682 * Sets ACL resource associated with this page
684 * @param string|AclResource $resource [optional] resource to associate
685 * with page. Default is null, which
687 * @return AbstractPage fluent interface, returns self
688 * @throws Exception\InvalidArgumentException if $resource is invalid
690 public function setResource($resource = null)
692 if (null === $resource
693 ||
is_string($resource)
694 ||
$resource instanceof AclResource
696 $this->resource = $resource;
698 throw new Exception\
InvalidArgumentException(
699 'Invalid argument: $resource must be null, a string, ' .
700 'or an instance of Zend\Permissions\Acl\Resource\ResourceInterface'
708 * Returns ACL resource associated with this page
710 * @return string|AclResource|null ACL resource or null
712 public function getResource()
714 return $this->resource;
718 * Sets ACL privilege associated with this page
720 * @param string|null $privilege [optional] ACL privilege to associate
721 * with this page. Default is null, which
724 * @return AbstractPage fluent interface, returns self
726 public function setPrivilege($privilege = null)
728 $this->privilege
= is_string($privilege) ?
$privilege : null;
733 * Returns ACL privilege associated with this page
735 * @return string|null ACL privilege or null
737 public function getPrivilege()
739 return $this->privilege
;
743 * Sets permission associated with this page
745 * @param mixed|null $permission [optional] permission to associate
746 * with this page. Default is null, which
747 * sets no permission.
749 * @return AbstractPage fluent interface, returns self
751 public function setPermission($permission = null)
753 $this->permission
= $permission;
758 * Returns permission associated with this page
760 * @return mixed|null permission or null
762 public function getPermission()
764 return $this->permission
;
768 * Sets text domain for translation
770 * @param string|null $textDomain [optional] text domain to associate
771 * with this page. Default is null, which
772 * sets no text domain.
774 * @return AbstractPage fluent interface, returns self
776 public function setTextDomain($textDomain = null)
778 if (null !== $textDomain) {
779 $this->textDomain
= $textDomain;
785 * Returns text domain for translation
787 * @return mixed|null text domain or null
789 public function getTextDomain()
791 return $this->textDomain
;
795 * Sets whether page should be considered active or not
797 * @param bool $active [optional] whether page should be
798 * considered active or not. Default is true.
800 * @return AbstractPage fluent interface, returns self
802 public function setActive($active = true)
804 $this->active
= (bool) $active;
809 * Returns whether page should be considered active or not
811 * @param bool $recursive [optional] whether page should be considered
812 * active if any child pages are active. Default is
814 * @return bool whether page should be considered active
816 public function isActive($recursive = false)
818 if (!$this->active
&& $recursive) {
819 foreach ($this->pages
as $page) {
820 if ($page->isActive(true)) {
827 return $this->active
;
831 * Proxy to isActive()
833 * @param bool $recursive [optional] whether page should be considered
834 * active if any child pages are active. Default
837 * @return bool whether page should be considered active
839 public function getActive($recursive = false)
841 return $this->isActive($recursive);
845 * Sets whether the page should be visible or not
847 * @param bool $visible [optional] whether page should be
848 * considered visible or not. Default is true.
849 * @return AbstractPage fluent interface, returns self
851 public function setVisible($visible = true)
853 if (is_string($visible) && 'false' == strtolower($visible)) {
856 $this->visible
= (bool) $visible;
861 * Returns a boolean value indicating whether the page is visible
863 * @param bool $recursive [optional] whether page should be considered
864 * invisible if parent is invisible. Default is
867 * @return bool whether page should be considered visible
869 public function isVisible($recursive = false)
872 && isset($this->parent
)
873 && $this->parent
instanceof self
875 if (!$this->parent
->isVisible(true)) {
880 return $this->visible
;
884 * Proxy to isVisible()
886 * Returns a boolean value indicating whether the page is visible
888 * @param bool $recursive [optional] whether page should be considered
889 * invisible if parent is invisible. Default is
892 * @return bool whether page should be considered visible
894 public function getVisible($recursive = false)
896 return $this->isVisible($recursive);
900 * Sets parent container
902 * @param AbstractContainer $parent [optional] new parent to set.
903 * Default is null which will set no parent.
904 * @throws Exception\InvalidArgumentException
905 * @return AbstractPage fluent interface, returns self
907 public function setParent(AbstractContainer
$parent = null)
909 if ($parent === $this) {
910 throw new Exception\
InvalidArgumentException(
911 'A page cannot have itself as a parent'
915 // return if the given parent already is parent
916 if ($parent === $this->parent
) {
920 // remove from old parent
921 if (null !== $this->parent
) {
922 $this->parent
->removePage($this);
926 $this->parent
= $parent;
928 // add to parent if page and not already a child
929 if (null !== $this->parent
&& !$this->parent
->hasPage($this, false)) {
930 $this->parent
->addPage($this);
937 * Returns parent container
939 * @return AbstractContainer|null parent container or null
941 public function getParent()
943 return $this->parent
;
947 * Sets the given property
949 * If the given property is native (id, class, title, etc), the matching
950 * set method will be used. Otherwise, it will be set as a custom property.
952 * @param string $property property name
953 * @param mixed $value value to set
954 * @return AbstractPage fluent interface, returns self
955 * @throws Exception\InvalidArgumentException if property name is invalid
957 public function set($property, $value)
959 if (!is_string($property) ||
empty($property)) {
960 throw new Exception\
InvalidArgumentException(
961 'Invalid argument: $property must be a non-empty string'
965 $method = 'set' . static::normalizePropertyName($property);
967 if ($method != 'setOptions' && method_exists($this, $method)
969 $this->$method($value);
971 $this->properties
[$property] = $value;
978 * Returns the value of the given property
980 * If the given property is native (id, class, title, etc), the matching
981 * get method will be used. Otherwise, it will return the matching custom
982 * property, or null if not found.
984 * @param string $property property name
985 * @return mixed the property's value or null
986 * @throws Exception\InvalidArgumentException if property name is invalid
988 public function get($property)
990 if (!is_string($property) ||
empty($property)) {
991 throw new Exception\
InvalidArgumentException(
992 'Invalid argument: $property must be a non-empty string'
996 $method = 'get' . static::normalizePropertyName($property);
998 if (method_exists($this, $method)) {
999 return $this->$method();
1000 } elseif (isset($this->properties
[$property])) {
1001 return $this->properties
[$property];
1010 * Sets a custom property
1012 * Magic overload for enabling <code>$page->propname = $value</code>.
1014 * @param string $name property name
1015 * @param mixed $value value to set
1017 * @throws Exception\InvalidArgumentException if property name is invalid
1019 public function __set($name, $value)
1021 $this->set($name, $value);
1025 * Returns a property, or null if it doesn't exist
1027 * Magic overload for enabling <code>$page->propname</code>.
1029 * @param string $name property name
1030 * @return mixed property value or null
1031 * @throws Exception\InvalidArgumentException if property name is invalid
1033 public function __get($name)
1035 return $this->get($name);
1039 * Checks if a property is set
1041 * Magic overload for enabling <code>isset($page->propname)</code>.
1043 * Returns true if the property is native (id, class, title, etc), and
1044 * true or false if it's a custom property (depending on whether the
1045 * property actually is set).
1047 * @param string $name property name
1048 * @return bool whether the given property exists
1050 public function __isset($name)
1052 $method = 'get' . static::normalizePropertyName($name);
1053 if (method_exists($this, $method)) {
1057 return isset($this->properties
[$name]);
1061 * Unsets the given custom property
1063 * Magic overload for enabling <code>unset($page->propname)</code>.
1065 * @param string $name property name
1067 * @throws Exception\InvalidArgumentException if the property is native
1069 public function __unset($name)
1071 $method = 'set' . static::normalizePropertyName($name);
1072 if (method_exists($this, $method)) {
1073 throw new Exception\
InvalidArgumentException(
1075 'Unsetting native property "%s" is not allowed',
1081 if (isset($this->properties
[$name])) {
1082 unset($this->properties
[$name]);
1087 * Returns page label
1089 * Magic overload for enabling <code>echo $page</code>.
1091 * @return string page label
1093 public function __toString()
1095 return $this->label
;
1101 * Adds a forward relation to the page
1103 * @param string $relation relation name (e.g. alternate, glossary,
1105 * @param mixed $value value to set for relation
1106 * @return AbstractPage fluent interface, returns self
1108 public function addRel($relation, $value)
1110 if (is_string($relation)) {
1111 $this->rel
[$relation] = $value;
1117 * Adds a reverse relation to the page
1119 * @param string $relation relation name (e.g. alternate, glossary,
1121 * @param mixed $value value to set for relation
1122 * @return AbstractPage fluent interface, returns self
1124 public function addRev($relation, $value)
1126 if (is_string($relation)) {
1127 $this->rev
[$relation] = $value;
1133 * Removes a forward relation from the page
1135 * @param string $relation name of relation to remove
1136 * @return AbstractPage fluent interface, returns self
1138 public function removeRel($relation)
1140 if (isset($this->rel
[$relation])) {
1141 unset($this->rel
[$relation]);
1148 * Removes a reverse relation from the page
1150 * @param string $relation name of relation to remove
1151 * @return AbstractPage fluent interface, returns self
1153 public function removeRev($relation)
1155 if (isset($this->rev
[$relation])) {
1156 unset($this->rev
[$relation]);
1163 * Returns an array containing the defined forward relations
1165 * @return array defined forward relations
1167 public function getDefinedRel()
1169 return array_keys($this->rel
);
1173 * Returns an array containing the defined reverse relations
1175 * @return array defined reverse relations
1177 public function getDefinedRev()
1179 return array_keys($this->rev
);
1183 * Returns custom properties as an array
1185 * @return array an array containing custom properties
1187 public function getCustomProperties()
1189 return $this->properties
;
1193 * Returns a hash code value for the page
1195 * @return string a hash code value for this page
1197 final public function hashCode()
1199 return spl_object_hash($this);
1203 * Returns an array representation of the page
1205 * @return array associative array containing all page properties
1207 public function toArray()
1209 return array_merge($this->getCustomProperties(), array(
1210 'label' => $this->getLabel(),
1211 'fragment' => $this->getFragment(),
1212 'id' => $this->getId(),
1213 'class' => $this->getClass(),
1214 'title' => $this->getTitle(),
1215 'target' => $this->getTarget(),
1216 'rel' => $this->getRel(),
1217 'rev' => $this->getRev(),
1218 'order' => $this->getOrder(),
1219 'resource' => $this->getResource(),
1220 'privilege' => $this->getPrivilege(),
1221 'permission' => $this->getPermission(),
1222 'active' => $this->isActive(),
1223 'visible' => $this->isVisible(),
1224 'type' => get_class($this),
1225 'pages' => parent
::toArray(),
1229 // Internal methods:
1232 * Normalizes a property name
1234 * @param string $property property name to normalize
1235 * @return string normalized property name
1237 protected static function normalizePropertyName($property)
1239 return str_replace(' ', '', ucwords(str_replace('_', ' ', $property)));
1242 // Abstract methods:
1245 * Returns href for this page
1247 * @return string the page's href
1249 abstract public function getHref();