PageRenderTime 53ms CodeModel.GetById 32ms app.highlight 14ms RepoModel.GetById 1ms app.codeStats 1ms

/src/frapi/library/Zend/Navigation/Page.php

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