Overview

Packages

  • awl
    • AuthPlugin
    • AwlDatabase
    • Browser
    • classEditor
    • DataEntry
    • DataUpdate
    • EMail
    • iCalendar
    • MenuSet
    • PgQuery
    • Session
    • Translation
    • User
    • Utilities
    • Validation
    • vCalendar
    • vComponent
    • XMLDocument
    • XMLElement
  • None

Classes

  • AuthPlugin
  • AwlCache
  • AwlDatabase
  • AwlDBDialect
  • AwlQuery
  • AwlUpgrader
  • Browser
  • BrowserColumn
  • DBRecord
  • Editor
  • EditorField
  • EMail
  • EntryField
  • EntryForm
  • iCalComponent
  • iCalendar
  • iCalProp
  • MenuOption
  • MenuSet
  • Multipart
  • PgQuery
  • Session
  • SinglePart
  • User
  • Validation
  • vCalendar
  • vComponent
  • vObject
  • vProperty
  • XMLDocument
  • XMLElement

Functions

  • _awl_connect_configured_database
  • _CompareMenuSequence
  • auth_external
  • auth_other_awl
  • awl_replace_sql_args
  • awl_set_locale
  • awl_version
  • BuildXMLTree
  • check_by_regex
  • check_temporary_passwords
  • clean_string
  • connect_configured_database
  • dbg_error_log
  • dbg_log_array
  • define_byte_mappings
  • deprecated
  • duration
  • fatal
  • force_utf8
  • get_fields
  • getCacheInstance
  • gzdecode
  • i18n
  • init_gettext
  • olson_from_tzstring
  • param_to_global
  • qpg
  • quoted_printable_encode
  • replace_uri_params
  • session_salted_md5
  • session_salted_sha1
  • session_simple_md5
  • session_validate_password
  • sql_from_object
  • sql_from_post
  • trace_bug
  • translate
  • uuid
  • Overview
  • Package
  • Class
  1:   2:   3:   4:   5:   6:   7:   8:   9:  10:  11:  12:  13:  14:  15:  16:  17:  18:  19:  20:  21:  22:  23:  24:  25:  26:  27:  28:  29:  30:  31:  32:  33:  34:  35:  36:  37:  38:  39:  40:  41:  42:  43:  44:  45:  46:  47:  48:  49:  50:  51:  52:  53:  54:  55:  56:  57:  58:  59:  60:  61:  62:  63:  64:  65:  66:  67:  68:  69:  70:  71:  72:  73:  74:  75:  76:  77:  78:  79:  80:  81:  82:  83:  84:  85:  86:  87:  88:  89:  90:  91:  92:  93:  94:  95:  96:  97:  98:  99: 100: 101: 102: 103: 104: 105: 106: 107: 108: 109: 110: 111: 112: 113: 114: 115: 116: 117: 118: 119: 120: 121: 122: 123: 124: 125: 126: 127: 128: 129: 130: 131: 132: 133: 134: 135: 136: 137: 138: 139: 140: 141: 142: 143: 144: 145: 146: 147: 148: 149: 150: 151: 152: 153: 154: 155: 156: 157: 158: 159: 160: 161: 162: 163: 164: 165: 166: 167: 168: 169: 170: 171: 172: 173: 174: 175: 176: 177: 178: 179: 180: 181: 182: 183: 184: 185: 186: 187: 188: 189: 190: 191: 192: 193: 194: 195: 196: 197: 198: 199: 200: 201: 202: 203: 204: 205: 206: 207: 208: 209: 210: 211: 212: 213: 214: 215: 216: 217: 218: 219: 220: 221: 222: 223: 224: 225: 226: 227: 228: 229: 230: 231: 232: 233: 234: 235: 236: 237: 238: 239: 240: 241: 242: 243: 244: 245: 246: 247: 248: 249: 250: 251: 252: 253: 254: 255: 256: 257: 258: 259: 260: 261: 262: 263: 264: 265: 266: 267: 268: 269: 270: 271: 272: 273: 274: 275: 276: 277: 278: 279: 280: 281: 282: 283: 284: 285: 286: 287: 288: 289: 290: 291: 292: 293: 294: 295: 296: 297: 298: 299: 300: 301: 302: 303: 304: 305: 306: 307: 308: 309: 310: 311: 312: 313: 314: 315: 316: 317: 318: 319: 320: 321: 322: 323: 324: 325: 326: 327: 328: 329: 330: 331: 332: 333: 334: 335: 336: 337: 338: 339: 340: 341: 342: 343: 344: 345: 346: 347: 348: 349: 350: 351: 352: 353: 354: 355: 356: 357: 358: 359: 360: 361: 362: 363: 364: 365: 366: 367: 368: 369: 370: 371: 372: 373: 374: 375: 376: 377: 378: 379: 380: 381: 382: 383: 384: 385: 386: 387: 388: 389: 390: 391: 392: 393: 394: 395: 396: 397: 398: 399: 400: 401: 402: 403: 404: 405: 406: 407: 408: 409: 410: 411: 412: 413: 414: 415: 416: 417: 418: 419: 420: 421: 422: 423: 424: 425: 426: 427: 428: 429: 430: 431: 432: 433: 434: 435: 436: 437: 438: 439: 440: 441: 442: 443: 444: 445: 446: 447: 448: 449: 450: 451: 452: 453: 454: 455: 456: 457: 458: 459: 460: 461: 462: 463: 464: 465: 466: 467: 468: 469: 470: 471: 472: 473: 474: 475: 476: 477: 478: 479: 480: 481: 482: 483: 484: 485: 486: 487: 488: 489: 490: 491: 492: 493: 494: 495: 496: 497: 498: 499: 500: 501: 502: 503: 504: 505: 506: 507: 508: 509: 510: 511: 512: 513: 514: 515: 516: 517: 518: 
