PageRenderTime 65ms CodeModel.GetById 16ms app.highlight 36ms RepoModel.GetById 1ms app.codeStats 1ms

/com/layout.php

http://github.com/unirgy/buckyball
PHP | 2316 lines | 1284 code | 253 blank | 779 comment | 215 complexity | 1910fa1329738f2ea18a2f3fbcd20389 MD5 | raw file

Large files files are truncated, but you can click here to view the full file

   1<?php
   2/**
   3 * Copyright 2011 Unirgy LLC
   4 *
   5 * Licensed under the Apache License, Version 2.0 (the "License");
   6 * you may not use this file except in compliance with the License.
   7 * You may obtain a copy of the License at
   8 *
   9 * http://www.apache.org/licenses/LICENSE-2.0
  10 *
  11 * Unless required by applicable law or agreed to in writing, software
  12 * distributed under the License is distributed on an "AS IS" BASIS,
  13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14 * See the License for the specific language governing permissions and
  15 * limitations under the License.
  16 *
  17 * @package BuckyBall
  18 * @link http://github.com/unirgy/buckyball
  19 * @author Boris Gurvich <boris@unirgy.com>
  20 * @copyright (c) 2010-2012 Boris Gurvich
  21 * @license http://www.apache.org/licenses/LICENSE-2.0.html
  22 */
  23
  24/**
  25 * Layout facility to register views and render output from views
  26 */
  27class BLayout extends BClass
  28{
  29    /**
  30     * Installed themes registry
  31     *
  32     * @var array
  33     */
  34    protected $_themes = array();
  35
  36    /**
  37     * Default theme name (current area / main module)
  38     *
  39     * @var string|array
  40     */
  41    protected $_defaultTheme;
  42
  43    /**
  44     * Layouts declarations registry
  45     *
  46     * @var array
  47     */
  48    protected $_layouts = array();
  49
  50    /**
  51     * View objects registry
  52     *
  53     * @var array
  54     */
  55    protected $_views = array();
  56
  57    /**
  58     * Main (root) view to be rendered first
  59     *
  60     * @var BView
  61     */
  62    protected $_rootViewName = 'root';
  63
  64    /**
  65     * Main root dir for view files if operating outside of a module
  66     *
  67     * @var mixed
  68     */
  69    protected $_viewRootDir;
  70
  71    /**
  72     * Default class name for newly created views
  73     *
  74     * @var string
  75     */
  76    protected $_defaultViewClass;
  77
  78    /**
  79     * @var array
  80     */
  81    protected static $_metaDirectives = array(
  82        'remove'   => 'BLayout::metaDirectiveRemoveCallback',
  83        'callback' => 'BLayout::metaDirectiveCallback',
  84        'layout'   => 'BLayout::metaDirectiveIncludeCallback',
  85        'include'   => 'BLayout::metaDirectiveIncludeCallback',
  86        'root'     => 'BLayout::metaDirectiveRootCallback',
  87        'hook'     => 'BLayout::metaDirectiveHookCallback',
  88        'view'     => 'BLayout::metaDirectiveViewCallback',
  89    );
  90
  91    protected static $_renderers = array();
  92
  93    /**
  94     * @var array
  95     */
  96    protected static $_extRenderers = array(
  97        '.php' => array('callback' => null),
  98    );
  99
 100    /**
 101     * @var string
 102     */
 103    protected static $_extRegex = '\.php';
 104
 105    /**
 106     * Shortcut to help with IDE autocompletion
 107     *
 108     * @param bool  $new
 109     * @param array $args
 110     * @return BLayout
 111     */
 112    public static function i($new = false, array $args = array())
 113    {
 114        return BClassRegistry::i()->instance(__CLASS__, $args, !$new);
 115    }
 116
 117    /**
 118     * Set root dir for view templates, relative to current module root
 119     *
 120     * @deprecated
 121     * @param string $rootDir
 122     * @return BLayout
 123     */
 124    public function viewRootDir($rootDir = null)
 125    {
 126        if (is_null($rootDir)) {
 127            return $this->getViewRootDir();
 128        }
 129
 130        return $this->setViewRootDir($rootDir);
 131    }
 132
 133    /**
 134     * Get view root dir
 135     * If there is a module in registry as current module, its view root dir will be used, else default one.
 136     *
 137     * @return string
 138     */
 139    public function getViewRootDir()
 140    {
 141        $module = BModuleRegistry::i()->currentModule();
 142
 143        return $module ? $module->view_root_dir : $this->_viewRootDir;
 144    }
 145
 146    /**
 147     * Set view root dir
 148     * If there is current module in registry, set it to it, else set it to layout.
 149     *
 150     * @param $rootDir
 151     * @return $this
 152     */
 153    public function setViewRootDir($rootDir, $module=null)
 154    {
 155        if (is_null($module)) {
 156            $module = BModuleRegistry::i()->currentModule();
 157        }
 158        $isAbsPath = strpos($rootDir, '/') === 0 || strpos($rootDir, ':') === 1;
 159        if ($module) {
 160            $module->view_root_dir = $isAbsPath ? $rootDir : $module->root_dir . '/' . $rootDir;
 161        } else {
 162            $this->_viewRootDir = $rootDir;
 163        }
 164
 165        return $this;
 166    }
 167
 168    /**
 169     * Add extension renderer
 170     *
 171     * Set renderer for particular file extension. E.g. '.php'
 172     * For renderer to work, params should either be array with 'renderer' field
 173     * or a string representing renderer class.
 174     *
 175     * @param string $ext
 176     * @param array $params
 177     * @return $this
 178     */
 179    public function addRenderer($name, $params)
 180    {
 181        if (is_string($name) && is_string($params)) {
 182            $params = array('file_ext' => array($name), 'callback' => $params);
 183        }
 184        if (is_string($params['file_ext'])) {
 185            $params['file_ext'] = explode(';', $params['file_ext']);
 186        }
 187
 188        static::$_renderers[$name] = $params;
 189
 190        foreach ($params['file_ext'] as $ext) {
 191            static::$_extRenderers[$ext] = $params;
 192        }
 193        static::$_extRegex = join('|', array_map('preg_quote', array_keys(static::$_extRenderers)));
 194        BDebug::debug('ADD RENDERER: '.join('; ', $params['file_ext']));
 195        return $this;
 196    }
 197
 198    public function getAllRenderers($asOptions=false)
 199    {
 200        if ($asOptions) {
 201            $options = array();
 202            foreach (static::$_renderers as $k=>$r) {
 203                $options[$k] = !empty($r['description']) ? $r['description'] : $k;
 204            }
 205            asort($options);
 206            return $options;
 207        }
 208        return static::$_renderers;
 209    }
 210
 211    public function getRenderer($name)
 212    {
 213        return !empty(static::$_renderers[$name]) ? static::$_renderers[$name] : null;
 214    }
 215
 216    /**
 217     * Alias for addAllViews()
 218     *
 219     * @deprecated alias
 220     * @param mixed $rootDir
 221     * @param mixed $prefix
 222     * @return BLayout
 223     */
 224    public function allViews($rootDir = null, $prefix = '')
 225    {
 226        return $this->addAllViews($rootDir, $prefix);
 227    }
 228
 229    /**
 230     * Find and register all templates within a folder as view objects
 231     *
 232     * View objects will be named by template file paths, stripped of extension (.php)
 233     *
 234     * @param string $rootDir Folder with view templates, relative to current module root
 235     *                        Can end with slash or not - make sure to specify
 236     * @param string $prefix Optional: add prefix to view names
 237     * @return BLayout
 238     */
 239    public function addAllViews($rootDir = null, $prefix = '')
 240    {
 241        if (is_null($rootDir)) {
 242            return $this->_views;
 243        }
 244        $curModule = BModuleRegistry::i()->currentModule();
 245        if ($curModule && !BUtil::isPathAbsolute($rootDir)) {
 246            $rootDir = $curModule->root_dir . '/' . $rootDir;
 247        }
 248        if (!is_dir($rootDir)) {
 249            BDebug::warning('Not a valid directory: ' . $rootDir);
 250
 251            return $this;
 252        }
 253        $rootDir = realpath($rootDir);
 254
 255        $this->setViewRootDir($rootDir);
 256
 257        $files = BUtil::globRecursive($rootDir . '/*');
 258        if (!$files) {
 259            return $this;
 260        }
 261
 262        if ($prefix) {
 263            $prefix = rtrim($prefix, '/') . '/';
 264        }
 265        $re = '#^(' . preg_quote(realpath($rootDir) . '/', '#') . ')(.*)(' . static::$_extRegex . ')$#';
 266        foreach ($files as $file) {
 267            if (!is_file($file)) {
 268                continue;
 269            }
 270            if (preg_match($re, $file, $m)) {
 271                //$this->view($prefix.$m[2], array('template'=>$m[2].$m[3]));
 272                $viewParams = array('template' => $file, 'file_ext' => $m[3]);
 273                $viewParams['renderer'] = static::$_extRenderers[$m[3]]['callback'];
 274                $this->addView($prefix . $m[2], $viewParams);
 275            }
 276        }
 277
 278        BEvents::i()->fire(__METHOD__, array('root_dir'=>$rootDir, 'prefix'=>$prefix, 'module'=>$curModule));
 279
 280        return $this;
 281    }
 282
 283    /**
 284     * Set default view class
 285     *
 286     * @todo rename to setDefaultViewClass()
 287     * @param mixed $className
 288     * @return BLayout
 289     */
 290    public function defaultViewClass($className)
 291    {
 292        $this->_defaultViewClass = $className;
 293        return $this;
 294    }
 295
 296    /**
 297     * Register or retrieve a view object
 298     *
 299     * @todo remove adding view from here
 300     * @param string  $viewName
 301     * @param array   $params View parameters
 302     *   - template: optional, for templated views
 303     *   - view_class: optional, for custom views
 304     *   - module_name: optional, to use template from a specific module
 305     * @param boolean $reset update or reset view params //TODO
 306     * @return BView|BLayout
 307     */
 308    public function view($viewName, $params = null, $reset = false)
 309    {
 310        if ($params) {
 311            $this->addView($viewName, $params, $reset);
 312
 313            return $this;
 314        }
 315
 316        return $this->getView($viewName);
 317    }
 318
 319    /**
 320     * Not sure whether to leave view() for convenience
 321     *
 322     * Return registered view
 323     *
 324     * @param mixed $viewName
 325     * @return null|BView
 326     */
 327    public function getView($viewName)
 328    {
 329        return isset($this->_views[$viewName]) ? $this->_views[$viewName] : BViewEmpty::i();
 330    }
 331
 332    /**
 333     * Add or update view to layout
 334     * Adds or updates a view to layout.
 335     * If view already exists, will replace its params with provided ones.
 336     *
 337     * @param string|array $viewName
 338     * @param string|array $params if string - view class name
 339     * @param bool $reset
 340     * @return $this
 341     * @throws BException
 342     */
 343    public function addView($viewName, $params = array(), $reset = false)
 344    {
 345        if (is_array($viewName)) {
 346            foreach ($viewName as $i => $view) {
 347                if (!is_numeric($i)) {
 348                    throw new BException(BLocale::_('Invalid argument: %s', print_r($viewName, 1)));
 349                }
 350                $this->addView($view[0], $view[1], $reset); // if self::view is possible to disappear better not use it.
 351            }
 352
 353            return $this;
 354        }
 355        if (is_string($params)) {
 356            $params = array('view_class' => $params);
 357        }
 358        if (empty($params['module_name']) && ($moduleName = BModuleRegistry::i()->currentModuleName())) {
 359            $params['module_name'] = $moduleName;
 360        }
 361        $viewAlias = !empty($params['view_alias']) ? $params['view_alias'] : $viewName;
 362        if (!isset($this->_views[$viewAlias]) || !empty($params['view_class'])) {
 363            if (empty($params['view_class'])) {
 364                /*
 365                if (!empty($params['module_name'])) {
 366                    $viewClass = BApp::m($params['module_name'])->default_view_class;
 367                    if ($viewClass) {
 368                        $params['view_class'] = $viewClass;
 369                    }
 370                } else
 371                */
 372                if (!empty($this->_defaultViewClass)) {
 373                    $params['view_class'] = $this->_defaultViewClass;
 374                }
 375            }
 376
 377            $this->_views[$viewAlias] = BView::i()->factory($viewName, $params);
 378            BEvents::i()->fire('BLayout::view:add:' . $viewAlias, array(
 379                'view' => $this->_views[$viewAlias],
 380            ));
 381        } else {
 382            $this->_views[$viewAlias]->setParam($params);
 383            BEvents::i()->fire('BLayout::view:update:' . $viewAlias, array(
 384                'view' => $this->_views[$viewAlias],
 385            ));
 386        }
 387
 388        return $this;
 389    }
 390
 391    /**
 392     * Find a view by matching its name to a regular expression
 393     *
 394     * @param string $re
 395     * @return array
 396     */
 397    public function findViewsRegex($re)
 398    {
 399        $views = array();
 400        foreach ($this->_views as $viewName => $view) {
 401            if (preg_match($re, $viewName)) {
 402                $views[$viewName] = $view;
 403            }
 404        }
 405
 406        return $views;
 407    }
 408
 409    /**
 410     * Set or retrieve main (root) view object
 411     *
 412     * @deprecated
 413     * @param string $viewName
 414     * @return BView|BLayout
 415     */
 416    public function rootView($viewName = BNULL)
 417    {
 418        if (BNULL === $viewName) {
 419//            return $this->_rootViewName ? $this->view($this->_rootViewName) : null;
 420            return $this->getRootView(); // the above seems like this method?!
 421        }
 422        /*
 423        if (empty($this->_views[$viewName])) {
 424            throw new BException(BLocale::_('Invalid view name for main view: %s', $viewName));
 425        }
 426        */
 427        $this->_rootViewName = $viewName;
 428
 429        return $this;
 430    }
 431
 432    /**
 433     * Set root view name
 434     * @param string $viewName
 435     * @return $this
 436     */
 437    public function setRootView($viewName)
 438    {
 439        $this->_rootViewName = $viewName;
 440
 441        return $this;
 442    }
 443
 444    /**
 445     * @return BLayout|BView|null
 446     */
 447    public function getRootView()
 448    {
 449        return $this->_rootViewName ? $this->getView($this->_rootViewName) : null;
 450    }
 451
 452    /**
 453     * @return string
 454     */
 455    public function getRootViewName()
 456    {
 457        return $this->_rootViewName;
 458    }
 459
 460    /**
 461     * Clone view object to another name
 462     *
 463     * @param string $from
 464     * @param string $to
 465     * @return BView
 466     */
 467    public function cloneView($from, $to = BNULL)
 468    {
 469        if (BNULL === $to) {
 470            $to = $from . '-copy';
 471            for ($i = 2; !empty($this->_views[$to]); $i++) {
 472                $to = $from . '-copy' . $i;
 473            }
 474        }
 475        $this->_views[$to] = clone $this->_views[$from];
 476        $this->_views[$to]->setParam('view_name', $to);
 477
 478        return $this->_views[$to];
 479    }
 480
 481    /**
 482     * Register a call back to a hook
 483     *
 484     * @param string $hookName
 485     * @param mixed  $callback
 486     * @param array  $args
 487     * @return $this
 488     */
 489    public function hook($hookName, $callback, $args = array(), $alias = null)
 490    {
 491        BEvents::i()->on('BLayout::hook:' . $hookName, $callback, $args, $alias);
 492
 493        return $this;
 494    }
 495
 496    /**
 497     * Register a view as call back to a hook
 498     * $viewName should either be a string with a name of view,
 499     * or an array in which first field is view name and the rest are view parameters.
 500     *
 501     * @param string $hookName
 502     * @param string|array $viewName
 503     * @param array $args
 504     * @return $this
 505     */
 506    public function hookView($hookName, $viewName, $args = array())
 507    {
 508        if (is_array($viewName)) {
 509            $params   = $viewName;
 510            $viewName = array_shift($params);
 511            BLayout::i()->addView($viewName, $params);
 512        }
 513        $view = BLayout::i()->getView($viewName);
 514        if (!$view) {
 515            BDebug::warning('Invalid view name: ' . $viewName, 1);
 516
 517            return $this;
 518        }
 519        //$view->set($args);
 520        return $this->hook($hookName, $view, $args, $viewName);
 521    }
 522
 523    public function hookClear($hookName, $viewNames)
 524    {
 525
 526        $eventHlp = BEvents::i();
 527        $eventName = 'BLayout::hook:' . $hookName;
 528        if (true === $viewNames || 'ALL' === $viewNames) {
 529            $eventHlp->off($eventName, true);
 530        } else {
 531            foreach ((array)$viewNames as $clearViewName) {
 532                $eventHlp->off($eventName, $clearViewName);
 533            }
 534        }
 535        return $this;
 536    }
 537
 538    /**
 539     *
 540     * @deprecated
 541     * @param mixed $layoutName
 542     * @param mixed $layout
 543     * @return BLayout
 544     */
 545    public function layout($layoutName, $layout = null)
 546    {
 547        if (is_array($layoutName) || !is_null($layout)) {
 548            $this->addLayout($layoutName, $layout);
 549        } else {
 550            $this->applyLayout($layoutName);
 551        }
 552
 553        return $this;
 554    }
 555
 556    /**
 557    * Load layout update from file
 558    *
 559    * @param string $layoutFilename
 560    * @return BLayout
 561    */
 562    public function loadLayout($layoutFilename)
 563    {
 564#echo "<pre>"; debug_print_backtrace(); echo "</pre>";
 565        $ext = strtolower(pathinfo($layoutFilename, PATHINFO_EXTENSION));
 566        if (!BUtil::isPathAbsolute($layoutFilename)) {
 567            $mod = BModuleRegistry::i()->currentModule();
 568            if ($mod) {
 569                $layoutFilename = $mod->root_dir.'/'.$layoutFilename;
 570            }
 571        }
 572        BDebug::debug('LAYOUT.LOAD: '.$layoutFilename);
 573        switch ($ext) {
 574            case 'yml': case 'yaml': $layoutData = BYAML::i()->load($layoutFilename); break;
 575            case 'json': $layoutData = json_decode(file_get_contents($layoutFilename)); break;
 576            case 'php': $layoutData = include($layoutFilename); break;
 577            default: throw new BException('Unknown layout file type: '.$layoutFilename);
 578        }
 579        BLayout::i()->addLayout($layoutData);
 580        return $this;
 581    }
 582
 583    /**
 584    * Load layout update after theme has been initialized
 585    *
 586    * @param string $layoutFilename
 587    * @return BLayout
 588    */
 589    public function loadLayoutAfterTheme($layoutFilename)
 590    {
 591        if (!BUtil::isPathAbsolute($layoutFilename)) {
 592            $mod = BModuleRegistry::i()->currentModule();
 593            if ($mod) {
 594                $layoutFilename = $mod->root_dir.'/'.$layoutFilename;
 595            }
 596        }
 597        $this->onAfterTheme(function() use($layoutFilename) {
 598            BLayout::i()->loadLayout($layoutFilename);
 599        });
 600        return $this;
 601    }
 602
 603    /**
 604     * @param      $layoutName
 605     * @param null $layout
 606     * @return $this
 607     */
 608    public function addLayout($layoutName, $layout = null)
 609    {
 610        if (is_array($layoutName)) {
 611            foreach ($layoutName as $l => $def) {
 612                $this->addLayout($l, $def);
 613            }
 614
 615            return $this;
 616        }
 617        if (!is_array($layout)) {
 618            BDebug::debug('LAYOUT.ADD ' . $layoutName . ': Invalid or empty layout');
 619        } else {
 620            if (!isset($this->_layouts[$layoutName])) {
 621                BDebug::debug('LAYOUT.ADD ' . $layoutName);
 622                $this->_layouts[$layoutName] = $layout;
 623            } else {
 624                BDebug::debug('LAYOUT.UPDATE ' . $layoutName);
 625                $this->_layouts[$layoutName] = array_merge_recursive($this->_layouts[$layoutName], $layout);
 626            }
 627        }
 628
 629        return $this;
 630    }
 631
 632    /**
 633     * @param $layoutName
 634     * @return $this
 635     */
 636    public function applyLayout($layoutName)
 637    {
 638        if (empty($this->_layouts[$layoutName])) {
 639            BDebug::debug('LAYOUT.EMPTY ' . $layoutName);
 640
 641            return $this;
 642        }
 643        BDebug::debug('LAYOUT.APPLY ' . $layoutName);
 644
 645        // collect callbacks
 646        $callbacks = array();
 647        foreach ($this->_layouts[$layoutName] as $d) {
 648            if (empty($d['type'])) {
 649                if (!is_array($d)) {
 650                    var_dump($layoutName, $d);
 651                }
 652                if (!empty($d[0])) {
 653                    $d['type'] = $d[0];
 654                } else {
 655                    foreach ($d as $k=>$n) {
 656                        if (!empty(self::$_metaDirectives[$k])) {
 657                            $d['type'] = $k;
 658                            $d['name'] = $n;
 659                            break;
 660                        }
 661                    }
 662                }
 663                if (empty($d['type'])) {
 664                    BDebug::dump($d);
 665                }
 666            }
 667            $d['type'] = trim($d['type']);
 668            if (empty($d['type']) || empty(self::$_metaDirectives[$d['type']])) {
 669                BDebug::error('Unknown directive: ' . $d['type']);
 670                continue;
 671            }
 672            if (empty($d['name']) && !empty($d[1])) {
 673                $d['name'] = $d[1];
 674            }
 675            $d['name'] = trim($d['name']);
 676            $d['layout_name'] = $layoutName;
 677            $callback = self::$_metaDirectives[$d['type']];
 678
 679            if ($d['type'] === 'remove') {
 680                if ($d['name'] === 'ALL') { //TODO: allow removing specific instructions
 681                    BDebug::debug('LAYOUT.REMOVE');
 682                    $callbacks = array();
 683                }
 684            } else {
 685                $callbacks[] = array($callback, $d);
 686            }
 687        }
 688
 689        // perform all callbacks
 690        foreach ($callbacks as $cb) {
 691            call_user_func($cb[0], $cb[1]);
 692        }
 693
 694        return $this;
 695    }
 696
 697    /**
 698     * @param $d
 699     */
 700    public function metaDirectiveCallback($d)
 701    {
 702        call_user_func($d['name'], $d);
 703    }
 704
 705    /**
 706     * @param $d
 707     */
 708    public function metaDirectiveRemoveCallback($d)
 709    {
 710        //TODO: implement
 711    }
 712
 713    /**
 714     * @param $d
 715     */
 716    public function metaDirectiveIncludeCallback($d)
 717    {
 718        if ($d['name'] == $d['layout_name']) { // simple 1 level recursion stop
 719            BDebug::error('Layout recursion detected: ' . $d['name']);
 720
 721            return;
 722        }
 723        static $layoutsApplied = array();
 724        if (!empty($layoutsApplied[$d['name']]) && empty($d['repeat'])) {
 725            return;
 726        }
 727        $layoutsApplied[$d['name']] = 1;
 728        $this->applyLayout($d['name']);
 729    }
 730
 731    /**
 732     * @param array $d
 733     */
 734    public function metaDirectiveRootCallback($d)
 735    {
 736        $this->setRootView($d['name']);
 737    }
 738
 739    /**
 740     * @param array $d
 741     */
 742    public function metaDirectiveHookCallback($d)
 743    {
 744        $args = !empty($d['args']) ? $d['args'] : array();
 745        if (!empty($d['position'])) {
 746            $args['position'] = $d['position'];
 747        }
 748        if (!empty($d['callbacks'])) {
 749            foreach ($d['callbacks'] as $cb) {
 750                $this->hook($d['name'], $cb, $args);
 751            }
 752        }
 753        if (!empty($d['clear'])) {
 754            $this->hookClear($d['name'], $d['clear']);
 755        }
 756        if (!empty($d['views'])) {
 757            foreach ((array)$d['views'] as $v) {
 758                $this->hookView($d['name'], $v, $args);
 759            }
 760            if (!empty($d['use_meta'])) {
 761                $this->view($v)->useMetaData();
 762            }
 763        }
 764    }
 765
 766    /**
 767     * @param $d
 768     */
 769    public function metaDirectiveViewCallback($d)
 770    {
 771        $view = $this->getView($d['name']);
 772        if (!empty($d['set'])) {
 773            foreach ($d['set'] as $k => $v) {
 774                $view->set($k, $v);
 775            }
 776        }
 777        if (!empty($d['param'])) {
 778            foreach ($d['param'] as $k => $v) {
 779                $view->setParam($k, $v);
 780            }
 781        }
 782        if (!empty($d['do'])) {
 783            foreach ($d['do'] as $args) {
 784                $method = array_shift($args);
 785                BDebug::debug('LAYOUT.view.do ' . $method);
 786                call_user_func_array(array($view, $method), $args);
 787            }
 788        }
 789    }
 790
 791    /**
 792     * @deprecated
 793     *
 794     * @param mixed $themeName
 795     * @return BLayout
 796     */
 797    public function defaultTheme($themeName = null)
 798    {
 799        if (is_null($themeName)) {
 800            return $this->_defaultTheme;
 801        }
 802        $this->_defaultTheme = $themeName;
 803        BDebug::debug('THEME.DEFAULT: ' . $themeName);
 804
 805        return $this;
 806    }
 807
 808    /**
 809     * @param $themeName
 810     * @return $this
 811     */
 812    public function setDefaultTheme($themeName)
 813    {
 814        $this->_defaultTheme = $themeName;
 815        BDebug::debug('THEME.DEFAULT: ' . $themeName);
 816
 817        return $this;
 818    }
 819
 820    /**
 821     * @return array|string
 822     */
 823    public function getDefaultTheme()
 824    {
 825        return $this->_defaultTheme;
 826    }
 827
 828    /**
 829     * @param $themeName
 830     * @param $params
 831     * @return $this
 832     */
 833    public function addTheme($themeName, $params)
 834    {
 835        BDebug::debug('THEME.ADD ' . $themeName);
 836        $this->_themes[$themeName] = $params;
 837
 838        return $this;
 839    }
 840
 841    /**
 842     * @param null $area
 843     * @param bool $asOptions
 844     * @return array
 845     */
 846    public function getThemes($area = null, $asOptions = false)
 847    {
 848        if (is_null($area)) {
 849            return $this->_themes;
 850        }
 851        $themes = array();
 852        foreach ($this->_themes as $name => $theme) {
 853            if (!empty($theme['area']) && $theme['area'] === $area) {
 854                if ($asOptions) {
 855                    $themes[$name] = !empty($theme['description']) ? $theme['description'] : $name;
 856                } else {
 857                    $themes[$name] = $theme;
 858                }
 859            }
 860        }
 861
 862        return $themes;
 863    }
 864
 865    /**
 866     * @param null $themeName
 867     * @return $this
 868     */
 869    public function applyTheme($themeName = null)
 870    {
 871        if (is_null($themeName)) {
 872            if (!$this->_defaultTheme) {
 873                BDebug::error('Empty theme supplied and no default theme is set');
 874            }
 875            $themeName = $this->_defaultTheme;
 876        }
 877        if (is_array($themeName)) {
 878            foreach ($themeName as $n) {
 879                $this->applyTheme($n);
 880            }
 881            return $this;
 882        }
 883        BDebug::debug('THEME.APPLY ' . $themeName);
 884        BEvents::i()->fire('BLayout::applyTheme:before', array('theme_name' => $themeName));
 885        $this->loadTheme($themeName);
 886        BEvents::i()->fire('BLayout::applyTheme:after', array('theme_name' => $themeName));
 887
 888        return $this;
 889    }
 890
 891    public function loadTheme($themeName)
 892    {
 893        if (empty($this->_themes[$themeName])) {
 894            BDebug::warning('Invalid theme name: ' . $themeName);
 895            return false;
 896        }
 897
 898        $theme = $this->_themes[$themeName];
 899
 900        $area = BApp::i()->get('area');
 901        if (!empty($theme['area']) && !in_array($area, (array)$theme['area'])) {
 902            BDebug::debug('Theme ' . $themeName . ' can not be used in ' . $area);
 903            return false;
 904        }
 905
 906        if (!empty($theme['parent'])) {
 907            foreach ((array)$theme['parent'] as $parentThemeName) {
 908                if ($this->loadTheme($parentThemeName)) {
 909                    break; // load the first available parent theme
 910                }
 911            }
 912        }
 913
 914        BEvents::i()->fire('BLayout::loadTheme:before', array('theme_name' => $themeName, 'theme' => $theme));
 915
 916        $modRootDir = !empty($theme['module_name']) ? BApp::m($theme['module_name'])->root_dir.'/' : '';
 917        if (!empty($theme['layout'])) {
 918            BLayout::i()->loadLayout($modRootDir.$theme['layout']);
 919        }
 920        if (!empty($theme['views'])) {
 921            BLayout::i()->addAllViews($modRootDir.$theme['views']);
 922        }
 923        if (!empty($theme['callback'])) {
 924            BUtil::i()->call($theme['callback']);
 925        }
 926
 927        BEvents::i()->fire('BLayout::loadTheme:after', array('theme_name' => $themeName, 'theme' => $theme));
 928
 929        return true;
 930    }
 931
 932    /**
 933     * Shortcut for event registration
 934     * @param $callback
 935     * @return $this
 936     */
 937    public function onAfterTheme($callback)
 938    {
 939        BEvents::i()->on('BLayout::applyTheme:after', $callback);
 940
 941        return $this;
 942    }
 943
 944    /**
 945     * Dispatch layout event, for both general observers and route specific observers
 946     *
 947     * Observers should watch for these events:
 948     * - BLayout::{event}
 949     * - BLayout::{event}: GET {route}
 950     *
 951     * @param mixed $eventName
 952     * @param mixed $routeName
 953     * @param mixed $args
 954     * @return array
 955     */
 956    public function dispatch($eventName, $routeName = null, $args = array())
 957    {
 958        if (is_null($routeName) && ($route = BRouting::i()->currentRoute())) {
 959            $args['route_name'] = $routeName = $route->route_name;
 960        }
 961        $result = BEvents::i()->fire("BLayout::{$eventName}", $args);
 962
 963        $routes = is_string($routeName) ? explode(',', $routeName) : (array)$routeName;
 964        foreach ($routes as $route) {
 965            $args['route_name'] = $route;
 966            $r2                 = BEvents::i()->fire("BLayout::{$eventName}: {$route}", $args);
 967            $result             = BUtil::arrayMerge($result, $r2);
 968        }
 969
 970        return $result;
 971    }
 972
 973    /**
 974     * Render layout starting with main (root) view
 975     *
 976     * @param string $routeName Optional: render a specific route, default current route
 977     * @param array  $args Render arguments
 978     * @return mixed
 979     */
 980    public function render($routeName = null, $args = array())
 981    {
 982        $this->dispatch('render:before', $routeName, $args);
 983
 984        $rootView = $this->getRootView();
 985        BDebug::debug('LAYOUT.RENDER ' . var_export($rootView, 1));
 986        if (!$rootView) {
 987            BDebug::error(BLocale::_('Main view not found: %s', $this->_rootViewName));
 988        }
 989        $result = $rootView->render($args);
 990
 991        $args['output'] =& $result;
 992        $this->dispatch('render:after', $routeName, $args);
 993
 994        //BSession::i()->dirty(false); // disallow session change during layout render
 995
 996        return $result;
 997    }
 998
 999    /**
1000     * @return void
1001     */
1002    public function debugPrintViews()
1003    {
1004        foreach ($this->_views as $viewName => $view) {
1005            echo $viewName . ':<pre>';
1006            print_r($view);
1007            echo '</pre><hr>';
1008        }
1009    }
1010
1011    /**
1012     *
1013     */
1014    public function debugPrintLayouts()
1015    {
1016        echo "<pre>";
1017        print_r($this->_layouts);
1018        echo "</pre>";
1019    }
1020}
1021
1022/**
1023 * First parent view class
1024 */
1025class BView extends BClass
1026{
1027    /**
1028     * @var
1029     */
1030    protected static $_renderer;
1031
1032    /**
1033     * @var string
1034     */
1035    protected static $_metaDataRegex = '#<!--\s*\{\s*([^:]+):\s*(.*?)\s*\}\s*-->#';
1036
1037    /**
1038     * View parameters
1039     * - view_class
1040     * - template
1041     * - module_name
1042     * - args
1043     *
1044     * @var array
1045     */
1046    protected $_params;
1047
1048    /**
1049     * Factory to generate view instances
1050     *
1051     * @param string $viewName
1052     * @param array  $params
1053     * @return BView
1054     */
1055    static public function factory($viewName, array $params = array())
1056    {
1057        $params['view_name'] = $viewName;
1058        $className           = !empty($params['view_class']) ? $params['view_class'] : get_called_class();
1059        $view                = BClassRegistry::i()->instance($className, $params);
1060
1061        return $view;
1062    }
1063
1064    /**
1065     * Constructor, set initial view parameters
1066     *
1067     * @param array $params
1068     * @return BView
1069     */
1070    public function __construct(array $params)
1071    {
1072        $this->_params = $params;
1073    }
1074
1075    /**
1076     * Retrieve view parameters
1077     *
1078     * @param string $key
1079     * @return mixed|BView
1080     */
1081    public function param($key = null)
1082    {
1083        if (is_null($key)) {
1084            return $this->_params;
1085        }
1086
1087        return isset($this->_params[$key]) ? $this->_params[$key] : null;
1088    }
1089
1090    /**
1091     * @param      $key
1092     * @param null $value
1093     * @return $this
1094     */
1095    public function setParam($key, $value = null)
1096    {
1097        if (is_array($key)) {
1098            foreach ($key as $k => $v) {
1099                $this->setParam($k, $v);
1100            }
1101
1102            return $this;
1103        }
1104        $this->_params[$key] = $value;
1105
1106        return $this;
1107    }
1108
1109    /**
1110     * @param $key
1111     * @return null
1112     */
1113    public function getParam($key)
1114    {
1115        return isset($this->_params[$key]) ? $this->_params[$key] : null;
1116    }
1117
1118    /**
1119     * @param      $name
1120     * @param null $value
1121     * @return $this
1122     */
1123    public function set($name, $value = null)
1124    {
1125        if (is_array($name)) {
1126            foreach ($name as $k => $v) {
1127                $this->_params['args'][$k] = $v;
1128            }
1129
1130            return $this;
1131        }
1132        $this->_params['args'][$name] = $value;
1133
1134        return $this;
1135    }
1136
1137    /**
1138     * @param $name
1139     * @return null
1140     */
1141    public function get($name)
1142    {
1143        return isset($this->_params['args'][$name]) ? $this->_params['args'][$name] : null;
1144    }
1145
1146    /**
1147     * @return array
1148     */
1149    public function getAllArgs()
1150    {
1151        return !empty($this->_params['args']) ? $this->_params['args'] : array();
1152    }
1153
1154    /**
1155     * Magic method to retrieve argument, accessible from view/template as $this->var
1156     *
1157     * @param string $name
1158     * @return mixed
1159     */
1160    public function __get($name)
1161    {
1162        return $this->get($name);
1163    }
1164
1165    /**
1166     * Magic method to set argument, stored in params['args']
1167     *
1168     * @param string $name
1169     * @param mixed  $value
1170     * @return $this
1171     */
1172    public function __set($name, $value)
1173    {
1174        return $this->set($name, $value);
1175    }
1176
1177    /**
1178     * Magic method to check if argument is set
1179     *
1180     * @param string $name
1181     * @return bool
1182     */
1183    public function __isset($name)
1184    {
1185        return isset($this->_params['args'][$name]);
1186    }
1187
1188    /**
1189     * Magic method to unset argument
1190     *
1191     * @param string $name
1192     */
1193    public function __unset($name)
1194    {
1195        unset($this->_params['args'][$name]);
1196    }
1197
1198    /**
1199     * Retrieve view object
1200     *
1201     * @todo detect multi-level circular references
1202     * @param string $viewName
1203     * @param array  $params
1204     * @throws BException
1205     * @return BView|null
1206     */
1207    public function view($viewName, $params = null)
1208    {
1209        if ($viewName === $this->param('view_name')) {
1210            throw new BException(BLocale::_('Circular reference detected: %s', $viewName));
1211        }
1212
1213        $view = BLayout::i()->getView($viewName);
1214
1215        if ($view && $params) {
1216            $view->set($params);
1217        }
1218
1219        return $view;
1220    }
1221
1222    /**
1223     * Collect output from subscribers of a layout event
1224     *
1225     * @param string $hookName
1226     * @param array  $args
1227     * @return string
1228     */
1229    public function hook($hookName, $args = array())
1230    {
1231        $args['_viewname'] = $this->param('view_name');
1232        $result = '';
1233
1234        $debug = BDebug::is('DEBUG');
1235        if ($debug) {
1236            $result .= "<!-- START HOOK: {$hookName} -->\n";
1237        }
1238
1239        $result .= join('', BEvents::i()->fire('BView::hook:before', array('view' => $this, 'name' => $hookName)));
1240
1241        $result .= join('', BEvents::i()->fire('BLayout::hook:' . $hookName, $args));
1242
1243        $result .= join('', BEvents::i()->fire('BView::hook:after', array('view' => $this, 'name' => $hookName)));
1244
1245        if ($debug) {
1246            $result .= "<!-- END HOOK: {$hookName} -->\n";
1247        }
1248
1249        return $result;
1250    }
1251
1252    /**
1253     * @param string $defaultFileExt
1254     * @param bool $quiet
1255     * @return BView|mixed|string
1256     */
1257    public function getTemplateFileName($fileExt = null, $quiet = false)
1258    {
1259        if (is_null($fileExt)) {
1260            $fileExt = $this->getParam('file_ext');
1261        }
1262        $template = $this->param('template');
1263        if (!$template && ($viewName = $this->param('view_name'))) {
1264            $template = $viewName . $fileExt;
1265        }
1266        if ($template) {
1267            if (!BUtil::isPathAbsolute($template)) {
1268                $template = BLayout::i()->getViewRootDir() . '/' . $template;
1269            }
1270            if (!is_readable($template) && !$quiet) {
1271                BDebug::notice('TEMPLATE NOT FOUND: ' . $template);
1272            } else {
1273                BDebug::debug('TEMPLATE ' . $template);
1274            }
1275        }
1276
1277        return $template;
1278    }
1279
1280    /**
1281    * Used by external renderers to include compiled PHP file within $this context
1282    *
1283    * @param mixed $file
1284    */
1285    public function renderFile($file)
1286    {
1287        ob_start();
1288        include $file;
1289        return ob_get_clean();
1290    }
1291
1292    public function renderEval($source)
1293    {
1294        ob_start();
1295        eval($source);
1296        return ob_get_clean();
1297    }
1298
1299    /**
1300     * View class specific rendering
1301     *
1302     * @return string
1303     */
1304    protected function _render()
1305    {
1306        $renderer = $this->param('renderer');
1307        if ($renderer) {
1308            return call_user_func($renderer, $this);
1309        }
1310
1311        ob_start();
1312        include $this->getTemplateFileName();
1313        return ob_get_clean();
1314    }
1315
1316    /**
1317     * General render public method
1318     *
1319     * @param array $args
1320     * @param bool  $retrieveMetaData
1321     * @return string
1322     */
1323    public function render(array $args = array(), $retrieveMetaData = true)
1324    {
1325        $debug = BDebug::is('DEBUG') && !$this->get('no_debug');
1326        $viewName = $this->param('view_name');
1327
1328        $timer = BDebug::debug('RENDER.VIEW ' . $viewName);
1329        if ($this->param('raw_text') !== null) {
1330            return $this->param('raw_text');
1331        }
1332        foreach ($args as $k => $v) {
1333            $this->_params['args'][$k] = $v;
1334        }
1335        if (($modName = $this->param('module_name'))) {
1336            BModuleRegistry::i()->pushModule($modName);
1337        }
1338        $result = '';
1339        if (!$this->_beforeRender()) {
1340            BDebug::debug('BEFORE.RENDER failed');
1341            if ($debug) {
1342                $result .= "<!-- FAILED VIEW: {$viewName} -->\n";
1343            }
1344            return $result;
1345        }
1346
1347        $showDebugTags = $debug && $modName && $viewName && BLayout::i()->getRootViewName()!==$viewName;
1348
1349        if ($showDebugTags) {
1350            $result .= "<!-- START VIEW: @{$modName}/{$viewName} -->\n";
1351        }
1352        $result .= join('', BEvents::i()->fire('BView::render:before', array('view' => $this)));
1353
1354        $viewContent = $this->_render();
1355
1356        if ($retrieveMetaData) {
1357            $metaData = array();
1358            if (preg_match_all(static::$_metaDataRegex, $viewContent, $matches, PREG_SET_ORDER)) {
1359                foreach ($matches as $m) {
1360                    $metaData[$m[1]] = $m[2];
1361                    $viewContent     = str_replace($m[0], '', $viewContent);
1362                }
1363            }
1364            $this->setParam('meta_data', $metaData);
1365        }
1366        $result .= $viewContent;
1367        $result .= join('', BEvents::i()->fire('BView::render:after', array('view' => $this)));
1368
1369        if ($showDebugTags) {
1370            $result .= "<!-- END VIEW: @{$modName}/{$viewName} -->\n";
1371        }
1372        BDebug::profile($timer);
1373
1374        $this->_afterRender();
1375        if ($modName) {
1376            BModuleRegistry::i()->popModule();
1377        }
1378
1379        return $result;
1380    }
1381
1382    /**
1383     * Use meta data declared in the view template to set head meta tags
1384     */
1385    public function useMetaData()
1386    {
1387        $this->render();
1388        $metaData = $this->param('meta_data');
1389        if ($metaData) {
1390            if (!empty($metaData['layout.yml'])) {
1391                $layoutData = BYAML::i()->parse(trim($metaData['layout.yml']));
1392                BLayout::i()->addLayout('viewproxy-metadata', $layoutData)->applyLayout('viewproxy-metadata');
1393            }
1394            if (($head = $this->view('head'))) {
1395                foreach ($metaData as $k=>$v) {
1396                    $k = strtolower($k);
1397                    switch ($k) {
1398                    case 'title':
1399                        $head->addTitle($v); break;
1400                    case 'meta_title': case 'meta_description': case 'meta_keywords':
1401                        $head->meta(str_replace('meta_','',$k), $v); break;
1402                    }
1403                }
1404            }
1405        }
1406        return $this;
1407    }
1408
1409    /**
1410     * @return bool
1411     */
1412    protected function _beforeRender()
1413    {
1414        return true;
1415    }
1416
1417    /**
1418     *
1419     */
1420    protected function _afterRender()
1421    {
1422
1423    }
1424
1425    /**
1426     * Clear parameters to avoid circular reference memory leaks
1427     *
1428     */
1429    public function clear()
1430    {
1431        unset($this->_params);
1432    }
1433
1434    /**
1435     * Clear params on destruct
1436     *
1437     */
1438    public function __destruct()
1439    {
1440        $this->clear();
1441    }
1442
1443    /**
1444     * Render as string
1445     *
1446     * If there's exception during render, output as string as well
1447     *
1448     * @return string
1449     */
1450    public function __toString()
1451    {
1452        try {
1453            $result = $this->render();
1454        } catch (PDOException $e) {
1455            $result = '<hr>' . get_class($e) . ': ' . $e->getMessage() . '<hr>' . ORM::get_last_query() . '<hr>';
1456        } catch (Exception $e) {
1457            $result = '<hr>' . get_class($e) . ': ' . $e->getMessage() . '<hr>';
1458        }
1459
1460        return $result;
1461    }
1462
1463    /**
1464     * Escape HTML
1465     *
1466     * @param string $str
1467     * @param array  $args
1468     * @return string
1469     */
1470    public function q($str, $args = array())
1471    {
1472        if (is_null($str)) {
1473            return '';
1474        }
1475        if (!is_scalar($str)) {
1476            var_dump($str);
1477
1478            return ' ** ERROR ** ';
1479        }
1480
1481        return htmlspecialchars($args ? BUtil::sprintfn($str, $args) : $str);
1482    }
1483
1484    /**
1485     * @param      $str
1486     * @param null $tags
1487     * @return string
1488     */
1489    public function s($str, $tags = null)
1490    {
1491        return strip_tags($str, $tags);
1492    }
1493
1494    /**
1495     * @deprecated by BUtil::optionsHtml()
1496     * @param        $options
1497     * @param string $default
1498     * @return string
1499     */
1500    public function optionsHtml($options, $default = '')
1501    {
1502        return BUtil::optionsHtml($options, $default);
1503    }
1504
1505    /**
1506     * Send email using the content of the view as body using standard PHP mail()
1507     *
1508     * Templates can include the following syntax for default headers:
1509     * - <!--{ From: Support <support@example.com> }-->
1510     * - <!--{ Subject: New order notification #<?php echo $this->order_id?> }-->
1511     *
1512     * $p accepts following parameters:
1513     * - to: email OR "name" <email>
1514     * - from: email OR "name" <email>
1515     * - subject: email subject
1516     * - cc: email OR "name" <email> OR array of these
1517     * - bcc: same as cc
1518     * - reply-to
1519     * - return-path
1520     *
1521     * All parameters are also available in the template as $this->{param}
1522     *
1523     * @param array|string $p if string, used as "To:" header
1524     * @return bool true if successful
1525     */
1526    public function email($p = array())
1527    {
1528        if (is_string($p)) {
1529            $p = array('to' => $p);
1530        }
1531
1532        $body = $this->render($p, true);
1533
1534        $data = array_merge(
1535            array_change_key_case($this->param('meta_data'), CASE_LOWER),
1536            array_change_key_case($p, CASE_LOWER)
1537        );
1538        $data['body'] = $body;
1539
1540        return BEmail::i()->send($data);
1541    }
1542
1543    /**
1544     * Translate string within view class method or template
1545     *
1546     * @param string $string
1547     * @param array  $params
1548     * @param string $module if null, try to get current view module
1549     * @return \false|string
1550     */
1551    public function _($string, $params = array(), $module = null)
1552    {
1553        if (empty($module) && !empty($this->_params['module_name'])) {
1554            $module = $this->_params['module_name'];
1555        }
1556
1557        return BLocale::_($string, $params, $module);
1558    }
1559
1560    protected $_validators = array();
1561
1562    public function validator($formName, $data = null)
1563    {
1564        if (empty($this->_validators[$formName])) {
1565            $this->_validators[$formName] = BValidateViewHelper::i(true, array(
1566                'form' => $formName,
1567                'data' => $data,
1568            ));
1569        }
1570        return $this->_validators[$formName];
1571    }
1572}
1573
1574/**
1575 * Helper view to avoid errors of using views from disabled modules
1576 */
1577class BViewEmpty extends BView
1578{
1579    public function render(array $args = array(), $retrieveMetaData = true)
1580    {
1581        return '';
1582    }
1583}
1584
1585/**
1586 * View dedicated for rendering HTML HEAD tags
1587 */
1588class BViewHead extends BView
1589{
1590    /**
1591     * @var array
1592     */
1593    protected $_title = array();
1594
1595    /**
1596     * @var string
1597     */
1598    protected $_titleSeparator = ' :: ';
1599
1600    /**
1601     * @var bool
1602     */
1603    protected $_titleReverse = true;
1604
1605    /**
1606     * Substitution variables
1607     *
1608     * @var array
1609     */
1610    protected $_subst = array();
1611
1612    /**
1613     * Meta tags
1614     *
1615     * @var array
1616     */
1617    protected $_meta = array();
1618
1619    /**
1620     * External resources (JS and CSS)
1621     *
1622     * @var array
1623     */
1624    protected $_elements = array();
1625
1626    /**
1627     * Support for head.js
1628     *
1629     * @see http://headjs.com/
1630     * @var array
1631     */
1632    protected $_headJs = array('enabled' => false, 'loaded' => false, 'jquery' => null, 'scripts' => array());
1633
1634    /**
1635     * Support for require.js
1636     *
1637     * @see http://requirejs.org/
1638     * @var array
1639     */
1640    protected $_requireJs = array('config' => array(), 'run' => array());
1641
1642    /**
1643     * Default tag templates for JS and CSS resources
1644     *
1645     * @var array
1646     */
1647    protected $_defaultTag = array(
1648        'js'      => '<script type="text/javascript" src="%s" %a></script>',
1649        'js_raw'  => '<script type="text/javascript" %a>%c</script>',
1650        'css'     => '<link rel="stylesheet" type="text/css" href="%s" %a/>',
1651        'css_raw' => '<style type="text/css" %a>%c</style>',
1652        //'less' => '<link rel="stylesheet" type="text/less" href="%s" %a/>',
1653        'less'    => '<link rel="stylesheet/less" type="text/css" href="%s" %a/>',
1654        'icon'    => '<link rel="icon" href="%s" type="image/x-icon" %a/><link rel="shortcut icon" href="%s" type="image/x-icon" %a/>',
1655    );
1656
1657    /**
1658     * Current IE <!--[if]--> context
1659     *
1660     * @var string
1661     */
1662    protected $_currentIfContext = null;
1663
1664    /**
1665     * @param      $from
1666     * @param null $to
1667     * @return $this|string
1668     */
1669    public function subst($from, $to = null)
1670    {
1671        if (is_null($to)) {
1672            return str_replace(array_keys($this->_subst), array_values($this->_subst), $from);
1673        }
1674        $this->_subst['{' . $from . '}'] = $to;
1675
1676        return $this;
1677    }
1678
1679    /**
1680     * Enable/disable head js
1681     *
1682     * @param bool $enable
1683     * @return $this
1684     */
1685    public function headJs($enable = true)
1686    {
1687        $this->_headJs['enabled'] = $enable;
1688
1689        return $this;
1690    }
1691
1692    /**
1693     * Alias for addTitle($title)
1694     *
1695     * @deprecated
1696     * @param mixed $title
1697     * @param bool  $start
1698     * @return BViewHead
1699     */
1700    public function title($title, $start = false)
1701    {
1702        $this->addTitle($title, $start);
1703    }
1704
1705    /**
1706     * Add meta tag, or return meta tag(s)
1707     *
1708     * @deprecated
1709     *
1710     * @param string $name If not specified, will return all meta tags as string
1711     * @param string $content If not specified, will return meta tag by name
1712     * @param bool   $httpEquiv Whether the tag is http-equiv
1713     * @return BViewHead
1714     */
1715    public function meta($name = null, $content = null, $httpEquiv = false)
1716    {
1717        if (is_null($content)) {
1718            return $this->getMeta($name);
1719        }
1720        $this->addMeta($name, $content, $httpEquiv);
1721
1722        return $this;
1723    }
1724
1725    public function csrf_token()
1726    {
1727        $this->addMeta('csrf-token', BSession::i()->csrfToken());
1728        return $this;
1729    }
1730
1731    /**
1732     * Add canonical link
1733     * @param $href
1734     * @return $this
1735     */
1736    public function canonical($href)
1737    {
1738        $this->addElement('link', 'canonical', array('tag' => '<link rel="canonical" href="' . $href . '"/>'));
1739
1740        return $this;
1741    }
1742
1743    /**
1744     * Add rss link
1745     *
1746     * @param $href
1747     */
1748    public function rss($href)
1749    {
1750        $this->addElement('link', 'rss', array('tag' => '<link rel="alternate" type="application/rss+xml" title="RSS" href="' . $href . '">'));
1751    }
1752
1753    /**
1754     * Enable direct call of different item types as methods (js, css, icon, less)
1755     *
1756     * @param string $name
1757     * @param array  $args
1758     * @return mixed
1759     */
1760    public function __call($name, $args)
1761    {
1762        if (!empty($this->_defaultTag[$name])) {
1763            array_unshift($args, $name);
1764            return call_user_func_array(array($this, 'addElement'), $args);
1765        } else {
1766            BDebug::error('Invalid method: ' . $name);
1767        }
1768    }
1769
1770    public function removeAll()
1771    {
1772        $this->_elements = array();
1773        $this->_headJs = array();
1774        return $this;
1775    }
1776
1777    /**
1778     * Remove JS/CSS elements by type and pattern (strpos)
1779     *
1780     * @param string $type
1781     * @param string $pattern
1782     * @return BViewHead
1783     */
1784    public function remove($type, $pattern)
1785    {
1786        if ($type === 'js' && $this->_headJs['loaded']) {
1787            foreach ($this->_headJs['scripts'] as $i => $file) {
1788                if (true===$pattern || strpos($file, $pattern) !== false) {
1789                    unset($this->_headJs['scripts'][$i]);
1790                }
1791            }
1792        }
1793        foreach ($this->_elements as $k => $args) {
1794            if (strpos($k, $type) === 0 && (true===$pattern || strpos($k, $pattern) !== false)) {
1795                unset($this->_elements[$k]);
1796            }
1797        }
1798
1799        return $this;
1800    }
1801
1802    /**
1803     * Set title
1804     * This will replace any current title
1805     *
1806     * @param $title
1807     * @return $this
1808     */
1809    public function setTitle($title)
1810    {
1811        $this->_title = array($title);
1812        return $this;
1813    }
1814
1815    /**
1816     * Add title
1817     * Add title to be appended to or replace current titles
1818     *
1819     * @param      $title
1820     * @param bool $start
1821     * @return $this
1822     */
1823    public function addTitle($title, $start = false)
1824    {
1825        if ($start) {
1826            array_splice($this->_title, 0, 1, $title);
1827        } else {
1828            $this->_title[] = $title;
1829        }
1830
1831        return $this;
1832    }
1833
1834    /**
1835     * Set title separator
1836     * Set character or string to be used to separate title values.
1837     *
1838     * @param $sep
1839     * @return $this
1840     */
1841    public function setTitleSeparator($sep)
1842    {
1843        $this->_titleSeparator = $sep;
1844        return $this;
1845    }
1846
1847    /**
1848     * Should title be composed in reverse order
1849     *
1850     * @param $reverse
1851     * @return $this
1852     */
1853    public function setTitleReverse($reverse)
1854    {
1855        $this->_titleReverse = $reverse;
1856        return $this;
1857    }
1858
1859    /**
1860     * Compose and return title
1861     * Title is composed by all elements in $_title object field separated by _titleSeparator
1862     *
1863     * @return string
1864     */
1865    public function getTitle()
1866    {
1867        if (!$this->_title) {
1868            return '';
1869        }
1870   

Large files files are truncated, but you can click here to view the full file