xref: /plugin/combo/syntax/webcode.php (revision 4cadd4f8c541149bdda95f080e38a6d4e3a640ca)
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