Autodoc
  • Namespace
  • Class
  • Tree

Namespaces

  • BlueTihi
    • Context
  • Brickrouge
    • Element
      • Nodes
    • Renderer
    • Widget
  • ICanBoogie
    • ActiveRecord
    • AutoConfig
    • CLDR
    • Composer
    • Core
    • Event
    • Exception
    • HTTP
      • Dispatcher
      • Request
    • I18n
      • Translator
    • Mailer
    • Modules
      • Taxonomy
        • Support
      • Thumbnailer
        • Versions
    • Object
    • Operation
      • Dispatcher
    • Prototype
    • Routes
    • Routing
      • Dispatcher
    • Session
  • Icybee
    • ActiveRecord
      • Model
    • ConfigOperation
    • Document
    • EditBlock
    • Element
      • ActionbarContextual
      • ActionbarSearch
      • ActionbarToolbar
    • FormBlock
    • Installer
    • ManageBlock
    • Modules
      • Articles
      • Cache
        • Collection
        • ManageBlock
      • Comments
        • ManageBlock
      • Contents
        • ManageBlock
      • Dashboard
      • Editor
        • Collection
      • Files
        • File
        • ManageBlock
      • Forms
        • Form
        • ManageBlock
      • I18n
      • Images
        • ManageBlock
      • Members
      • Modules
        • ManageBlock
      • Nodes
        • ManageBlock
        • Module
      • Pages
        • BreadcrumbElement
        • LanguagesElement
        • ManageBlock
        • NavigationBranchElement
        • NavigationElement
        • Page
        • PageController
      • Registry
      • Search
      • Seo
      • Sites
        • ManageBlock
      • Taxonomy
        • Terms
          • ManageBlock
        • Vocabulary
          • ManageBlock
      • Users
        • ManageBlock
        • NonceLogin
        • Roles
      • Views
        • ActiveRecordProvider
        • Collection
        • View
    • Operation
      • ActiveRecord
      • Constructor
      • Module
      • Widget
    • Rendering
  • None
  • Patron
  • PHP