<?php
/**
* Some intelligence and standardisation around presenting a menu hierarchy.
*
* See the MenuSet class for examples as that is the primary interface.
* @see MenuSet
*
* @package awl
* @subpackage   MenuSet
* @author    Andrew McMillan <andrew@mcmillan.net.nz>
* @copyright Catalyst IT Ltd, Morphoss Ltd <http://www.morphoss.com/>
* @license   http://gnu.org/copyleft/gpl.html GNU GPL v2 or later
*/

require_once("AWLUtilities.php");

/**
* Each menu option is an object.
* @package awl
*/
class MenuOption {
  /**#@+
  * @access private
  */
  /**
  * The label for the menu item
  * @var string
  */
  var $label;

  /**
  * The target URL for the menu
  * @var string
  */
  var $target;

  /**
  * The title for the item when moused over, which should be displayed as a tooltip.
  * @var string
  */
  var $title;

  /**
  * Whether the menu option is active
  * @var string
  */
  var $active;

  /**
  * For sorting menu options
  * @var string
  */
  var $sortkey;

  /**
  * Style to render the menu option with.
  * @var string
  */
  var $style;

  /**
  * The MenuSet that this menu is a parent of
  * @var string
  */
  var $submenu_set;
  /**#@-*/

  /**
  * A reference to this menu option itself
  * @var reference
  */
  var $self;

  /**#@+
  * @access public
  */
  /**
  * The rendered HTML fragment (once it has been).
  * @var string
  */
  var $rendered;
  /**#@-*/

  /**
  * The thing we click
  * @param string $label The label to display for this option.
  * @param string $target The URL to target for this option.
  * @param string $title Some tooltip help for the title tag.
  * @param string $style A base class name for this option.
  * @param int $sortkey An (optional) value to allow option ordering.
  */
  function MenuOption( $label, $target, $title="", $style="menu", $sortkey=1000 ) {
    $this->label  = $label;
    $this->target = $target;
    $this->title  = $title;
    $this->style  = $style;
    $this->attributes = array();
    $this->active = false;
    $this->sortkey = $sortkey;

    $this->rendered = "";
    $this->self  =& $this;
  }

  /**
  * Convert the menu option into an HTML string
  * @return string The HTML fragment for the menu option.
  */
  function Render( ) {
    $r = sprintf('<a href="%s" class="%s" title="%s"%s>%s</a>',
            $this->target, $this->style, htmlspecialchars($this->title), "%%attributes%%",
            htmlspecialchars($this->label), $this->style );

    // Now process the generic attributes
    $attribute_values = "";
    foreach( $this->attributes AS $k => $v ) {
      if ( substr($k, 0, 1) == '_' ) continue;
      $attribute_values .= ' '.$k.'="'.htmlspecialchars($v).'"';
    }
    $r = str_replace( '%%attributes%%', $attribute_values, $r );

    $this->rendered = $r;
    return "$r";
  }

