<?php
/**
* @module Q
*/
/**
* This class deals with returning a response
* @class Q_Response
*/
class Q_Response
{
/**
* Sets the content of a slot
* @method setSlot
* @static
* @param {string} $slotName The name of the slot.
* @param {string} $content The content to set
* @return {string} returns the content again
*/
static function setSlot(
$slotName,
$content)
{
self::$slots[$slotName] = $content;
return $content;
}
/**
* Gets the content of a slot, or null if it wasn't filled
* @method getSlot
* @static
* @param {string} $slotName The name of the slot.
* @return {string|null}
*/
static function getSlot(
$slotName)
{
return isset(self::$slots[$slotName]) ? self::$slots[$slotName] : null;
}
/**
* Gets the current content of a slot, if any.
* If slot content is null, then raises an event
* to try to fill the slot. If it is filled,
* returns the content. Otherwise, returns null.
* @method fillSlot
* @static
* @param {string|array} $slotName The name of the slot.
* @param {boolean} [$default_slotName=null] If the slot named in $slotName returns null,
* the handler corresponding to the default slot will be called,
* passing it the requested slot's name in the 'slotName' parameter,
* and its value will be returned instead.
* Note: this does not fill the slot named $default_slotName!
* That is to say, the computed value is not saved, so that
* the slot's handler is called again if it is ever consulted again.
* @param {string} [$prefix=null] Sets a prefix for the HTML ids of all the elements in the slot.
* @return {string|null}
*/
static function fillSlot(
$slotName,
$default_slotName = null,
$prefix = null)
{
if (isset(self::$slots[$slotName])) {
return self::$slots[$slotName];
}
$prev_slotName = self::$slotName;
self::$slotName = $slotName;
if (isset($prefix)) {
Q_Html::pushIdPrefix($prefix);
}
try {
if (isset($default_slotName)) {
if (!Q::canHandle("Q/response/$slotName")) {
/**
* @event Q/response/$default_slotName
* @param {string} slotName
* @return {string}
*/
$result = Q::event(
"Q/response/$default_slotName",
compact('slotName')
);
if (isset(self::$slots[$slotName])) {
// The slot was already filled, while we were rendering it
// so discard the $result and return the slot's contents
return self::$slots[$slotName];
}
return self::$slots[$slotName] = $result;
}
}
/**
* @event Q/response/$slotName
* @return {string}
*/
$result = Q::event("Q/response/$slotName");
} catch (Exception $e) {
self::$slotName = $prev_slotName;
if (isset($prefix)) {
Q_Html::popIdPrefix();
}
throw $e;
}
self::$slotName = $prev_slotName;
if (isset($prefix)) {
Q_Html::popIdPrefix();
}
if (isset(self::$slots[$slotName])) {
// The slot was already filled, while we were rendering it
// so discard the $result and return the slot's contents
return self::$slots[$slotName];
}
if (isset($result)) {
self::setSlot($slotName, $result);
return $result;
}
// Otherwise, render default slot
if (!isset($default_slotName)) {
return null;
}
/**
* @event Q/response/$default_slotName
* @param {string} slotName
* @return {string}
*/
return Q::event(
"Q/response/$default_slotName",
compact('slotName')
);
}
/**
* Clears a slot, so the next call to Q_Response::fillSlot would fill it again
* @method clearSlot
* @static
* @param {string} $slotName The name of the slot.
*/
static function clearSlot(
$slotName)
{
self::$slots[$slotName] = null;
}
/**
* Gets all the slots that have been set
* @method getAllSlots
* @static
* @return {array}
*/
static function getAllSlots()
{
return self::$slots;
}
/**
* Gets all the requested slots using Q_Request::slotNames()
* @method getRequestedSlots
* @static
* @return {array}
*/
static function getRequestedSlots()
{
$return = array();
$slotNames = Q_Request::slotNames(true);
foreach ($slotNames as $sn) {
$sn_parts = explode('.', $sn);
$slotName = end($sn_parts);
$return[$slotName] = self::fillSlot($slotName);
}
return $return;
}
/**
* Adds an error to the response
* @method addError
* @static
* @param {Q_Exception|array} $exception Either a Q_Exception or an array of them
*/
static function addError(
$exception)
{
if (is_array($exception)) {
self::$errors = array_merge(self::$errors, $exception);
} else {
self::$errors[] = $exception;
}
}
/**
* Returns all the errors added so far to the response.
* @method getErrors
* @static
* @return {array}
*/
static function getErrors()
{
return self::$errors;
}
/**
* Adds a notice
* @method setNotice
* @static
* @param {string} $key
* @param {string} $notice
* @param {array} [$options=array] Different options
* @param {boolean} [$options.persistent=false] If false, save notice in the session to show again if page reloaded.
* @param {boolean} [$options.closeable=true] If true, add cross icon to close notice.
* @param {boolean} [$options.handler=null] URL to redirect when notice clicked.
* @param {boolean|number} [$options.timeout=false] Whether to save this notice to session to show after page refresh.
*/
static function setNotice($key, $notice, $options = array())
{
// For backwards compatibility
if (gettype($options) == 'boolean') {
$options = array('persistent' => $options);
}
// set default options
$options['persistent'] = Q::ifset($options, 'persistent', false);
$options['closeable'] = Q::ifset($options, 'closeable', true);
$options['handler'] = Q::ifset($options, 'handler', null);
$options['timeout'] = Q::ifset($options, 'timeout', null);
// save options in notice
$notice = compact('notice', 'options');
self::$notices[$key] = $notice;
if ($options['persistent'] and Q_Session::id()) {
$_SESSION['Q']['notices'][$key] = Q::t($notice);
}
unset(self::$removedNotices[$key]);
}
/**
* Removes a notice
* @method removeNotice
* @static
* @param {string} $key
* @return {boolean} true if notice has been deleted, false otherwise
*/
static function removeNotice($key)
{
if (!isset(self::$notices[$key])) {
return false;
}
unset(self::$notices[$key]);
if (Q_Session::id()) {
$_SESSION['Q']['notices'][$key] = null;
}
self::$removedNotices[$key] = true;
return true;
}
/**
* Get or set the layout view that should be used in the response
* @method layoutView
* @static
* @param {string} $newView optionally set the new view, or unset it by passing false
* @return {string}
*/
static function layoutView($newView = null)
{
if (isset($newView)) {
return Q_Response::$layoutView = $newView;
}
if (Q_Response::$layoutView) {
return Q_Response::$layoutView;
}
$app = Q::app();
$layout_view = Q_Config::get('Q', 'response', 'layout', 'html', "$app/layout/html.php");
$desktop_layout_view = Q_Config::get('Q', 'response', 'layout', 'desktop', false);
if ($desktop_layout_view) {
$layout_view = $desktop_layout_view;
}
if (Q_Request::isTablet()) {
$tablet_layout_view = Q_Config::get('Q', 'response', 'layout', 'tablet', false);
if ($tablet_layout_view) {
$layout_view = $tablet_layout_view;
}
} else if (Q_Request::isMobile()) {
$mobile_layout_view = Q_Config::get('Q', 'response', 'layout', 'mobile', false);
if ($mobile_layout_view) {
$layout_view = $mobile_layout_view;
}
}
return $layout_view;
}
/**
* Returns all the notices added so far to the response.
* @method getNotices
* @static
* @return {array}
*/
static function getNotices()
{
return self::$notices;
}
/**
* Returns all the notices removed so far during this request
* @method getRemovedNotices
* @static
* @return {array}
*/
static function getRemovedNotices()
{
return self::$removedNotices;
}
/**
* Sets one of the attributes of a style to a value.
* @method setStyle
* @static
* @param {string|array} $keys Can be a key or array of keys in the style of Q_Tree->set
* @param {mixed} [$value=null]
* @param {string} [$slotName=null]
*/
static function setStyle($keys, $value = null, $slotName = null)
{
if (Q::isAssociative($keys)) {
foreach ($keys as $k => $v) {
self::setStyle($k, $v, $value);
}
return;
}
$args = is_array($keys) ? $keys : array($keys);
$args[] = $value;
$p = new Q_Tree(self::$styles);
call_user_func_array(array($p, 'set'), $args);
// Now, for the slot
if (!isset($slotName)) {
$slotName = isset(self::$slotName) ? self::$slotName : '';
}
if (!isset(self::$stylesForSlot[$slotName])) {
self::$stylesForSlot[$slotName] = array();
}
$p = new Q_Tree(self::$stylesForSlot[$slotName]);
call_user_func_array(array($p, 'set'), $args);
}
/**
* Return the array of styles added so far
* @method stylesArray
* @static
* @param {string} [$slotName=null] If provided, returns only the styles set while filling this slot.
* If you leave this empty, you get styles information for the "right" slots.
* If you pass false here, you will just get the entire $styles array without any processing.
* @param {string} [$urls=true] If true, transforms all the 'src' values into URLs before returning.
* @return {array} if $slotName is an array or empty, returns array of $slotName => $styles, otherwise returns $styles
*/
static function stylesArray ($slotName = null, $urls = true)
{
if ($slotName === false) {
return is_array(self::$styles) ? self::$styles : array();
}
if (!isset($slotName) or $slotName === true) {
$slotName = self::allSlotNames();
}
if (is_array($slotName)) {
$styles = array();
foreach ($slotName as $sn) {
$styles[$sn] = self::stylesArray($sn, $urls);
}
return $styles;
}
if (!isset(self::$stylesForSlot[$slotName])) {
return array();
}
$styles = self::$stylesForSlot[$slotName];
return is_array($styles) ? $styles : array();
}
/**
* Returns text describing all the styles inline which have been added with setStyle()
* @method styles
* @static
* @param {string} [$slotName=null] If provided, returns only the styles set while filling this slot.
* @param {string} [$between=''] Optional text to insert between the <style> tags or blocks of text.
* @param {boolean} [$tags=true] Whether to return the text already wrapped in <style> tags. Defaults to true.
* @return {string}
*/
static function styles($slotName = null, $between = '', $tags = true)
{
$styles = self::stylesArray($slotName);
if (!is_array($styles)) {
return '';
}
$texts = array();
if (is_string($slotName)) {
$styles = array($slotName => $styles);
}
foreach ($styles as $sn => $s) {
$result = '';
if (is_string($s)) {
$result = $s;
} else {
foreach ($s as $selector => $style) {
$result .= "\n\t\t$selector {\n\t\t\t";
if (is_string($style)) {
$result .= $style;
} else if (is_array($style)) {
foreach ($style as $property => $value) {
$result .= "$property: $value; ";
}
}
$result .= "\n\t\t}\n\t";
}
}
if ($result) {
$texts[$sn] = $result;
}
}
if (!$tags) {
return implode("\n", $texts);
}
$tags = array();
foreach ($texts as $sn => $text) {
$tags[] = Q_Html::tag('style', array('type' => 'text/css', 'data-slot' => $sn), $texts[$sn]);
}
return implode($between, $tags);
}
/**
* Returns text describing all the styles inline which have been added with setStyle(),
* to be included between <style></style> tags.
* @method stylesInline
* @static
* @param {string} [$slotName=null] If provided, returns only the styles set while filling this slot.
* @param {string} [$between=''] Optional text to insert between the blocks of style text
* @return {string}
*/
static function stylesInline($slotName = null, $between = "\n")
{
return self::styles($slotName, $between, false);
}
/**
* Sets a particular meta tag
* @method setMeta
* @static
* @param {string} $name The name of the meta tag
* @param {mixed} $content The content of the meta tag
* @param {string} [$slotName=null]
*/
static function setMeta($name, $content, $slotName = null)
{
self::$metas[$name] = $content;
// Now, for the slot
if (!$slotName) {
$slotName = isset(self::$slotName) ? self::$slotName : '';
}
self::$metasForSlot[$slotName][$name] = $content;
}
/**
* Return the array of metas added so far
* @method metasArray
* @static
* @param {string} [$slotName=null] If provided, returns only the metas set while filling this slot.
* If you leave this empty, you get metas information for the "right" slots.
* If you pass false here, you will just get the entire $metas array without any processing.
* @param {string} [$urls=true] If true, transforms all the 'src' values into URLs before returning.
* @return {array} if $slotName is an array or true, returns array of $slotName => $metas, otherwise returns $metas
*/
static function metasArray ($slotName = null, $urls = true)
{
if ($slotName === false) {
return is_array(self::$metas) ? self::$metas : array();
}
if (!isset($slotName) or $slotName === true) {
$slotName = self::allSlotNames();
}
if (is_array($slotName)) {
$metas = array();
foreach ($slotName as $sn) {
$metas[$sn] = self::metasArray($sn, $urls);
}
return $metas;
}
if (!isset(self::$metasForSlot[$slotName])) {
return array();
}
$metas = self::$metasForSlot[$slotName];
return is_array($metas) ? $metas : array();
}
/**
* Returns text describing all the metas inline which have been added with setMeta()
* @method metas
* @static
* @param {string} [$slotName=null] If provided, returns only the metas set while filling this slot.
* @param {string} [$between=''] Optional text to insert between the <meta> tags or blocks of text.
* @param {string} [$alsoAsProperty='og'] Also output this meta tag as a meta "property", see ogp.me
* @return {string}
*/
static function metas($slotName = null, $between = "\n", $alsoAsProperty='og')
{
$metas = self::metasArray($slotName);
if (!is_array($metas)) {
return '';
}
$tags = array();
foreach ($metas as $sn => $m) {
foreach ($m as $name => $content) {
$tags[] = Q_Html::tag('meta', array(
'name' => $name,
'content' => $content,
'data-slot' => $sn
));
}
}
if ($alsoAsProperty === 'og') {
foreach ($metas as $sn => $m) {
foreach ($m as $name => $content) {
$tags[] = Q_Html::tag('meta', array(
'property' => "og:$name",
'content' => $content,
'data-slot' => $sn
));
}
}
}
return implode($between, $tags);
}
/**
* Returns text describing all the metas inline which have been added with setMeta(),
* to be included between <meta></meta> tags.
* @method metasInline
* @static
* @param {string} [$slotName=null] If provided, returns only the metas set while filling this slot.
* @param {string} [$between=''] Optional text to insert between the blocks of meta text
* @return {string}
*/
static function metasInline($slotName = null, $between = "\n")
{
return self::metas($slotName, $between, false);
}
/**
* Adds a line of script to be echoed in a layout
* @method addScriptLine
* @static
* @param {string} $line The line of script
* @param {array} [$replace=array()] Keys in this array are globally replaced in the $line
* with the json_encoded values, before the line is added.
* @param {array} [$slotName=null] A way to override the slot name. Pass "" here to
* have the script lines be returned first by Q_Response::scriptLines.
* The other special value, "Q", is intended for internal use.
*/
static function addScriptLine($line, $replace = array(), $slotName = null)
{
if (is_string($replace)) {
$slotName = $replace;
$replace = array();
} else if (empty($replace)) {
$replace = array();
}
if (!isset($slotName)) {
$slotName = isset(self::$slotName) ? self::$slotName : '';
}
if (!isset(self::$scriptLinesForSlot[$slotName])) {
self::$scriptLinesForSlot[$slotName] = array();
}
foreach ($replace as $k => $v) {
$line = str_replace($k, Q::json_encode($v), $line);
}
self::$scriptLinesForSlot[$slotName][] = $line;
self::$scriptLines[] = $line;
}
/**
* Sets some data for the script
* @method setScriptData
* @param {string} $path The path to the variable where the data will be saved, such as "Q.info" or "MyApp.foo.bar".
* @param {array} $data The data to set. It will be JSON-encoded and stored in the specified variable.
* @param {array} [$slotName=null] A way to override the slot name. Pass "" here to
* have the script lines be returned first by Q_Response::scriptLines.
* The other special value, "Q", is intended for internal use.
*/
static function setScriptData($path, $data, $slotName = null)
{
if (!isset($slotName)) {
$slotName = isset(self::$slotName) ? self::$slotName : '';
}
self::$scriptDataForSlot[$slotName][$path] = $data;
}
/**
* Return the array of script lines added so far
* @method scriptLinesArray
* @static
* @param {string} [$slotName=null] Pass a slot name here to return only the script lines added while filling this slot.
* You can also pass an array of slot names here.
* Pass false here to return the script lines in the order they were added.
* @param {boolean} [$without_script_data=false] By default, a few script lines are prepended
* to insert the script data, for backward compatibility with apps that were built before
* scriptData was introduced. Pass true here if you don't want to include them in the result.
* @return {array} if $slotName is an array or true, returns array of $slotName => $lines, otherwise returns $lines
*/
static function scriptLinesArray ($slotName = null, $without_script_data = false)
{
if ($slotName === false) {
return is_array(self::$scriptLines) ? self::$scriptLines : array();
}
if (!isset($slotName) or $slotName === true) {
$slotName = self::allSlotNames();
}
if (is_array($slotName)) {
$scriptLines = array();
foreach ($slotName as $sn) {
$scriptLines[$sn] = self::scriptLinesArray($sn, $without_script_data);
}
return $scriptLines;
}
$scriptLines = isset(self::$scriptLinesForSlot[$slotName])
? self::$scriptLinesForSlot[$slotName]
: array();
$scriptDataLines = array();
if (!$without_script_data) {
$tested = array();
if ($data = self::scriptData($slotName)) {
$extend = array();
foreach ($data as $k => $v) {
$parts = explode(".", $k);
$e = &$extend;
foreach ($parts as $p) {
$e = &$e[$p]; // inserts null under key if nothing was there
}
$e = $v;
}
$options = Q_Config::get('Q', 'javascript', 'prettyPrintData', true)
? JSON_PRETTY_PRINT
: 0;
if (!empty($extend['Q'])) {
$json = Q::json_encode($extend['Q'], $options);
$scriptDataLines[] = "Q.extend(Q, 100, $json);";
unset($extend['Q']);
}
if (!empty($extend)) {
$json = Q::json_encode($extend, $options);
$scriptDataLines[] = "Q.extend(window, 100, $json);";
}
}
}
$scriptLines = array_merge($scriptDataLines, $scriptLines);
return is_array($scriptLines) ? $scriptLines : array();
}
/**
* Returns text describing all the scripts lines that have been added,
* to be included between <script></script> tags.
* @method scriptLines
* @static
* @param {string} [$slotName=null] By default, returns all the lines in their intended order.
* Pass a slot name here to return only the script lines added while filling this slot.
* Pass false here to return the script lines in the order they were added.
* @param {boolean} [$without_script_data=false] By default, a few script lines are prepended
* to insert the script data, for backward compatibility with apps that were built before
* scriptData was introduced. Pass true here if you don't want to include them in the result.
* @param {string} [$between=''] Optional text to insert between the <style> tags or blocks of text.
* @param {boolean} [$tags=true] Whether to return the text already wrapped in <style> tags. Defaults to true.
* @return {string}
*/
static function scriptLines($slotName = null, $without_script_data = false, $between = "\n", $tags = true)
{
if ($slotName === true) {
$slotName = null; // for backward compatibility
}
$lines = self::scriptLinesArray($slotName, $without_script_data);
if (!$lines or !is_array($lines)) {
return '';
}
if (!$tags) {
$parts = array();
foreach ($lines as $sn => $ls) {
$parts[] = is_array($ls) ? implode($between, $ls) : $ls;
}
return "\n".implode($between, $parts)."\n";
}
$tags = array();
foreach ($lines as $sn => $ls) {
if (!$ls) continue;
$tags[] = Q_Html::script(
is_array($ls) ? implode($between, $ls) : $ls,
array('data-slot' => $sn)
);
}
return implode($between, $tags);
}
/**
* Returns json describing all the script data that has been added to various slots
* @method scriptData
* @static
* @param {string} [$slotName=null] By default, returns all the lines in their intended order.
* Pass a slot name here to return only the script lines added while filling this slot.
* Pass false here to return the script lines in the order they were added.
* @return {string}
*/
static function scriptData($slotName = null)
{
if (is_string($slotName)) {
return isset(self::$scriptDataForSlot[$slotName])
? self::$scriptDataForSlot[$slotName]
: null;
}
return self::$scriptDataForSlot;
}
/**
* Set the options for a tool to be rendered, on top of those possibly already added.
* @method setToolOptions
* @static
* @param {string|array} $name The name of the option. Can also pass an array
* of ($name => $value) pairs here here -- in this case leave $value blank.
* @param {mixed} [$value=null] The value of the option
*/
static function setToolOptions($name, $value = null)
{
$tool_name = Q::$toolName;
$idPrefix = Q_Html::getIdPrefix($tool_name);
$to_set = is_array($name) ? $name : array($name => $value);
foreach ($to_set as $k => $v) {
if (substr($k, 0, 2) == 'Q_') {
continue;
}
self::$toolOptions[$idPrefix][$tool_name][$k] = $v;
}
}
/**
* Get all the options set for a tool so far
* @method getToolOptions
* @static
* @param {string} toolName Optional name of the tool that is being rendered
* @return {array} The options for the tool currently being rendered
*/
static function getToolOptions($tool_name = null)
{
if (!isset($tool_name)) {
$tool_name = Q::$toolName;
}
$idPrefix = Q_Html::getIdPrefix($tool_name);
return isset(self::$toolOptions[$idPrefix][$tool_name])
? self::$toolOptions[$idPrefix][$tool_name]
: null;
}
/**
* @method toolRetain
* @static
* @param {boolean} [$toRetain=true] Defaults to true. Whether to cache the tool being rendered.
*/
static function toolRetain($toRetain = true)
{
$idPrefix = Q_Html::getIdPrefix();
self::$toolRetain[$idPrefix] = $toRetain;
}
/**
* @method toolReplace
* @static
* @param {boolean} [$toReplaceWith=true] Defaults to true. Whether to cache the tool being rendered.
*/
static function toolReplace($toRetain = true)
{
$idPrefix = Q_Html::getIdPrefix();
self::$toolReplace[$idPrefix] = $toRetain;
}
/**
* @method shouldRetainTool
* @static
* @param {string} The id prefix of the tool
* @return {boolean} Whether to cache the tool with this idPrefix
*/
static function shouldRetainTool($idPrefix)
{
return !empty(self::$toolRetain[$idPrefix]);
}
/**
* @method shouldReplaceWithTool
* @static
* @param {string} The id prefix of the tool
* @return {boolean} Whether to cache the tool with this idPrefix
*/
static function shouldReplaceWithTool($idPrefix)
{
return !empty(self::$toolReplace[$idPrefix]);
}
/**
* @method retainSlot
* @static
* @param {boolean} [$toRetain=true] Defaults to true. Whether to cache the slot being rendered.
*/
static function retainSlot($toRetain = true)
{
if (!isset(self::$slotName)) {
throw new Q_Exception("Q_Response::retainSlot can only be called while rendering a slot");
}
self::setScriptData(
'Q.loadUrl.options.retainSlots.'.self::$slotName,
$toRetain,
''
);
$slotName = self::$slotName;
self::addScriptLine(
"Q.loadUrl.retainedSlots.$slotName = document.getElementById('{$slotName}_slot');"
);
self::$retainSlot[self::$slotName] = $toRetain;
}
/**
* The slot currently being rendered
* @return {string}
*/
static function slotName()
{
return self::$slotName;
}
/**
* The names of all the slot names for scripts, stylesheets, etc. to load in order.
* You can use any of these names in your addScript(), addStylesheet(),
* setStyle(), setScriptData(), addTemplate(), etc.
* They are "@start", "", "Q", then the plugin names, the app name, then the slot names and finally '@end'
* @return {array}
*/
static function allSlotNames()
{
$modules = array_keys(Q_Bootstrap::plugins());
$modules[] = Q::app();
return array_merge(
array('@start'),
$modules,
Q_Request::slotNames(true),
array('', '@end')
);
}
/**
* Adds a script reference to the response
* @method addScript
* @static
* @param {string} $src
* @param {string} [$slotName=null]
* @param {string} [$type='text/javascript']
* @return {boolean} returns false if script was already added, else returns true
*/
static function addScript ($src, $slotName = null, $type = 'text/javascript')
{
/**
* @event Q/response/addScript {before}
* @param {string} src
* @param {string} type
* @return {array}
*/
$modify = Q::event('Q/response/addScript', compact('src', 'type'), 'before');
if ($modify) {
extract($modify);
}
foreach (self::$scripts as $script) {
if ($script['src'] == $src && $script['type'] == $type) {
return false; // already added
}
}
self::$scripts[] = compact('src', 'type');
// Now, for the slot
if (!isset($slotName)) {
// By default, scripts won't be added "to a slot"
// because it is more likely they should be executed only one time in the document.
$slotName = ''; // isset(self::$slotName) ? self::$slotName : '';
}
if (!isset(self::$scriptsForSlot[$slotName])) {
self::$scriptsForSlot[$slotName] = array();
}
foreach (self::$scriptsForSlot[$slotName] as $script) {
if ($script['src'] == $src && $script['type'] == $type)
return false; // already added
}
self::$scriptsForSlot[$slotName][] = compact('src', 'type');
return true;
}
/**
* Adds inline template to the response
* @method addTemplate
* @static
* @param {string} $name The location of the template file relative to the "views" folder
* @param {array} [$slotName=null] A way to override the slot name. Pass "" here to
* have the script lines be returned first by Q_Response::scriptLines.
* The other special value, "Q", is intended for internal use.
* @param {string} [$type="handlebars"] The type of the template, such as "php" or "handlebars".
* @param {array} [$params=array()] Optional array of parameters to pass to PHP
* @return {boolean} returns false if template was already added, else returns true
*/
static function addTemplate (
$name,
$slotName = null,
$type = 'handlebars',
$params = array())
{
self::$templates[] = compact('name', 'type');
// Now, for the slot
if (!isset($slotName)) {
$slotName = isset(self::$slotName) ? self::$slotName : '';
}
if (!isset(self::$inlineTemplates[$slotName])) {
self::$inlineTemplates[$slotName] = array();
}
foreach (self::$inlineTemplates[$slotName] as $template) {
if ($template['name'] == $name && $template['type'] == $type) {
return false; // already added
}
}
$filename = "views/$name.$type";
$realpath = Q::realPath($filename);
if (!$realpath) {
throw new Q_Exception_MissingFile(compact('filename'));
}
if ($type === 'php') {
$ob = new Q_OutputBuffer();
Q::includeFile($realpath, $params, false);
$content = $ob->getClean();
} else {
$config = Q_Config::get('Q', 'templates', array());
$content = Q::readFile($realpath, Q::take($config, array(
'ignoreCache' => true,
'dontCache' => true,
'duration' => 3600
)));
}
if (!$content) {
throw new Q_Exception("Failed to load template '$name'");
}
if ($fields = Q_Config::get('Q', 'views', 'fields', null)) {
$params = array_merge($fields, $params);
}
$parts = explode('/', "$name.$type");
$text = array_merge($params, Q_Text::sources($parts));
self::$inlineTemplates[$slotName][] = compact(
'name', 'content', 'type', 'text'
);
return true;
}
/**
* Return the array of templates added so far
* @method templatesArray
* @static
* @param {string} [$slotName=null] If provided, returns only the templates added while filling this slot.
* If you leave this empty, you get templates information for the "right" slots.
* If you pass false here, you will just get the entire $inlineTemplates array
* @return {array} the array of templates added so far
*/
static function templatesArray ($slotName = null)
{
if ($slotName === false) {
$slotName = array_keys(self::$inlineTemplates);
}
if (!isset($slotName) or $slotName === true) {
$slotName = array_merge(array(''), Q_Request::slotNames(true));
}
if (is_array($slotName)) {
$templates = array();
foreach ($slotName as $sn) {
foreach (self::templatesArray($sn) as $b) {
foreach ($templates as $a) {
if ($a['name'] === $b['name']
&& $a['type'] === $b['type']) {
break 2;
}
}
$b['slot'] = $sn;
$templates[] = $b; // a unique new template to add to the list
}
}
} else {
if (!isset(self::$inlineTemplates[$slotName])) {
return array();
}
$templates = self::$inlineTemplates[$slotName];
}
if (!is_array($templates))
return array();
return $templates;
}
/**
* Returns json describing all the templates that has been added to various slots
* @method scriptData
* @static
* @param {string} [$slotName=null] By default, returns all the lines in their intended order.
* Pass a slot name here to return only the templates added while filling this slot.
* Pass false here to return the templates in the order they were added.
* @return {string}
*/
static function templateData($slotName = null) {
return self::templatesArray($slotName);
}
/**
* Returns the HTML markup for referencing all the templates added so far
* @method templates
* @static
* @param {string} [$slotName=null] If provided, returns only the templates added while filling this slot.
* @param {string} [$between=''] Optional text to insert between the <link> tags.
* @return {string} the HTML markup for referencing all the templates added so far
*/
static function templates ($slotName = null, $between = "\n")
{
$templates = self::templatesArray($slotName);
if (empty($templates)) return '';
$tags = array();
foreach ($templates as $template) {
$attributes = array(
'cdata' => false,
'raw' => true,
'type' => 'text/'.$template['type'],
'id' => Q_Utils::normalize($template['name']),
'data-slot' => $template['slot']
);
foreach (array('text', 'partials', 'helpers') as $aspect) {
if (!empty($template[$aspect])) {
$attributes["data-$aspect"] = Q::json_encode($template[$aspect]);
}
}
$tags[] = Q_Html::script($template['content'], $attributes);
}
return implode($between, $tags);
}
/**
* Return the array of scripts added so far
* @method scriptsArray
* @static
* @param {string} [$slotName=null] If provided, returns only the scripts added while filling this slot.
* You can also pass an array of slot names here.
* If you pass the boolean constant true here, slotName is set to Q_Request::slotNames(true)
* @param {string} [$urls=true] If true, transforms all the 'src' values into URLs before returning.
* @return {array} the array of scripts added so far
*/
static function scriptsArray ($slotName = null, $urls = true)
{
if (isset($slotName)) {
if ($slotName === true) {
$slotName = self::allSlotNames();
}
if (is_array($slotName)) {
$scripts = array();
$srcs = array();
foreach ($slotName as $sn) {
foreach (self::scriptsArray($sn, $urls) as $b) {
$key = $b['src'];
if (!empty($srcs[$key])) {
continue;
}
$b['slot'] = $sn;
$scripts[] = $b; // a unique new script to add to the list
$srcs[$key] = true;
}
}
return $scripts;
} else {
if (!isset(self::$scriptsForSlot[$slotName])) {
return array();
}
$scripts = self::$scriptsForSlot[$slotName];
}
} else {
$scripts = self::$scripts;
}
if (!is_array($scripts))
return array();
$srcs = array();
$result = array();
foreach ($scripts as $k => $b) {
if ($urls) {
list($src, $filename, $hash) = Q_Html::themedUrlFilenameAndHash($b['src']);
$b['src'] = $src;
$b['hash'] = $hash;
}
if (!empty($srcs[ $b['src'] ])) {
continue;
}
$result[] = $b; // a unique new script to add to the list
$srcs[ $b['src'] ] = true;
}
return $result;
}
/**
* Returns markup for referencing all the scripts added so far
* @method scriptsInline
* @static
* @param {string} [$slotName=null] If provided, returns only the scripts added while filling this slot.
* @return {string} the script tags and their contents inline
*/
static function scriptsInline ($slotName = null)
{
$scripts = self::scriptsArray($slotName, false);
if (empty($scripts))
return '';
$scripts_for_slots = array();
foreach ($scripts as $script) {
$src = '';
extract($script, EXTR_IF_EXISTS);
$ob = new Q_OutputBuffer();
if (Q_Valid::url($src)) {
try {
include($src);
} catch (Exception $e) {}
} else {
list ($src, $filename) = Q_Html::themedUrlFilenameAndHash($src);
try {
Q::includeFile($filename);
} catch (Exception $e) {}
}
$scripts_for_slots[$script['slot']][] = "\n/* Included inline from $src */\n"
. $ob->getClean();
}
$parts = array();
foreach ($scripts_for_slots as $slot => $texts) {
$parts[] = Q_Html::script(implode("\n\n", $texts), array('data-slot' => $slot));
}
return implode("", $parts);
}
/**
* Returns the HTML markup for referencing all the scripts added so far
* @method scripts
* @static
* @param {string} [$between=''] Optional text to insert between the <link> tags.
* @param {string} [$slotName=null] If provided, returns only the scripts added while filling this slot.
* If you pass the boolean constant true here, slotName is set to Q_Request::slotNames(true)
* @return {string} the HTML markup for referencing all the scripts added so far
*/
static function scripts ($slotName = null, $between = "\n")
{
$scripts = self::scriptsArray($slotName);
if (empty($scripts))
return '';
$tags = array();
foreach ($scripts as $script) {
$src = '';
// $media = 'screen,print';
$type = 'text/css';
$hash = null;
extract($script, EXTR_IF_EXISTS);
$tags[] = Q_Html::tag(
'script',
array('type' => $type, 'src' => $src, 'data-slot' => $script['slot']),
null,
compact('hash')
) . '</script>';
}
return implode($between, $tags);
}
/**
* Adds a stylesheet
* @method addStylesheet
* @static
* @param {string} $href
* @param {string} [$slotName=null]
* @param {string} [$media='screen,print']
* @param {string} [$type='text/css']
* @return {boolean} returns false if a stylesheet with exactly the same parameters has already been added, else true.
*/
static function addStylesheet ($href, $slotName = null, $media = 'screen,print', $type = 'text/css')
{
/**
* @event Q/response/addStylesheet {before}
* @param {string} href
* @param {string} media
* @param {string} type
* @return {array}
*/
$modify = Q::event('Q/response/addStylesheet', compact('href', 'media', 'type'), 'before');
if ($modify) {
extract($modify);
}
foreach (self::$stylesheets as $stylesheet) {
if ($stylesheet['href'] == $href
&& $stylesheet['media'] == $media
&& $stylesheet['type'] == $type) {
// already added
return false;
}
}
self::$stylesheets[] = compact('href', 'media', 'type');
// Now, for the slot
if (!isset($slotName)) {
$slotName = isset(self::$slotName) ? self::$slotName : '';
}
if (!isset(self::$stylesheetsForSlot[$slotName])) {
self::$stylesheetsForSlot[$slotName] = array();
}
foreach (self::$stylesheetsForSlot[$slotName] as $stylesheet) {
if ($stylesheet['href'] == $href
&& $stylesheet['media'] == $media
&& $stylesheet['type'] == $type) {
// already added
return false;
}
}
self::$stylesheetsForSlot[$slotName][] = compact('href', 'media', 'type');
return true;
}
/**
* Returns array of stylesheets that have been added so far
* @method stylesheetsArray
* @static
* @param {string} [$slotName=null] If provided, returns only the stylesheets added while filling this slot.
* If you leave this empty, you get stylesheets information for the "right" slots.
* If you pass false here, you will just get the entire $stylesheets array without any processing.
* @param {string} [$urls=true] If true, transforms all the 'src' values into URLs before returning.
* @return {array} the array of stylesheets that have been added so far
*/
static function stylesheetsArray ($slotName = null, $urls = true)
{
if ($slotName === false) {
return $sheets = self::$stylesheets;
}
if (!isset($slotName) or $slotName === true) {
$slotName = self::allSlotNames();
}
if (is_array($slotName)) {
$sheets = array();
$saw = array();
foreach ($slotName as $sn) {
foreach (self::stylesheetsArray($sn, $urls) as $b) {
$key = $b['href'].' '.$b['media'];
if (!empty($saw[$key])) {
continue;
}
$b['slot'] = $sn;
$sheets[] = $b; // a unique new script to add to the list
$saw[$key] = true;
}
}
return $sheets;
} else {
if (!isset(self::$stylesheetsForSlot[$slotName])) {
return array();
}
$sheets = self::$stylesheetsForSlot[$slotName];
}
if (!is_array($sheets)) {
return array();
}
$result = array();
$saw = array();
foreach ($sheets as $b) {
if ($urls) {
list($href, $filename, $hash) = Q_Html::themedUrlFilenameAndHash($b['href']);
$b['href'] = $href;
$b['hash'] = $hash;
}
$key = $b['href'].' '.$b['media'];
if (!empty($saw[$key])) {
continue;
}
$result[] = $b; // a unique new script to add to the list
$saw[$key] = true;
}
return $result;
}
/**
* Returns a <style> tag with the content of all the stylesheets included inline
* @method stylesheetsInline
* @static
* @param {array} [$styles=array()] If not empty, this associative array contains styles which will be
* included at the end of the generated <style> tag.
* @param {string} [$slotName=null] If provided, returns only the stylesheets added while filling this slot.
* @return {string} the style tags and their contents inline
*/
static function stylesheetsInline ($styles = array(), $slotName = null)
{
if (empty(self::$stylesheets)) {
return '';
}
$sheets = self::stylesheetsArray($slotName, false);
$sheets_for_slots = array();
if (!empty($sheets)) {
foreach ($sheets as $stylesheet) {
$href = '';
$media = 'screen,print';
$type = 'text/css';
extract($stylesheet, EXTR_IF_EXISTS);
$ob = new Q_OutputBuffer();
if (Q_Valid::url($href)) {
try {
include($href);
} catch (Exception $e) {}
} else {
list ($href, $filename) = Q_Html::themedUrlFilenameAndHash($href);
try {
Q::includeFile($filename);
} catch (Exception $e) {}
}
$sheets_for_slots[$stylesheet['slot']][] = "\n/* Included inline from $href */\n"
. $ob->getClean();
}
}
$parts = array();
foreach ($sheets_for_slots as $slot => $texts) {
$parts[] = Q_Html::tag('style', array('data-slot' => $slot), implode("\n\n", $texts));
}
return implode("", $parts);
}
/**
* Returns the HTML markup for referencing all the stylesheets added so far
* @method stylesheets
* @static.
* @param {string} [$slotName=null] If provided, returns only the stylesheets added while filling this slot.
* @param {string} [$between=''] Optional text to insert between the <link> tags
* @return {string} the HTML markup for referencing all the stylesheets added so far
*/
static function stylesheets ($slotName = null, $between = "\n")
{
$stylesheets = self::stylesheetsArray($slotName);
if (empty($stylesheets))
return '';
$tags = array();
foreach ($stylesheets as $stylesheet) {
$rel = 'stylesheet';
$href = '';
$media = 'screen,print';
$type = 'text/css';
$hash = null;
extract($stylesheet, EXTR_IF_EXISTS);
$attributes = compact('rel', 'type', 'href', 'media');
$attributes['data-slot'] = $stylesheet['slot'];
$tags[] = Q_Html::tag('link', $attributes, null, compact('hash'));
}
return implode($between, $tags);
}
/**
* Call this method to add a css class to the HTML element in the layout
* @method addHtmlCssClass
* @static
* @param {string} $className
*/
static function addHtmlCssClass($className)
{
self::$htmlCssClasses[$className] = true;
}
/**
* Used to get or set the language (two-letter lowercase ISO code)
* of the output page. Defaults to "en". It is used in htmlAttributes method.
* @method language
* @static
* @param {string} [$newLanguage] Set a new code here to change the language
* @return {string}
*/
static function language($newLanguage = null)
{
if (isset($newLanguage)) {
self::$language = $newLanguage;
}
return self::$language;
}
/**
* Returns the string containing all the html attributes
* @method htmlAttributes
* @static
* @return {string}
*/
static function htmlAttributes()
{
$touchscreen = Q_Request::isTouchscreen() ? 'Q_touchscreen' : 'Q_notTouchscreen';
$mobile = Q_Request::isMobile() ? 'Q_mobile' : 'Q_notMobile';
$cordova = Q_Request::isCordova() ? 'Q_cordova': 'Q_notCordova';
$platform = 'Q_'.Q_Request::platform();
$ie = Q_Request::isIE() ? 'Q_ie' : 'Q_notIE';
$ie8 = Q_Request::isIE(0, 8) ? 'Q_ie8OrBelow' : 'Q_notIE8OrBelow';
$uri = Q_Dispatcher::uri();
$classes = "{$uri->module} {$uri->module}_{$uri->action}";
foreach (self::$htmlCssClasses as $k => $v) {
$classes .= Q_Html::text(" $k");
}
$language = self::language();
return 'lang="' . $language . '" '
. 'prefix="og: http://ogp.me/ns# object: http://ogp.me/ns/object#" '
. "class='$touchscreen $mobile $cordova $platform $ie $ie8 $classes'";
}
/**
* Gets/sets the favicon url
* @method faviconUrl
* @static
* @param {string} [$new_url=null]
* @return {string}
*/
static function faviconUrl($new_url=null)
{
if (!isset(self::$faviconUrl)) {
self::$faviconUrl = Q_Request::baseUrl().'/favicon.ico';
}
$old_url = self::$faviconUrl;
if (isset($new_url)) {
self::$faviconUrl = $new_url;
}
return $old_url;
}
/**
* Gets or sets whether the response is buffered.
* @method isBuffered
* @static
* @param {boolean|string} [$new_value=null] If not present,
* just returns the current value of this setting.
* If true or false, sets the setting to this value,
* and returns the setting's old value.
* @return {boolean|string}
*/
static function isBuffered($new_value = null)
{
$old_value = self::$isBuffered;
if (isset($new_value)) {
self::$isBuffered = $new_value;
}
return $old_value;
}
/**
* Returns array of all the slots that have been filled
* @method slots
* @static
* @param {boolean} [$requestedOnesOnly=true] Set to true in order to return only the slots that were requested
* @return {array}
*/
static function slots($requestedOnesOnly = true)
{
if (!$requestedOnesOnly) {
return self::$slots;
}
$result = array();
$slotNames = Q_Request::slotNames(true);
foreach ($slotNames as $sn) {
$result[$sn] = isset(self::$slots[$sn]) ? self::$slots[$sn] : null;
}
return $result;
}
/**
* Sets a header to redirect to a given URI or URL.
* @method redirect
* @static
* @param {string} $uri The URL or internal URI to redirect to
* @param {array} $options An array of options that can include:
* @param {boolean} [$options.loop=false] If false, and current URL is the same as the new one, skips setting the redirect header and just returns false.
* @param {boolean} [$options.permanently=false] If true, sets response code as 304 instead of 302
* @param {boolean} [$options.noProxy=false] If true, doesn't use the proxy mapping to determine URL
* @param {boolean} [$noProxy=false]
* @return {boolean}
* Return whether the redirect header was set.
*/
static function redirect($uri, $options = array())
{
extract($options);
$url = Q_Uri::url($uri, null, !empty($noProxy));
if ($url === Q_Uri::unreachableUri()) {
throw new Q_Exception_BadValue(array(
'internal' => 'uri',
'problem' => 'no url routes to it'
));
}
$level = ob_get_level();
for ($i=0; $i<$level; ++$i) {
ob_clean();
}
/**
* @event Q/response {before}
* @param {string} permanently
* @param {string} uri
* @param {string} url
* @param {string} loop
* @return {boolean}
*/
$result = Q::event(
'Q/redirect',
compact('uri', 'url', 'loop', 'permanently', 'noProxy', 'level'),
'before'
);
if (isset($result)) {
return $result;
}
if (!empty($loop) and Q_Request::url() === $url) {
return false;
}
if (!Q_Request::isAjax()) {
if (!empty($permanently)) {
header("HTTP/1.1 301 Moved Permanently");
}
header("Location: $url");
}
self::$redirected = $uri;
return true;
}
/**
* Used to get/set output that the Q/response handler should consult.
* @method output
* @static
* @param {string} [$new_output=null] Pass a string here to return as output,
* instead of the usual layout.
* Or, pass true here to indicate that we have already returned the output,
* and to skip rendering a layout.
* @param {boolean} [$override=false] If an output string is already set,
* calling this method wouldn't override it unless you pass true here.
* @return {string} Returns the output that was set, if any
*/
static function output($new_output = null, $override = false)
{
static $output = null;
if (isset($new_output)) {
if (!isset($output) or $override === true) {
$output = $new_output;
}
}
return $output;
}
/**
* @method setCookie
* @static
* @param {string} $name The name of the cookie
* @param {string} $value The value of the cookie
* @param {string} [$expires=0] The number of seconds since the epoch, 0 means expire when browser session ends
* @param {string} [$path=false] You can specify a path on the server here for the cookie
* @return {string}
*/
static function setCookie($name, $value, $expires = 0, $path = false)
{
if (empty($_SERVER['HTTP_HOST'])) {
Q::log('Warning: Ignoring call to Q_Response::setCookie() without $_SERVER["HTTP_HOST"]'.PHP_EOL);
return false;
}
if (isset($_COOKIE[$name]) and $_COOKIE[$name] === $value) {
return;
}
if (Q_Dispatcher::$startedResponse) {
throw new Q_Exception("Q_Response::setCookie must be called before Q/response event");
}
// see https://bugs.php.net/bug.php?id=38104
self::$cookies[$name] = array($value, $expires, $path);
$_COOKIE[$name] = $value;
return $value;
}
/**
* @method clearCookie
* @static
* @param {string} $name
* @param {string} [$path=false]
* @return {string}
*/
static function clearCookie($name, $path = false)
{
self::setCookie($name, '', 1, $path);
unset($_COOKIE[$name]);
}
/**
* Outputs all the headers for effecting changes in cookies set by Q_Response::setCookies.
* This is called automatically by the dispatcher before the Q/response event.
* @method sendCookieHeaders
* @static
*/
static function sendCookieHeaders()
{
if (empty(self::$cookies)) {
return;
}
foreach (self::$cookies as $name => $args) {
list($value, $expires, $path) = $args;
self::_cookie($name, $value, $expires, $path);
}
// A reasonable P3P policy for old IE to allow 3rd party cookies.
// Consider overriding it with a real P3P policy for your app.
$header = 'P3P: CP="IDC DSP COR CURa ADMa OUR IND PHY ONL COM STA"';
$header = Q::event('Q/Response/setCookie',
compact('name', 'value', 'expires', 'path', 'header'),
'before', false, $header
);
header($header);
self::$cookies = array();
}
protected static function _cookie($name, $value, $expires, $path)
{
$parts = parse_url(Q_Request::baseUrl());
$path = $path ? $path : (!empty($parts['path']) ? $parts['path'] : '/');
$domain = (strpos($parts['host'], '.') !== false ? '.' : '').$parts['host'];
setcookie($name, $value, $expires, $path, $domain);
}
/**
* Call this to support CORS
* @method supportCORS
* @param {string} [$from='*']
*/
static function supportCORS($from = '*')
{
header("Access-Control-Allow-Origin: $from");
header("Access-Control-Allow-Credentials: true");
}
/**
* @method setIgnoreUserAbort
* @static
* @param {boolean} [$value=null]
*/
static function setIgnoreUserAbort($value = null)
{
if (!isset($value)) {
$value = Q_Config::get('Q', 'web', 'ignoreUserAbort', null);
}
if (isset($value)) {
ignore_user_abort($value);
}
}
/**
* @property $batch
* @type boolean
* @static
*/
public static $batch = false;
/**
* @property $redirected
* @type boolean
* @static
*/
public static $redirected = false;
/**
* @property $slots
* @static
* @type array
*/
protected static $slots = array();
/**
* @property $scripts
* @static
* @type array
*/
protected static $scripts = array();
/**
* @property $stylesheets
* @static
* @type array
*/
protected static $stylesheets = array();
/**
* @property $styles
* @static
* @type array
*/
protected static $styles = array();
/**
* @property $scriptLines
* @static
* @type array
*/
protected static $scriptLines = array();
/**
* @property $metas
* @static
* @type array
*/
protected static $metas = array();
/**
* @property $templates
* @static
* @type array
*/
protected static $templates = array();
/**
* @property $inlineTemplates
* @static
* @type array
*/
protected static $inlineTemplates = array();
/**
* @property $errors
* @static
* @type array
*/
protected static $errors = array();
/**
* @property $notices
* @static
* @type array
*/
protected static $notices = array();
/**
* @property $removedNotices
* @static
* @type array
*/
protected static $removedNotices = array();
/**
* @property $slotName
* @static
* @type array
*/
protected static $slotName = null;
/**
* @property $scriptsForSlot
* @static
* @type array
*/
protected static $scriptsForSlot = array();
/**
* @property $stylesheetsForSlot
* @static
* @type array
*/
protected static $stylesheetsForSlot = array();
/**
* @property $stylesForSlot
* @static
* @type array
*/
protected static $stylesForSlot = array();
/**
* @property $scriptLinesForSlot
* @static
* @type array
*/
protected static $scriptLinesForSlot = array();
/**
* @property $scriptDataForSlot
* @static
* @type array
*/
protected static $scriptDataForSlot = array();
/**
* @property $metasForSlot
* @static
* @type array
*/
protected static $metasForSlot = array();
/**
* @property $toolOptions
* @static
* @type array
*/
protected static $toolOptions = array();
/**
* @property $toolRetain
* @static
* @type array
*/
protected static $toolRetain = array();
/**
* @property $toolRetain
* @static
* @type array
*/
protected static $toolReplace = array();
/**
* @property $retainSlot
* @static
* @type array
*/
protected static $retainSlot = array();
/**
* @property $faviconUrl
* @static
* @type string
*/
protected static $faviconUrl = null;
/**
* @property $layout_view
* @static
* @type string
*/
protected static $layoutView = null;
/**
* @property $isBuffered
* @static
* @protected
* @type boolean
*/
protected static $isBuffered = true; // buffer responses by default
/**
* @property $cookies
* @static
* @type array
*/
public static $cookies = array();
/**
* @property $htmlCssClasses
* @static
* @type array
*/
public static $htmlCssClasses = array();
/**
* @property $language
* @static
* @type string
*/
public static $language = "en";
}