'\\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', '&'); 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 ''; echo ''; self::getHead(); echo ''; echo ''; echo GetMessages(); $page->GetGpxContent(); echo ''; echo ''; self::HeadContent(); } public static function AdminHtml(){ global $page; //\gp\tool\Output::$inline_vars['gp_bodyashtml'] = true; self::StandardHeaders(); echo ''; echo ''; self::getHead(); echo ''; echo ''; echo GetMessages(); $page->GetGpxContent(); echo ''; echo ''; 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 $outKeys =array(); 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 ' . $gpOutCmd . ' 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([' ', '_', ':'], [' ', ' ', ': '], $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 ''; } //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 ''; 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 ''; 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 ''; echo $edit_link; echo \gp\tool::Link( 'Admin/Menu', $langmessage['file_manager'], '', ['class' => 'nodisplay'] ); echo ''; self::$edit_area_id = 'ExtraEditArea' . $edit_index; } } self::$editlinks .= ob_get_clean(); echo '
'; self::ExecArea($info); echo '
'; $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 .= '' . $edit_link . ''; $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 ''; } /* * * 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 '
'; // 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 '
'; } } /** * 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: * @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 '' . $result . ''; } if( $wrapper_class ){ return '' . $result . ''; } 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 portion of the html document * */ public static function GetHead(){ \gp\tool\Plugins::Action('GetHead'); self::PrepGadgetContent(); echo ''; } 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 to self::$head_js * */ public static function MoveScript($string){ //conditional comments with script tags $patt = '#' . preg_quote('', '#') . '#s'; if( preg_match_all($patt,$string, $matches) ){ foreach($matches[0] as $match){ if( strpos($match,'#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" . ''; } echo "\n" . ''; } /** * Add the 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) . ''; return $page_title; } /** * Add the 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"; } /** * Add the 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" . ''; } } /** * 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 = ' . $layout_js_vars . ''); 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" . '' . "\n"; } /** * Add language values to the current page * @static */ public static function GetHead_Lang(){ global $langmessage; if( !count(self::$lang_values) ){ return; } echo "\n" . '"; } /** * 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 ''; return; } if( !$combine || $page->head_force_inline ){ echo "\n"; } 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 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 = ''; 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 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 $buffer = self::AddToBody($buffer, self::$head_js); //add jquery if needed $placeholder = ''; $replacement = ''; if( !empty(self::$head_js) || stripos($buffer, ''); $len = strpos($buffer, '') - $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 tag * */ public static function AddToBody($buffer, $add_string){ if( empty($add_string) ){ return $buffer; } $pos_body = stripos($buffer, ''; }else{ $message .= '

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.

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.

More information is available ' . 'in the Documentation.' . '

Reload this page to continue.

'; } return $message; } $message .= '

Error Details

' .pre($error_details) . '

Reload this page

' . '

Note: Error details are only ' . 'displayed for logged in administrators

' . \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['?'] = \gp\admin\Tools::FormatBytes(memory_get_usage()); $stats['?'] = \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['?'] = round($time, 3); $stats['?'] = 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 ' '; echo \gp\tool::Link( 'Special_Site_Map', $langmessage['site_map'] ); \gp\tool\Plugins::Action('GetSitemapLink'); echo ''; } } /** * 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 ''; } } 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 ''; echo 'Powered by ' . \CMS_NAME . ''; \gp\tool\Plugins::Action('GetPoweredByLink'); echo ''; } } } /** * 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()){ // case 'flush': self::Flush(); break; // remote request // file browser case 'body': \gp\tool::CheckTheme(); self::BodyAsHTML(); break; case 'admin': self::AdminHtml(); break; // // // 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