  /**
  * Set arbitrary attributes of the menu option
  * @param string $attribute An arbitrary attribute to be set in the hyperlink.
  * @param string $value A value for this attribute.
  */
  function Set( $attribute, $value ) {
    $this->attributes[$attribute] = $value;
  }

  /**
  * Mark it as active, with a fancy style to distinguish that
  * @param string $style A style used to highlight that the option is active.
  */
  function Active( $style=false ) {
    $this->active = true;
    if ( $style ) $this->style = $style;
  }

  /**
  * This menu option is now promoted to the head of a tree
  */
  function AddSubmenu( &$submenu_set ) {
    $this->submenu_set = &$submenu_set;
  }

  /**
  * Whether this option is currently active.
  * @return boolean The value of the active flag.
  */
  function IsActive( ) {
    return ( $this->active );
  }

  /**
  * Whether this option is currently active.
  * @return boolean The value of the active flag.
  */
  function MaybeActive( $test_pattern, $active_style ) {
    if ( is_string($test_pattern) && preg_match($test_pattern,$_SERVER['REQUEST_URI']) ) {
      $this->Active($active_style);
    }
    return ( $this->active );
  }
}


/**
* _CompareMenuSequence is used in sorting the menu options into the sequence order
*
* @param objectref $a The first menu option
* @param objectref $b The second menu option
* @return int ( $a == b ? 0 ( $a > b ? 1 : -1 ))
*/
function _CompareMenuSequence( $a, $b ) {
  dbg_error_log("MenuSet", ":_CompareMenuSequence: Comparing %d with %d", $a->sortkey, $b->sortkey);
  return ($a->sortkey - $b->sortkey);
}



/**
* A MenuSet is a hierarchy of MenuOptions, some of which might be
* MenuSet objects themselves.
*
* The menu options are presented in HTML span tags, and the menus
* themselves are presented inside HTML div tags.  All layout and
* styling is expected to be provide by CSS.
*
* A non-trivial example would look something like this:
*<code>
*require("MenuSet.php");
*$main_menu = new MenuSet('menu', 'menu', 'menu_active');
*  ...
*$other_menu = new MenuSet('submenu', 'submenu', 'submenu_active');
*$other_menu->AddOption("Extra Other","/extraother.php","Submenu option to do extra things.");
*$other_menu->AddOption("Super Other","/superother.php","Submenu option to do super things.");
*$other_menu->AddOption("Meta Other","/metaother.php","Submenu option to do meta things.");
*  ...
*$main_menu->AddOption("Do This","/dothis.php","Option to do this thing.");
*$main_menu->AddOption("Do That","/dothat.php","Option to do all of that.");
*$main_menu->AddSubMenu( $other_menu, "Do The Other","/dotheother.php","Submenu to do all of the other things.", true);
*  ...
*if ( isset($main_menu) && is_object($main_menu) ) {
*  $main_menu->AddOption("Home","/","Go back to the home page");
*  echo $main_menu->Render();
*}
*</code>
* In a hierarchical menu tree, like the example above, only one sub-menu will be
* shown, which will be the first one that is found to have active menu options.
*
* The menu display will generally recognise the current URL and mark as active the
* menu option that matches it, but in some cases it might be desirable to force one
* or another option to be marked as active using the appropriate parameter to the
* AddOption or AddSubMenu call.
* @package awl
*/
class MenuSet {
  /**#@+
  * @access private
  */
  /**
  * CSS style to use for the div around the options
  * @var string
  */
  var $div_id;

  /**
  * CSS style to use for normal menu option
  * @var string
  */
  var $main_class;

  /**
  * CSS style to use for active menu option
  * @var string
  */
  var $active_class;

  /**
  * An array of MenuOption objects
  * @var array
  */
  var $options;

  /**
  * Any menu option that happens to parent this set
  * @var reference
  */
  var $parent;

  /**
  * The sortkey used by any previous option
  * @var last_sortkey
  */
  var $last_sortkey;

  /**
  * Will be set to true or false when we link active sub-menus, but will be
  * unset until we do that.
  * @var reference
  */
  var $has_active_options;
  /**#@-*/