Classes

  • BeforeRenderEvent
  • RenderEvent
  • RescueEvent
  1 <?php
  2 
  3 /*
  4  * This file is part of the Icybee package.
  5  *
  6  * (c) Olivier Laviale <olivier.laviale@gmail.com>
  7  *
  8  * For the full copyright and license information, please view the LICENSE
  9  * file that was distributed with this source code.
 10  */
 11 
 12 namespace Icybee\Modules\Views;
 13 
 14 use ICanBoogie\ActiveRecord\Model;
 15 use ICanBoogie\Debug;
 16 use ICanBoogie\Event;
 17 use ICanBoogie\Exception;
 18 use ICanBoogie\I18n;
 19 use ICanBoogie\HTTP\HTTPError;
 20 use ICanBoogie\Module;
 21 use ICanBoogie\Object;
 22 
 23 use Brickrouge\Document;
 24 use Brickrouge\Element;
 25 use Brickrouge\Pager;
 26 
 27 use BlueTihi\Context;
 28 
 29 use Icybee\Modules\Nodes\Node;
 30 
 31 /**
 32  * A view on provided data.
 33  *
 34  * @property-read string $id The identifier of the view.
 35  * @property-read mixed $data The data provided by the view's provider.
 36  */
 37 class View extends Object
 38 {
 39     const ACCESS_CALLBACK = 'access_callback';
 40     const ASSETS = 'assets';
 41     const CLASSNAME = 'class';
 42     const PROVIDER = 'provider';
 43     const RENDERS = 'renders';
 44     const RENDERS_ONE = 1;
 45     const RENDERS_MANY = 2;
 46     const RENDERS_OTHER = 3;
 47     const TITLE = 'title';
 48 
 49     // FIXME-20121226: defined the conditions handled by the provider of the view, particuliarly
 50     // required ones.
 51 
 52     protected $id;
 53 
 54     protected function get_id()
 55     {
 56         return $this->id;
 57     }
 58 
 59     /**
 60      * The amount of data the view is rendering.
 61      *
 62      * - RENDERS_ONE: Renders a record.
 63      *
 64      * - RENDERS_MANY: Renders an array of records. A 'range' value is added to the rendering
 65      * context the following properties:
 66      *     - (int) limit: The maximum number of record to render.
 67      *     - (int) page: The starting page.
 68      *     - (int) count: The total number of records. This value is to be entered by the provider.
 69      *
 70      * - RENDERS_OTHER: Renders an unknown amount of data.
 71      *
 72      * The property is read-only.
 73      *
 74      * @var int
 75      */
 76     protected $renders;
 77     protected $options;
 78 
 79     protected function get_options()
 80     {
 81         return $this->options;
 82     }
 83 
 84     protected $engine;
 85     protected $document;
 86     protected $page;
 87     protected $template;
 88 
 89     protected $module;
 90 
 91     protected function lazy_get_module()
 92     {
 93         global $core;
 94 
 95         if (isset($this->module))
 96         {
 97             return $this->module;
 98         }
 99 
100         return $core->modules[$this->module_id];
101     }
102 
103     private $data;
104 
105     protected function get_data()
106     {
107         return $this->data;
108     }
109 
110     public $module_id;
111     public $type;
112 
113     public function __construct($id, array $options, $engine, $document, $page, $template=null)
114     {
115         unset($this->module);
116 
117         $this->options = $options;
118 
119         $this->id = $id;
120         $this->type = $options['type'];
121         $this->module_id = $options['module'];
122         $this->renders = $options['renders'];
123 
124         $this->engine = $engine;
125         $this->document = $document;
126         $this->page = $page;
127         $this->template = $template;
128     }
129 
130     /**
131      * Renders the view.
132      *
133      * @return mixed
134      */
135     public function __invoke()
136     {
137         $this->validate_access();
138 
139         $assets = array('css' => array(), 'js' => array());
140         $options = $this->options;
141 
142         if (isset($options['assets']))
143         {
144             $assets = $options['assets'];
145         }
146 
147         $this->add_assets($this->document, $assets);
148 
149         #
150 
151         try
152         {
153             $this->fire_render_before(array('id' => $this->id));
154 
155             $rc = $this->render_outer_html();
156 
157             $this->fire_render(array('id' => $this->id, 'rc' => &$rc));
158 
159             return $rc;
160         }
161         catch (\Brickrouge\ElementIsEmpty $e)
162         {
163             return '';
164         }
165     }
166 
167     /**
168      * Alters template context.
169      *
170      * @param \BlueTihi\Context $context
171      *
172      * @return \BlueTihi\Context
173      */
174     protected function alter_context(Context $context)
175     {
176         $context['pagination'] = '';
177 
178         if (isset($context['range']['limit']) && isset($context['range']['count']))
179         {
180             $range = $context['range'];
181 
182             $context['pagination'] = new Pager
183             (
184                 'div', array
185                 (
186                     Pager::T_COUNT => $range['count'],
187                     Pager::T_LIMIT => $range['limit'],
188                     Pager::T_POSITION => $range['page']
189                 )
190             );
191         }
192 
193         $context['view'] = $this;
194 
195         return $context;
196     }
197 
198     /**
199      * Adds view's assets to the document.
200      *
201      * @param WdDocument $document
202      * @param array $assets
203      */
204     protected function add_assets(Document $document, array $assets=array())
205     {
206         if (isset($assets['js']))
207         {
208             foreach ((array) $assets['js'] as $asset)
209             {
210                 list($file, $priority) = (array) $asset + array(1 => 0);
211 
212                 $document->js->add($file, $priority);
213             }
214         }
215 
216         if (isset($assets['css']))
217         {
218             foreach ((array) $assets['css'] as $asset)
219             {
220                 list($file, $priority) = (array) $asset + array(1 => 0);
221 
222                 $document->css->add($file, $priority);
223             }
224         }
225     }
226 
227     /**
228      * Fires the `render:before` event on the view using the specified parameters.
229      *
230      * @param array $params
231      *
232      * @return mixed
233      */
234     protected function fire_render_before(array $params=array())
235     {
236         return new View\BeforeRenderEvent($this, $params);
237     }
238 
239     /**
240      * Fires the `render` event on the view using the specified parameters.
241      *
242      * @param array $params
243      *
244      * @return mixed
245      */
246     protected function fire_render(array $params=array())
247     {
248         return new View\RenderEvent($this, $params);
249     }
250 
251     /**
252      * Returns the placeholder for the empty view.
253      *
254      * @return string
255      */
256     protected function render_empty_inner_html()
257     {
258         global $core;
259 
260         $html = null;
261         $default = I18n\t('The view %name is empty.', array('%name' => $this->id));
262         $type = $this->type;
263         $module = $this->module;
264         $module_flat_id = $module->flat_id;
265 
266         if (isset($view['on_empty']))
267         {
268             $html = call_user_func($view['on_empty'], $this);
269         }
270         else if ($module)
271         {
272             $placeholder = $core->site->metas[$module_flat_id . ".{$this->type}.placeholder"];
273 
274             if (!$placeholder)
275             {
276                 $placeholder = $core->site->metas[$module_flat_id . '.placeholder'];
277             }
278 
279             if ($placeholder)
280             {
281                 $html = $placeholder;
282             }
283             else
284             {
285                 $default = 'No record found.';
286             }
287 
288             $default .= I18n\t
289             (
290                 ' <ul><li>The placeholder %placeholder was tried, but it does not exists.</li><li>The %message was tried, but it does not exists.</li></ul>', array
291                 (
292                     'placeholder' => "$module_flat_id.$type.placeholder",
293                     'message' => "$module_flat_id.$type.empty_view"
294                 )
295             );
296         }
297 
298         if (!$html)
299         {
300             $html = I18n\t('empty_view', array(), array('scope' => $module_flat_id . '.' . $type, 'default' => $default));
301 
302             /*
303             if ($html)
304             {
305                 $html = '<div class="alert">' . $html . '</div>';
306             }
307             */
308         }
309 
310         return $html;
311     }
312 
313     /**
314      * Fires {@link View\RescueEvent} using the specified payload.
315      *
316      * @param array $payload
317      *
318      * @return mixed
319      */
320     protected function fire_render_empty_inner_html(array $payload=array())
321     {
322         return new View\RescueEvent($this, $payload);
323     }
324 
325     protected function init_range()
326     {
327         global $core;
328 
329         $limit_key = $this->module->flat_id . '.limits.' . $this->type;
330         $limit = $core->site->metas[$limit_key] ?: null;
331 
332         return array
333         (
334             'page' => empty($_GET['page']) ? 0 : (int) $_GET['page'],
335             'limit' => $limit,
336             'count' => null
337         );
338     }
339 
340     protected function provide($provider, &$context, array $conditions)
341     {
342         if (!class_exists($provider))
343         {
344             throw new \InvalidArgumentException(\ICanBoogie\format
345             (
346                 'Provider class %class for view %id does not exists', array
347                 (
348                     'class' => $provider,
349                     'id' => $this->id
350                 )
351             ));
352         }
353 
354         $provider = new $provider($this, $context, $this->module, $conditions, $this->renders);
355 
356         return $provider();
357     }
358 
359     /**
360      * Renders the inner HTML of the view.
361      *
362      * If the data provided implements {@link \Brickrouge\CSSClassNames}, the class names of the
363      * record are added those of the view element.
364      *
365      * @throws Exception
366      *
367      * @return string The inner HTML of the view element.
368      */
369     protected function render_inner_html($template_path, $engine)
370     {
371         global $core;
372 
373         $view = $this->options;
374         $bind = null;
375         $id = $this->id;
376         $page = $this->page;
377 
378         if ($view['provider'])
379         {
380             list($constructor, $name) = explode('/', $id);
381 
382             $this->range = $this->init_range();
383 
384             $bind = $this->provide($this->options['provider'], $engine->context, $page->url_variables + $_GET); // FIXME-20120628: we should be using Request here
385 
386             $this->data = $bind;
387 
388             $engine->context['this'] = $bind;
389             $engine->context['range'] = $this->range;
390 
391             if (is_array($bind) && current($bind) instanceof Node)
392             {
393                 new \BlueTihi\Context\LoadedNodesEvent($engine->context, $bind);
394             }
395             else if ($bind instanceof Node)
396             {
397                 new \BlueTihi\Context\LoadedNodesEvent($engine->context, array($bind));
398             }
399             else if (!$bind)
400             {
401                 $this->element->add_class('empty');
402 
403                 $html = (string) $this->render_empty_inner_html();
404 
405                 $this->fire_render_empty_inner_html
406                 (
407                     array
408                     (
409                         'html' => &$html
410                     )
411                 );
412 
413                 return $html;
414             }
415 
416             #
417             # appending record's css class names to the view element's class.
418             #
419 
420             if ($bind instanceof \Brickrouge\CSSClassNames)
421             {
422                 $this->element['class'] .= ' ' . $bind->css_class;
423             }
424         }
425 
426         #
427         #
428         #
429 
430         $rc = '';
431 
432         if (!$template_path)
433         {
434             throw new Exception('Unable to resolve template for view %id', array('id' => $id));
435         }
436 
437         I18n::push_scope($this->module->flat_id);
438 
439         try
440         {
441             $extension = pathinfo($template_path, PATHINFO_EXTENSION);
442 
443             $module = $core->modules[$this->module_id];
444 
445             $engine->context['core'] = $core;
446             $engine->context['document'] = $core->document;
447             $engine->context['page'] = $page;
448             $engine->context['module'] = $module;
449             $engine->context['view'] = $this;
450 
451             $engine->context = $this->alter_context($engine->context);
452 
453             if ('php' == $extension)
454             {
455                 $rc = null;
456 
457                 ob_start();
458 
459                 try
460                 {
461                     $isolated_require = function ($__file__, $__exposed__)
462                     {
463                         extract($__exposed__);
464 
465                         require $__file__;
466                     };
467 
468                     $isolated_require
469                     (
470                         $template_path, array
471                         (
472                             'bind' => $bind,
473                             'context' => &$engine->context,
474                             'core' => $core,
475                             'document' => $core->document,
476                             'page' => $page,
477                             'module' => $module,
478                             'view' => $this
479                         )
480                     );
481 
482                     $rc = ob_get_clean();
483                 }
484                 catch (\ICanBoogie\Exception\Config $e)
485                 {
486                     $rc = '<div class="alert">' . $e->getMessage() . '</div>';
487 
488                     ob_clean();
489                 }
490                 catch (\Exception $e)
491                 {
492                     ob_clean();
493 
494                     throw $e;
495                 }
496             }
497             else if ('html' == $extension)
498             {
499                 $template = file_get_contents($template_path);
500 
501                 if ($template === false)
502                 {
503                     throw new \Exception("Unable to read template from <q>$template_path</q>");
504                 }
505 
506                 $rc = $engine($template, $bind, array('file' => $template_path));
507 
508                 if ($rc === null)
509                 {
510                     var_dump($template_path, file_get_contents($template_path), $rc);
511                 }
512             }
513             else
514             {
515                 throw new Exception('Unable to process file %file, unsupported type', array('file' => $template_path));
516             }
517         }
518         catch (\Exception $e)
519         {
520             I18n::pop_scope();
521 
522             throw $e;
523         }
524 
525         I18n::pop_scope();
526 
527         return $rc;
528     }
529 
530     protected $element;
531 
532     protected function get_element()
533     {
534         return $this->element;
535     }
536 
537     protected function alter_element(Element $element)
538     {
539         return $element;
540     }
541 
542     /**
543      * Returns the HTML representation of the view element and its content.
544      *
545      * @return string
546      */
547     protected function render_outer_html()
548     {
549         $class = '';
550         $type = \ICanBoogie\normalize($this->type);
551         $m = $this->module;
552 
553         while ($m)
554         {
555             $normalized_id = \ICanBoogie\normalize($m->id);
556             $class = "view--$normalized_id--$type $class";
557 
558             $m = $m->parent;
559         }
560 
561         $this->element = new Element
562         (
563             'div', array
564             (
565                 'id' => 'view-' . \ICanBoogie\normalize($this->id),
566                 'class' => trim("view view--$type $class"),
567                 'data-constructor' => $this->module->id
568             )
569         );
570 
571         $this->element = $this->alter_element($this->element);
572 
573 //      \ICanBoogie\log("class: {$this->element->class}, type: $type, assets: " . \ICanBoogie\dump($this->options['assets']));
574 
575         $template_path = $this->resolve_template_location();
576 
577         $html = $this->render_inner_html($template_path, $this->engine);
578 
579         if (preg_match('#\.html$#', $this->page->template))
580         {
581             if (Debug::$mode == Debug::MODE_DEV)
582             {
583 
584                 $possible_templates = implode(PHP_EOL, $this->template_resolver->templates);
585 
586                 $html = <<<EOT
587 
588 <!-- Possible templates for view "{$this->id}":
589 
590 $possible_templates
591 
592 -->
593 $html
594 EOT;
595             }
596 
597             $this->element[Element::INNER_HTML] = $html;
598 
599             $html = (string) $this->element;
600         }
601 
602         return $html;
603     }
604 
605     /**
606      * Returns the template resolver of the view.
607      *
608      * @return \Icybee\Modules\Views\TemplateResolver
609      */
610     protected function lazy_get_template_resolver()
611     {
612         return new TemplateResolver($this->id, $this->type, $this->module_id);
613     }
614 
615     /**
616      * Resolves the template location of the view.
617      *
618      * The template location is resolved using a {@link TemplateResolver} instance.
619      *
620      * @throws Exception if the template location could not be resolved.
621      *
622      * @return string
623      */
624     protected function resolve_template_location()
625     {
626         $resolver = $this->template_resolver;
627         $template = $resolver();
628 
629         if (!$template)
630         {
631             throw new Exception
632             (
633                 'Unable to resolve template for view %id. Tried: :list', array
634                 (
635                     'id' => $this->id,
636                     ':list' => implode("\n<br />", $resolver->templates)
637                 )
638             );
639         }
640 
641         return $template;
642     }
643 
644     /**
645      * Checks if the view access is valid.
646      *
647      * @throws HTTPError when the view access requires authentication.
648      *
649      * @return boolean true
650      */
651     protected function validate_access()
652     {
653         $access_callback = $this->options[self::ACCESS_CALLBACK];
654 
655         if ($access_callback && !call_user_func($access_callback, $this))
656         {
657             throw new HTTPError
658             (
659                 \ICanBoogie\format('The requested URL %uri requires authentication.', array
660                 (
661                     '%uri' => $_SERVER['REQUEST_URI']
662                 )),
663 
664                 401
665             );
666         }
667 
668         return true;
669     }
670 }
671 
672 namespace Icybee\Modules\Views\View;
673 
674 /**
675  * Event fired before the view is rendered.
676  */
677 class BeforeRenderEvent extends \ICanBoogie\Event
678 {
679     public function __construct(\Icybee\Modules\Views\View $target, array $payload)
680     {
681         parent::__construct($target, 'render:before', $payload);
682     }
683 }
684 
685 /**
686  * Event fired after the view was rendered.
687  */
688 class RenderEvent extends \ICanBoogie\Event
689 {
690     public function __construct(\Icybee\Modules\Views\View $target, array $payload)
691     {
692         parent::__construct($target, 'render', $payload);
693     }
694 }
695 
696 /**
697  * Event fired when the view inner HTML is empty.
698  */
699 class RescueEvent extends \ICanBoogie\Event
700 {
701     /**
702      * Reference to the rescued HTML.
703      *
704      * @var string
705      */
706     public $html;
707 
708     public function __construct(\Icybee\Modules\Views\View $target, array $payload)
709     {
710         parent::__construct($target, 'rescue', $payload);
711     }
712 }
Autodoc API documentation generated by ApiGen 2.8.0