PageRenderTime 58ms CodeModel.GetById 16ms app.highlight 34ms RepoModel.GetById 1ms app.codeStats 1ms

/applications/core/lib/Zend/Navigation/Page.php

#
PHP | 1116 lines | 468 code | 118 blank | 530 comment | 72 complexity | a9ce3855b7dc1a53a51dd20354a77a2d MD5 | raw file
   1<?php
   2/**
   3 * Zend Framework
   4 *
   5 * LICENSE
   6 *
   7 * This source file is subject to the new BSD license that is bundled
   8 * with this package in the file LICENSE.txt.
   9 * It is also available through the world-wide-web at this URL:
  10 * http://framework.zend.com/license/new-bsd
  11 * If you did not receive a copy of the license and are unable to
  12 * obtain it through the world-wide-web, please send an email
  13 * to license@zend.com so we can send you a copy immediately.
  14 *
  15 * @category  Zend
  16 * @package   Zend_Navigation
  17 * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com)
  18 * @license   http://framework.zend.com/license/new-bsd     New BSD License
  19 */
  20
  21/**
  22 * @see Zend_Navigation_Container
  23 */
  24require_once 'Zend/Navigation/Container.php';
  25
  26/**
  27 * Base class for Zend_Navigation_Page pages
  28 *
  29 * @category  Zend
  30 * @package   Zend_Navigation
  31 * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com)
  32 * @license   http://framework.zend.com/license/new-bsd     New BSD License
  33 */
  34abstract class Zend_Navigation_Page extends Zend_Navigation_Container
  35{
  36    /**
  37     * Page label
  38     *
  39     * @var string|null
  40     */
  41    protected $_label;
  42
  43    /**
  44     * Page id
  45     *
  46     * @var string|null
  47     */
  48    protected $_id;
  49
  50    /**
  51     * Style class for this page (CSS)
  52     *
  53     * @var string|null
  54     */
  55    protected $_class;
  56
  57    /**
  58     * A more descriptive title for this page
  59     *
  60     * @var string|null
  61     */
  62    protected $_title;
  63
  64    /**
  65     * This page's target
  66     *
  67     * @var string|null
  68     */
  69    protected $_target;
  70
  71    /**
  72     * Forward links to other pages
  73     *
  74     * @link http://www.w3.org/TR/html4/struct/links.html#h-12.3.1
  75     *
  76     * @var array
  77     */
  78    protected $_rel = array();
  79
  80    /**
  81     * Reverse links to other pages
  82     *
  83     * @link http://www.w3.org/TR/html4/struct/links.html#h-12.3.1
  84     *
  85     * @var array
  86     */
  87    protected $_rev = array();
  88
  89    /**
  90     * Page order used by parent container
  91     *
  92     * @var int|null
  93     */
  94    protected $_order;
  95
  96    /**
  97     * ACL resource associated with this page
  98     *
  99     * @var string|Zend_Acl_Resource_Interface|null
 100     */
 101    protected $_resource;
 102
 103    /**
 104     * ACL privilege associated with this page
 105     *
 106     * @var string|null
 107     */
 108    protected $_privilege;
 109
 110    /**
 111     * Whether this page should be considered active
 112     *
 113     * @var bool
 114     */
 115    protected $_active = false;
 116
 117    /**
 118     * Whether this page should be considered visible
 119     *
 120     * @var bool
 121     */
 122    protected $_visible = true;
 123
 124    /**
 125     * Parent container
 126     *
 127     * @var Zend_Navigation_Container|null
 128     */
 129    protected $_parent;
 130
 131    /**
 132     * Custom page properties, used by __set(), __get() and __isset()
 133     *
 134     * @var array
 135     */
 136    protected $_properties = array();
 137
 138    // Initialization:
 139
 140    /**
 141     * Factory for Zend_Navigation_Page classes
 142     *
 143     * A specific type to construct can be specified by specifying the key
 144     * 'type' in $options. If type is 'uri' or 'mvc', the type will be resolved
 145     * to Zend_Navigation_Page_Uri or Zend_Navigation_Page_Mvc. Any other value
 146     * for 'type' will be considered the full name of the class to construct.
 147     * A valid custom page class must extend Zend_Navigation_Page.
 148     *
 149     * If 'type' is not given, the type of page to construct will be determined
 150     * by the following rules:
 151     * - If $options contains either of the keys 'action', 'controller',
 152     *   'module', or 'route', a Zend_Navigation_Page_Mvc page will be created.
 153     * - If $options contains the key 'uri', a Zend_Navigation_Page_Uri page
 154     *   will be created.
 155     *
 156     * @param  array|Zend_Config $options  options used for creating page
 157     * @return Zend_Navigation_Page        a page instance
 158     * @throws Zend_Navigation_Exception   if $options is not array/Zend_Config
 159     * @throws Zend_Exception              if 'type' is specified and
 160     *                                     Zend_Loader is unable to load the
 161     *                                     class
 162     * @throws Zend_Navigation_Exception   if something goes wrong during
 163     *                                     instantiation of the page
 164     * @throws Zend_Navigation_Exception   if 'type' is given, and the specified
 165     *                                     type does not extend this class
 166     * @throws Zend_Navigation_Exception   if unable to determine which class
 167     *                                     to instantiate
 168     */
 169    public static function factory($options)
 170    {
 171        if ($options instanceof Zend_Config) {
 172            $options = $options->toArray();
 173        }
 174
 175        if (!is_array($options)) {
 176            require_once 'Zend/Navigation/Exception.php';
 177            throw new Zend_Navigation_Exception(
 178                'Invalid argument: $options must be an array or Zend_Config');
 179        }
 180
 181        if (isset($options['type'])) {
 182            $type = $options['type'];
 183            if (is_string($type) && !empty($type)) {
 184                switch (strtolower($type)) {
 185                    case 'mvc':
 186                        $type = 'Zend_Navigation_Page_Mvc';
 187                        break;
 188                    case 'uri':
 189                        $type = 'Zend_Navigation_Page_Uri';
 190                        break;
 191                }
 192
 193                require_once 'Zend/Loader.php';
 194                @Zend_Loader::loadClass($type);
 195
 196                $page = new $type($options);
 197                if (!$page instanceof Zend_Navigation_Page) {
 198                    require_once 'Zend/Navigation/Exception.php';
 199                    throw new Zend_Navigation_Exception(sprintf(
 200                            'Invalid argument: Detected type "%s", which ' .
 201                            'is not an instance of Zend_Navigation_Page',
 202                            $type));
 203                }
 204                return $page;
 205            }
 206        }
 207
 208        $hasUri = isset($options['uri']);
 209        $hasMvc = isset($options['action']) || isset($options['controller']) ||
 210                  isset($options['module']) || isset($options['route']);
 211
 212        if ($hasMvc) {
 213            require_once 'Zend/Navigation/Page/Mvc.php';
 214            return new Zend_Navigation_Page_Mvc($options);
 215        } elseif ($hasUri) {
 216            require_once 'Zend/Navigation/Page/Uri.php';
 217            return new Zend_Navigation_Page_Uri($options);
 218        } else {
 219            require_once 'Zend/Navigation/Exception.php';
 220            throw new Zend_Navigation_Exception(
 221                'Invalid argument: Unable to determine class to instantiate');
 222        }
 223    }
 224
 225    /**
 226     * Page constructor
 227     *
 228     * @param  array|Zend_Config $options   [optional] page options. Default is
 229     *                                      null, which should set defaults.
 230     * @throws Zend_Navigation_Exception    if invalid options are given
 231     */
 232    public function __construct($options = null)
 233    {
 234        if (is_array($options)) {
 235            $this->setOptions($options);
 236        } elseif ($options instanceof Zend_Config) {
 237            $this->setConfig($config);
 238        }
 239
 240        // do custom initialization
 241        $this->_init();
 242    }
 243
 244    /**
 245     * Initializes page (used by subclasses)
 246     *
 247     * @return void
 248     */
 249    protected function _init()
 250    {
 251    }
 252
 253    /**
 254     * Sets page properties using a Zend_Config object
 255     *
 256     * @param  Zend_Config $config        config object to get properties from
 257     * @return Zend_Navigation_Page       fluent interface, returns self
 258     * @throws Zend_Navigation_Exception  if invalid options are given
 259     */
 260    public function setConfig(Zend_Config $config)
 261    {
 262        return $this->setOptions($config->toArray());
 263    }
 264
 265    /**
 266     * Sets page properties using options from an associative array
 267     *
 268     * Each key in the array corresponds to the according set*() method, and
 269     * each word is separated by underscores, e.g. the option 'target'
 270     * corresponds to setTarget(), and the option 'reset_params' corresponds to
 271     * the method setResetParams().
 272     *
 273     * @param  array $options             associative array of options to set
 274     * @return Zend_Navigation_Page       fluent interface, returns self
 275     * @throws Zend_Navigation_Exception  if invalid options are given
 276     */
 277    public function setOptions(array $options)
 278    {
 279        foreach ($options as $key => $value) {
 280            $this->set($key, $value);
 281        }
 282
 283        return $this;
 284    }
 285
 286    // Accessors:
 287
 288    /**
 289     * Sets page label
 290     *
 291     * @param  string $label              new page label
 292     * @return Zend_Navigation_Page       fluent interface, returns self
 293     * @throws Zend_Navigation_Exception  if empty/no string is given
 294     */
 295    public function setLabel($label)
 296    {
 297        if (null !== $label && !is_string($label)) {
 298            require_once 'Zend/Navigation/Exception.php';
 299            throw new Zend_Navigation_Exception(
 300                    'Invalid argument: $label must be a string or null');
 301        }
 302
 303        $this->_label = $label;
 304        return $this;
 305    }
 306
 307    /**
 308     * Returns page label
 309     *
 310     * @return string  page label or null
 311     */
 312    public function getLabel()
 313    {
 314        return $this->_label;
 315    }
 316
 317    /**
 318     * Sets page id
 319     *
 320     * @param  string|null $id            [optional] id to set. Default is null,
 321     *                                    which sets no id.
 322     * @return Zend_Navigation_Page       fluent interface, returns self
 323     * @throws Zend_Navigation_Exception  if not given string or null
 324     */
 325    public function setId($id = null)
 326    {
 327        if (null !== $id && !is_string($id) && !is_numeric($id)) {
 328            require_once 'Zend/Navigation/Exception.php';
 329            throw new Zend_Navigation_Exception(
 330                    'Invalid argument: $id must be a string, number or null');
 331        }
 332
 333        $this->_id = null === $id ? $id : (string) $id;
 334
 335        return $this;
 336    }
 337
 338    /**
 339     * Returns page id
 340     *
 341     * @return string|null  page id or null
 342     */
 343    public function getId()
 344    {
 345        return $this->_id;
 346    }
 347
 348    /**
 349     * Sets page CSS class
 350     *
 351     * @param  string|null $class         [optional] CSS class to set. Default
 352     *                                    is null, which sets no CSS class.
 353     * @return Zend_Navigation_Page       fluent interface, returns self
 354     * @throws Zend_Navigation_Exception  if not given string or null
 355     */
 356    public function setClass($class = null)
 357    {
 358        if (null !== $class && !is_string($class)) {
 359            require_once 'Zend/Navigation/Exception.php';
 360            throw new Zend_Navigation_Exception(
 361                    'Invalid argument: $class must be a string or null');
 362        }
 363
 364        $this->_class = $class;
 365        return $this;
 366    }
 367
 368    /**
 369     * Returns page class (CSS)
 370     *
 371     * @return string|null  page's CSS class or null
 372     */
 373    public function getClass()
 374    {
 375        return $this->_class;
 376    }
 377
 378    /**
 379     * Sets page title
 380     *
 381     * @param  string $title              [optional] page title. Default is
 382     *                                    null, which sets no title.
 383     * @return Zend_Navigation_Page       fluent interface, returns self
 384     * @throws Zend_Navigation_Exception  if not given string or null
 385     */
 386    public function setTitle($title = null)
 387    {
 388        if (null !== $title && !is_string($title)) {
 389            require_once 'Zend/Navigation/Exception.php';
 390            throw new Zend_Navigation_Exception(
 391                    'Invalid argument: $title must be a non-empty string');
 392        }
 393
 394        $this->_title = $title;
 395        return $this;
 396    }
 397
 398    /**
 399     * Returns page title
 400     *
 401     * @return string|null  page title or null
 402     */
 403    public function getTitle()
 404    {
 405        return $this->_title;
 406    }
 407
 408    /**
 409     * Sets page target
 410     *
 411     * @param  string|null $target        [optional] target to set. Default is
 412     *                                    null, which sets no target.
 413     * @return Zend_Navigation_Page       fluent interface, returns self
 414     * @throws Zend_Navigation_Exception  if target is not string or null
 415     */
 416    public function setTarget($target = null)
 417    {
 418        if (null !== $target && !is_string($target)) {
 419            require_once 'Zend/Navigation/Exception.php';
 420            throw new Zend_Navigation_Exception(
 421                    'Invalid argument: $target must be a string or null');
 422        }
 423
 424        $this->_target = $target;
 425        return $this;
 426    }
 427
 428    /**
 429     * Returns page target
 430     *
 431     * @return string|null  page target or null
 432     */
 433    public function getTarget()
 434    {
 435        return $this->_target;
 436    }
 437
 438    /**
 439     * Sets the page's forward links to other pages
 440     *
 441     * This method expects an associative array of forward links to other pages,
 442     * where each element's key is the name of the relation (e.g. alternate,
 443     * prev, next, help, etc), and the value is a mixed value that could somehow
 444     * be considered a page.
 445     *
 446     * @param  array|Zend_Config $relations  [optional] an associative array of
 447     *                                       forward links to other pages
 448     * @return Zend_Navigation_Page          fluent interface, returns self
 449     */
 450    public function setRel($relations = null)
 451    {
 452        $this->_rel = array();
 453
 454        if (null !== $relations) {
 455            if ($relations instanceof Zend_Config) {
 456                $relations = $relations->toArray();
 457            }
 458
 459            if (!is_array($relations)) {
 460                require_once 'Zend/Navigation/Exception.php';
 461                throw new Zend_Navigation_Exception(
 462                        'Invalid argument: $relations must be an ' .
 463                        'array or an instance of Zend_Config');
 464            }
 465
 466            foreach ($relations as $name => $relation) {
 467                if (is_string($name)) {
 468                    $this->_rel[$name] = $relation;
 469                }
 470            }
 471        }
 472
 473        return $this;
 474    }
 475
 476    /**
 477     * Returns the page's forward links to other pages
 478     *
 479     * This method returns an associative array of forward links to other pages,
 480     * where each element's key is the name of the relation (e.g. alternate,
 481     * prev, next, help, etc), and the value is a mixed value that could somehow
 482     * be considered a page.
 483     *
 484     * @param  string $relation  [optional] name of relation to return. If not
 485     *                           given, all relations will be returned.
 486     * @return array             an array of relations. If $relation is not
 487     *                           specified, all relations will be returned in
 488     *                           an associative array.
 489     */
 490    public function getRel($relation = null)
 491    {
 492        if (null !== $relation) {
 493            return isset($this->_rel[$relation]) ?
 494                   $this->_rel[$relation] :
 495                   null;
 496        }
 497
 498        return $this->_rel;
 499    }
 500
 501    /**
 502     * Sets the page's reverse links to other pages
 503     *
 504     * This method expects an associative array of reverse links to other pages,
 505     * where each element's key is the name of the relation (e.g. alternate,
 506     * prev, next, help, etc), and the value is a mixed value that could somehow
 507     * be considered a page.
 508     *
 509     * @param  array|Zend_Config $relations  [optional] an associative array of
 510     *                                       reverse links to other pages
 511     * @return Zend_Navigation_Page          fluent interface, returns self
 512     */
 513    public function setRev($relations = null)
 514    {
 515        $this->_rev = array();
 516
 517        if (null !== $relations) {
 518            if ($relations instanceof Zend_Config) {
 519                $relations = $relations->toArray();
 520            }
 521
 522            if (!is_array($relations)) {
 523                require_once 'Zend/Navigation/Exception.php';
 524                throw new Zend_Navigation_Exception(
 525                        'Invalid argument: $relations must be an ' .
 526                        'array or an instance of Zend_Config');
 527            }
 528
 529            foreach ($relations as $name => $relation) {
 530                if (is_string($name)) {
 531                    $this->_rev[$name] = $relation;
 532                }
 533            }
 534        }
 535
 536        return $this;
 537    }
 538
 539    /**
 540     * Returns the page's reverse links to other pages
 541     *
 542     * This method returns an associative array of forward links to other pages,
 543     * where each element's key is the name of the relation (e.g. alternate,
 544     * prev, next, help, etc), and the value is a mixed value that could somehow
 545     * be considered a page.
 546     *
 547     * @param  string $relation  [optional] name of relation to return. If not
 548     *                           given, all relations will be returned.
 549     * @return array             an array of relations. If $relation is not
 550     *                           specified, all relations will be returned in
 551     *                           an associative array.
 552     */
 553    public function getRev($relation = null)
 554    {
 555        if (null !== $relation) {
 556            return isset($this->_rev[$relation]) ?
 557                   $this->_rev[$relation] :
 558                   null;
 559        }
 560
 561        return $this->_rev;
 562    }
 563
 564    /**
 565     * Sets page order to use in parent container
 566     *
 567     * @param  int $order                 [optional] page order in container.
 568     *                                    Default is null, which sets no
 569     *                                    specific order.
 570     * @return Zend_Navigation_Page       fluent interface, returns self
 571     * @throws Zend_Navigation_Exception  if order is not integer or null
 572     */
 573    public function setOrder($order = null)
 574    {
 575        if (is_string($order)) {
 576            $temp = (int) $order;
 577            if ($temp < 0 || $temp > 0 || $order == '0') {
 578                $order = $temp;
 579            }
 580        }
 581
 582        if (null !== $order && !is_int($order)) {
 583            require_once 'Zend/Navigation/Exception.php';
 584            throw new Zend_Navigation_Exception(
 585                    'Invalid argument: $order must be an integer or null, ' .
 586                    'or a string that casts to an integer');
 587        }
 588
 589        $this->_order = $order;
 590
 591        // notify parent, if any
 592        if (isset($this->_parent)) {
 593            $this->_parent->notifyOrderUpdated();
 594        }
 595
 596        return $this;
 597    }
 598
 599    /**
 600     * Returns page order used in parent container
 601     *
 602     * @return int|null  page order or null
 603     */
 604    public function getOrder()
 605    {
 606        return $this->_order;
 607    }
 608
 609    /**
 610     * Sets ACL resource assoicated with this page
 611     *
 612     * @param  string|Zend_Acl_Resource_Interface $resource  [optional] resource
 613     *                                                       to associate with
 614     *                                                       page. Default is
 615     *                                                       null, which sets no
 616     *                                                       resource.
 617     * @throws Zend_Navigation_Exception                     if $resource if
 618     *                                                       invalid
 619     * @return Zend_Navigation_Page                          fluent interface,
 620     *                                                       returns self
 621     */
 622    public function setResource($resource = null)
 623    {
 624        if (null === $resource || is_string($resource) ||
 625            $resource instanceof Zend_Acl_Resource_Interface) {
 626            $this->_resource = $resource;
 627        } else {
 628            require_once 'Zend/Navigation/Exception.php';
 629            throw new Zend_Navigation_Exception(
 630                    'Invalid argument: $resource must be null, a string, ' .
 631                    ' or an instance of Zend_Acl_Resource_Interface');
 632        }
 633
 634        return $this;
 635    }
 636
 637    /**
 638     * Returns ACL resource assoicated with this page
 639     *
 640     * @return string|Zend_Acl_Resource_Interface|null  ACL resource or null
 641     */
 642    public function getResource()
 643    {
 644        return $this->_resource;
 645    }
 646
 647    /**
 648     * Sets ACL privilege associated with this page
 649     *
 650     * @param  string|null $privilege  [optional] ACL privilege to associate
 651     *                                 with this page. Default is null, which
 652     *                                 sets no privilege.
 653     * @return Zend_Navigation_Page    fluent interface, returns self
 654     */
 655    public function setPrivilege($privilege = null)
 656    {
 657        $this->_privilege = is_string($privilege) ? $privilege : null;
 658        return $this;
 659    }
 660
 661    /**
 662     * Returns ACL privilege associated with this page
 663     *
 664     * @return string|null  ACL privilege or null
 665     */
 666    public function getPrivilege()
 667    {
 668        return $this->_privilege;
 669    }
 670
 671    /**
 672     * Sets whether page should be considered active or not
 673     *
 674     * @param  bool $active          [optional] whether page should be
 675     *                               considered active or not. Default is true.
 676     * @return Zend_Navigation_Page  fluent interface, returns self
 677     */
 678    public function setActive($active = true)
 679    {
 680        $this->_active = (bool) $active;
 681        return $this;
 682    }
 683
 684    /**
 685     * Returns whether page should be considered active or not
 686     *
 687     * @param  bool $recursive  [optional] whether page should be considered
 688     *                          active if any child pages are active. Default is
 689     *                          false.
 690     * @return bool             whether page should be considered active
 691     */
 692    public function isActive($recursive = false)
 693    {
 694        if (!$this->_active && $recursive) {
 695            foreach ($this->_pages as $page) {
 696                if ($page->isActive(true)) {
 697                    return true;
 698                }
 699            }
 700            return false;
 701        }
 702
 703        return $this->_active;
 704    }
 705
 706    /**
 707     * Proxy to isActive()
 708     *
 709     * @param  bool $recursive  [optional] whether page should be considered
 710     *                          active if any child pages are active. Default
 711     *                          is false.
 712     * @return bool             whether page should be considered active
 713     */
 714    public function getActive($recursive = false)
 715    {
 716        return $this->isActive($recursive);
 717    }
 718
 719    /**
 720     * Sets whether the page should be visible or not
 721     *
 722     * @param  bool $visible         [optional] whether page should be
 723     *                               considered visible or not. Default is true.
 724     * @return Zend_Navigation_Page  fluent interface, returns self
 725     */
 726    public function setVisible($visible = true)
 727    {
 728        $this->_visible = (bool) $visible;
 729        return $this;
 730    }
 731
 732    /**
 733     * Returns a boolean value indicating whether the page is visible
 734     *
 735     * @param  bool $recursive  [optional] whether page should be considered
 736     *                          invisible if parent is invisible. Default is
 737     *                          false.
 738     * @return bool             whether page should be considered visible
 739     */
 740    public function isVisible($recursive = false)
 741    {
 742        if ($recursive && isset($this->_parent) &&
 743            $this->_parent instanceof Zend_Navigation_Page) {
 744            if (!$this->_parent->isVisible(true)) {
 745                return false;
 746            }
 747        }
 748
 749        return $this->_visible;
 750    }
 751
 752    /**
 753     * Proxy to isVisible()
 754     *
 755     * Returns a boolean value indicating whether the page is visible
 756     *
 757     * @param  bool $recursive  [optional] whether page should be considered
 758     *                          invisible if parent is invisible. Default is
 759     *                          false.
 760     * @return bool             whether page should be considered visible
 761     */
 762    public function getVisible($recursive = false)
 763    {
 764        return $this->isVisible($recursive);
 765    }
 766
 767    /**
 768     * Sets parent container
 769     *
 770     * @param  Zend_Navigation_Container $parent  [optional] new parent to set.
 771     *                                            Default is null which will set
 772     *                                            no parent.
 773     * @return Zend_Navigation_Page               fluent interface, returns self
 774     */
 775    public function setParent(Zend_Navigation_Container $parent = null)
 776    {
 777        if ($parent === $this) {
 778            require_once 'Zend/Navigation/Exception.php';
 779            throw new Zend_Navigation_Exception(
 780                'A page cannot have itself as a parent');
 781        }
 782
 783        // return if the given parent already is parent
 784        if ($parent === $this->_parent) {
 785            return $this;
 786        }
 787
 788        // remove from old parent
 789        if (null !== $this->_parent) {
 790            $this->_parent->removePage($this);
 791        }
 792
 793        // set new parent
 794        $this->_parent = $parent;
 795
 796        // add to parent if page and not already a child
 797        if (null !== $this->_parent && !$this->_parent->hasPage($this, false)) {
 798            $this->_parent->addPage($this);
 799        }
 800
 801        return $this;
 802    }
 803
 804    /**
 805     * Returns parent container
 806     *
 807     * @return Zend_Navigation_Container|null  parent container or null
 808     */
 809    public function getParent()
 810    {
 811        return $this->_parent;
 812    }
 813
 814    /**
 815     * Sets the given property
 816     *
 817     * If the given property is native (id, class, title, etc), the matching
 818     * set method will be used. Otherwise, it will be set as a custom property.
 819     *
 820     * @param  string $property           property name
 821     * @param  mixed  $value              value to set
 822     * @return Zend_Navigation_Page       fluent interface, returns self
 823     * @throws Zend_Navigation_Exception  if property name is invalid
 824     */
 825    public function set($property, $value)
 826    {
 827        if (!is_string($property) || empty($property)) {
 828            require_once 'Zend/Navigation/Exception.php';
 829            throw new Zend_Navigation_Exception(
 830                    'Invalid argument: $property must be a non-empty string');
 831        }
 832
 833        $method = 'set' . self::_normalizePropertyName($property);
 834
 835        if ($method != 'setOptions' && $method != 'setConfig' &&
 836            method_exists($this, $method)) {
 837            $this->$method($value);
 838        } else {
 839            $this->_properties[$property] = $value;
 840        }
 841
 842        return $this;
 843    }
 844
 845    /**
 846     * Returns the value of the given property
 847     *
 848     * If the given property is native (id, class, title, etc), the matching
 849     * get method will be used. Otherwise, it will return the matching custom
 850     * property, or null if not found.
 851     *
 852     * @param  string $property           property name
 853     * @return mixed                      the property's value or null
 854     * @throws Zend_Navigation_Exception  if property name is invalid
 855     */
 856    public function get($property)
 857    {
 858        if (!is_string($property) || empty($property)) {
 859            require_once 'Zend/Navigation/Exception.php';
 860            throw new Zend_Navigation_Exception(
 861                    'Invalid argument: $property must be a non-empty string');
 862        }
 863
 864        $method = 'get' . self::_normalizePropertyName($property);
 865
 866        if (method_exists($this, $method)) {
 867            return $this->$method();
 868        } elseif (isset($this->_properties[$property])) {
 869            return $this->_properties[$property];
 870        }
 871
 872        return null;
 873    }
 874
 875    // Magic overloads:
 876
 877    /**
 878     * Sets a custom property
 879     *
 880     * Magic overload for enabling <code>$page->propname = $value</code>.
 881     *
 882     * @param  string $name               property name
 883     * @param  mixed  $value              value to set
 884     * @return void
 885     * @throws Zend_Navigation_Exception  if property name is invalid
 886     */
 887    public function __set($name, $value)
 888    {
 889        $this->set($name, $value);
 890    }
 891
 892    /**
 893     * Returns a property, or null if it doesn't exist
 894     *
 895     * Magic overload for enabling <code>$page->propname</code>.
 896     *
 897     * @param  string $name               property name
 898     * @return mixed                      property value or null
 899     * @throws Zend_Navigation_Exception  if property name is invalid
 900     */
 901    public function __get($name)
 902    {
 903        return $this->get($name);
 904    }
 905
 906    /**
 907     * Checks if a property is set
 908     *
 909     * Magic overload for enabling <code>isset($page->propname)</code>.
 910     *
 911     * Returns true if the property is native (id, class, title, etc), and
 912     * true or false if it's a custom property (depending on whether the
 913     * property actually is set).
 914     *
 915     * @param  string $name  property name
 916     * @return bool          whether the given property exists
 917     */
 918    public function __isset($name)
 919    {
 920        $method = 'get' . self::_normalizePropertyName($name);
 921        if (method_exists($this, $method)) {
 922            return true;
 923        }
 924
 925        return isset($this->_properties[$name]);
 926    }
 927
 928    /**
 929     * Unsets the given custom property
 930     *
 931     * Magic overload for enabling <code>unset($page->propname)</code>.
 932     *
 933     * @param  string $name               property name
 934     * @return void
 935     * @throws Zend_Navigation_Exception  if the property is native
 936     */
 937    public function __unset($name)
 938    {
 939        $method = 'set' . self::_normalizePropertyName($name);
 940        if (method_exists($this, $method)) {
 941            require_once 'Zend/Navigation/Exception.php';
 942            throw new Zend_Navigation_Exception(sprintf(
 943                    'Unsetting native property "%s" is not allowed',
 944                    $name));
 945        }
 946
 947        if (isset($this->_properties[$name])) {
 948            unset($this->_properties[$name]);
 949        }
 950    }
 951
 952    /**
 953     * Returns page label
 954     *
 955     * Magic overload for enabling <code>echo $page</code>.
 956     *
 957     * @return string  page label
 958     */
 959    public function __toString()
 960    {
 961        return $this->_label;
 962    }
 963
 964    // Public methods:
 965
 966    /**
 967     * Adds a forward relation to the page
 968     *
 969     * @param  string $relation      relation name (e.g. alternate, glossary,
 970     *                               canonical, etc)
 971     * @param  mixed  $value         value to set for relation
 972     * @return Zend_Navigation_Page  fluent interface, returns self
 973     */
 974    public function addRel($relation, $value)
 975    {
 976        if (is_string($relation)) {
 977            $this->_rel[$relation] = $value;
 978        }
 979        return $this;
 980    }
 981
 982    /**
 983     * Adds a reverse relation to the page
 984     *
 985     * @param  string $relation      relation name (e.g. alternate, glossary,
 986     *                               canonical, etc)
 987     * @param  mixed  $value         value to set for relation
 988     * @return Zend_Navigation_Page  fluent interface, returns self
 989     */
 990    public function addRev($relation, $value)
 991    {
 992        if (is_string($relation)) {
 993            $this->_rev[$relation] = $value;
 994        }
 995        return $this;
 996    }
 997
 998    /**
 999     * Removes a forward relation from the page
1000     *
1001     * @param  string $relation      name of relation to remove
1002     * @return Zend_Navigation_Page  fluent interface, returns self
1003     */
1004    public function removeRel($relation)
1005    {
1006        if (isset($this->_rel[$relation])) {
1007            unset($this->_rel[$relation]);
1008        }
1009
1010        return $this;
1011    }
1012
1013    /**
1014     * Removes a reverse relation from the page
1015     *
1016     * @param  string $relation      name of relation to remove
1017     * @return Zend_Navigation_Page  fluent interface, returns self
1018     */
1019    public function removeRev($relation)
1020    {
1021        if (isset($this->_rev[$relation])) {
1022            unset($this->_rev[$relation]);
1023        }
1024
1025        return $this;
1026    }
1027
1028    /**
1029     * Returns an array containing the defined forward relations
1030     *
1031     * @return array  defined forward relations
1032     */
1033    public function getDefinedRel()
1034    {
1035        return array_keys($this->_rel);
1036    }
1037
1038    /**
1039     * Returns an array containing the defined reverse relations
1040     *
1041     * @return array  defined reverse relations
1042     */
1043    public function getDefinedRev()
1044    {
1045        return array_keys($this->_rev);
1046    }
1047
1048    /**
1049     * Returns custom properties as an array
1050     *
1051     * @return array  an array containing custom properties
1052     */
1053    public function getCustomProperties()
1054    {
1055        return $this->_properties;
1056    }
1057
1058    /**
1059     * Returns a hash code value for the page
1060     *
1061     * @return string  a hash code value for this page
1062     */
1063    public final function hashCode()
1064    {
1065        return spl_object_hash($this);
1066    }
1067
1068    /**
1069     * Returns an array representation of the page
1070     *
1071     * @return array  associative array containing all page properties
1072     */
1073    public function toArray()
1074    {
1075        return array_merge(
1076            $this->getCustomProperties(),
1077            array(
1078                'label'     => $this->getlabel(),
1079                'id'        => $this->getId(),
1080                'class'     => $this->getClass(),
1081                'title'     => $this->getTitle(),
1082                'target'    => $this->getTarget(),
1083                'rel'       => $this->getRel(),
1084                'rev'       => $this->getRev(),
1085                'order'     => $this->getOrder(),
1086                'resource'  => $this->getResource(),
1087                'privilege' => $this->getPrivilege(),
1088                'active'    => $this->isActive(),
1089                'visible'   => $this->isVisible(),
1090                'type'      => get_class($this),
1091                'pages'     => parent::toArray()
1092            ));
1093    }
1094
1095    // Internal methods:
1096
1097    /**
1098     * Normalizes a property name
1099     *
1100     * @param  string $property  property name to normalize
1101     * @return string            normalized property name
1102     */
1103    protected static function _normalizePropertyName($property)
1104    {
1105        return str_replace(' ', '', ucwords(str_replace('_', ' ', $property)));
1106    }
1107
1108    // Abstract methods:
1109
1110    /**
1111     * Returns href for this page
1112     *
1113     * @return string  the page's href
1114     */
1115    abstract public function getHref();
1116}