  /**
  * Start a new MenuSet with no options.
  * @param string $div_id An ID for the HTML div that the menu will be presented in.
  * @param string $main_class A CSS class for most menu options.
  * @param string $active_class A CSS class for active menu options.
  */
  function MenuSet( $div_id, $main_class = '', $active_class = 'active' ) {
    $this->options = array();
    $this->main_class = $main_class;
    $this->active_class = $active_class;
    $this->div_id = $div_id;
  }

  /**
  * Add an option, which is a link.
  * The call will attempt to work out whether the option should be marked as
  * active, and will sometimes get it wrong.
  * @param string $label A Label for the new menu option
  * @param string $target The URL to target for this option.
  * @param string $title Some tooltip help for the title tag.
  * @param string $active Whether this option should be marked as Active.
  * @param int $sortkey An (optional) value to allow option ordering.
  * @param external open this link in a new window/tab.
  * @return mixed A reference to the MenuOption that was added, or false if none were added.
  */
  function &AddOption( $label, $target, $title="", $active=false, $sortkey=null, $external=false ) {
    if ( !isset($sortkey) ) {
      $sortkey = (isset($this->last_sortkey) ? $this->last_sortkey + 100 : 1000);
    }
    $this->last_sortkey = $sortkey;
    if ( version_compare(phpversion(), '5.0') < 0) {
      $new_option = new MenuOption( $label, $target, $title, $this->main_class, $sortkey );
    }
    else {
      $new_option = new MenuOption( $label, $target, $title, $this->main_class, $sortkey );
    }
    if ( ($old_option = $this->_OptionExists( $label )) === false ) {
      $this->options[] = &$new_option ;
    }
    else {
      dbg_error_log("MenuSet",":AddOption: Replacing existing option # $old_option ($label)");
      $this->options[$old_option] = &$new_option;  // Overwrite the existing option
    }
    if ( is_bool($active) && $active == false && $_SERVER['REQUEST_URI'] == $target ) {
      // If $active is not set, then we look for an exact match to the current URL
      $new_option->Active( $this->active_class );
    }
    else if ( is_bool($active) && $active ) {
      // When active is specified as a boolean, the recognition has been done externally
      $new_option->Active( $this->active_class );
    }
    else if ( is_string($active) && preg_match($active,$_SERVER['REQUEST_URI']) ) {
      // If $active is a string, then we match the current URL to that as a Perl regex
      $new_option->Active( $this->active_class );
    }

    if ( $external == true ) $new_option->Set('target', '_blank');

    return $new_option ;
  }

  /**
  * Add an option, which is a submenu
  * @param object &$submenu_set A reference to a menu tree
  * @param string $label A Label for the new menu option
  * @param string $target The URL to target for this option.
  * @param string $title Some tooltip help for the title tag.
  * @param string $active Whether this option should be marked as Active.
  * @param int $sortkey An (optional) value to allow option ordering.
  * @return mixed A reference to the MenuOption that was added, or false if none were added.
  */
  function &AddSubMenu( &$submenu_set, $label, $target, $title="", $active=false, $sortkey=2000 ) {
    $new_option =& $this->AddOption( $label, $target, $title, $active, $sortkey );
    $submenu_set->parent = &$new_option ;
    $new_option->AddSubmenu( $submenu_set );
    return $new_option ;
  }

  /**
  * Does the menu have any options that are active.
  * Most likely used so that we can then set the parent menu as active.
  * @param string $label A Label for the new menu option
  * @return boolean Whether the menu has options that are active.
  */
  function _HasActive( ) {
    if ( isset($this->has_active_options) ) {
      return $this->has_active_options;
    }
    foreach( $this->options AS $k => $v ) {
      if ( $v->IsActive() ) {
        $rc = true;
        return $rc;
      }
    }
    $rc = false;
    return $rc;
  }

  /**
  * Find out how many options the menu has.
  * @return int The number of options in the menu.
  */
  function Size( ) {
    return count($this->options);
  }

  /**
  * See if a menu already has this option
  * @return boolean Whether the option already exists in the menu.
  */
  function _OptionExists( $newlabel ) {
    $rc = false;
    foreach( $this->options AS $k => $v ) {
      if ( $newlabel == $v->label ) return $k;
    }
    return $rc;
  }

