xref: /plugin/combo/ComboStrap/Tag/WebCodeTag.php (revision 1e6623d9be7c26643a021b887e027c9b902710d7)
104fd306cSNickeau<?php
204fd306cSNickeau
304fd306cSNickeaunamespace ComboStrap\Tag;
404fd306cSNickeau
504fd306cSNickeauuse ComboStrap\CallStack;
604fd306cSNickeauuse ComboStrap\Dimension;
704fd306cSNickeauuse ComboStrap\Display;
804fd306cSNickeauuse ComboStrap\ExceptionBadState;
904fd306cSNickeauuse ComboStrap\ExceptionCompile;
1004fd306cSNickeauuse ComboStrap\ExceptionNotFound;
1104fd306cSNickeauuse ComboStrap\ExecutionContext;
1204fd306cSNickeauuse ComboStrap\FetcherMarkup;
1304fd306cSNickeauuse ComboStrap\FetcherMarkupWebcode;
1404fd306cSNickeauuse ComboStrap\FetcherRawLocalPath;
1504fd306cSNickeauuse ComboStrap\LogUtility;
1604fd306cSNickeauuse ComboStrap\PluginUtility;
1704fd306cSNickeauuse ComboStrap\TagAttribute\StyleAttribute;
1804fd306cSNickeauuse ComboStrap\TagAttributes;
1904fd306cSNickeauuse ComboStrap\WikiPath;
2004fd306cSNickeauuse syntax_plugin_combo_code;
2104fd306cSNickeauuse syntax_plugin_combo_codemarkdown;
2204fd306cSNickeau
2304fd306cSNickeauclass WebCodeTag
2404fd306cSNickeau{
2504fd306cSNickeau
2604fd306cSNickeau    public const TAG = 'webcode';
2704fd306cSNickeau    /**
2804fd306cSNickeau     * The tag that have codes
2904fd306cSNickeau     */
3004fd306cSNickeau    public const CODE_TAGS = array(
3104fd306cSNickeau        syntax_plugin_combo_code::CODE_TAG,
3204fd306cSNickeau        "plugin_combo_code",
3304fd306cSNickeau        syntax_plugin_combo_codemarkdown::TAG
3404fd306cSNickeau    );
3504fd306cSNickeau    /**
3604fd306cSNickeau     * The attribute names in the array
3704fd306cSNickeau     */
3804fd306cSNickeau    public const CODES_ATTRIBUTE = "codes";
3904fd306cSNickeau    public const EXTERNAL_RESOURCES_ATTRIBUTE_DISPLAY = 'externalResources';
4004fd306cSNickeau    public const USE_CONSOLE_ATTRIBUTE = "useConsole";
4104fd306cSNickeau    public const RENDERING_ONLY_RESULT_DEPRECATED = "onlyresult";
4204fd306cSNickeau    public const CANONICAL = WebCodeTag::TAG;
4304fd306cSNickeau    public const DOKUWIKI_LANG = 'dw';
4404fd306cSNickeau    public const FRAMEBORDER_ATTRIBUTE = "frameborder";
4504fd306cSNickeau    /**
4604fd306cSNickeau     * @deprecated for type
4704fd306cSNickeau     */
4804fd306cSNickeau    public const RENDERING_MODE_ATTRIBUTE = 'renderingmode';
4904fd306cSNickeau    public const MARKIS = [WebCodeTag::MARKI_LANG, WebCodeTag::DOKUWIKI_LANG];
5004fd306cSNickeau    public const EXTERNAL_RESOURCES_ATTRIBUTE_KEY = 'externalresources';
5104fd306cSNickeau    /**
5204fd306cSNickeau     * Marki code
5304fd306cSNickeau     */
5404fd306cSNickeau    public const MARKI_LANG = 'marki';
5504fd306cSNickeau    public const IFRAME_BOOLEAN_ATTRIBUTE = "iframe";
5604fd306cSNickeau    const STORY_TYPE = "story";
5704fd306cSNickeau    const RESULT_TYPE = "result";
5804fd306cSNickeau    const INJECT_TYPE = "inject";
5904fd306cSNickeau
6004fd306cSNickeau    public static function getClass(): string
6104fd306cSNickeau    {
6204fd306cSNickeau        return StyleAttribute::addComboStrapSuffix(WebCodeTag::TAG);
6304fd306cSNickeau    }
6404fd306cSNickeau
6504fd306cSNickeau    public static function getKnownTypes(): array
6604fd306cSNickeau    {
6704fd306cSNickeau        return [self::STORY_TYPE, self::RESULT_TYPE, self::INJECT_TYPE];
6804fd306cSNickeau    }
6904fd306cSNickeau
7004fd306cSNickeau    public static function getDefaultAttributes(): array
7104fd306cSNickeau    {
7204fd306cSNickeau        $defaultAttributes = array();
7304fd306cSNickeau        $defaultAttributes[Dimension::WIDTH_KEY] = '100%';
7404fd306cSNickeau        // 'type': no default to see if it was set because the default now is dependent on the content
7504fd306cSNickeau        // 'height' is set by the javascript if not set
7604fd306cSNickeau        // 'width' and 'scrolling' gets their natural value
7704fd306cSNickeau        return $defaultAttributes;
7804fd306cSNickeau    }
7904fd306cSNickeau
8004fd306cSNickeau    public static function handleExit(\Doku_Handler $handler): array
8104fd306cSNickeau    {
8204fd306cSNickeau        /**
8304fd306cSNickeau         * Capture all codes
8404fd306cSNickeau         */
8504fd306cSNickeau        $codes = array();
8604fd306cSNickeau        /**
8704fd306cSNickeau         * Does the javascript contains a console statement
8804fd306cSNickeau         */
8904fd306cSNickeau        $useConsole = false;
9004fd306cSNickeau
9104fd306cSNickeau        /**
9204fd306cSNickeau         * Callstack
9304fd306cSNickeau         */
9404fd306cSNickeau        $callStack = CallStack::createFromHandler($handler);
9504fd306cSNickeau        $openingTag = $callStack->moveToPreviousCorrespondingOpeningCall();
9604fd306cSNickeau        $type = $openingTag->getType();
9704fd306cSNickeau        $renderingMode = $openingTag->getAttribute(WebCodeTag::RENDERING_MODE_ATTRIBUTE);
9804fd306cSNickeau        if ($renderingMode !== null) {
9904fd306cSNickeau            LogUtility::warning("The `renderingmode` attribute has been deprecated for the webcode `type` attribute.");
10004fd306cSNickeau            if ($type === null) {
10104fd306cSNickeau                $type = strtolower($renderingMode);
10204fd306cSNickeau            }
10304fd306cSNickeau        }
10404fd306cSNickeau        if ($type === WebCodeTag::RENDERING_ONLY_RESULT_DEPRECATED) {
10504fd306cSNickeau            LogUtility::warning("The `type` value (" . self::RENDERING_ONLY_RESULT_DEPRECATED . ") should be replaced by (" . self::RESULT_TYPE . ")");
10604fd306cSNickeau            $type = WebCodeTag::RESULT_TYPE;
10704fd306cSNickeau        }
10804fd306cSNickeau
10904fd306cSNickeau        /**
11004fd306cSNickeau         * The mime (ie xml,html, ...) and code content are in two differents
11104fd306cSNickeau         * call. To be able to set the content to the good type
11204fd306cSNickeau         * we keep a trace of it
11304fd306cSNickeau         */
11404fd306cSNickeau        $actualCodeType = "";
11504fd306cSNickeau
11604fd306cSNickeau        /**
11704fd306cSNickeau         * Loop
11804fd306cSNickeau         */
11904fd306cSNickeau        while ($actualTag = $callStack->next()) {
12004fd306cSNickeau
12104fd306cSNickeau
12204fd306cSNickeau            $tagName = $actualTag->getTagName();
12304fd306cSNickeau            if (in_array($tagName, WebCodeTag::CODE_TAGS)) {
12404fd306cSNickeau
12504fd306cSNickeau                /**
12604fd306cSNickeau                 * Only result or inject mode, we don't display the code
12704fd306cSNickeau                 * on all node (enter, exit and unmatched)
12804fd306cSNickeau                 */
12904fd306cSNickeau                if (in_array($type, [WebCodeTag::RESULT_TYPE, self::INJECT_TYPE])) {
13004fd306cSNickeau                    $actualTag->addAttribute(Display::DISPLAY, Display::DISPLAY_NONE_VALUE);
13104fd306cSNickeau                }
13204fd306cSNickeau
13304fd306cSNickeau                switch ($actualTag->getState()) {
13404fd306cSNickeau
13504fd306cSNickeau                    case DOKU_LEXER_ENTER:
13604fd306cSNickeau                        // Get the code (The content between the code nodes)
13704fd306cSNickeau                        // We ltrim because the match gives us the \n at the beginning and at the end
13804fd306cSNickeau                        $actualCodeType = strtolower(trim($actualTag->getType()));
13904fd306cSNickeau
14004fd306cSNickeau                        // Xml is html
14104fd306cSNickeau                        if ($actualCodeType === 'xml') {
14204fd306cSNickeau                            $actualCodeType = 'html';
14304fd306cSNickeau                        }
14404fd306cSNickeau
14504fd306cSNickeau                        // markdown, dokuwiki is marki
14604fd306cSNickeau                        if (in_array($actualCodeType, ['md', 'markdown', 'dw'])) {
14704fd306cSNickeau                            $actualCodeType = WebCodeTag::MARKI_LANG;
14804fd306cSNickeau                        }
14904fd306cSNickeau
15004fd306cSNickeau                        // The code for a language may be scattered in multiple block
15104fd306cSNickeau                        if (!isset($codes[$actualCodeType])) {
15204fd306cSNickeau                            $codes[$actualCodeType] = "";
15304fd306cSNickeau                        }
15404fd306cSNickeau
15504fd306cSNickeau                        continue 2;
15604fd306cSNickeau
15704fd306cSNickeau                    case DOKU_LEXER_UNMATCHED:
15804fd306cSNickeau
15904fd306cSNickeau                        $codeContent = $actualTag->getPluginData()[PluginUtility::PAYLOAD];
16004fd306cSNickeau
16104fd306cSNickeau                        if (empty($actualCodeType)) {
16204fd306cSNickeau                            LogUtility::msg("The type of the code should not be null for the code content " . $codeContent, LogUtility::LVL_MSG_WARNING, WebCodeTag::TAG);
16304fd306cSNickeau                            continue 2;
16404fd306cSNickeau                        }
16504fd306cSNickeau
16604fd306cSNickeau                        // Append it
16704fd306cSNickeau                        $codes[$actualCodeType] = $codes[$actualCodeType] . $codeContent;
16804fd306cSNickeau
16904fd306cSNickeau                        // Check if a javascript console function is used, only if the flag is not set to true
17004fd306cSNickeau                        if (!$useConsole) {
17104fd306cSNickeau                            if (in_array($actualCodeType, array('babel', 'javascript', 'html', 'xml'))) {
17204fd306cSNickeau                                // if the code contains 'console.'
17304fd306cSNickeau                                $result = preg_match('/' . 'console\.' . '/is', $codeContent);
17404fd306cSNickeau                                if ($result) {
17504fd306cSNickeau                                    $useConsole = true;
17604fd306cSNickeau                                }
17704fd306cSNickeau                            }
17804fd306cSNickeau                        }
17904fd306cSNickeau                        // Reset
18004fd306cSNickeau                        $actualCodeType = "";
18104fd306cSNickeau                        break;
18204fd306cSNickeau
18304fd306cSNickeau                }
18404fd306cSNickeau            }
18504fd306cSNickeau
18604fd306cSNickeau        }
18704fd306cSNickeau
18804fd306cSNickeau        /**
18904fd306cSNickeau         * By default, markup code
19004fd306cSNickeau         * is rendered inside the page
19104fd306cSNickeau         * We got less problem such as iframe overflow
19204fd306cSNickeau         * due to lazy loading, such as relative link, ...
19304fd306cSNickeau         */
19404fd306cSNickeau        if (
19504fd306cSNickeau            array_key_exists(WebCodeTag::MARKI_LANG, $codes)
19604fd306cSNickeau            && count($codes) === 1
19704fd306cSNickeau            && $openingTag->getAttribute(WebCodeTag::IFRAME_BOOLEAN_ATTRIBUTE) === null
19804fd306cSNickeau            && $openingTag->getType() === null
19904fd306cSNickeau        ) {
20004fd306cSNickeau            $openingTag->setType(self::INJECT_TYPE);
20104fd306cSNickeau        }
20204fd306cSNickeau
20304fd306cSNickeau        return [
20404fd306cSNickeau            WebCodeTag::CODES_ATTRIBUTE => $codes,
20504fd306cSNickeau            WebCodeTag::USE_CONSOLE_ATTRIBUTE => $useConsole,
20604fd306cSNickeau            PluginUtility::ATTRIBUTES => $openingTag->getAttributes()
20704fd306cSNickeau        ];
20804fd306cSNickeau    }
20904fd306cSNickeau
21004fd306cSNickeau    /**
21104fd306cSNickeau     * Tag is of an iframe (Web code) or a div (wiki markup)
21204fd306cSNickeau     */
21304fd306cSNickeau    public static function renderExit(TagAttributes $tagAttributes, array $data)
21404fd306cSNickeau    {
21504fd306cSNickeau
21604fd306cSNickeau        $codes = $data[WebCodeTag::CODES_ATTRIBUTE];
21704fd306cSNickeau
21804fd306cSNickeau        $type = $tagAttributes->getType();
21904fd306cSNickeau        if ($type === null) {
22004fd306cSNickeau            $type = self::STORY_TYPE;
22104fd306cSNickeau        }
22204fd306cSNickeau
22304fd306cSNickeau        /**
22404fd306cSNickeau         * Rendering mode is used in handle exit, we delete it
22504fd306cSNickeau         * to not get it in the HTML output
22604fd306cSNickeau         */
22704fd306cSNickeau        $tagAttributes->removeComponentAttributeIfPresent(WebCodeTag::RENDERING_MODE_ATTRIBUTE);
22804fd306cSNickeau
22904fd306cSNickeau        // Create the real output of webcode
23004fd306cSNickeau        if (sizeof($codes) == 0) {
23104fd306cSNickeau            return false;
23204fd306cSNickeau        }
23304fd306cSNickeau
23404fd306cSNickeau
23504fd306cSNickeau        // Css
23604fd306cSNickeau        $snippetSystem = PluginUtility::getSnippetManager();
23704fd306cSNickeau        $snippetSystem->attachCssInternalStyleSheet(WebCodeTag::TAG);
23804fd306cSNickeau        $snippetSystem->attachJavascriptFromComponentId(WebCodeTag::TAG);
23904fd306cSNickeau
24004fd306cSNickeau        // Mermaid code ?
24104fd306cSNickeau        if (array_key_exists(MermaidTag::MERMAID_CODE, $codes)) {
24204fd306cSNickeau            $mermaidCode = "";
24304fd306cSNickeau            foreach ($codes as $codeKey => $code) {
24404fd306cSNickeau                if ($codeKey !== MermaidTag::MERMAID_CODE) {
24504fd306cSNickeau                    LogUtility::error("The code type ($codeKey) was mixed with mermaid code in a webcode and this is not yet supported. The code was skipped");
24604fd306cSNickeau                    continue;
24704fd306cSNickeau                }
24804fd306cSNickeau                $mermaidCode .= $code;
24904fd306cSNickeau            }
25004fd306cSNickeau            $tagAttributes->addComponentAttributeValue(MermaidTag::MARKUP_CONTENT_ATTRIBUTE, $mermaidCode);
25104fd306cSNickeau            return MermaidTag::renderEnter($tagAttributes);
25204fd306cSNickeau        }
25304fd306cSNickeau
25404fd306cSNickeau        /**
25504fd306cSNickeau         * Dokuwiki Code
25604fd306cSNickeau         * (Just HTML)
25704fd306cSNickeau         */
25804fd306cSNickeau        if (array_key_exists(WebCodeTag::MARKI_LANG, $codes)) {
25904fd306cSNickeau
26004fd306cSNickeau            $markupCode = $codes[WebCodeTag::MARKI_LANG];
26104fd306cSNickeau
26204fd306cSNickeau            if ($type === self::INJECT_TYPE) {
26304fd306cSNickeau                /**
26404fd306cSNickeau                 * the div is to be able to apply some CSS
26504fd306cSNickeau                 * such as don't show editbutton on webcode
26604fd306cSNickeau                 */
26704fd306cSNickeau                $html = $tagAttributes->toHtmlEnterTag("div");
26804fd306cSNickeau                try {
26904fd306cSNickeau                    $contextPath = ExecutionContext::getActualOrCreateFromEnv()
27004fd306cSNickeau                        ->getContextPath();
27104fd306cSNickeau                    $html .= FetcherMarkup::confChild()
27204fd306cSNickeau                        ->setRequestedMarkupString($markupCode)
27304fd306cSNickeau                        ->setDeleteRootBlockElement(false)
27404fd306cSNickeau                        ->setIsDocument(false)
27504fd306cSNickeau                        ->setRequestedContextPath($contextPath)
27604fd306cSNickeau                        ->setRequestedMimeToXhtml()
27704fd306cSNickeau                        ->build()
27804fd306cSNickeau                        ->getFetchString();
27904fd306cSNickeau                } catch (ExceptionCompile $e) {
28004fd306cSNickeau                    $html .= $e->getMessage();
28104fd306cSNickeau                    LogUtility::log2file("Error while rendering webcode", LogUtility::LVL_MSG_ERROR, WebCodeTag::CANONICAL, $e);
28204fd306cSNickeau                }
28304fd306cSNickeau                $html .= "</div>";
28404fd306cSNickeau                return $html;
28504fd306cSNickeau            }
28604fd306cSNickeau
28704fd306cSNickeau            /**
28804fd306cSNickeau             * Iframe output
28904fd306cSNickeau             */
29004fd306cSNickeau            $tagAttributes->removeComponentAttribute(WebCodeTag::IFRAME_BOOLEAN_ATTRIBUTE);
29104fd306cSNickeau
29204fd306cSNickeau            if (!$tagAttributes->hasAttribute(TagAttributes::NAME_ATTRIBUTE)) {
29304fd306cSNickeau                $tagAttributes->addOutputAttributeValueIfNotEmpty(TagAttributes::NAME_ATTRIBUTE, "WebCode iFrame");
29404fd306cSNickeau            }
29504fd306cSNickeau            try {
29604fd306cSNickeau                $url = FetcherMarkupWebcode::createFetcherMarkup($markupCode)
29704fd306cSNickeau                    ->getFetchUrl()
29804fd306cSNickeau                    ->toString();
29904fd306cSNickeau                $tagAttributes->addOutputAttributeValue("src", $url);
30004fd306cSNickeau            } catch (ExceptionBadState $e) {
30104fd306cSNickeau                // The markup is provided, we shouldn't have a bad state
30204fd306cSNickeau                LogUtility::internalError("We were unable to set the iframe URL. Error:{$e->getMessage()}", WebCodeTag::CANONICAL);
30304fd306cSNickeau            }
30404fd306cSNickeau            return self::finishIframe($tagAttributes);
30504fd306cSNickeau
30604fd306cSNickeau
30704fd306cSNickeau        }
30804fd306cSNickeau
30904fd306cSNickeau
31004fd306cSNickeau        /**
31104fd306cSNickeau         * Js Html Css language
31204fd306cSNickeau         */
31304fd306cSNickeau        if ($type === self::INJECT_TYPE) {
31404fd306cSNickeau            $htmlToInject = self::getCss($codes);
31504fd306cSNickeau            return $htmlToInject . self::getBodyHtmlAndJavascript($codes, false);
31604fd306cSNickeau        }
31704fd306cSNickeau
31804fd306cSNickeau        /** @noinspection JSUnresolvedLibraryURL */
31904fd306cSNickeau
32004fd306cSNickeau        $headIFrame = <<<EOF
32104fd306cSNickeau<meta http-equiv="content-type" content="text/html; charset=UTF-8"/>
32204fd306cSNickeau<link id="normalize" rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/normalize/3.0.3/normalize.min.css"/>
32304fd306cSNickeauEOF;
32404fd306cSNickeau
32504fd306cSNickeau
32604fd306cSNickeau        // External Resources such as css stylesheet or js
32704fd306cSNickeau        $externalResources = [];
32804fd306cSNickeau        if ($tagAttributes->hasComponentAttribute(WebCodeTag::EXTERNAL_RESOURCES_ATTRIBUTE_KEY)) {
32904fd306cSNickeau            LogUtility::warning("The (" . WebCodeTag::EXTERNAL_RESOURCES_ATTRIBUTE_KEY . ") has been deprecated. You should put your script/link in a code block with the `display` attribute set to `none`.");
33004fd306cSNickeau            $resources = $tagAttributes->getValueAndRemove(WebCodeTag::EXTERNAL_RESOURCES_ATTRIBUTE_KEY);
33104fd306cSNickeau            $externalResources = explode(",", $resources);
33204fd306cSNickeau        }
33304fd306cSNickeau
334*1e6623d9Sgerardnico        // Jsx / Babel Preprocessor, if babel is used, add it to the external resources
33504fd306cSNickeau        if (array_key_exists('babel', $codes)) {
33604fd306cSNickeau            $babelMin = "https://unpkg.com/babel-standalone@6/babel.min.js";
33704fd306cSNickeau            // a load of babel invoke it (be sure to not have it twice
33804fd306cSNickeau            if (!(array_key_exists($babelMin, $externalResources))) {
33904fd306cSNickeau                $externalResources[] = $babelMin;
34004fd306cSNickeau            }
34104fd306cSNickeau        }
34204fd306cSNickeau
34304fd306cSNickeau        // Add the external resources
34404fd306cSNickeau        foreach ($externalResources as $externalResource) {
34504fd306cSNickeau            $pathInfo = pathinfo($externalResource);
34604fd306cSNickeau            $fileExtension = $pathInfo['extension'];
34704fd306cSNickeau            switch ($fileExtension) {
34804fd306cSNickeau                case 'css':
34904fd306cSNickeau                    $headIFrame .= "<link rel=\"stylesheet\" type=\"text/css\" href=\"$externalResource\"/>";
35004fd306cSNickeau                    break;
35104fd306cSNickeau                case 'js':
35204fd306cSNickeau                    $headIFrame .= "<script type=\"text/javascript\" src=\"$externalResource\"></script>";
35304fd306cSNickeau                    break;
35404fd306cSNickeau            }
35504fd306cSNickeau        }
35604fd306cSNickeau
35704fd306cSNickeau        // WebConsole style sheet
35804fd306cSNickeau        $webcodeClass = WebCodeTag::getClass();
35904fd306cSNickeau        $cssUrl = FetcherRawLocalPath::createFromPath(WikiPath::createComboResource("webcode:webcode-iframe.css"))->getFetchUrl()->toHtmlString();
36004fd306cSNickeau        $headIFrame .= "<link class='$webcodeClass' rel=\"stylesheet\" type=\"text/css\" href=\"$cssUrl\"/>";
36104fd306cSNickeau
36204fd306cSNickeau        // A little margin to make it neater
36304fd306cSNickeau        // that can be overwritten via cascade
36404fd306cSNickeau        $headIFrame .= "<style class=\"$webcodeClass\">body { margin:10px } /* default margin */</style>";
36504fd306cSNickeau
36604fd306cSNickeau        // The css
36704fd306cSNickeau        $headIFrame .= self::getCss($codes);
36804fd306cSNickeau
36904fd306cSNickeau        // The javascript console script should be first to handle console.log in the content
37004fd306cSNickeau        $useConsole = $data[WebCodeTag::USE_CONSOLE_ATTRIBUTE];
37104fd306cSNickeau        if ($useConsole) {
37204fd306cSNickeau            $url = FetcherRawLocalPath::createFromPath(WikiPath::createComboResource("webcode:webcode-console.js"))->getFetchUrl()->toHtmlString();
37304fd306cSNickeau            $headIFrame .= <<<EOF
37404fd306cSNickeau<script class="$webcodeClass" type="text/javascript" src="$url"></script>
37504fd306cSNickeauEOF;
37604fd306cSNickeau        }
37704fd306cSNickeau        $body = self::getBodyHtmlAndJavascript($codes, $useConsole);
37804fd306cSNickeau        $iframeSrcValue = <<<EOF
37904fd306cSNickeau<html lang="en">
38004fd306cSNickeau<head>
38104fd306cSNickeau<title>Made by WebCode</title>
38204fd306cSNickeau$headIFrame
38304fd306cSNickeau</head>
38404fd306cSNickeau<body>
38504fd306cSNickeau$body
38604fd306cSNickeau</body>
38704fd306cSNickeau</html>
38804fd306cSNickeauEOF;
38904fd306cSNickeau        $tagAttributes->addOutputAttributeValue("srcdoc", $iframeSrcValue);
39004fd306cSNickeau
39104fd306cSNickeau        // Code bar with button
39204fd306cSNickeau        // Credits bar
39304fd306cSNickeau        $bar = '<div class="webcode-bar">';
39404fd306cSNickeau        $bar .= '<div class="webcode-bar-item">' . PluginUtility::getDocumentationHyperLink(WebCodeTag::TAG, "Rendered by WebCode", false) . '</div>';
39504fd306cSNickeau        $bar .= '<div class="webcode-bar-item">' . self::addJsFiddleButton($codes, $externalResources, $useConsole, $tagAttributes->getValue("name")) . '</div>';
39604fd306cSNickeau        $bar .= '</div>';
39704fd306cSNickeau
39804fd306cSNickeau        return self::finishIframe($tagAttributes, $bar);
39904fd306cSNickeau
40004fd306cSNickeau
40104fd306cSNickeau    }
40204fd306cSNickeau
40304fd306cSNickeau    /**
40404fd306cSNickeau     * @param array $codes the array containing the codes
40504fd306cSNickeau     * @param array $externalResources the attributes of a call (for now the externalResources)
40604fd306cSNickeau     * @param bool $useConsole
40704fd306cSNickeau     * @param null $snippetTitle
40804fd306cSNickeau     * @return string the HTML form code
40904fd306cSNickeau     *
41004fd306cSNickeau     * Specification, see http://doc.jsfiddle.net/api/post.html
41104fd306cSNickeau     */
41204fd306cSNickeau    public static function addJsFiddleButton($codes, $externalResources, $useConsole = false, $snippetTitle = null): string
41304fd306cSNickeau    {
41404fd306cSNickeau
41504fd306cSNickeau        $postURL = "https://jsfiddle.net/api/post/library/pure/"; //No Framework
41604fd306cSNickeau
41704fd306cSNickeau
41804fd306cSNickeau        if ($useConsole) {
41904fd306cSNickeau            // If their is a console.log function, add the Firebug Lite support of JsFiddle
42004fd306cSNickeau            // Seems to work only with the Edge version of jQuery
42104fd306cSNickeau            // $postURL .= "edge/dependencies/Lite/";
42204fd306cSNickeau            // The firebug logging is not working anymore because of 404
42304fd306cSNickeau
42404fd306cSNickeau            // Adding them here
42504fd306cSNickeau            // The firebug resources for the console.log features
42604fd306cSNickeau            try {
42704fd306cSNickeau                $externalResources[] = FetcherRawLocalPath::createFromPath(WikiPath::createComboResource(':firebug:firebug-lite.css'))->getFetchUrl()->toString();
42804fd306cSNickeau                $externalResources[] = FetcherRawLocalPath::createFromPath(WikiPath::createComboResource(':firebug:firebug-lite-1.2.js'))->getFetchUrl()->toString();
42904fd306cSNickeau            } catch (ExceptionNotFound $e) {
43004fd306cSNickeau                LogUtility::internalError("We were unable to add the firebug css and js. Error: {$e->getMessage()}", WebCodeTag::CANONICAL);
43104fd306cSNickeau            }
43204fd306cSNickeau
43304fd306cSNickeau        }
43404fd306cSNickeau
43504fd306cSNickeau        // The below code is to prevent this JsFiddle bug: https://github.com/jsfiddle/jsfiddle-issues/issues/726
43604fd306cSNickeau        // The order of the resources is not guaranteed
43704fd306cSNickeau        // We pass then the resources only if their is one resources
43804fd306cSNickeau        // Otherwise we pass them as a script element in the HTML.
43904fd306cSNickeau        if (count($externalResources) <= 1) {
44004fd306cSNickeau            $externalResourcesInput = '<input type="hidden" name="resources" value="' . implode(",", $externalResources) . '"/>';
44104fd306cSNickeau        } else {
44270bbd7f1Sgerardnico            $externalResourcesInput = '';
44370bbd7f1Sgerardnico            if (!array_key_exists('html', $codes)) {
44470bbd7f1Sgerardnico                $codes['html'] = '';
44570bbd7f1Sgerardnico            }
44604fd306cSNickeau            $codes['html'] .= "\n\n\n\n\n<!-- The resources -->\n";
44704fd306cSNickeau            $codes['html'] .= "<!-- They have been added here because their order is not guarantee through the API. -->\n";
44804fd306cSNickeau            $codes['html'] .= "<!-- See: https://github.com/jsfiddle/jsfiddle-issues/issues/726 -->\n";
44904fd306cSNickeau            foreach ($externalResources as $externalResource) {
45004fd306cSNickeau                if ($externalResource !== "") {
45104fd306cSNickeau                    $extension = pathinfo($externalResource)['extension'];
45204fd306cSNickeau                    switch ($extension) {
45304fd306cSNickeau                        case "css":
45404fd306cSNickeau                            $codes['html'] .= "<link href=\"$externalResource\" rel=\"stylesheet\"/>\n";
45504fd306cSNickeau                            break;
45604fd306cSNickeau                        case "js":
45704fd306cSNickeau                            $codes['html'] .= "<script src=\"$externalResource\"></script>\n";
45804fd306cSNickeau                            break;
45904fd306cSNickeau                        default:
46004fd306cSNickeau                            $codes['html'] .= "<!-- " . $externalResource . " -->\n";
46104fd306cSNickeau                    }
46204fd306cSNickeau                }
46304fd306cSNickeau            }
46404fd306cSNickeau        }
46504fd306cSNickeau
46670bbd7f1Sgerardnico        $jsCode = $codes['javascript'] ?? null;
46704fd306cSNickeau        $jsPanel = 0; // language for the js specific panel (0 = JavaScript)
46804fd306cSNickeau        if (array_key_exists('babel', $codes)) {
46904fd306cSNickeau            $jsCode = $codes['babel'];
47004fd306cSNickeau            $jsPanel = 3; // 3 = Babel
47104fd306cSNickeau        }
47204fd306cSNickeau
47304fd306cSNickeau        // Title and description
47404fd306cSNickeau        global $ID;
47504fd306cSNickeau        $pageTitle = tpl_pagetitle($ID, true);
47604fd306cSNickeau        if (!$snippetTitle) {
47704fd306cSNickeau
47804fd306cSNickeau            $snippetTitle = "Code from " . $pageTitle;
47904fd306cSNickeau        }
48004fd306cSNickeau        $description = "Code from the page '" . $pageTitle . "' \n" . wl($ID, $absolute = true);
48104fd306cSNickeau        return '<form  method="post" action="' . $postURL . '" target="_blank">' .
48204fd306cSNickeau            '<input type="hidden" name="title" value="' . htmlentities($snippetTitle) . '"/>' .
48304fd306cSNickeau            '<input type="hidden" name="description" value="' . htmlentities($description) . '"/>' .
48470bbd7f1Sgerardnico            '<input type="hidden" name="css" value="' . htmlentities($codes['css'] ?? '') . '"/>' .
48570bbd7f1Sgerardnico            '<input type="hidden" name="html" value="' . htmlentities("<!-- The HTML -->" . $codes['html'] ?? '') . '"/>' .
48604fd306cSNickeau            '<input type="hidden" name="js" value="' . htmlentities($jsCode) . '"/>' .
48704fd306cSNickeau            '<input type="hidden" name="panel_js" value="' . htmlentities($jsPanel) . '"/>' .
48804fd306cSNickeau            '<input type="hidden" name="wrap" value="b"/>' .  //javascript no wrap in body
48904fd306cSNickeau            $externalResourcesInput .
49004fd306cSNickeau            '<button>Try the code</button>' .
49104fd306cSNickeau            '</form>';
49204fd306cSNickeau
49304fd306cSNickeau    }
49404fd306cSNickeau
49504fd306cSNickeau    private static function finishIframe(TagAttributes $tagAttributes, string $bar = ""): string
49604fd306cSNickeau    {
49704fd306cSNickeau        /**
49804fd306cSNickeau         * The iframe does not have any width
49904fd306cSNickeau         * By default, we set it to 100% and it can be
50004fd306cSNickeau         * constraint with the `width` attributes that will
50104fd306cSNickeau         * set a a max-width
50204fd306cSNickeau         */
50304fd306cSNickeau        $tagAttributes->addStyleDeclarationIfNotSet("width", "100%");
50404fd306cSNickeau
50504fd306cSNickeau        /**
50604fd306cSNickeau         * FrameBorder
50704fd306cSNickeau         */
50804fd306cSNickeau        $frameBorder = $tagAttributes->getValueAndRemoveIfPresent(WebCodeTag::FRAMEBORDER_ATTRIBUTE);
50904fd306cSNickeau        if ($frameBorder !== null && $frameBorder == 0) {
51004fd306cSNickeau            $tagAttributes->addStyleDeclarationIfNotSet("border", "none");
51104fd306cSNickeau        }
51204fd306cSNickeau
51304fd306cSNickeau        $iFrameHtml = $tagAttributes->toHtmlEnterTag("iframe") . '</iframe>';
51404fd306cSNickeau        return "<div class=\"webcode-wrapper\">" . $iFrameHtml . $bar . '</div>';
51504fd306cSNickeau    }
51604fd306cSNickeau
51704fd306cSNickeau    /**
51804fd306cSNickeau     * Return the body
51904fd306cSNickeau     * @param $codes - the code to apply
52004fd306cSNickeau     * @param $useConsole - if the console area should be printed
52104fd306cSNickeau     * @return string - the html and javascript
52204fd306cSNickeau     */
52304fd306cSNickeau    private static function getBodyHtmlAndJavascript($codes, $useConsole): string
52404fd306cSNickeau    {
52504fd306cSNickeau
52604fd306cSNickeau        $body = "";
52704fd306cSNickeau        if (array_key_exists('html', $codes)) {
52804fd306cSNickeau            // The HTML code
52904fd306cSNickeau            $body .= $codes['html'];
53004fd306cSNickeau        }
53104fd306cSNickeau        // The javascript console area is based at the end of the HTML document
53204fd306cSNickeau        if ($useConsole) {
53304fd306cSNickeau
53404fd306cSNickeau            $body .= <<<EOF
53504fd306cSNickeau<!-- WebCode Console -->
53604fd306cSNickeau<div class="webcode-console-wrapper">
53704fd306cSNickeau    <p class="webConsoleTitle">Console Output:</p>
53804fd306cSNickeau    <div id="webCodeConsole"></div>
53904fd306cSNickeau</div>
54004fd306cSNickeauEOF;
54104fd306cSNickeau        }
54204fd306cSNickeau        // The javascript comes at the end because it may want to be applied on previous HTML element
54304fd306cSNickeau        // as the page load in the IO order, javascript must be placed at the end
54404fd306cSNickeau        if (array_key_exists('javascript', $codes)) {
54504fd306cSNickeau            /**
54604fd306cSNickeau             * The user should escapes the following character * <, >, ", ', \, and &.
54704fd306cSNickeau             * because they will interfere with the HTML parser
54804fd306cSNickeau             *
54904fd306cSNickeau             * The user should write `<\/script>` and note `</script>`
55004fd306cSNickeau             */
55104fd306cSNickeau            // The Javascript code
55204fd306cSNickeau            $body .= '<script class="webcode-javascript" type="text/javascript">' . $codes['javascript'] . '</script>';
55304fd306cSNickeau        }
55404fd306cSNickeau        if (array_key_exists('babel', $codes)) {
55504fd306cSNickeau            // The Babel code
55604fd306cSNickeau            $body .= '<script type="text/babel">' . $codes['babel'] . '</script>';
55704fd306cSNickeau        }
55804fd306cSNickeau        return $body;
55904fd306cSNickeau
56004fd306cSNickeau    }
56104fd306cSNickeau
56204fd306cSNickeau    private static function getCss($codes): string
56304fd306cSNickeau    {
56404fd306cSNickeau        if (array_key_exists('css', $codes)) {
56504fd306cSNickeau            return '<style>' . $codes['css'] . '</style>';
56604fd306cSNickeau        };
56704fd306cSNickeau        return "";
56804fd306cSNickeau    }
56904fd306cSNickeau}
570