Typesetter/include/tool/Output.php

2353 lines
58 KiB
PHP

<?php
namespace gp\tool{
defined('is_running') or die('Not an entry point...');
global $GP_ARRANGE, $gpOutConf;
$GP_ARRANGE = true;
$gpOutConf = [];
//named menus should just be shortcuts to the numbers in custom menu
// custom menu format: $top_level,$bottom_level,$expand_level
//custom menu: 0,0,0,0
$gpOutConf['FullMenu'] = [
'class' => '\\gp\\tool\\Output\\Menu',
'method' => 'GetFullMenu',
'link' => 'all_links',
];
//custom menu: 0,0,1,1
$gpOutConf['ExpandMenu'] = [
'class' => '\\gp\\tool\\Output\\Menu',
'method' => 'GetExpandMenu',
'link' => 'expanding_links',
];
//custom menu: 0,0,2,1
$gpOutConf['ExpandLastMenu'] = [
'class' => '\\gp\\tool\\Output\\Menu',
'method' => 'GetExpandLastMenu',
'link' => 'expanding_bottom_links',
];
//custom menu: 0,1,0,0
$gpOutConf['Menu'] = [
'class' => '\\gp\\tool\\Output\\Menu',
'method' => 'GetMenu',
'link' => 'top_level_links',
];
//custom menu: 1,0,0,0
$gpOutConf['SubMenu'] = [
'class' => '\\gp\\tool\\Output\\Menu',
'method' => 'GetSubMenu',
'link' => 'subgroup_links',
];
//custom menu: 0,2,0,0
$gpOutConf['TopTwoMenu'] = [
'class' => '\\gp\\tool\\Output\\Menu',
'method' => 'GetTopTwoMenu',
'link' => 'top_two_links',
];
//custom menu: does not translate, this pays no attention to grouping
$gpOutConf['BottomTwoMenu'] = [
'class' => '\\gp\\tool\\Output\\Menu',
'method' => 'GetBottomTwoMenu',
'link' => 'bottom_two_links',
];
//custom menu: 1,2,0,0
$gpOutConf['MiddleSubMenu'] = [
'class' => '\\gp\\tool\\Output\\Menu',
'method' => 'GetSecondSubMenu',
'link' => 'second_sub_links',
];
//custom menu: 2,3,0,0
$gpOutConf['BottomSubMenu'] = [
'class' => '\\gp\\tool\\Output\\Menu',
'method' => 'GetThirdSubMenu',
'link' => 'third_sub_links',
];
//custom menu
$gpOutConf['CustomMenu'] = [
'class' => '\\gp\\tool\\Output\\Menu',
'method' => 'CustomMenu',
];
//breadcrumb nav
$gpOutConf['Breadcrumbs'] = [
'class' => '\\gp\\tool\\Output\\Menu',
'method' => 'BreadcrumbNav',
'link' => 'Breadcrumb Links',
];
//$gpOutConf['Breadcrumbs']['method'] = ['\\gp\\tool\\Output', 'BreadcrumbNav'];
//$gpOutConf['Breadcrumbs']['link'] = 'Breadcrumb Links';
$gpOutConf['Extra']['method'] = ['\\gp\\tool\\Output', 'GetExtra'];
//$gpOutConf['Text']['method'] = ['\\gp\\tool\\Output','GetText']; //use Area() and GetArea() instead
//$gpOutConf['Image']['method'] = ['\\gp\\tool\\Output','GetImage'];
/* The following methods should be used with \gp\tool\Output'::Fetch() */
$gpOutConf['Gadget']['method'] = ['\\gp\\tool\\Output', 'GetGadget'];
class Output{
public static $components = '';
public static $editlinks = '';
public static $template_included = false;
private static $out_started = false;
private static $gadget_cache = [];
public static $edit_area_id = '';
private static $catchable = [];
public static $lang_values = [];
public static $inline_vars = [];
public static $nested_edit = false;
private static $edit_index = 0;
private static $head_css = '';
private static $head_content = '';
private static $head_js = '';
/**
* Backwards compat for functions moved to \gp\tool\Output\Menu
*
*/
public static function __callStatic($name, $args){
if( method_exists('\\gp\\tool\\Output\\Menu', $name) ){
$menu = new \gp\tool\Output\Menu();
return call_user_func_array([$menu, $name], $args);
}
throw new \Exception('Call to undefined method gp\\tool\\Output::' . $name);
}
/*
*
* Request Type Functions
* functions used in conjuction with $_REQUEST['gpreq']
*
*/
public static function Prep(){
global $page;
if( !isset($page->rewrite_urls) ){
return;
}
ini_set('arg_separator.output', '&amp;');
foreach($page->rewrite_urls as $key => $value){
output_add_rewrite_var($key, $value);
}
}
/**
* Send only messages and the content buffer to the client
* @static
*/
public static function Flush(){
global $page;
self::StandardHeaders();
echo GetMessages();
echo $page->contentBuffer;
}
public static function Content(){
global $page;
self::StandardHeaders();
echo GetMessages();
$page->GetGpxContent();
}
public static function StandardHeaders(){
header('Content-Type: text/html; charset=utf-8');
Header('Vary: Accept,Accept-Encoding'); // for proxies
}
/**
* Send only the messages and content as a simple html document
* @static
*/
public static function BodyAsHTML(){
global $page;
self::$inline_vars['gp_bodyashtml'] = true;
self::TemplateSettings();
self::StandardHeaders();
echo '<!DOCTYPE html>';
echo '<html lang="' . $page->lang . '"><head><meta charset="UTF-8" />';
self::getHead();
echo '</head>';
echo '<body class="gpbody">';
echo GetMessages();
$page->GetGpxContent();
echo '</body>';
echo '</html>';
self::HeadContent();
}
public static function AdminHtml(){
global $page;
//\gp\tool\Output::$inline_vars['gp_bodyashtml'] = true;
self::StandardHeaders();
echo '<!DOCTYPE html>';
echo '<html class="admin_body" lang="' . $page->lang . '"><head><meta charset="UTF-8" />';
self::getHead();
echo '</head>';
echo '<body class="gpbody">';
echo GetMessages();
$page->GetGpxContent();
echo '</body>';
echo '</html>';
self::HeadContent();
}
/**
* Get default values from customizer if it exists
* return empty array otherwise
*
* Layout installer will pass a customizer file path
*
* @static
* @since 5.2
* @param string $used_in
* @param string $customizer_file
* @return array
*
*/
public static function GetCustomizerDefaults($used_in='', $customizer_file=''){
global $page;
if( empty($customizer_file) ){
$layout_dir = $page->theme_dir . '/' . $page->theme_color;
$customizer_file = $layout_dir . '/customizer.php';
}
if( !file_exists($customizer_file) ){
// msg('customizer file ' . htmlspecialchars($customizer_file) . ' does not exist'); // TODO remove
return [];
};
$customizer = \gp\tool\Files::Get($customizer_file, 'customizer');
// debug('$customizer = ' . pre($customizer));
$defaults = [];
foreach($customizer as $section => $section_data){
foreach($section_data['items'] as $item_name => $item_data){
if( !empty($used_in) &&
isset($item_data['control']['used_in']) &&
is_array($item_data['control']['used_in']) &&
!in_array($used_in, $item_data['control']['used_in'])
){
continue;
}
$defaults[$item_name]['value'] = $item_data['default_value'];
if( !empty($item_data['default_units']) ){
$defaults[$item_name]['units'] = $item_data['default_units'];
}
}
}
return $defaults;
}
/**
* Get javascript values from the layout
* if the layout has no stored js_vals, try to get customizer defaults
* otherwise return empty string
*
* @static
* @since 5.2
* @return string js expression like 'var layout_config = JSON;'
*
*/
public static function GetLayoutJsVars(){
global $page, $gpLayouts;
if( $page->gpLayout ){
$layout_info = $gpLayouts[$page->gpLayout];
if( isset($layout_info['js_vars']) ){
return $layout_info['js_vars'];
}
}
$js_vars = self::GetCustomizerDefaults('js');
return "\n" . 'var layout_config = ' . json_encode($js_vars) . ';' . "\n";
}
/**
* Get configuration array from the layout
* if the layout has no stored config, try to get customizer defaults
* otherwise return empty array
*
* @static
* @since 5.2
* @return array layout configuration
*
*/
public static function GetLayoutConfig(){
global $page, $gpLayouts;
if( $page->gpLayout ){
$layout_info = $gpLayouts[$page->gpLayout];
if( isset($layout_info['config']) ){
return $layout_info['config'];
}
}
return self::GetCustomizerDefaults('php');
}
/**
* Send all content according to the current layout
* @static
*
*/
public static function Template(){
global $page, $gpLayouts, $layout_config;
global $GP_ARRANGE, $GP_STYLES, $get_all_gadgets_called;
global $addon_current_id, $GP_MENU_LINKS, $GP_MENU_CLASS;
global $GP_MENU_CLASSES, $GP_MENU_ELEMENTS;
$get_all_gadgets_called = false;
self::$template_included = true;
if( isset($page->theme_addon_id) ){
$addon_current_id = $page->theme_addon_id;
}
self::TemplateSettings();
self::StandardHeaders();
if( !empty($page->preview_layout_config) ){
// only exists in Layout Editor preview mode
$layout_config = $page->preview_layout_config;
}elseif( empty($layout_config) ){
$layout_config = self::GetLayoutConfig();
}
// debug('$layout_config = ' . pre($layout_config));
$path = $page->theme_dir . '/template.php';
$return = IncludeScript(
$path,
'require',
[
'page', 'layout_config',
'GP_ARRANGE',
'GP_MENU_LINKS', 'GP_MENU_CLASS',
'GP_MENU_CLASSES', 'GP_MENU_ELEMENTS'
]
);
//return will be false if there's a fatal error with the template.php file
if( $return === false ){
self::BodyAsHtml();
}
\gp\tool\Plugins::ClearDataFolder();
self::HeadContent();
}
/**
* Get the settings for the current theme if settings.php exists
* @static
*/
public static function TemplateSettings(){
global $page;
$path = $page->theme_dir . '/settings.php';
IncludeScript($path, 'require_if', ['page', 'GP_GETALLGADGETS']);
}
/**
* Add a Header to the response
* The header will be discarded if it's an ajax request or similar
*
* @param string $header
* @param bool $replace
* @param int $code
* @return bool
*/
public static function AddHeader($header, $replace=true, $code=null){
if( !empty($_REQUEST['gpreq']) ){
return false;
}
if( !is_null($code) ){
\gp\tool::status_header($code, $header);
}else{
header($header, $replace);
}
return true;
}
/*
*
* Content Area Functions
*
*/
public static function GetContainerID($name, $arg=false){
static $indices;
$name = str_replace(
['+', '/', '='],
['', '', ''],
base64_encode($name)
);
if( !isset($indices[$name]) ){
$indices[$name] = 0;
}else{
$indices[$name]++;
}
return $name . '_' . $indices[$name];
}
/**
* Fetch the output and return as a string
*
*/
public static function Fetch($default, $arg=''){
ob_start();
self::Get($default, $arg);
return ob_get_clean();
}
public static function Get($default='', $arg=''){
global $page, $gpLayouts, $gpOutConf;
$outSet = false;
$outKeys = false;
$layout_info =& $gpLayouts[$page->gpLayout];
//container id
$container_id = $default . ':' . substr($arg, 0, 10);
$container_id = self::GetContainerID($container_id);
if( isset($layout_info) && isset($layout_info['handlers']) ){
$handlers =& $layout_info['handlers'];
if( isset($handlers[$container_id]) ){
$outKeys = $handlers[$container_id];
$outSet = true;
}
}
//default values
if( !$outSet && isset($gpOutConf[$default]) ){
$outKeys[] = trim($default . ':' . $arg, ':');
}
self::ForEachOutput($outKeys, $container_id);
}
public static function ForEachOutput($outKeys, $container_id){
if( !is_array($outKeys) || (count($outKeys) == 0) ){
$info = [];
$info['gpOutCmd'] = '';
self::CallOutput($info, $container_id);
return;
}
foreach($outKeys as $gpOutCmd){
$info = self::GetgpOutInfo($gpOutCmd);
if( $info === false ){
trigger_error('gpOutCmd <i>' . $gpOutCmd . '</i> not set');
continue;
}
$info['gpOutCmd'] = $gpOutCmd;
self::CallOutput($info, $container_id);
}
}
/* static */
public static function GetgpOutInfo($gpOutCmd){
global $gpOutConf, $config;
$key = $gpOutCmd = trim($gpOutCmd, ':');
$info = false;
$arg = '';
$pos = mb_strpos($key, ':');
if( $pos > 0 ){
$arg = mb_substr($key, $pos + 1);
$key = mb_substr($key, 0, $pos);
}
if( isset($gpOutConf[$key]) ){
$info = $gpOutConf[$key];
}elseif( isset($config['gadgets'][$key]) ){
$info = $config['gadgets'][$key];
$info['is_gadget'] = true;
}else{
return false;
}
$info['key'] = $key;
$info['arg'] = $arg;
$info['gpOutCmd'] = $gpOutCmd;
return $info;
}
public static function GpOutLabel($info){
global $langmessage;
$info += ['arg' => '']; // suppress warning with older themes TODO: check the cause
$label = $info['arg'];
if( empty($label) ){
$label = $info['gpOutCmd'];
}
if( isset($info['link']) && isset($langmessage[$info['link']]) ){
$label = $langmessage[$info['link']];
}
return str_replace([' ', '_', ':'], ['&nbsp;', '&nbsp;', ':&nbsp;'], $label);
}
public static function CallOutput($info,$container_id){
global $GP_ARRANGE, $page, $langmessage, $GP_MENU_LINKS;
global $GP_MENU_CLASS, $GP_MENU_CLASSES, $gp_current_container;
$gp_current_container = $container_id;
self::$out_started = true;
self::$edit_area_id = '';
if( isset($info['disabled']) ){
return;
}
//gpOutCmd identifies the output function used, there can only be one
if( !isset($info['gpOutCmd']) ){
trigger_error('gpOutCmd not set for $info in CallOutput()');
return;
}
//generate a class based on the area $info
if( isset($info['html']) ){
$class = $info['key'];
$class = preg_replace('#\[.*\]#', '', $class);
}else{
$class = $info['gpOutCmd'];
}
//add gpMenu class to all menu areas for better styling
$add_menu_class = '';
if( isset($info['class']) && $info['class'] == '\gp\tool\Output\Menu' ){
$add_menu_class = 'gpMenu ';
}
$class = $add_menu_class . 'gpArea_' . str_replace([':', ','], ['_', ''], trim($class, ':'));
$param = $container_id . '|' . $info['gpOutCmd'];
$permission = self::ShowEditLink('Admin_Theme_Content');
ob_start();
//for theme content arrangement
if( $GP_ARRANGE && $permission && isset($GLOBALS['GP_ARRANGE_CONTENT']) ){
$empty_container = empty($info['gpOutCmd']); //empty containers can't be removed and don't have labels
$class .= ' gp_output_area';
echo '<div class="gp_inner_links nodisplay"><div>';
echo \gp\tool::Link(
'Admin_Theme_Content/Edit/' . $page->gpLayout,
$param,
'cmd=DragArea&dragging=' . urlencode($param) . '&to=%s',
['data-cmd' => 'creq', 'class' => 'dragdroplink nodisplay']
); //drag-drop link
echo '<div class="output_area_label">';
if( $empty_container ){
echo $langmessage['Empty Container'];
}else{
echo self::GpOutLabel($info);
}
echo '</div>';
echo '<div class="output_area_link">';
echo ' ' . \gp\tool::Link(
'Admin_Theme_Content/Edit/' . $page->gpLayout,
'<i class="fa fa-plus"></i> ' . $langmessage['insert'],
'cmd=SelectContent&param=' . $param,
['data-cmd' => 'gpabox']
);
if( !$empty_container ){
echo ' ' . \gp\tool::Link(
'Admin_Theme_Content/Edit/' . $page->gpLayout,
'<i class="fa fa-times"></i> ' . $langmessage['remove'],
'cmd=RemoveArea&param=' . $param,
['data-cmd' => 'creq']
);
}
echo '</div>';
echo '</div></div>';
}
//editable links only .. other editable_areas are handled by their output functions
if( $permission ){
if( isset($info['link']) ){
$label = $langmessage[$info['link']];
$edit_link = self::EditAreaLink(
$edit_index,
'Admin_Theme_Content/Edit/' . urlencode($page->gpLayout),
$langmessage['edit'],
'cmd=LayoutMenu&handle=' . $param,
['data-cmd' => 'gpabox', 'title' => $label ]
);
echo '<span class="nodisplay" id="ExtraEditLnks' . $edit_index . '">';
echo $edit_link;
echo \gp\tool::Link(
'Admin/Menu',
$langmessage['file_manager'],
'',
['class' => 'nodisplay']
);
//call to current also not needed, there will only be 1 entry);
echo '</span>';
self::$edit_area_id = 'ExtraEditArea'.$edit_index;
}elseif( isset($info['key']) && ($info['key'] == 'CustomMenu') ){
$edit_link = self::EditAreaLink(
$edit_index,
'Admin_Theme_Content/Edit/' . urlencode($page->gpLayout),
$langmessage['edit'],
'cmd=LayoutMenu&handle=' . $param,
['data-cmd' => 'gpabox', 'title' => $langmessage['Links']]
);
echo '<span class="nodisplay" id="ExtraEditLnks' . $edit_index . '">';
echo $edit_link;
echo \gp\tool::Link(
'Admin/Menu',
$langmessage['file_manager'],
'',
['class' => 'nodisplay']
);
echo '</span>';
self::$edit_area_id = 'ExtraEditArea' . $edit_index;
}
}
self::$editlinks .= ob_get_clean();
echo '<div class="' . $class . ' GPAREA">';
self::ExecArea($info);
echo '</div>';
$GP_ARRANGE = true;
$gp_current_container = false;
}
public static function ExecArea($info){
//retreive from gadget cache if set
if( isset($info['gpOutCmd']) ){
$gadget = $info['gpOutCmd'];
if( substr($gadget, 0, 7) == 'Gadget:' ){
$gadget = substr($gadget, 7);
}
if( isset(self::$gadget_cache[$gadget]) ){
echo self::$gadget_cache[$gadget];
return;
}
}
$info += ['arg' => ''];
$args = [$info['arg'], $info];
$info = \gp\tool\Plugins::Filter('ExecArea', [$info, $args]);
if( !$info ){
return;
}
self::ExecInfo($info, $args);
}
/**
* Execute a set of directives for theme areas, hooks and special pages
*
*/
public static function ExecInfo($info, $args=[]){
global $addonFolderName, $installed_addon, $page;
$args += ['page' => $page];
//addonDir is deprecated as of 2.0b3
$addon = false;
if( isset($info['addonDir']) ){
$addon = $info['addonDir'];
}elseif( isset($info['addon']) ){
$addon = $info['addon'];
}
if( $addon !== false ){
if( gp_safe_mode ){
return $args;
}
\gp\tool\Plugins::SetDataFolder($addon);
}
//if addon was just installed
if( $installed_addon && $installed_addon === $addonFolderName){
\gp\tool\Plugins::ClearDataFolder();
return $args;
}
// check for fatal errors
if( self::FatalNotice('exec', $info) ){
return $args;
}
try{
$args = self::_ExecInfo($info, $args);
}catch(\Throwable $e){
\showError(E_ERROR,
'ExecInfo() Fatal Error: ' . $e->getMessage(),
$e->GetFile(),
$e->GetLine(),
[],
$e->getTrace()
);
}
if( $addon !== false ){
\gp\tool\Plugins::ClearDataFolder();
}
self::PopCatchable();
return $args;
}
public static function _ExecInfo($info, $args=[]){
global $dataDir, $gp_overwrite_scripts;
// get data
if( !empty($info['data']) ){
IncludeScript($dataDir . $info['data'], 'include_if', ['page', 'dataDir', 'langmessage']);
}
// get script
$has_script = false;
if( !empty($info['script']) ){
if( is_array($gp_overwrite_scripts) && isset($gp_overwrite_scripts[$info['script']]) ){
$full_path = $gp_overwrite_scripts[$info['script']];
}else{
$full_path = $dataDir . $info['script'];
}
if( !file_exists($full_path) ){
self::ExecError(\CMS_NAME . ' Error: Addon hook script doesn\'t exist.', $info, 'script');
return $args;
}
if( IncludeScript($full_path, 'include_once', ['page', 'dataDir', 'langmessage']) ){
$has_script = true;
}
}
//class & method execution
if( !empty($info['class_admin']) && \gp\tool::LoggedIn() ){
return self::ExecClass($has_script, $info['class_admin'], $info, $args);
}elseif( !empty($info['class']) ){
return self::ExecClass($has_script, $info['class'], $info, $args);
}
//method execution
if( !empty($info['method']) ){
return self::ExecMethod($has_script, $info, $args);
}
return $args;
}
/**
* Execute hooks that have a ['class'] defined
*
*/
private static function ExecClass($has_script, $exec_class, $info, $args){
if( !class_exists($exec_class) ){
self::ExecError(\CMS_NAME . ' Error: Addon class doesn\'t exist.', $info, 'class');
return $args;
}
$object = new $exec_class($args);
if( !empty($info['method']) ){
if( method_exists($object, $info['method']) ){
$args[0] = call_user_func_array([$object, $info['method']], array_values($args));
}elseif( $has_script ){
self::ExecError(\CMS_NAME . ' Error: Addon hook method doesn\'t exist (1).', $info, 'method');
}
}
return $args;
}
/**
* Execute hooks that have a ['method'] defined
*
*/
private static function ExecMethod($has_script, $info, $args){
$callback = $info['method'];
//object callbacks since 3.0
if( is_string($callback) && strpos($callback, '->') !== false ){
$has_script = true;
list($object, $method) = explode('->', $callback);
if( isset($GLOBALS[$object]) &&
is_object($GLOBALS[$object]) &&
method_exists($GLOBALS[$object], $method)
){
$callback = [$GLOBALS[$object], $method];
}
}
if( is_callable($callback) ){
$args[0] = call_user_func_array($callback, array_values($args));
}elseif( $has_script ){
self::ExecError(\CMS_NAME.' Error: Addon hook method doesn\'t exist (2).', $info, 'method');
}
return $args;
}
/**
* Trigger an error
*
*/
public static function ExecError($msg, $exec_info, $error_info){
global $config, $addonFolderName;
// append addon name
if( !empty($addonFolderName) && isset($config['addons'][$addonFolderName]) ){
$msg .= ' Addon: ' . $config['addons'][$addonFolderName]['name'] . '. ';
}
// which piece of $exec_info is the problem
if( !isset($exec_info[$error_info]) ){
$msg .= $error_info;
}elseif( is_array($exec_info[$error_info]) ){
$msg .= $error_info . ': ' . implode('::', $exec_info[$error_info]);
}else{
$msg .= $error_info . ': ' . $exec_info[$error_info];
}
trigger_error($msg);
}
/**
* Check for fatal errors corresponing to $hash
* Notify administrators of disabled components
*
*/
public static function FatalNotice($type, $info){
global $dataDir, $page;
static $notified = false;
$info = (array)$info;
$info['catchable_type'] = $type;
$hash_dir = $dataDir . '/data/_site/fatal_' . $type . '_' . \gp\tool::ArrayHash($info);
$hash_request = $hash_dir . '/' . \gp\tool::ArrayHash($_REQUEST);
self::$catchable[$hash_request] = $info;
if( !self::FatalLimit($hash_dir) ){
return false;
}
if( !$notified ){
error_log( 'Warning: A component of this page has been disabled because it caused fatal errors' );
$notified = true;
}
self::PopCatchable();
return true;
}
/**
* Return true if the limit of fatal errors has been reached
*
*/
public static function FatalLimit($hash_dir){
//no folder = no fatal error
if( !file_exists($hash_dir) ){
return false;
}
// if the error didn't occur for the exact request and it hasn't happend a lot, allow the code to keep working
$fatal_hashes = scandir($hash_dir);
if( $fatal_hashes !== false && count($fatal_hashes) < (gp_allowed_fatal_errors + 3) ){
// add 3 for ".", ".." and "index.html" entries
return false;
}
return true;
}
public static function PopCatchable(){
array_pop(self::$catchable);
}
/**
* Determine if an inline edit link should be shown for the current user
*
* @param string $permission
* @return bool
*/
public static function ShowEditLink($permission=null){
if( !is_null($permission) ){
return !self::$nested_edit && \gp\tool::LoggedIn() && \gp\admin\Tools::HasPermission($permission);
}
return !self::$nested_edit && \gp\tool::LoggedIn();
}
/**
* @param int $index
* @param string $href
* @param string $label
* @param string $query
* @param string|array $attr
*
*/
public static function EditAreaLink(&$index, $href, $label, $query='', $attr=''){
self::$edit_index++;
$index = self::$edit_index; //since &$index is passed by reference
if( is_array($attr) ){
$attr += [
'class' => 'ExtraEditLink nodisplay',
'id' => 'ExtraEditLink' . $index,
'data-gp-area-id' => $index,
];
}else{
$attr .= ' class="ExtraEditLink nodisplay" ' .
'id="ExtraEditLink' . $index . '" ' .
'data-gp-area-id="' . $index . '"';
}
return \gp\tool::Link($href, $label, $query, $attr);
}
/**
* Unless the gadget area is customized by the user, this function will output all active gadgets
* If the area has been reorganized, it will output the customized areas
* This function is not called from \gp\tool\Output::Get('GetAllGadgets')
* so that each individual gadget area can be used as a drag area
*
*/
public static function GetAllGadgets(){
global $config, $page, $gpLayouts, $get_all_gadgets_called;
$get_all_gadgets_called = true;
//if we have handler info
if( isset($gpLayouts[$page->gpLayout]['handlers']['GetAllGadgets']) ){
self::ForEachOutput($gpLayouts[$page->gpLayout]['handlers']['GetAllGadgets'], 'GetAllGadgets');
return;
}
//show all gadgets if no changes have been made
if( !empty($config['gadgets']) ){
$count = 0;
foreach($config['gadgets'] as $gadget => $info){
if( isset($info['addon']) ){
$info['gpOutCmd'] = $info['key'] = $gadget;
self::CallOutput($info, 'GetAllGadgets');
$count++;
}
}
if( $count ){
return;
}
}
//Show the area as editable if there isn't anything to show
$info = [];
$info['gpOutCmd'] = '';
self::CallOutput($info, 'GetAllGadgets');
}
/**
* Simply determine if a Gadget exists
* allows us to check whether a plugin gadget exists before loading it in template.php
* @since 5.2-rc
* @param string Gadget id
* @return boolean
*/
public static function GadgetExists($id){
global $config;
return isset($config['gadgets'][$id]);
}
/**
* Get a Single Gadget
* This method should be called using \gp\tool\Output::Fetch('Gadget',$gadget_name)
*
*/
public static function GetGadget($id){
global $config;
if( !isset($config['gadgets'][$id]) ){
return;
}
self::ExecArea($config['gadgets'][$id]);
}
/**
* Prepare the gadget content before getting template.php
* so that gadget functions can add css and js to the head
* @return null
*/
public static function PrepGadgetContent(){
global $page;
//not needed for admin pages
if( $page->pagetype == 'admin_display' ){
return;
}
$gadget_info = self::WhichGadgets($page->gpLayout);
foreach($gadget_info as $gpOutCmd => $info){
if( !isset(self::$gadget_cache[$gpOutCmd]) ){
ob_start();
self::ExecArea($info);
self::$gadget_cache[$gpOutCmd] = ob_get_clean();
}
}
}
/**
* Return information about the gadgets being used in the current layout
* @return array
*/
public static function WhichGadgets($layout){
global $config, $gpLayouts;
$gadget_info = $temp_info = [];
if( !isset($config['gadgets']) ){
return $gadget_info;
}
$layout_info = & $gpLayouts[$layout];
$GetAllGadgets = true;
if( isset($layout_info['all_gadgets']) && !$layout_info['all_gadgets'] ){
$GetAllGadgets = false;
}
if( isset($layout_info['handlers']) ){
foreach($layout_info['handlers'] as $handler => $out_cmds){
//don't prep even if GetAllGadgets is set in the layout's config
if( $handler == 'GetAllGadgets' && !$GetAllGadgets ){
continue;
}
foreach($out_cmds as $gpOutCmd){
$temp_info[$gpOutCmd] = self::GetgpOutInfo($gpOutCmd);
}
}
}
//add all gadgets if $GetAllGadgets is true and the GetAllGadgets handler isn't overwritten
if( $GetAllGadgets && !isset($layout_info['handlers']['GetAllGadgets']) ){
foreach($config['gadgets'] as $gadget => $temp){
if( isset($temp['addon']) ){
$temp_info[$gadget] = self::GetgpOutInfo($gadget);
}
}
}
foreach($temp_info as $gpOutCmd => $info){
if( isset($info['is_gadget']) &&
$info['is_gadget'] &&
!isset($info['disabled'])
){
$gadget_info[$gpOutCmd] = $info;
}
}
return $gadget_info;
}
public static function GetExtra($name='Side_Menu', $info=[]){
echo \gp\tool\Output\Extra::GetExtra($name);
}
public static function GetImage($src, $attributes=[]){
global $page, $dataDir, $langmessage, $gpLayouts;
//$width,$height,$attributes = ''
$attributes = (array)$attributes;
$attributes += ['class' => ''];
$attributes['class'] .= 'GPAREA filetype-image';
unset($attributes['id']);
//default image information
$img_rel = dirname($page->theme_rel) . '/' . ltrim($src, '/');
//container id
$container_id = 'Image:' . $src;
$container_id = self::GetContainerID($container_id);
//select custom image
if( isset($gpLayouts[$page->gpLayout]) &&
isset($gpLayouts[$page->gpLayout]['images']) &&
isset($gpLayouts[$page->gpLayout]['images'][$container_id]) &&
is_array($gpLayouts[$page->gpLayout]['images'][$container_id])
){
//shuffle($gpLayouts[$page->gpLayout]['images'][$container_id]);
//Does not make sense ? There will always be only 1 entry in
//for this container as it is per img element
//call to current also not needed, there will only be 1 entry
$image = $gpLayouts[$page->gpLayout]['images'][$container_id][0];
$img_full = $dataDir.$image['img_rel'];
if( file_exists($img_full) ){
$img_rel = $image['img_rel'];
$attributes['width'] = $image['width'];
$attributes['height'] = $image['height'];
}
}
//attributes
if( !isset($attributes['alt']) ){
$attributes['alt'] = '';
}
//edit options
$editable = self::ShowEditLink('Admin_Theme_Content');
if( $editable ){
$edit_link = self::EditAreaLink(
$edit_index,
'Admin_Theme_Content/Image/' . $page->gpLayout,
$langmessage['edit'],
'file=' . rawurlencode($img_rel) . '&container=' . $container_id . '&time=' . time(),
['title' => 'Edit Image', 'data-cmd' => 'inline_edit_generic' ]
);
self::$editlinks .= '<span class="nodisplay" id="ExtraEditLnks' .
$edit_index . '">' .
$edit_link .
'</span>';
$attributes['class'] .= ' editable_area';
$attributes['id'] = 'ExtraEditArea' . $edit_index;
}
//remove class if empty, commented because class will never be empty anymore because of filetype-image
// $attributes['class'] = trim($attributes['class']);
// if( empty($attributes['class']) ){
// unset($attributes['class']);
//}
//convert attributes to string
$str = '';
foreach($attributes as $key => $value){
$str .= ' ' . $key . '="' . htmlspecialchars($value, ENT_COMPAT, 'UTF-8', false) . '"';
}
echo '<img src="' . \gp\tool::GetDir($img_rel, true) . '"' . $str . '/>';
}
/*
*
* Output Additional Areas
*
*/
/* draggable html and editable text */
public static function Area($name,$html){
global $gpOutConf;
if( self::$out_started ){
trigger_error('\gp\tool\Output::Area() must be called before all other output functions');
return;
}
$name = '[text]' . $name;
$gpOutConf[$name] = [];
$gpOutConf[$name]['method'] = ['\\gp\\tool\\Output', 'GetAreaOut'];
$gpOutConf[$name]['html'] = $html;
}
public static function GetArea($name, $text){
$name = '[text]' . $name;
self::Get($name, $text);
}
public static function GetAreaOut($text,$info){
global $config, $langmessage, $page;
$html =& $info['html'];
$wrap = self::ShowEditLink('Admin_Theme_Content');
if( $wrap ){
self::$editlinks .= self::EditAreaLink(
$edit_index,
'Admin_Theme_Content/Text',
$langmessage['edit'],
'cmd=EditText&key=' . urlencode($text) . '&return=' . urlencode($page->title),
['title' => htmlspecialchars($text), 'data-cmd' => 'gpabox']
);
echo '<div class="editable_area inner_size" id="ExtraEditArea' . $edit_index . '">';
// class="edit_area" added by javascript
}
if( isset($config['customlang'][$text]) ){
$text = $config['customlang'][$text];
}elseif( isset($langmessage[$text]) ){
$text = $langmessage[$text];
}
echo str_replace('%s', $text, $html); //in case there's more than one %s
if( $wrap ){
echo '</div>';
}
}
/**
* Get additional CSS classes that can be added to the
* html or body elements' class atrribute
* e.g. to display an larger header only on the homepage
* Use in template.php: <html class="<?php gpOutput::GetPageInfoClasses(); ?>">
* @since 5.2-rc
* @param boolean $echo_classes (defaults to true), output the class names as space-separated string
* @return array of css class names that apply
*
*/
public static function GetPageInfoClasses($echo_classes=true){
global $page;
$classes = [];
if( self::is_front_page() ){
$classes[] = 'is-homepage';
}
if( $page->pagetype == 'special_display' ){
$classes[] = 'is-special-page';
}
switch( $page->gp_index ){
case 'special_contact':
$classes[] = 'is-contactform-page';
break;
case 'special_galleries':
$classes[] = 'is-galleries-page';
break;
case 'special_gpsearch':
$classes[] = 'is-search-page';
break;
case 'special_missing':
$classes[] = 'is-missing-page';
break;
case 'special_blog':
$classes[] = 'is-blog-page';
if( isset($_GET['id']) ){
$classes[] = 'is-single-post-page';
}
break;
case 'special_blog_categories':
$classes[] = 'is-blog-page';
$classes[] = 'is-blog-categories-page';
if( isset($_GET['cat']) ){
$classes[] = 'is-single-category-page';
}
break;
}
if( $echo_classes && !empty($classes) ){
echo ' ' . implode(' ', $classes) . ' ';
}
return $classes;
}
/*
*
* editable text, not draggable
*
*
*/
/**
* similar to ReturnText() but links to script for editing all addon texts
* the $html parameter should primarily be used when the text is to be placed
* inside of a link or other element that cannot have a link and/or span as a child node
*/
public static function GetAddonText($key, $html='%s', $wrapper_class=''){
global $addonFolderName;
if( !$addonFolderName ){
return self::ReturnText($key, $html, $wrapper_class);
}
$query = 'cmd=AddonTextForm&addon=' . urlencode($addonFolderName) . '&key=' . urlencode($key);
return self::ReturnTextWorker($key, $html, $query, $wrapper_class);
}
public static function ReturnText($key,$html='%s', $wrapper_class=''){
$query = 'cmd=EditText&key='.urlencode($key);
return self::ReturnTextWorker($key, $html, $query, $wrapper_class);
}
public static function ReturnTextWorker($key, $html, $query, $wrapper_class=''){
global $langmessage;
$text = self::SelectText($key);
$result = str_replace('%s', $text, $html); //in case there's more than one %s
$editable = self::ShowEditLink('Admin_Theme_Content');
if( $editable ){
$title = htmlspecialchars(strip_tags($key));
if( strlen($title) > 20 ){
$title = substr($title, 0, 20) . '...'; //javscript may shorten it as well
}
self::$editlinks .= self::EditAreaLink(
$edit_index,
'Admin_Theme_Content/Text',
$langmessage['edit'],
$query,
['title' => $title, 'data-cmd' => 'gpabox']
);
return '<span class="editable_area ' . $wrapper_class .'" '
. 'id="ExtraEditArea' . $edit_index . '">' . $result . '</span>';
}
if( $wrapper_class ){
return '<span class="' . $wrapper_class . '">' . $result . '</span>';
}
return $result;
}
/**
* Returns the user translated string if it exists or
* $key (the untranslated string) if a translation doesn't exist
*
*/
public static function SelectText($key){
global $config,$langmessage;
$text = $key;
if( isset($config['customlang'][$key]) ){
$text = $config['customlang'][$key];
}elseif( isset($langmessage[$key]) ){
$text = $langmessage[$key];
}
return $text;
}
/*
*
* Generate and output the <head> portion of the html document
*
*/
public static function GetHead(){
\gp\tool\Plugins::Action('GetHead');
self::PrepGadgetContent();
echo '<!-- get_head_placeholder ' . \gp_random . ' -->';
}
public static function HeadContent(){
global $config, $page, $wbMessageBuffer;
//before ob_start() so plugins can get buffer content
\gp\tool\Plugins::Action('HeadContent');
if( \gp\tool::LoggedIn() ){
\gp\tool::AddColorBox();
}
//always include javascript when there are messages
if( $page->admin_js || !empty($page->jQueryCode) || !empty($wbMessageBuffer) || isset($_COOKIE['cookie_cmd']) ){
\gp\tool::LoadComponents('gp-main');
}
//defaults
\gp\tool::LoadComponents('jquery,gp-additional');
//get css and js info
$scripts = \gp\tool\Output\Combine::ScriptInfo( self::$components );
ob_start();
self::GetHead_TKD();
self::$head_content = ob_get_clean();
ob_start();
self::GetHead_CSS($scripts['css']); //css before js so it's available to scripts
self::$head_css = ob_get_clean();
//javascript
ob_start();
self::GetHead_Lang();
self::GetHead_JS($scripts['js']);
self::GetHead_InlineJS();
self::$head_js = ob_get_clean();
//gadget info
if( isset($config['addons']) ){
foreach($config['addons'] as $addon_info){
if( !empty($addon_info['html_head']) ){
self::MoveScript($addon_info['html_head']);
}
}
}
if( !empty($page->head) ){
self::MoveScript($page->head);
}
}
/**
* Move <script>..</script> to self::$head_js
*
*/
public static function MoveScript($string){
//conditional comments with script tags
$patt = '#' . preg_quote('<!--[if', '#') . '.*?' . preg_quote('<![endif]-->', '#') . '#s';
if( preg_match_all($patt,$string, $matches) ){
foreach($matches[0] as $match){
if( strpos($match,'<script') !== false ){
$string = str_replace($match, '', $string);
self::$head_js .= "\n" . $match;
}
}
}
//script tags
if( preg_match_all('#<script.*?</script>#i',$string,$matches) ){
foreach($matches[0] as $match){
$string = str_replace($match, '', $string);
self::$head_js .= "\n" . $match;
}
}
//add the rest to the head_content
self::$head_content .= "\n" . $string;
}
/**
* Output the title, keywords, description and other meta for the current html document
* @static
*/
public static function GetHead_TKD(){
global $config, $page, $gpLayouts;
//charset
if( $page->gpLayout &&
isset($gpLayouts[$page->gpLayout]) &&
isset($gpLayouts[$page->gpLayout]['doctype'])
){
echo $gpLayouts[$page->gpLayout]['doctype'];
}
//title, keyords & description
$page_title = self::MetaTitle();
self::MetaKeywords($page_title);
self::MetaDescription($page_title);
if( !empty($page->TitleInfo['rel']) ){
echo "\n" . '<meta name="robots" content="' . $page->TitleInfo['rel'] . '" />';
}
echo "\n" . '<meta name="generator" content="' . \CMS_NAME_FULL . '" />';
}
/**
* Add the <title> tag to the page
* return the value
*
*/
public static function MetaTitle(){
global $page, $config;
$meta_title = '';
$page_title = '';
if( !empty($page->TitleInfo['browser_title']) ){
$page_title = $page->TitleInfo['browser_title'];
}elseif( !empty($page->label) ){
$page_title = strip_tags($page->label);
}elseif( isset($page->title) ){
$page_title = \gp\tool::GetBrowserTitle($page->title);
}
$meta_title .= $page_title;
if( !empty($page_title) && !empty($config['title']) ){
$meta_title .= ' - ';
}
$meta_title .= $config['title'];
$meta_title = \gp\tool\Plugins::Filter(
'MetaTitle',
[$meta_title, $page_title, $config['title']]
);
echo "\n" . '<title>' . strip_tags($meta_title) . '</title>';
return $page_title;
}
/**
* Add the <meta name="keywords"> tag to the page
*
*/
public static function MetaKeywords($page_title){
global $page, $config;
if( count($page->meta_keywords) ){
$keywords = $page->meta_keywords;
}elseif( !empty($page->TitleInfo['keywords']) ){
$keywords = explode(',', $page->TitleInfo['keywords']);
}
$keywords[] = strip_tags($page_title);
$keywords[] = strip_tags($page->label);
$site_keywords = explode(',', $config['keywords']);
$keywords = array_merge($keywords, $site_keywords);
$keywords = array_unique($keywords);
$keywords = array_filter($keywords);
echo "\n<meta name=\"keywords\" content=\"" . implode(', ', $keywords) . "\" />";
}
/**
* Add the <meta name="dscription"> tag to the page
*
*/
public static function MetaDescription($page_title){
global $page, $config;
$description = '';
if( !empty($page->meta_description) ){
$description .= $page->meta_description;
}elseif( !empty($page->TitleInfo['description']) ){
$description .= $page->TitleInfo['description'];
}else{
$description .= $page_title;
}
$description = self::EndPhrase($description);
if( !empty($config['desc']) ){
$description .= htmlspecialchars($config['desc']);
}
$description = trim($description);
if( !empty($description) ){
echo "\n" . '<meta name="description" content="' . $description . '" />';
}
}
/**
* Prepare and output any inline Javascript for the current page
* @static
*/
public static function GetHead_InlineJS(){
global $page, $gp_titles;
if( isset($page->gp_index) &&
isset($gp_titles[$page->gp_index]['vis']) &&
$gp_titles[$page->gp_index]['vis'] == 'private'
){
$page->jQueryCode .= '$("html").addClass("isPrivate");' . "\n";
}
if( \gp\tool::LoggedIn() && $page->pagetype !== 'admin_display' ){
$page->jQueryCode .= '$gp.HideAdminUI.init();' . "\n";
// get available classes
$avail_classes = \gp\admin\Settings\Classes::GetClasses();
$avail_classes = \gp\tool\Plugins::Filter('AvailableClasses', [$avail_classes]);
$page->head_script .= "\n" . 'var gp_avail_classes = ' . json_encode($avail_classes) . ';';
}
// get customizer js vars
$layout_js_vars = self::GetLayoutJsVars();
// debug('$layout_js_vars = <em>' . $layout_js_vars . '</em>');
ob_start();
echo $layout_js_vars;
echo $page->head_script . "\n";
if( !empty($page->jQueryCode) ){
echo '$(function(){' . "\n";
echo $page->jQueryCode . "\n";
echo '});';
}
$inline = ob_get_clean();
$inline = ltrim($inline);
echo "\n" . '<script type="text/javascript">' . "\n" . $inline . "\n" . '</script>' . "\n";
}
/**
* Add language values to the current page
* @static
*/
public static function GetHead_Lang(){
global $langmessage;
if( !count(self::$lang_values) ){
return;
}
echo "\n" . '<script type="text/javascript">';
echo 'var gplang = {';
$comma = '';
foreach(self::$lang_values as $from_key => $to_key){
echo $comma;
echo $to_key . ':"' . str_replace(['\\', '"'], ['\\\\', '\"'], $langmessage[$from_key]) . '"';
$comma = ',';
}
echo "}; </script>";
}
/**
* Prepare and output the Javascript for the current page
* @static
*/
public static function GetHead_JS($scripts){
global $page, $config;
$combine = $config['combinejs'] && !\gp\tool::loggedIn() && ($page->pagetype !== 'admin_display');
$scripts = self::GetHead_CDN('js', $scripts);
//just local jquery
if( !count($page->head_js) && count($scripts) === 1 && isset($scripts['jquery']) ){
echo '<!-- jquery_placeholder ' . \gp_random . ' -->';
return;
}
if( !$combine || $page->head_force_inline ){
echo "\n<script type=\"text/javascript\">\n";
\gp\tool::jsStart();
echo "\n</script>";
}
if( is_array($page->head_js) ){
$scripts += $page->head_js; //other js files
}else{
trigger_error('$page->head_js is not an array');
}
Output\Assets::CombineFiles($scripts, 'js', $combine);
}
/**
* Prepare and output the css for the current page
* @static
*/
public static function GetHead_CSS($to_add){
global $page, $config, $dataDir;
$scripts = [];
$to_add = self::GetHead_CDN('css', $to_add);
$scripts = Output\Assets::MergeScripts($scripts, $to_add);
if( isset($page->css_user) ){
$scripts = Output\Assets::MergeScripts($scripts, $page->css_user);
}
// add theme css
if( !empty($page->theme_name) && $page->get_theme_css === true ){
$scripts = Output\Assets::MergeScripts($scripts, Output\Assets::LayoutStyleFiles());
}
//styles that need to override admin.css should be added to $page->css_admin;
if( isset($page->css_admin) ){
$scripts = Output\Assets::MergeScripts($scripts, $page->css_admin);
}
// disable 'combine css' if 'create_css_sourcemaps' is set to true in /gpconfig.php
$combinecss = \create_css_sourcemaps ? false : $config['combinecss'];
Output\Assets::CombineFiles($scripts, 'css', $combinecss);
}
/**
* Add CDN hosted resources to the page
*
*/
public static function GetHead_CDN($type, $scripts){
global $config;
if( empty($config['cdn']) ){
return $scripts;
}
$cdn = $config['cdn'];
foreach($scripts as $key => $script_info){
if( !isset($script_info['cdn']) || !isset($script_info['cdn'][$cdn]) ){
continue;
}
$cdn_url = $script_info['cdn'][$cdn];
//remove packages
if( isset($script_info['package']) ){
foreach($scripts as $_key => $_info){
if( isset($_info['package']) && $_info['package'] == $script_info['package'] ){
unset($scripts[$_key]);
}
}
}
unset($scripts[$key]);
echo Output\Assets::FormatAsset($type,$cdn_url);
}
return $scripts;
}
/**
* Get the path for the custom css/scss/less file
*
*/
public static function CustomStyleFile($layout, $style_type){
global $dataDir;
if( $style_type == 'scss' ){
return $dataDir . '/data/_layouts/' . $layout . '/custom.scss';
}
return $dataDir . '/data/_layouts/' . $layout . '/custom.css';
}
/**
* Get the path for the customizer css/scss/less file
*
* @since 5.2
*/
public static function CustomizerStyleFile($layout, $style_type){
global $dataDir;
if( $style_type == 'scss' ){
return $dataDir . '/data/_layouts/' . $layout . '/customizer.scss';
}
return $dataDir . '/data/_layouts/' . $layout . '/customizer.css';
}
/**
* Get the path for the custom layout config file
*
* @since 5.2
*/
public static function LayoutConfigFile($layout){
global $dataDir;
return $dataDir . '/data/_layouts/' . $layout . '/config.php';
}
/**
* Get the filetype of the style.* file
*
* @return string
*/
public static function StyleType($dir){
$types = ['less','scss'];
foreach($types as $type){
$path = $dir . '/style.'.$type;
if( file_exists($path) ){
return $type;
}
}
return 'css';
}
/**
* Determines whether the passed directory qualifies as layout
* by checking whether a style.css, style.less or style.css file exists
* @return boolean
*/
public static function IsLayoutDir($dir){
$types = ['less','scss','css'];
foreach($types as $type){
$path = $dir . '/style.' . $type;
if( file_exists($path) ){
return true;
}
}
return false;
}
/**
* Complete the response by adding final content to the <head> of the document
* @static
* @since 2.4.1
* @param string $buffer html content
* @return string finalized response
*/
public static function BufferOut($buffer){
global $config;
//add error notice if there was a fatal error
if( !ini_get('display_errors') ){
$last_error = self::LastFatal();
if( !empty($last_error) ){
self::RecordFatal($last_error);
$buffer .= self::FatalMessage($last_error);
}
}
//remove lock
if( defined('gp_has_lock') && \gp_has_lock ){
\gp\tool\Files::Unlock('write', \gp_random);
}
//make sure whe have a complete html request
$placeholder = '<!-- get_head_placeholder ' . \gp_random . ' -->';
if( strpos($buffer,$placeholder) === false ){
return $buffer;
}
$replacements = [];
//performace stats
if( class_exists('admin_tools') ){
$replacements = self::PerformanceStats();
}
//head content
//add css to bottom of <body>
if( \load_css_in_body ){
$buffer = self::AddToBody($buffer, self::$head_css);
$replacements[$placeholder] = self::$head_content;
}else{
$replacements[$placeholder] = self::$head_css . self::$head_content;
}
//add js to bottom of <body>
$buffer = self::AddToBody($buffer, self::$head_js);
//add jquery if needed
$placeholder = '<!-- jquery_placeholder ' . \gp_random . ' -->';
$replacement = '';
if( !empty(self::$head_js) || stripos($buffer, '<script') !== false ){
$replacement = Output\Assets::FormatAsset('js',\gp\tool::GetDir('/include/thirdparty/js/jquery.js')); // TODO: restore this line
}
$replacements[$placeholder] = $replacement;
//messages
$pos = strpos($buffer, '<!-- message_start ' . \gp_random . ' -->');
$len = strpos($buffer, '<!-- message_end -->') - $pos;
if( $pos && $len ){
$replacement = GetMessages(false);
$buffer = substr_replace($buffer, $replacement, $pos, $len + 20);
}
return str_replace( array_keys($replacements), array_values($replacements), $buffer);
}
/**
* Add content to the html document before the </body> tag
*
*/
public static function AddToBody($buffer, $add_string){
if( empty($add_string) ){
return $buffer;
}
$pos_body = stripos($buffer, '</body');
if( $pos_body !== false ){
return substr_replace($buffer, "\n" . $add_string . "\n", $pos_body, 0);
}
return $buffer;
}
/**
* Return the message displayed when a fatal error has been caught
*
*/
public static function FatalMessage($error_details){
$message = '<p>Oops, an error occurred while generating this page.<p>';
if( !\gp\tool::LoggedIn() ){
//reload non-logged in users automatically if there were catchable errors
if( !empty(self::$catchable) ){
$message .= 'Reloading... <script type="text/javascript">'
. 'window.setTimeout(function(){window.location.href = '
. 'window.location.href},1000);</script>';
}else{
$message .= '<p>If you are the site administrator, you can troubleshoot '
. 'the problem by changing php\'s display_errors setting to 1 in '
. 'the gpconfig.php file.</p><p>If the problem is being caused by an addon, '
. 'you may also be able to bypass the error by enabling ' . \CMS_NAME . '\'s '
. 'safe mode in the gpconfig.php file.</p><p>More information is available '
. 'in the <a href="' . \CMS_DOMAIN . '/Docs/Main/Troubleshooting">Documentation</a>.'
. '</p><p><a href="?">Reload this page to continue</a>.</p>';
}
return $message;
}
$message .= '<h3>Error Details</h3>'
.pre($error_details)
. '<p><a href="?">Reload this page</a></p>'
. '<p style="font-size:90%">Note: Error details are only '
. 'displayed for logged in administrators</p>'
. \gp\tool::ErrorBuffer(true, false);
return $message;
}
/**
* Determine if a fatal error has been fired
* @return array
*/
public static function LastFatal(){
$fatal_errors = [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR];
$last_error = error_get_last();
if( is_array($last_error) && in_array($last_error['type'], $fatal_errors) ){
return $last_error;
}
}
/**
* Record fatal errors in /data/_site/ so we can prevent subsequent requests from having the same issue
*
*/
public static function RecordFatal($last_error){
global $config, $addon_current_id, $addonFolderName;
$last_error['request'] = $_SERVER['REQUEST_URI'];
if( $addon_current_id ){
$last_error['addon_name'] = $config['addons'][$addonFolderName]['name'];
$last_error['addon_id'] = $addon_current_id;
}
$last_error['file'] = realpath($last_error['file']);//may be redundant
showError(
$last_error['type'],
$last_error['message'],
$last_error['file'],
$last_error['line'],
false
); //send error to logger
if( empty(self::$catchable) ){
return;
}
$last_error['time'] = time();
$last_error['request_method'] = $_SERVER['REQUEST_METHOD'];
if( !empty($last_error['file']) ){
$last_error['file_modified'] = filemtime($last_error['file']);
$last_error['file_size'] = filesize($last_error['file']);
}
$content = json_encode($last_error);
$temp = array_reverse(self::$catchable);
foreach($temp as $filepath => $info){
\gp\tool\Files::Save($filepath,$content);
if( $info['catchable_type'] == 'exec' ){
break;
}
}
}
/**
* Return Performance Stats about the current request
*
* @return array
*/
public static function PerformanceStats(){
$stats = [];
if( function_exists('memory_get_peak_usage') ){
$stats['<span cms-memory-usage>?</span>'] = \gp\admin\Tools::FormatBytes(memory_get_usage());
$stats['<span cms-memory-max>?</span>'] = \gp\admin\Tools::FormatBytes(memory_get_peak_usage());
}
if( isset($_SERVER['REQUEST_TIME_FLOAT']) ){
$time = microtime(true) - $_SERVER['REQUEST_TIME_FLOAT'];
}else{
$time = microtime(true) - gp_start_time;
}
$stats['<span cms-seconds>?</span>'] = round($time, 3);
$stats['<span cms-ms>?</span>'] = round($time * 1000);
return $stats;
}
/**
* Return true if the user agent is a search engine bot
* Detection is rudimentary and shouldn't be relied on
* @return bool
*/
public static function DetectBot(){
$user_agent =& $_SERVER['HTTP_USER_AGENT'];
return (bool)preg_match('#bot|yahoo\! slurp|ask jeeves|ia_archiver|spider|crawler#i', $user_agent);
}
/**
* Return true if the current page is the home page
*/
public static function is_front_page(){
global $config, $page;
return $page->gp_index == $config['homepath_key'];
}
/**
* Outputs the sitemap link, admin login/logout link, powered by link and messages
* @static
*/
public static function GetAdminLink($messages=true){
self::GetSitemapLink(); // as of 5.2-rc
echo ' ';
self::GetLoginLink(); // as of 5.2-rc
echo ' ';
self::GetPoweredByLink(); // as of 5.2-rc
\gp\tool\Plugins::Action('GetAdminLink');
if( $messages ){
echo GetMessages();
}
}
/**
* Outputs only the sitemap link
* as of 5.2-rc
* @static
*/
public static function GetSitemapLink(){
global $config, $langmessage, $page;
if( !isset($config['showsitemap']) || $config['showsitemap'] ){
echo ' <span class="sitemap_link">';
echo \gp\tool::Link(
'Special_Site_Map',
$langmessage['site_map']
);
\gp\tool\Plugins::Action('GetSitemapLink');
echo '</span>';
}
}
/**
* Outputs only the login/logout link
* as of 5.2-rc
* @static
*/
public static function GetLoginLink($force_show=false){
global $config, $langmessage, $page;
if( $force_show || !isset($config['showlogin']) || $config['showlogin'] ){
echo '<span class="login_link">';
if( \gp\tool::LoggedIn() ){
echo \gp\tool::Link(
$page->title,
$langmessage['logout'],
'cmd=logout',
['data-cmd' => 'cnreq', 'rel' => 'nofollow']
);
}else{
echo \gp\tool::Link(
'Admin',
$langmessage['login'],
'file=' . rawurlencode($page->title),
['data-cmd' => 'login', 'rel' => 'nofollow']
);
}
\gp\tool\Plugins::Action('GetLoginLink');
echo '</span>';
}
}
public static function AdminLinkGadget(){
self::GetAdminLink(false);
}
public static function LoginLinkGadget(){
self::GetLoginLink(true);
}
/**
* Outputs only the powered_by link
* as of 5.2-rc
* @static
*/
public static function GetPoweredByLink($always_show=false){
global $config;
if( !isset($config['showgplink']) || $config['showgplink'] ){
if( self::is_front_page() || $always_show ){
echo '<span id="powered_by_link">';
echo 'Powered by <a href="' . \CMS_DOMAIN . '" target="_blank">' . \CMS_NAME . '</a>';
\gp\tool\Plugins::Action('GetPoweredByLink');
echo '</span>';
}
}
}
/**
* Add punctuation to the end of a string if it isn't already punctuated.
* Looks for !?.,;: characters
*
* @static
* @since 2.4RC1
*/
public static function EndPhrase($string){
$string = trim($string);
if( empty($string) ){
return $string;
}
$len = strspn($string, '!?.,;:', -1);
if( $len == 0 ){
$string .= '.';
}
return $string . ' ';
}
public static function RunOut(){
global $langmessage, $page;
$page->RunScript();
//prepare the admin content
if( \gp\tool::LoggedIn() ){
\gp\admin\Tools::AdminHtml();
}
//decide how to send the content
self::Prep();
switch(\gp\tool::RequestType()){
// <a data-cmd="admin_box">
case 'flush':
self::Flush();
break;
// remote request
// file browser
case 'body':
\gp\tool::CheckTheme();
self::BodyAsHTML();
break;
case 'admin':
self::AdminHtml();
break;
// <a data-cmd="gpajax">
// <a data-cmd="gpabox">
// <input data-cmd="gpabox">
case 'json':
\gp\tool::CheckTheme();
\gp\tool\Output\Ajax::Response();
break;
case 'content':
self::Content();
break;
default:
\gp\tool::CheckTheme();
self::Template();
break;
}
// if logged in, don't send 304 response
if( \gp\tool::LoggedIn() ){
//empty edit links if there isn't a layout
if( !$page->gpLayout ){
self::$editlinks = '';
}
return;
}
// attempt to send 304 response
if( $page->fileModTime > 0 ){
global $wbMessageBuffer;
$len = ob_get_length();
$etag = \gp\tool::GenEtag(
$page->fileModTime,
$len,
json_encode($wbMessageBuffer),
self::$head_content,
self::$head_js
);
\gp\tool::Send304($etag);
}
}
/**
* Add one or more components to the page. Output the <script> and/or <style> immediately
* @param string $names comma separated list of components
*
*/
public static function GetComponents($names=''){
$scripts = \gp\tool\Output\Combine::ScriptInfo($names);
$scripts['css'] = self::GetHead_CDN('css', $scripts['css']);
Output\Assets::CombineFiles($scripts['css'], 'css', false);
$scripts['js'] = self::GetHead_CDN('js', $scripts['js']);
Output\Assets::CombineFiles($scripts['js'], 'js', false);
}
}
}
namespace{
class gpOutput extends gp\tool\Output{}
}