  /**
  * Mark each MenuOption as active that has an active sub-menu entry.
  *
  * Currently needs to be called manually before rendering but
  * really should probably be called as part of the render now,
  * and then this could be a private routine.
  */
  function LinkActiveSubMenus( ) {
    $this->has_active_options = false;
    foreach( $this->options AS $k => $v ) {
      if ( isset($v->submenu_set) && $v->submenu_set->_HasActive() ) {
        // Note that we need to do it this way, since $v is a copy, not a reference
        $this->options[$k]->Active( $this->active_class );
        $this->has_active_options = true;
      }
    }
  }

  /**
  * Mark each MenuOption as active that has an active sub-menu entry.
  *
  * Currently needs to be called manually before rendering but
  * really should probably be called as part of the render now,
  * and then this could be a private routine.
  */
  function MakeSomethingActive( $test_pattern ) {
    if ( $this->has_active_options ) return;  // Already true.
    foreach( $this->options AS $k => $v ) {
      if ( isset($v->submenu_set) && $v->submenu_set->_HasActive() ) {
        // Note that we need to do it this way, since $v is a copy, not a reference
        $this->options[$k]->Active( $this->active_class );
        $this->has_active_options = true;
        return $this->has_active_options;
      }
    }

    foreach( $this->options AS $k => $v ) {
      if ( isset($v->submenu_set) && $v->submenu_set->MakeSomethingActive($test_pattern) ) {
        // Note that we need to do it this way, since $v is a copy, not a reference
        $this->options[$k]->Active( $this->active_class );
        $this->has_active_options = true;
        return $this->has_active_options;
      }
      else {
        if ( $this->options[$k]->MaybeActive( $test_pattern, $this->active_class ) ) {
          $this->has_active_options = true;
          return $this->has_active_options;
        }
      }
    }
    return false;
  }

  /**
  * _CompareSequence is used in sorting the menu options into the sequence order
  *
  * @param objectref $a The first menu option
  * @param objectref $b The second menu option
  * @return int ( $a == b ? 0 ( $a > b ? 1 : -1 ))
  */
  function _CompareSequence( $a, $b ) {
    dbg_error_log("MenuSet",":_CompareSequence: Comparing %d with %d", $a->sortkey, $b->sortkey);
    return ($a->sortkey - $b->sortkey);
  }


  /**
  * Render the menu tree to an HTML fragment.
  *
  * @param boolean $submenus_inline Indicate whether to render the sub-menus within
  *   the menus, or render them entirely separately after we finish rendering the
  *   top level ones.
  * @return string The HTML fragment.
  */
  function Render( $submenus_inline = false ) {
    if ( !isset($this->has_active_options) ) {
      $this->LinkActiveSubMenus();
    }
    $options = $this->options;
    usort($options,"_CompareMenuSequence");
    $render_sub_menus = false;
    $r = "<div id=\"$this->div_id\">\n";
    foreach( $options AS $k => $v ) {
      $r .= $v->Render();
      if ( $v->IsActive() && isset($v->submenu_set) && $v->submenu_set->Size() > 0 ) {
        $render_sub_menus = $v->submenu_set;
        if ( $submenus_inline )
          $r .= $render_sub_menus->Render();
      }
    }
    $r .="</div>\n";
    if ( !$submenus_inline && $render_sub_menus != false ) {
      $r .= $render_sub_menus->Render();
    }
    return $r;
  }


  /**
  * Render the menu tree to an HTML fragment.
  *
  * @param boolean $submenus_inline Indicate whether to render the sub-menus within
  *   the menus, or render them entirely separately after we finish rendering the
  *   top level ones.
  * @return string The HTML fragment.
  */
  function RenderAsCSS( $depth = 0, $skip_empty = true ) {
    $this->LinkActiveSubMenus();

    if ( $depth > 0 )
      $class = "submenu" . $depth;
    else
      $class = "menu";

    $options = $this->options;
    usort($options,"_CompareMenuSequence");

    $r = "<div id=\"$this->div_id\" class=\"$class\">\n<ul>\n";
    foreach( $options AS $k => $v ) {
      if ( $skip_empty && isset($v->submenu_set) && $v->submenu_set->Size() < 1 ) continue;
      $r .= "<li>".$v->Render();
      if ( isset($v->submenu_set) && $v->submenu_set->Size() > 0 ) {
        $r .= $v->submenu_set->RenderAsCSS($depth+1);
      }
      $r .= "</li>\n";
    }
    $r .="</ul></div>\n";
    return $r;
  }
}
API documentation generated by ApiGen