FRED™  3.0
FRED™: Framework for Rapid and Easy Development
Html.php
Go to the documentation of this file.
1 <?php
2 
4 
5 class Html extends \Rsi\Fred\Controller\View{
6 
7  const RESOURCE_NO_PUSH = 1; //!< Do not add a push header for the resource.
8  const RESOURCE_PROXY = 2; //!< Serve the resource through the proxy component.
9  const RESOURCE_ASYNC = 4; //!< Serve the resource with the asynchronous attribute.
10  const RESOURCE_DEFER = 8; //!< Serve the resource with the deferred attribute.
11 
12  public $convert = true;
13 
14  public $debugMinifyProbability = 4; //!< Chance (1 in x) for using minified file in debug mode.
15  public $asyncPrefix = '@'; //!< Resource ID prefix to indicate asynchronous loading.
16  public $deferPrefix = '$'; //!< Resource ID prefix to indicate deferred loading.
17  public $integrityAlgo = 'sha256'; //!< Hashing algorithm to use for subresource integrity (empty = do not use).
18  public $widgetClassNames = []; //!< View widget class name (value) per widget class name (key).
19  public $defaultWidgetNamespace = __CLASS__ . '\\Widget';
20  public $defaultWidgetClassName = __CLASS__ . '\\Widget';
21  public $renderedWidgetIds = [];
22  public $builderClassNames = [null => __CLASS__ . '\\Builder']; //!< Default builder class name per client type (null =
23  // default).
24  public $actionCaptionPrefix = 'action'; //!< Prefix to prepend to the action name to get a translation ID.
25  public $defaultTemplatePath = null;
26  public $formClass = 'fred';
27  public $formInvalidMessage = null;
28 
29  protected $_integrityCacheFile = null;
30  protected $_integrityHashes = null;
31 
32  protected $_style = [];
33  protected $_styleSrc = [];
34  protected $_script = [];
35  protected $_scriptInit = [];
36  protected $_scriptSrc = [];
37  protected $_noPush = [];
38  protected $_widgets = [];
39  protected $_template = null;
40 
41  protected function init(){
42  parent::init();
43  $this->publish('template');
44  }
45 
46  protected function addResource(&$resource,$id,$value,$options = null){
47  if(!$id) $id = count($resource);
48  if($options & self::RESOURCE_PROXY) $this->_noPush[] = $value = $this->component('proxy')->add($value);
49  elseif($options & self::RESOURCE_NO_PUSH) $this->_noPush[] = $value;
50  if($options & self::RESOURCE_ASYNC) $id = $this->asyncPrefix . $id;
51  elseif($options & self::RESOURCE_DEFER) $id = $this->deferPrefix . $id;
52  $resource[$id] = $value;
53  }
54 
55  public function addStyle($style,$id = null){
56  $this->addResource($this->_style,$id,$style);
57  }
58 
59  public function addStyleSrc($source,$id = null,$options = null){
60  $this->addResource($this->_styleSrc,$id,$source,$options);
61  }
62 
63  public function addScript($script,$id = null){
64  $this->addResource($this->_script,$id,$script);
65  }
66 
67  public function addScriptInit($script,$id = null){
68  $this->addResource($this->_scriptInit,$id,$script);
69  }
70 
71  public function addScriptSrc($source,$id = null,$options = null){
72  $this->addResource($this->_scriptSrc,$id,$source,$options);
73  }
74 
75  protected function integrity($resource,$hash = null){
76  if($this->_integrityHashes === null){
77  $this->_integrityHashes = [];
78  if($this->_integrityCacheFile){
79  try{
80  $this->_integrityHashes = include($this->_integrityCacheFile);
81  }
82  catch(\Exception $e){
83  $this->component('log')->info($e);
84  }
85  if($hash){
86  $this->_integrityHashes[$resource] = $this->integrityAlgo . '-' . base64_encode($hash);
87  file_put_contents($this->_integrityCacheFile,'<?php return ' . var_export($this->_integrityHashes,true) . ';');
88  }
89  }
90  }
91  return $this->_integrityHashes[$resource] ?? false;
92  }
93 
94  public function minifyFiles($type,$sources,$target = null,$id = null,$options = null){
95  $target = $this->minifiedPath . $target;
96  $root = \Rsi\Http::docRoot();
97  $resource = substr($target,strlen($root));
98  $func = [$this,'add' . ucfirst($type) . 'Src'];
99  if(!$this->_fred->debug || !rand(0,$this->debugMinifyProbability)) return call_user_func($func,$resource,$id,$options);
100  $this->component('minify')->handler($type)->files(\Rsi\Record::prefix($sources,$root),$target);
101  if($this->integrityAlgo) $this->integrity($resource,hash_file($this->integrityAlgo,$target,true));
102  foreach($sources as $source) call_user_func($func,$source,null,$options);
103  }
104 
105  public function minifyScripts($sources,$target = null,$id = null,$options = null){
106  $this->minifyFiles('script',$sources,$target,$id,$options);
107  }
108 
109  public function minifyStyles($sources,$target = null,$id = null,$options = null){
110  $this->minifyFiles('style',$sources,$target,$id,$options);
111  }
112 
113  public function head(){
114  $html = $this->component('html');
115  $location = $this->component('location');
116  $head = $this->title ? $html->_title($this->title) : null;
117  foreach($this->_styleSrc as $source){
118  $url = $location->rewrite($source);
119  $push = !in_array($source,$this->_noPush) && \Rsi\Http::pushHeader($url,'style');
120  $head .= $html->style(null,[
121  'integrity' => $push ? false : $this->integrity($url)
122  ],$url);
123  }
124  if($this->_style) $head .= $html->style(implode("\n",$this->_style));
125  foreach($this->_scriptSrc as $id => $source){
126  $url = $location->rewrite($source);
127  $push = !in_array($source,$this->_noPush) && \Rsi\Http::pushHeader($url,'script');
128  $head .= $html->script(null,[
129  'async' => \Rsi\Str::startsWith($id,$this->asyncPrefix),
130  'defer' => \Rsi\Str::startsWith($id,$this->deferPrefix),
131  'integrity' => $push ? false : $this->integrity($url)
132  ],$url);
133  }
134  return $head;
135  }
136 
137  public function script(){
138  $client_config = array_merge($this->_fred->clientConfig(),$extra_config = [
139  'controller' => $this->_controller->clientConfig()
140  ]);
141  $session = new \Rsi\Fred\Component\Session(get_class());
142  $local_config = $session->localConfig ?: [];
143  $session->localConfig = array_merge($local_config,$client_config);
144  if($session->configCheck && ($session->configCheck == \Rsi\Http::getCookie('fred-configCheck'))){
145  foreach($client_config as $name => &$config) if(
146  ($this->_fred->has($name) || array_key_exists($name,$extra_config)) &&
147  ($config === \Rsi\Record::get($local_config,$name))
148  ) $config = null;
149  unset($config);
150  }
151  $client_config['check'] = $session->configCheck = \Rsi\Str::random(8);
152  if($this->mobile) $client_config['mobile'] = true;
153  return ($this->_fred->debug ? '"use strict";' : '') . '
154  var fred;
155  document.addEventListener("DOMContentLoaded",function(){' .
156  implode("\n",$this->_script) . '
157  fred = new rsi.Fred(' . $this->jsonEncode($client_config) . ');' .
158  ($this->_scriptInit ? '
159  $(document).on("init.fred",function(){
160  ' . implode("\n",$this->_scriptInit) . '
161  });' : '') . '
162  fred.init();
163  },false);';
164  }
165  /**
166  * Create a view widget.
167  * First the complete model widget class name is looked up in the widgetClassNames. If not found this is retried with only
168  * the basename. If not found the basename is sought in the defaultWidgetNamespace, and the same goes for the client config
169  * class name (stripped from the default model widget namespace). If still no result, the defaultWidgetClassName is used.
170  * @param string $id Widget ID.
171  * @param \\Rsi\\Fred\\Conroller\\Widget $widget Model widget.
172  * @return \\Rsi\\Fred\\Controller\\View\\Html\\Widget
173  */
174  public function createWidget($id,$widget){
175  $widget_class_name = get_class($widget);
176  if(
177  array_key_exists($widget_class_name,$this->widgetClassNames) ||
178  array_key_exists($widget_class_name = \Rsi\File::basename($widget_class_name),$this->widgetClassNames)
179  ) $class_name = $this->widgetClassNames[$widget_class_name];
180  elseif(
181  !class_exists($class_name = $this->defaultWidgetNamespace . '\\' . $widget_class_name) &&
182  !class_exists($class_name = $this->defaultWidgetNamespace . '\\' . str_replace($this->_controller->defaultWidgetNamespace . '\\','',\Rsi\Record::get($widget->clientConfig(),\Rsi\Fred\Controller\Widget::CLASS_NAME)))
183  ) $class_name = $this->defaultWidgetClassName;
184  return new $class_name($id,$this,$widget);
185  }
186 
187  public function widget($id){
188  if(!array_key_exists($id,$this->_widgets)) $this->_widgets[$id] = $this->createWidget($id,$this->_controller->widgets[$id]);
189  return $this->_widgets[$id];
190  }
191 
192  public function notRenderedWidgetIds($ids = null){
193  return array_diff(is_array($ids) ? $ids : array_keys($this->_controller->widgets),$this->renderedWidgetIds);
194  }
195 
196  public function renderWidget($id){
197  $this->renderedWidgetIds[] = $id;
198  $value = null;
199  $raw = false;
200  $request = $this->_fred->request;
201  if(array_key_exists($id,$request->data))
202  $value = $request->data[$id];
203  elseif($raw = array_key_exists($id,$request->errors))
204  $value = $request->get($id);
205  else
206  $value = $this->_controller->widgets[$id]->defaultValue;
207  return $this->widget($id)->render($value,$raw);
208  }
209  /**
210  * Render the widgets.
211  * @param array $ids ID's of the widgets to render (default = all not rendered yet).
212  * @param Rsi\Fred\Controller\View\Html\Builder $builder Builder to use (uses builderClassNames to create a new if empty).
213  * @return string Rendered HTML.
214  */
215  protected function renderWidgets($ids = null,$builder = null){
216  if(!$ids) $ids = $this->notRenderedWidgetIds();
217  if(!$ids) return null;
218  if(!$builder){
219  $class_name = array_key_exists($type = $this->_fred->client->type ?: null,$this->builderClassNames)
220  ? $this->builderClassNames[$type]
221  : $this->builderClassNames[null];
222  $builder = new $class_name($this);
223  }
224  return $builder->build($ids);
225  }
226 
227  protected function renderFormAction($action){
228  return $this->component('html')->button(
229  $this->component('trans')->id($this->actionCaptionPrefix . $action,null,$action),
230  ['type' => 'submit','id' => 'action' . ucfirst($action),'name' => 'action','value' => $action,'class' => 'action']
231  );
232  }
233 
234  protected function renderFormActions(){
235  $content = '';
236  foreach($this->_controller->actions('form') as $action) $content .= $this->renderFormAction($action);
237  return $this->component('html')->div($content,null,'fred-actions');
238  }
239  /**
240  * Render the form.
241  * @param string $content The content for the form (default = all widgets).
242  * @return string HTML for the form.
243  */
244  protected function renderForm($content = null){
245  if($content === null) $content = $this->renderWidgets() . $this->renderFormActions();
246  return $this->component('html')->form($content,['class' => $this->formClass],$this->_controller->route());
247  }
248 
249  public function render(){
250  if($fragment = $this->_controller->fragmentId){
251  if(($str = $this->component('trans')->fragment($fragment)) !== false) print($str);
252  elseif(method_exists($this,$method = 'fragment' . ucfirst($fragment))) print(call_user_func([$this,$method]));
253  else $this->_fred->externalError('Unknown fragment',['class' => get_called_class(),'fragment' => $fragment]);
254  }
255  elseif($redir = $this->_fred->request->redir) \Rsi\Http::redirHeader($redir);
256  else require($this->template ?: $this->defaultTemplatePath . \Rsi\File::basename(strtolower(get_called_class())) . '.php');
257  }
258 
259  protected function getMobile(){
260  return $this->_fred->client->mobile;
261  }
262 
263 }
addScriptInit($script, $id=null)
Definition: Html.php:67
renderForm($content=null)
Render the form.
Definition: Html.php:244
const RESOURCE_DEFER
Serve the resource with the deferred attribute.
Definition: Html.php:10
$asyncPrefix
Resource ID prefix to indicate asynchronous loading.
Definition: Html.php:15
notRenderedWidgetIds($ids=null)
Definition: Html.php:192
minifyStyles($sources, $target=null, $id=null, $options=null)
Definition: Html.php:109
$actionCaptionPrefix
Prefix to prepend to the action name to get a translation ID.
Definition: Html.php:24
addScript($script, $id=null)
Definition: Html.php:63
const RESOURCE_PROXY
Serve the resource through the proxy component.
Definition: Html.php:8
$widgetClassNames
View widget class name (value) per widget class name (key).
Definition: Html.php:18
addResource(&$resource, $id, $value, $options=null)
Definition: Html.php:46
createWidget($id, $widget)
Create a view widget.
Definition: Html.php:174
addStyle($style, $id=null)
Definition: Html.php:55
minifyScripts($sources, $target=null, $id=null, $options=null)
Definition: Html.php:105
publish($property, $visibility=self::READABLE)
Publish a property (or hide it again).
Definition: Thing.php:27
integrity($resource, $hash=null)
Definition: Html.php:75
$deferPrefix
Resource ID prefix to indicate deferred loading.
Definition: Html.php:16
$builderClassNames
Default builder class name per client type (null =.
Definition: Html.php:22
const RESOURCE_NO_PUSH
Do not add a push header for the resource.
Definition: Html.php:7
const RESOURCE_ASYNC
Serve the resource with the asynchronous attribute.
Definition: Html.php:9
addStyleSrc($source, $id=null, $options=null)
Definition: Html.php:59
component($name)
Get a component (local or default).
Definition: Component.php:80
minifyFiles($type, $sources, $target=null, $id=null, $options=null)
Definition: Html.php:94
addScriptSrc($source, $id=null, $options=null)
Definition: Html.php:71
$debugMinifyProbability
Chance (1 in x) for using minified file in debug mode.
Definition: Html.php:14
$integrityAlgo
Hashing algorithm to use for subresource integrity (empty = do not use).
Definition: Html.php:17
renderWidgets($ids=null, $builder=null)
Render the widgets.
Definition: Html.php:215