15f891b7eSNickeau<?php 25f891b7eSNickeau/** 35f891b7eSNickeau * Copyright (c) 2021. ComboStrap, Inc. and its affiliates. All Rights Reserved. 45f891b7eSNickeau * 55f891b7eSNickeau * This source code is licensed under the GPL license found in the 65f891b7eSNickeau * COPYING file in the root directory of this source tree. 75f891b7eSNickeau * 85f891b7eSNickeau * @license GPL 3 (https://www.gnu.org/licenses/gpl-3.0.en.html) 95f891b7eSNickeau * @author ComboStrap <support@combostrap.com> 105f891b7eSNickeau * 115f891b7eSNickeau */ 125f891b7eSNickeau 135f891b7eSNickeau/** 145f891b7eSNickeau * Plugin Webcode: Show webcode (Css, HTML) in a iframe 155f891b7eSNickeau * 165f891b7eSNickeau */ 175f891b7eSNickeau 185f891b7eSNickeau// must be run within Dokuwiki 19531e725cSNickeauuse ComboStrap\CallStack; 20a6bf47aaSNickeauuse ComboStrap\Dimension; 21*4cadd4f8SNickeauuse ComboStrap\Display; 22*4cadd4f8SNickeauuse ComboStrap\DokuwikiUrl; 235f891b7eSNickeauuse ComboStrap\LogUtility; 245f891b7eSNickeauuse ComboStrap\PluginUtility; 25531e725cSNickeauuse ComboStrap\Site; 2621913ab3SNickeauuse ComboStrap\TagAttributes; 275f891b7eSNickeau 285f891b7eSNickeauif (!defined('DOKU_INC')) die(); 295f891b7eSNickeau 305f891b7eSNickeau/** 315f891b7eSNickeau * Webcode 325f891b7eSNickeau */ 335f891b7eSNickeauclass syntax_plugin_combo_webcode extends DokuWiki_Syntax_Plugin 345f891b7eSNickeau{ 355f891b7eSNickeau 365f891b7eSNickeau const EXTERNAL_RESOURCES_ATTRIBUTE_DISPLAY = 'externalResources'; // In the action bar 375f891b7eSNickeau const EXTERNAL_RESOURCES_ATTRIBUTE_KEY = 'externalresources'; // In the code 385f891b7eSNickeau 395f891b7eSNickeau // Simple cache bursting implementation for the webCodeConsole.(js|css) file 405f891b7eSNickeau // They must be incremented manually when they changed 415f891b7eSNickeau const WEB_CSS_VERSION = 1.1; 425f891b7eSNickeau const WEB_CONSOLE_JS_VERSION = 2.1; 435f891b7eSNickeau 445f891b7eSNickeau const TAG = 'webcode'; 455f891b7eSNickeau 465f891b7eSNickeau /** 475f891b7eSNickeau * The tag that have codes 485f891b7eSNickeau */ 49531e725cSNickeau const CODE_TAGS = 50531e725cSNickeau array( 51531e725cSNickeau syntax_plugin_combo_code::CODE_TAG, 52531e725cSNickeau "plugin_combo_code", 53531e725cSNickeau syntax_plugin_combo_codemarkdown::TAG 54531e725cSNickeau ); 555f891b7eSNickeau 565f891b7eSNickeau /** 575f891b7eSNickeau * The attribute names in the array 585f891b7eSNickeau */ 595f891b7eSNickeau const CODES_ATTRIBUTE = "codes"; 605f891b7eSNickeau const USE_CONSOLE_ATTRIBUTE = "useConsole"; 61531e725cSNickeau const RENDERING_MODE_ATTRIBUTE = 'renderingmode'; 6221913ab3SNickeau const RENDERING_ONLY_RESULT = "onlyresult"; 635f891b7eSNickeau 645f891b7eSNickeau /** 65531e725cSNickeau * Marki code 665f891b7eSNickeau */ 67531e725cSNickeau const MARKI_LANG = 'marki'; 68531e725cSNickeau const DOKUWIKI_LANG = 'dw'; 69531e725cSNickeau const MARKIS = [self::MARKI_LANG, self::DOKUWIKI_LANG]; 705f891b7eSNickeau 715f891b7eSNickeau /** 725f891b7eSNickeau * Syntax Type. 735f891b7eSNickeau * 745f891b7eSNickeau * Needs to return one of the mode types defined in $PARSER_MODES in parser.php 755f891b7eSNickeau * @see https://www.dokuwiki.org/devel:syntax_plugins#syntax_types 765f891b7eSNickeau * 775f891b7eSNickeau * container because it may contain header in case of how to 785f891b7eSNickeau */ 795f891b7eSNickeau public function getType() 805f891b7eSNickeau { 815f891b7eSNickeau return 'container'; 825f891b7eSNickeau } 835f891b7eSNickeau 84531e725cSNickeau public function getPType() 85531e725cSNickeau { 86531e725cSNickeau return "stack"; 87531e725cSNickeau } 88531e725cSNickeau 89531e725cSNickeau 905f891b7eSNickeau /** 915f891b7eSNickeau * @return array 925f891b7eSNickeau * Allow which kind of plugin inside 935f891b7eSNickeau * 945f891b7eSNickeau * array('container', 'baseonly','formatting', 'substition', 'protected', 'disabled', 'paragraphs') 955f891b7eSNickeau * 965f891b7eSNickeau */ 975f891b7eSNickeau public function getAllowedTypes() 985f891b7eSNickeau { 995f891b7eSNickeau return array('container', 'baseonly', 'formatting', 'substition', 'protected', 'disabled', 'paragraphs'); 1005f891b7eSNickeau } 1015f891b7eSNickeau 1025f891b7eSNickeau 1035f891b7eSNickeau public function accepts($mode) 1045f891b7eSNickeau { 10521913ab3SNickeau 10621913ab3SNickeau return syntax_plugin_combo_preformatted::disablePreformatted($mode); 10721913ab3SNickeau 1085f891b7eSNickeau } 1095f891b7eSNickeau 1105f891b7eSNickeau /** 1115f891b7eSNickeau * @see Doku_Parser_Mode::getSort() 1125f891b7eSNickeau * The mode (plugin) with the lowest sort number will win out 1135f891b7eSNickeau * 1145f891b7eSNickeau * See {@link Doku_Parser_Mode_code} 1155f891b7eSNickeau */ 1165f891b7eSNickeau public function getSort() 1175f891b7eSNickeau { 1185f891b7eSNickeau return 99; 1195f891b7eSNickeau } 1205f891b7eSNickeau 1215f891b7eSNickeau /** 1225f891b7eSNickeau * Called before any calls to ConnectTo 1235f891b7eSNickeau * @return void 1245f891b7eSNickeau */ 1255f891b7eSNickeau function preConnect() 1265f891b7eSNickeau { 1275f891b7eSNickeau } 1285f891b7eSNickeau 1295f891b7eSNickeau /** 1305f891b7eSNickeau * Create a pattern that will called this plugin 1315f891b7eSNickeau * 1325f891b7eSNickeau * @param string $mode 1335f891b7eSNickeau * 1345f891b7eSNickeau * All dokuwiki mode can be seen in the parser.php file 1355f891b7eSNickeau * @see Doku_Parser_Mode::connectTo() 1365f891b7eSNickeau */ 1375f891b7eSNickeau public function connectTo($mode) 1385f891b7eSNickeau { 1395f891b7eSNickeau 1405f891b7eSNickeau $pattern = PluginUtility::getContainerTagPattern(self::TAG); 1419337a630SNickeau $this->Lexer->addEntryPattern($pattern, $mode, PluginUtility::getModeFromTag($this->getPluginComponent())); 1425f891b7eSNickeau 1435f891b7eSNickeau } 1445f891b7eSNickeau 1455f891b7eSNickeau 1465f891b7eSNickeau // This where the addPattern and addExitPattern are defined 1475f891b7eSNickeau public function postConnect() 1485f891b7eSNickeau { 1499337a630SNickeau $this->Lexer->addExitPattern('</' . self::TAG . '>', PluginUtility::getModeFromTag($this->getPluginComponent())); 1505f891b7eSNickeau } 1515f891b7eSNickeau 1525f891b7eSNickeau 1535f891b7eSNickeau /** 1545f891b7eSNickeau * Handle the match 1555f891b7eSNickeau * You get the match for each pattern in the $match variable 1565f891b7eSNickeau * $state says if it's an entry, exit or match pattern 1575f891b7eSNickeau * 1585f891b7eSNickeau * This is an instruction block and is cached apart from the rendering output 1595f891b7eSNickeau * There is two caches levels 1605f891b7eSNickeau * This cache may be suppressed with the url parameters ?purge=true 1615f891b7eSNickeau * 1625f891b7eSNickeau * The returned values are cached in an array that will be passed to the render method 1635f891b7eSNickeau * The handle function goal is to parse the matched syntax through the pattern function 1645f891b7eSNickeau * and to return the result for use in the renderer 1655f891b7eSNickeau * This result is always cached until the page is modified. 1665f891b7eSNickeau * @param string $match 1675f891b7eSNickeau * @param int $state 1685f891b7eSNickeau * @param int $pos 1695f891b7eSNickeau * @param Doku_Handler $handler 1705f891b7eSNickeau * @return array|bool 1715f891b7eSNickeau * @throws Exception 1725f891b7eSNickeau * @see DokuWiki_Syntax_Plugin::handle() 1735f891b7eSNickeau * 1745f891b7eSNickeau */ 1755f891b7eSNickeau public function handle($match, $state, $pos, Doku_Handler $handler) 1765f891b7eSNickeau { 1775f891b7eSNickeau switch ($state) { 1785f891b7eSNickeau 1795f891b7eSNickeau case DOKU_LEXER_ENTER : 1805f891b7eSNickeau 181531e725cSNickeau // Default 182531e725cSNickeau $defaultAttributes = array(); 183531e725cSNickeau $defaultAttributes['frameborder'] = 1; 184531e725cSNickeau $defaultAttributes['width'] = '100%'; 185531e725cSNickeau $defaultAttributes['name'] = "WebCode iFrame"; 186531e725cSNickeau $defaultAttributes[self::RENDERING_MODE_ATTRIBUTE] = 'story'; 187531e725cSNickeau // 'height' is set by the javascript if not set 188531e725cSNickeau // 'width' and 'scrolling' gets their natural value 1895f891b7eSNickeau 190531e725cSNickeau // Parse and create the call stack array 191531e725cSNickeau $tagAttributes = TagAttributes::createFromTagMatch($match, $defaultAttributes); 192531e725cSNickeau $callStackArray = $tagAttributes->toCallStackArray(); 1935f891b7eSNickeau 1945f891b7eSNickeau return array( 1955f891b7eSNickeau PluginUtility::STATE => $state, 196531e725cSNickeau PluginUtility::ATTRIBUTES => $callStackArray 1975f891b7eSNickeau ); 1985f891b7eSNickeau 1995f891b7eSNickeau 2005f891b7eSNickeau case DOKU_LEXER_UNMATCHED : 2015f891b7eSNickeau 20232b85071SNickeau return PluginUtility::handleAndReturnUnmatchedData(self::TAG, $match, $handler); 20332b85071SNickeau 2045f891b7eSNickeau 2055f891b7eSNickeau case DOKU_LEXER_EXIT: 2065f891b7eSNickeau 2075f891b7eSNickeau /** 2085f891b7eSNickeau * Capture all codes 2095f891b7eSNickeau */ 2105f891b7eSNickeau $codes = array(); 2115f891b7eSNickeau /** 2125f891b7eSNickeau * Does the javascript contains a console statement 2135f891b7eSNickeau */ 2145f891b7eSNickeau $useConsole = false; 215531e725cSNickeau 2165f891b7eSNickeau /** 217531e725cSNickeau * Callstack 218531e725cSNickeau */ 219531e725cSNickeau $callStack = CallStack::createFromHandler($handler); 220531e725cSNickeau $openingTag = $callStack->moveToPreviousCorrespondingOpeningCall(); 221531e725cSNickeau $renderingMode = strtolower($openingTag->getAttribute(self::RENDERING_MODE_ATTRIBUTE)); 222531e725cSNickeau 223531e725cSNickeau /** 224531e725cSNickeau * The mime (ie xml,html, ...) and code content are in two differents 225531e725cSNickeau * call. To be able to set the content to the good type 2265f891b7eSNickeau * we keep a trace of it 2275f891b7eSNickeau */ 2285f891b7eSNickeau $actualCodeType = ""; 2295f891b7eSNickeau 23021913ab3SNickeau /** 231531e725cSNickeau * Loop 232531e725cSNickeau */ 233531e725cSNickeau while ($actualTag = $callStack->next()) { 234531e725cSNickeau 235531e725cSNickeau 236531e725cSNickeau $tagName = $actualTag->getTagName(); 237531e725cSNickeau if (in_array($tagName, self::CODE_TAGS)) { 238531e725cSNickeau 239531e725cSNickeau /** 240531e725cSNickeau * Only rendering mode, we don't display the node 241531e725cSNickeau * on all node (enter, exit and unmatched) 24221913ab3SNickeau */ 24321913ab3SNickeau if ($renderingMode == self::RENDERING_ONLY_RESULT) { 244*4cadd4f8SNickeau $actualTag->addAttribute(Display::DISPLAY, "none"); 24521913ab3SNickeau } 24621913ab3SNickeau 247531e725cSNickeau switch ($actualTag->getState()) { 248531e725cSNickeau 249531e725cSNickeau case DOKU_LEXER_ENTER: 2505f891b7eSNickeau // Get the code (The content between the code nodes) 2515f891b7eSNickeau // We ltrim because the match gives us the \n at the beginning and at the end 252531e725cSNickeau $actualCodeType = strtolower(trim($actualTag->getType())); 2535f891b7eSNickeau 2545f891b7eSNickeau // Xml is html 2555f891b7eSNickeau if ($actualCodeType == 'xml') { 2565f891b7eSNickeau $actualCodeType = 'html'; 2575f891b7eSNickeau } 258531e725cSNickeau 259531e725cSNickeau // markdown, dokuwiki is marki 260531e725cSNickeau if (in_array($actualCodeType, ['md', 'markdown', 'dw'])) { 261531e725cSNickeau $actualCodeType = self::MARKI_LANG; 262531e725cSNickeau } 263531e725cSNickeau 26421913ab3SNickeau // The code for a language may be scattered in multiple block 2655f891b7eSNickeau if (!isset($codes[$actualCodeType])) { 2665f891b7eSNickeau $codes[$actualCodeType] = ""; 2675f891b7eSNickeau } 26821913ab3SNickeau 269531e725cSNickeau continue 2; 2705f891b7eSNickeau 271531e725cSNickeau case DOKU_LEXER_UNMATCHED: 2725f891b7eSNickeau 273531e725cSNickeau $codeContent = $actualTag->getPluginData()[PluginUtility::PAYLOAD]; 2745f891b7eSNickeau 2755f891b7eSNickeau if (empty($actualCodeType)) { 2765f891b7eSNickeau LogUtility::msg("The type of the code should not be null for the code content " . $codeContent, LogUtility::LVL_MSG_WARNING, self::TAG); 277531e725cSNickeau continue 2; 2785f891b7eSNickeau } 2795f891b7eSNickeau 2805f891b7eSNickeau // Append it 2815f891b7eSNickeau $codes[$actualCodeType] = $codes[$actualCodeType] . $codeContent; 2825f891b7eSNickeau 2835f891b7eSNickeau // Check if a javascript console function is used, only if the flag is not set to true 2845f891b7eSNickeau if (!$useConsole == true) { 2855f891b7eSNickeau if (in_array($actualCodeType, array('babel', 'javascript', 'html', 'xml'))) { 2865f891b7eSNickeau // if the code contains 'console.' 2875f891b7eSNickeau $result = preg_match('/' . 'console\.' . '/is', $codeContent); 2885f891b7eSNickeau if ($result) { 2895f891b7eSNickeau $useConsole = true; 2905f891b7eSNickeau } 2915f891b7eSNickeau } 2925f891b7eSNickeau } 2935f891b7eSNickeau // Reset 2945f891b7eSNickeau $actualCodeType = ""; 295531e725cSNickeau break; 296531e725cSNickeau 2975f891b7eSNickeau } 2985f891b7eSNickeau } 299531e725cSNickeau 3005f891b7eSNickeau } 301531e725cSNickeau 3025f891b7eSNickeau return array( 3035f891b7eSNickeau PluginUtility::STATE => $state, 3045f891b7eSNickeau self::CODES_ATTRIBUTE => $codes, 305531e725cSNickeau self::USE_CONSOLE_ATTRIBUTE => $useConsole, 306531e725cSNickeau PluginUtility::ATTRIBUTES => $openingTag->getAttributes() 3075f891b7eSNickeau ); 3085f891b7eSNickeau 3095f891b7eSNickeau } 3105f891b7eSNickeau return false; 3115f891b7eSNickeau 3125f891b7eSNickeau } 3135f891b7eSNickeau 3145f891b7eSNickeau /** 3155f891b7eSNickeau * Render the output 3165f891b7eSNickeau * @param string $mode 3175f891b7eSNickeau * @param Doku_Renderer $renderer 3185f891b7eSNickeau * @param array $data - what the function handle() return'ed 3195f891b7eSNickeau * @return bool - rendered correctly (not used) 3205f891b7eSNickeau * 3215f891b7eSNickeau * The rendering process 3225f891b7eSNickeau * @see DokuWiki_Syntax_Plugin::render() 3235f891b7eSNickeau * 3245f891b7eSNickeau */ 325*4cadd4f8SNickeau public function render($mode, Doku_Renderer $renderer, $data): bool 3265f891b7eSNickeau { 3275f891b7eSNickeau // The $data variable comes from the handle() function 3285f891b7eSNickeau // 3295f891b7eSNickeau // $mode = 'xhtml' means that we output html 3305f891b7eSNickeau // There is other mode such as metadata where you can output data for the headers (Not 100% sure) 3315f891b7eSNickeau if ($mode == 'xhtml') { 3325f891b7eSNickeau 3335f891b7eSNickeau 3345f891b7eSNickeau /** @var Doku_Renderer_xhtml $renderer */ 3355f891b7eSNickeau 3365f891b7eSNickeau $state = $data[PluginUtility::STATE]; 3375f891b7eSNickeau switch ($state) { 3385f891b7eSNickeau 3395f891b7eSNickeau 3405f891b7eSNickeau case DOKU_LEXER_UNMATCHED : 3415f891b7eSNickeau 34232b85071SNickeau $renderer->doc .= PluginUtility::renderUnmatched($data); 3435f891b7eSNickeau break; 3445f891b7eSNickeau 3455f891b7eSNickeau case DOKU_LEXER_EXIT : 3465f891b7eSNickeau $codes = $data[self::CODES_ATTRIBUTE]; 347531e725cSNickeau $callStackArray = $data[PluginUtility::ATTRIBUTES]; 348531e725cSNickeau $iFrameAttributes = TagAttributes::createFromCallStackArray($callStackArray, self::TAG); 349531e725cSNickeau 3505f891b7eSNickeau // Create the real output of webcode 3515f891b7eSNickeau if (sizeof($codes) == 0) { 3525f891b7eSNickeau return false; 3535f891b7eSNickeau } 3545f891b7eSNickeau 355531e725cSNickeau // Credits bar 356531e725cSNickeau $bar = '<div class="webcode-bar">'; 357531e725cSNickeau 3585f891b7eSNickeau 3595f891b7eSNickeau // Dokuwiki Code ? 360531e725cSNickeau if (array_key_exists(self::MARKI_LANG, $codes)) { 3615f891b7eSNickeau 362531e725cSNickeau $markiCode = $codes[self::MARKI_LANG]; 363531e725cSNickeau /** 364531e725cSNickeau * By default, markup code 365531e725cSNickeau * is rendered inside the page 366531e725cSNickeau * We got less problem such as iframe overflow 367531e725cSNickeau * due to lazy loading, such as relative link, ... 368531e725cSNickeau * 369531e725cSNickeau */ 370531e725cSNickeau if (!$iFrameAttributes->hasComponentAttribute("iframe")) { 371531e725cSNickeau $renderer->doc .= PluginUtility::render($markiCode); 372531e725cSNickeau return true; 373531e725cSNickeau } 374531e725cSNickeau 375531e725cSNickeau $queryParams = array( 376531e725cSNickeau 'call' => action_plugin_combo_webcode::CALL_ID, 377531e725cSNickeau action_plugin_combo_webcode::MARKI_PARAM => $markiCode 378531e725cSNickeau ); 379*4cadd4f8SNickeau $queryString = http_build_query($queryParams,'', DokuwikiUrl::AMPERSAND_CHARACTER); 380531e725cSNickeau $url = Site::getAjaxUrl() . "?$queryString"; 381*4cadd4f8SNickeau $iFrameAttributes->addOutputAttributeValue("src", $url); 3825f891b7eSNickeau 3835f891b7eSNickeau } else { 3845f891b7eSNickeau 3855f891b7eSNickeau 3865f891b7eSNickeau // Js, Html, Css 387531e725cSNickeau $iframeSrcValue = '<html><head>'; 388e55e4e71SNickeau $iframeSrcValue .= '<meta http-equiv="content-type" content="text/html; charset=UTF-8"/>'; 389531e725cSNickeau $iframeSrcValue .= '<title>Made by WebCode</title>'; 390e55e4e71SNickeau $iframeSrcValue .= '<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/normalize/3.0.3/normalize.min.css"/>'; 3915f891b7eSNickeau 3925f891b7eSNickeau 3935f891b7eSNickeau // External Resources such as css stylesheet or js 394531e725cSNickeau $externalResources = []; 395531e725cSNickeau if ($iFrameAttributes->hasComponentAttribute(self::EXTERNAL_RESOURCES_ATTRIBUTE_KEY)) { 396531e725cSNickeau $resources = $iFrameAttributes->getValueAndRemove(self::EXTERNAL_RESOURCES_ATTRIBUTE_KEY); 397531e725cSNickeau $externalResources = explode(",", $resources); 3985f891b7eSNickeau } 3995f891b7eSNickeau 4005f891b7eSNickeau // Babel Preprocessor, if babel is used, add it to the external resources 4015f891b7eSNickeau if (array_key_exists('babel', $codes)) { 4025f891b7eSNickeau $babelMin = "https://unpkg.com/babel-standalone@6/babel.min.js"; 4035f891b7eSNickeau // a load of babel invoke it (be sure to not have it twice 4045f891b7eSNickeau if (!(array_key_exists($babelMin, $externalResources))) { 4055f891b7eSNickeau $externalResources[] = $babelMin; 4065f891b7eSNickeau } 4075f891b7eSNickeau } 4085f891b7eSNickeau 4095f891b7eSNickeau // Add the external resources 4105f891b7eSNickeau foreach ($externalResources as $externalResource) { 4115f891b7eSNickeau $pathInfo = pathinfo($externalResource); 4125f891b7eSNickeau $fileExtension = $pathInfo['extension']; 4135f891b7eSNickeau switch ($fileExtension) { 4145f891b7eSNickeau case 'css': 415e55e4e71SNickeau $iframeSrcValue .= '<link rel="stylesheet" type="text/css" href="' . $externalResource . '"/>'; 4165f891b7eSNickeau break; 4175f891b7eSNickeau case 'js': 418531e725cSNickeau $iframeSrcValue .= '<script type="text/javascript" src="' . $externalResource . '"></script>'; 4195f891b7eSNickeau break; 4205f891b7eSNickeau } 4215f891b7eSNickeau } 4225f891b7eSNickeau 4235f891b7eSNickeau 4245f891b7eSNickeau // WebConsole style sheet 425531e725cSNickeau $iframeSrcValue .= '<link rel="stylesheet" type="text/css" href="' . PluginUtility::getResourceBaseUrl() . '/webcode/webcode-iframe.css?ver=' . self::WEB_CSS_VERSION . '"/>'; 4265f891b7eSNickeau 42721913ab3SNickeau // A little margin to make it neater 42821913ab3SNickeau // that can be overwritten via cascade 429531e725cSNickeau $iframeSrcValue .= '<style>body { margin:10px } /* default margin */</style>'; 43021913ab3SNickeau 43121913ab3SNickeau // The css 4325f891b7eSNickeau if (array_key_exists('css', $codes)) { 433531e725cSNickeau $iframeSrcValue .= '<!-- The CSS code -->'; 434531e725cSNickeau $iframeSrcValue .= '<style>' . $codes['css'] . '</style>'; 4355f891b7eSNickeau }; 436531e725cSNickeau $iframeSrcValue .= '</head><body>'; 4375f891b7eSNickeau if (array_key_exists('html', $codes)) { 438531e725cSNickeau $iframeSrcValue .= '<!-- The HTML code -->'; 439531e725cSNickeau $iframeSrcValue .= $codes['html']; 4405f891b7eSNickeau } 4415f891b7eSNickeau // The javascript console area is based at the end of the HTML document 4425f891b7eSNickeau $useConsole = $data[self::USE_CONSOLE_ATTRIBUTE]; 4435f891b7eSNickeau if ($useConsole) { 444531e725cSNickeau $iframeSrcValue .= '<!-- WebCode Console -->'; 445e55e4e71SNickeau $iframeSrcValue .= '<div><p class="webConsoleTitle">Console Output:</p>'; 446e55e4e71SNickeau $iframeSrcValue .= '<div id="webCodeConsole"></div>'; 447e55e4e71SNickeau $iframeSrcValue .= '<script type="text/javascript" src="' . PluginUtility::getResourceBaseUrl() . '/webcode/webcode-console.js?ver=' . self::WEB_CONSOLE_JS_VERSION . '"></script>'; 448531e725cSNickeau $iframeSrcValue .= '</div>'; 4495f891b7eSNickeau } 4505f891b7eSNickeau // The javascript comes at the end because it may want to be applied on previous HTML element 4515f891b7eSNickeau // as the page load in the IO order, javascript must be placed at the end 4525f891b7eSNickeau if (array_key_exists('javascript', $codes)) { 453531e725cSNickeau $iframeSrcValue .= '<!-- The Javascript code -->'; 454531e725cSNickeau $iframeSrcValue .= '<script type="text/javascript">' . $codes['javascript'] . '</script>'; 4555f891b7eSNickeau } 4565f891b7eSNickeau if (array_key_exists('babel', $codes)) { 457531e725cSNickeau $iframeSrcValue .= '<!-- The Babel code -->'; 458531e725cSNickeau $iframeSrcValue .= '<script type="text/babel">' . $codes['babel'] . '</script>'; 4595f891b7eSNickeau } 460531e725cSNickeau $iframeSrcValue .= '</body></html>'; 461*4cadd4f8SNickeau $iFrameAttributes->addOutputAttributeValue("srcdoc", $iframeSrcValue); 4625f891b7eSNickeau 463531e725cSNickeau // Code bar with button 464c3437056SNickeau $bar .= '<div class="webcode-bar-item">' . PluginUtility::getDocumentationHyperLink(self::TAG, "Rendered by WebCode", false) . '</div>'; 465531e725cSNickeau $bar .= '<div class="webcode-bar-item">' . $this->addJsFiddleButton($codes, $externalResources, $useConsole, $iFrameAttributes->getValue("name")) . '</div>'; 4665f891b7eSNickeau 467531e725cSNickeau 4685f891b7eSNickeau } 4695f891b7eSNickeau 470531e725cSNickeau /** 471531e725cSNickeau * If there is no height 472531e725cSNickeau */ 473a6bf47aaSNickeau if (!$iFrameAttributes->hasComponentAttribute(Dimension::HEIGHT_KEY)) { 4745f891b7eSNickeau 475531e725cSNickeau /** 476531e725cSNickeau * Adjust the height attribute 477531e725cSNickeau * of the iframe element 478531e725cSNickeau * Any styling attribute would take over 479531e725cSNickeau */ 480*4cadd4f8SNickeau PluginUtility::getSnippetManager()->attachInternalJavascriptForSlot(self::TAG); 481531e725cSNickeau /** 482531e725cSNickeau * CSS Height auto works when an image is loaded asynchronously but not 483531e725cSNickeau * when there is only text in the iframe 484531e725cSNickeau */ 485531e725cSNickeau //$iFrameAttributes->addStyleDeclaration("height", "auto"); 486531e725cSNickeau /** 487531e725cSNickeau * Due to margin at the bottom with css height=auto, 488531e725cSNickeau * we may see a scroll bar 489531e725cSNickeau * This block of code is to avoid scrolling, 490531e725cSNickeau * then scrolling = no if not set 491531e725cSNickeau */ 492531e725cSNickeau if (!$iFrameAttributes->hasComponentAttribute("scrolling")) { 493*4cadd4f8SNickeau $iFrameAttributes->addOutputAttributeValue("scrolling", "no"); 4945f891b7eSNickeau } 4955f891b7eSNickeau 4965f891b7eSNickeau } 4975f891b7eSNickeau 498531e725cSNickeau 499*4cadd4f8SNickeau PluginUtility::getSnippetManager()->attachCssInternalStyleSheetForSlot(self::TAG); 500531e725cSNickeau 501e55e4e71SNickeau /** 502e55e4e71SNickeau * The iframe does not have any width 503e55e4e71SNickeau * By default, we set it to 100% and it can be 504e55e4e71SNickeau * constraint with the `width` attributes that will 505e55e4e71SNickeau * set a a max-width 506e55e4e71SNickeau */ 50782a60d03SNickeau $iFrameAttributes->addStyleDeclarationIfNotSet("width","100%"); 508e55e4e71SNickeau 509531e725cSNickeau $iFrameHtml = $iFrameAttributes->toHtmlEnterTag("iframe") . '</iframe>'; 510531e725cSNickeau $bar .= '</div>'; // close the bar 511531e725cSNickeau $renderer->doc .= "<div class=\"webcode-wrapper\">" . $iFrameHtml . $bar . '</div>'; 512531e725cSNickeau 513531e725cSNickeau 5145f891b7eSNickeau break; 5155f891b7eSNickeau } 5165f891b7eSNickeau 5175f891b7eSNickeau return true; 5185f891b7eSNickeau } 5195f891b7eSNickeau return false; 5205f891b7eSNickeau } 5215f891b7eSNickeau 5225f891b7eSNickeau /** 5235f891b7eSNickeau * @param array $codes the array containing the codes 524531e725cSNickeau * @param array $externalResources the attributes of a call (for now the externalResources) 525531e725cSNickeau * @param bool $useConsole 526531e725cSNickeau * @param string $snippetTitle 5275f891b7eSNickeau * @return string the HTML form code 5285f891b7eSNickeau * 5295f891b7eSNickeau * Specification, see http://doc.jsfiddle.net/api/post.html 5305f891b7eSNickeau */ 531531e725cSNickeau public function addJsFiddleButton($codes, $externalResources, $useConsole = false, $snippetTitle = null) 5325f891b7eSNickeau { 5335f891b7eSNickeau 5345f891b7eSNickeau $postURL = "https://jsfiddle.net/api/post/library/pure/"; //No Framework 5355f891b7eSNickeau 5365f891b7eSNickeau 537531e725cSNickeau if ($useConsole) { 5385f891b7eSNickeau // If their is a console.log function, add the Firebug Lite support of JsFiddle 5395f891b7eSNickeau // Seems to work only with the Edge version of jQuery 5405f891b7eSNickeau // $postURL .= "edge/dependencies/Lite/"; 5415f891b7eSNickeau // The firebug logging is not working anymore because of 404 5425f891b7eSNickeau // Adding them here 5435f891b7eSNickeau $externalResources[] = 'The firebug resources for the console.log features'; 5445f891b7eSNickeau $externalResources[] = PluginUtility::getResourceBaseUrl() . '/firebug/firebug-lite.css'; 5455f891b7eSNickeau $externalResources[] = PluginUtility::getResourceBaseUrl() . '/firebug/firebug-lite-1.2.js'; 5465f891b7eSNickeau } 5475f891b7eSNickeau 5485f891b7eSNickeau // The below code is to prevent this JsFiddle bug: https://github.com/jsfiddle/jsfiddle-issues/issues/726 5495f891b7eSNickeau // The order of the resources is not guaranteed 5505f891b7eSNickeau // We pass then the resources only if their is one resources 5515f891b7eSNickeau // Otherwise we pass them as a script element in the HTML. 5525f891b7eSNickeau if (count($externalResources) <= 1) { 5539337a630SNickeau $externalResourcesInput = '<input type="hidden" name="resources" value="' . implode(",", $externalResources) . '"/>'; 5545f891b7eSNickeau } else { 5555f891b7eSNickeau $codes['html'] .= "\n\n\n\n\n<!-- The resources -->\n"; 5565f891b7eSNickeau $codes['html'] .= "<!-- They have been added here because their order is not guarantee through the API. -->\n"; 5575f891b7eSNickeau $codes['html'] .= "<!-- See: https://github.com/jsfiddle/jsfiddle-issues/issues/726 -->\n"; 5585f891b7eSNickeau foreach ($externalResources as $externalResource) { 5595f891b7eSNickeau if ($externalResource != "") { 5605f891b7eSNickeau $extension = pathinfo($externalResource)['extension']; 5615f891b7eSNickeau switch ($extension) { 5625f891b7eSNickeau case "css": 5635f891b7eSNickeau $codes['html'] .= "<link href=\"" . $externalResource . "\" rel=\"stylesheet\">\n"; 5645f891b7eSNickeau break; 5655f891b7eSNickeau case "js": 5665f891b7eSNickeau $codes['html'] .= "<script src=\"" . $externalResource . "\"></script>\n"; 5675f891b7eSNickeau break; 5685f891b7eSNickeau default: 5695f891b7eSNickeau $codes['html'] .= "<!-- " . $externalResource . " -->\n"; 5705f891b7eSNickeau } 5715f891b7eSNickeau } 5725f891b7eSNickeau } 5735f891b7eSNickeau } 5745f891b7eSNickeau 5755f891b7eSNickeau $jsCode = $codes['javascript']; 5765f891b7eSNickeau $jsPanel = 0; // language for the js specific panel (0 = JavaScript) 5775f891b7eSNickeau if (array_key_exists('babel', $codes)) { 5785f891b7eSNickeau $jsCode = $codes['babel']; 5795f891b7eSNickeau $jsPanel = 3; // 3 = Babel 5805f891b7eSNickeau } 5815f891b7eSNickeau 5825f891b7eSNickeau // Title and description 5835f891b7eSNickeau global $ID; 5845f891b7eSNickeau $pageTitle = tpl_pagetitle($ID, true); 585531e725cSNickeau if (!$snippetTitle) { 5865f891b7eSNickeau 587531e725cSNickeau $snippetTitle = "Code from " . $pageTitle; 5885f891b7eSNickeau } 5895f891b7eSNickeau $description = "Code from the page '" . $pageTitle . "' \n" . wl($ID, $absolute = true); 5905f891b7eSNickeau return '<form method="post" action="' . $postURL . '" target="_blank">' . 5919337a630SNickeau '<input type="hidden" name="title" value="' . htmlentities($snippetTitle) . '"/>' . 5929337a630SNickeau '<input type="hidden" name="description" value="' . htmlentities($description) . '"/>' . 5939337a630SNickeau '<input type="hidden" name="css" value="' . htmlentities($codes['css']) . '"/>' . 5949337a630SNickeau '<input type="hidden" name="html" value="' . htmlentities("<!-- The HTML -->" . $codes['html']) . '"/>' . 5959337a630SNickeau '<input type="hidden" name="js" value="' . htmlentities($jsCode) . '"/>' . 5969337a630SNickeau '<input type="hidden" name="panel_js" value="' . htmlentities($jsPanel) . '"/>' . 5979337a630SNickeau '<input type="hidden" name="wrap" value="b"/>' . //javascript no wrap in body 5985f891b7eSNickeau $externalResourcesInput . 5995f891b7eSNickeau '<button>Try the code</button>' . 6005f891b7eSNickeau '</form>'; 6015f891b7eSNickeau 6025f891b7eSNickeau } 6035f891b7eSNickeau 6045f891b7eSNickeau /** 6055f891b7eSNickeau * @param $codes the array containing the codes 6065f891b7eSNickeau * @param $attributes the attributes of a call (for now the externalResources) 6075f891b7eSNickeau * @return string the HTML form code 6085f891b7eSNickeau */ 6095f891b7eSNickeau public function addCodePenButton($codes, $attributes) 6105f891b7eSNickeau { 6115f891b7eSNickeau // TODO 6125f891b7eSNickeau // http://blog.codepen.io/documentation/api/prefill/ 6135f891b7eSNickeau } 6145f891b7eSNickeau 6155f891b7eSNickeau 6165f891b7eSNickeau} 617