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