xref: /plugin/combo/syntax/webcode.php (revision 9337a630db122fdba0294f47d72bdf5433c2bf10)
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;
215f891b7eSNickeauuse ComboStrap\LogUtility;
225f891b7eSNickeauuse ComboStrap\PluginUtility;
23531e725cSNickeauuse ComboStrap\Site;
2421913ab3SNickeauuse ComboStrap\TagAttributes;
255f891b7eSNickeau
265f891b7eSNickeauif (!defined('DOKU_INC')) die();
275f891b7eSNickeau
285f891b7eSNickeau/**
295f891b7eSNickeau * Webcode
305f891b7eSNickeau */
315f891b7eSNickeauclass syntax_plugin_combo_webcode extends DokuWiki_Syntax_Plugin
325f891b7eSNickeau{
335f891b7eSNickeau
345f891b7eSNickeau    const EXTERNAL_RESOURCES_ATTRIBUTE_DISPLAY = 'externalResources'; // In the action bar
355f891b7eSNickeau    const EXTERNAL_RESOURCES_ATTRIBUTE_KEY = 'externalresources'; // In the code
365f891b7eSNickeau
375f891b7eSNickeau    // Simple cache bursting implementation for the webCodeConsole.(js|css) file
385f891b7eSNickeau    // They must be incremented manually when they changed
395f891b7eSNickeau    const WEB_CSS_VERSION = 1.1;
405f891b7eSNickeau    const WEB_CONSOLE_JS_VERSION = 2.1;
415f891b7eSNickeau
425f891b7eSNickeau    const TAG = 'webcode';
435f891b7eSNickeau
445f891b7eSNickeau    /**
455f891b7eSNickeau     * The tag that have codes
465f891b7eSNickeau     */
47531e725cSNickeau    const CODE_TAGS =
48531e725cSNickeau        array(
49531e725cSNickeau            syntax_plugin_combo_code::CODE_TAG,
50531e725cSNickeau            "plugin_combo_code",
51531e725cSNickeau            syntax_plugin_combo_codemarkdown::TAG
52531e725cSNickeau        );
535f891b7eSNickeau
545f891b7eSNickeau    /**
555f891b7eSNickeau     * The attribute names in the array
565f891b7eSNickeau     */
575f891b7eSNickeau    const CODES_ATTRIBUTE = "codes";
585f891b7eSNickeau    const USE_CONSOLE_ATTRIBUTE = "useConsole";
59531e725cSNickeau    const RENDERING_MODE_ATTRIBUTE = 'renderingmode';
6021913ab3SNickeau    const RENDERING_ONLY_RESULT = "onlyresult";
615f891b7eSNickeau
625f891b7eSNickeau    /**
63531e725cSNickeau     * Marki code
645f891b7eSNickeau     */
65531e725cSNickeau    const MARKI_LANG = 'marki';
66531e725cSNickeau    const DOKUWIKI_LANG = 'dw';
67531e725cSNickeau    const MARKIS = [self::MARKI_LANG, self::DOKUWIKI_LANG];
685f891b7eSNickeau
695f891b7eSNickeau    /**
705f891b7eSNickeau     * Syntax Type.
715f891b7eSNickeau     *
725f891b7eSNickeau     * Needs to return one of the mode types defined in $PARSER_MODES in parser.php
735f891b7eSNickeau     * @see https://www.dokuwiki.org/devel:syntax_plugins#syntax_types
745f891b7eSNickeau     *
755f891b7eSNickeau     * container because it may contain header in case of how to
765f891b7eSNickeau     */
775f891b7eSNickeau    public function getType()
785f891b7eSNickeau    {
795f891b7eSNickeau        return 'container';
805f891b7eSNickeau    }
815f891b7eSNickeau
82531e725cSNickeau    public function getPType()
83531e725cSNickeau    {
84531e725cSNickeau        return "stack";
85531e725cSNickeau    }
86531e725cSNickeau
87531e725cSNickeau
885f891b7eSNickeau    /**
895f891b7eSNickeau     * @return array
905f891b7eSNickeau     * Allow which kind of plugin inside
915f891b7eSNickeau     *
925f891b7eSNickeau     * array('container', 'baseonly','formatting', 'substition', 'protected', 'disabled', 'paragraphs')
935f891b7eSNickeau     *
945f891b7eSNickeau     */
955f891b7eSNickeau    public function getAllowedTypes()
965f891b7eSNickeau    {
975f891b7eSNickeau        return array('container', 'baseonly', 'formatting', 'substition', 'protected', 'disabled', 'paragraphs');
985f891b7eSNickeau    }
995f891b7eSNickeau
1005f891b7eSNickeau
1015f891b7eSNickeau    public function accepts($mode)
1025f891b7eSNickeau    {
10321913ab3SNickeau
10421913ab3SNickeau        return syntax_plugin_combo_preformatted::disablePreformatted($mode);
10521913ab3SNickeau
1065f891b7eSNickeau    }
1075f891b7eSNickeau
1085f891b7eSNickeau    /**
1095f891b7eSNickeau     * @see Doku_Parser_Mode::getSort()
1105f891b7eSNickeau     * The mode (plugin) with the lowest sort number will win out
1115f891b7eSNickeau     *
1125f891b7eSNickeau     * See {@link Doku_Parser_Mode_code}
1135f891b7eSNickeau     */
1145f891b7eSNickeau    public function getSort()
1155f891b7eSNickeau    {
1165f891b7eSNickeau        return 99;
1175f891b7eSNickeau    }
1185f891b7eSNickeau
1195f891b7eSNickeau    /**
1205f891b7eSNickeau     * Called before any calls to ConnectTo
1215f891b7eSNickeau     * @return void
1225f891b7eSNickeau     */
1235f891b7eSNickeau    function preConnect()
1245f891b7eSNickeau    {
1255f891b7eSNickeau    }
1265f891b7eSNickeau
1275f891b7eSNickeau    /**
1285f891b7eSNickeau     * Create a pattern that will called this plugin
1295f891b7eSNickeau     *
1305f891b7eSNickeau     * @param string $mode
1315f891b7eSNickeau     *
1325f891b7eSNickeau     * All dokuwiki mode can be seen in the parser.php file
1335f891b7eSNickeau     * @see Doku_Parser_Mode::connectTo()
1345f891b7eSNickeau     */
1355f891b7eSNickeau    public function connectTo($mode)
1365f891b7eSNickeau    {
1375f891b7eSNickeau
1385f891b7eSNickeau        $pattern = PluginUtility::getContainerTagPattern(self::TAG);
139*9337a630SNickeau        $this->Lexer->addEntryPattern($pattern, $mode, PluginUtility::getModeFromTag($this->getPluginComponent()));
1405f891b7eSNickeau
1415f891b7eSNickeau    }
1425f891b7eSNickeau
1435f891b7eSNickeau
1445f891b7eSNickeau    // This where the addPattern and addExitPattern are defined
1455f891b7eSNickeau    public function postConnect()
1465f891b7eSNickeau    {
147*9337a630SNickeau        $this->Lexer->addExitPattern('</' . self::TAG . '>', PluginUtility::getModeFromTag($this->getPluginComponent()));
1485f891b7eSNickeau    }
1495f891b7eSNickeau
1505f891b7eSNickeau
1515f891b7eSNickeau    /**
1525f891b7eSNickeau     * Handle the match
1535f891b7eSNickeau     * You get the match for each pattern in the $match variable
1545f891b7eSNickeau     * $state says if it's an entry, exit or match pattern
1555f891b7eSNickeau     *
1565f891b7eSNickeau     * This is an instruction block and is cached apart from the rendering output
1575f891b7eSNickeau     * There is two caches levels
1585f891b7eSNickeau     * This cache may be suppressed with the url parameters ?purge=true
1595f891b7eSNickeau     *
1605f891b7eSNickeau     * The returned values are cached in an array that will be passed to the render method
1615f891b7eSNickeau     * The handle function goal is to parse the matched syntax through the pattern function
1625f891b7eSNickeau     * and to return the result for use in the renderer
1635f891b7eSNickeau     * This result is always cached until the page is modified.
1645f891b7eSNickeau     * @param string $match
1655f891b7eSNickeau     * @param int $state
1665f891b7eSNickeau     * @param int $pos
1675f891b7eSNickeau     * @param Doku_Handler $handler
1685f891b7eSNickeau     * @return array|bool
1695f891b7eSNickeau     * @throws Exception
1705f891b7eSNickeau     * @see DokuWiki_Syntax_Plugin::handle()
1715f891b7eSNickeau     *
1725f891b7eSNickeau     */
1735f891b7eSNickeau    public function handle($match, $state, $pos, Doku_Handler $handler)
1745f891b7eSNickeau    {
1755f891b7eSNickeau        switch ($state) {
1765f891b7eSNickeau
1775f891b7eSNickeau            case DOKU_LEXER_ENTER :
1785f891b7eSNickeau
179531e725cSNickeau                // Default
180531e725cSNickeau                $defaultAttributes = array();
181531e725cSNickeau                $defaultAttributes['frameborder'] = 1;
182531e725cSNickeau                $defaultAttributes['width'] = '100%';
183531e725cSNickeau                $defaultAttributes['name'] = "WebCode iFrame";
184531e725cSNickeau                $defaultAttributes[self::RENDERING_MODE_ATTRIBUTE] = 'story';
185531e725cSNickeau                // 'height' is set by the javascript if not set
186531e725cSNickeau                // 'width' and 'scrolling' gets their natural value
1875f891b7eSNickeau
188531e725cSNickeau                // Parse and create the call stack array
189531e725cSNickeau                $tagAttributes = TagAttributes::createFromTagMatch($match, $defaultAttributes);
190531e725cSNickeau                $callStackArray = $tagAttributes->toCallStackArray();
1915f891b7eSNickeau
1925f891b7eSNickeau                return array(
1935f891b7eSNickeau                    PluginUtility::STATE => $state,
194531e725cSNickeau                    PluginUtility::ATTRIBUTES => $callStackArray
1955f891b7eSNickeau                );
1965f891b7eSNickeau
1975f891b7eSNickeau
1985f891b7eSNickeau            case DOKU_LEXER_UNMATCHED :
1995f891b7eSNickeau
20032b85071SNickeau                return PluginUtility::handleAndReturnUnmatchedData(self::TAG, $match, $handler);
20132b85071SNickeau
2025f891b7eSNickeau
2035f891b7eSNickeau            case DOKU_LEXER_EXIT:
2045f891b7eSNickeau
2055f891b7eSNickeau                /**
2065f891b7eSNickeau                 * Capture all codes
2075f891b7eSNickeau                 */
2085f891b7eSNickeau                $codes = array();
2095f891b7eSNickeau                /**
2105f891b7eSNickeau                 * Does the javascript contains a console statement
2115f891b7eSNickeau                 */
2125f891b7eSNickeau                $useConsole = false;
213531e725cSNickeau
2145f891b7eSNickeau                /**
215531e725cSNickeau                 * Callstack
216531e725cSNickeau                 */
217531e725cSNickeau                $callStack = CallStack::createFromHandler($handler);
218531e725cSNickeau                $openingTag = $callStack->moveToPreviousCorrespondingOpeningCall();
219531e725cSNickeau                $renderingMode = strtolower($openingTag->getAttribute(self::RENDERING_MODE_ATTRIBUTE));
220531e725cSNickeau
221531e725cSNickeau                /**
222531e725cSNickeau                 * The mime (ie xml,html, ...) and code content are in two differents
223531e725cSNickeau                 * call. To be able to set the content to the good type
2245f891b7eSNickeau                 * we keep a trace of it
2255f891b7eSNickeau                 */
2265f891b7eSNickeau                $actualCodeType = "";
2275f891b7eSNickeau
22821913ab3SNickeau                /**
229531e725cSNickeau                 * Loop
230531e725cSNickeau                 */
231531e725cSNickeau                while ($actualTag = $callStack->next()) {
232531e725cSNickeau
233531e725cSNickeau
234531e725cSNickeau                    $tagName = $actualTag->getTagName();
235531e725cSNickeau                    if (in_array($tagName, self::CODE_TAGS)) {
236531e725cSNickeau
237531e725cSNickeau                        /**
238531e725cSNickeau                         * Only rendering mode, we don't display the node
239531e725cSNickeau                         * on all node (enter, exit and unmatched)
24021913ab3SNickeau                         */
24121913ab3SNickeau                        if ($renderingMode == self::RENDERING_ONLY_RESULT) {
242531e725cSNickeau                            $actualTag->addAttribute(TagAttributes::DISPLAY, "none");
24321913ab3SNickeau                        }
24421913ab3SNickeau
245531e725cSNickeau                        switch ($actualTag->getState()) {
246531e725cSNickeau
247531e725cSNickeau                            case DOKU_LEXER_ENTER:
2485f891b7eSNickeau                                // Get the code (The content between the code nodes)
2495f891b7eSNickeau                                // We ltrim because the match gives us the \n at the beginning and at the end
250531e725cSNickeau                                $actualCodeType = strtolower(trim($actualTag->getType()));
2515f891b7eSNickeau
2525f891b7eSNickeau                                // Xml is html
2535f891b7eSNickeau                                if ($actualCodeType == 'xml') {
2545f891b7eSNickeau                                    $actualCodeType = 'html';
2555f891b7eSNickeau                                }
256531e725cSNickeau
257531e725cSNickeau                                // markdown, dokuwiki is marki
258531e725cSNickeau                                if (in_array($actualCodeType, ['md', 'markdown', 'dw'])) {
259531e725cSNickeau                                    $actualCodeType = self::MARKI_LANG;
260531e725cSNickeau                                }
261531e725cSNickeau
26221913ab3SNickeau                                // The code for a language may be scattered in multiple block
2635f891b7eSNickeau                                if (!isset($codes[$actualCodeType])) {
2645f891b7eSNickeau                                    $codes[$actualCodeType] = "";
2655f891b7eSNickeau                                }
26621913ab3SNickeau
267531e725cSNickeau                                continue 2;
2685f891b7eSNickeau
269531e725cSNickeau                            case DOKU_LEXER_UNMATCHED:
2705f891b7eSNickeau
271531e725cSNickeau                                $codeContent = $actualTag->getPluginData()[PluginUtility::PAYLOAD];
2725f891b7eSNickeau
2735f891b7eSNickeau                                if (empty($actualCodeType)) {
2745f891b7eSNickeau                                    LogUtility::msg("The type of the code should not be null for the code content " . $codeContent, LogUtility::LVL_MSG_WARNING, self::TAG);
275531e725cSNickeau                                    continue 2;
2765f891b7eSNickeau                                }
2775f891b7eSNickeau
2785f891b7eSNickeau                                // Append it
2795f891b7eSNickeau                                $codes[$actualCodeType] = $codes[$actualCodeType] . $codeContent;
2805f891b7eSNickeau
2815f891b7eSNickeau                                // Check if a javascript console function is used, only if the flag is not set to true
2825f891b7eSNickeau                                if (!$useConsole == true) {
2835f891b7eSNickeau                                    if (in_array($actualCodeType, array('babel', 'javascript', 'html', 'xml'))) {
2845f891b7eSNickeau                                        // if the code contains 'console.'
2855f891b7eSNickeau                                        $result = preg_match('/' . 'console\.' . '/is', $codeContent);
2865f891b7eSNickeau                                        if ($result) {
2875f891b7eSNickeau                                            $useConsole = true;
2885f891b7eSNickeau                                        }
2895f891b7eSNickeau                                    }
2905f891b7eSNickeau                                }
2915f891b7eSNickeau                                // Reset
2925f891b7eSNickeau                                $actualCodeType = "";
293531e725cSNickeau                                break;
294531e725cSNickeau
2955f891b7eSNickeau                        }
2965f891b7eSNickeau                    }
297531e725cSNickeau
2985f891b7eSNickeau                }
299531e725cSNickeau
3005f891b7eSNickeau                return array(
3015f891b7eSNickeau                    PluginUtility::STATE => $state,
3025f891b7eSNickeau                    self::CODES_ATTRIBUTE => $codes,
303531e725cSNickeau                    self::USE_CONSOLE_ATTRIBUTE => $useConsole,
304531e725cSNickeau                    PluginUtility::ATTRIBUTES => $openingTag->getAttributes()
3055f891b7eSNickeau                );
3065f891b7eSNickeau
3075f891b7eSNickeau        }
3085f891b7eSNickeau        return false;
3095f891b7eSNickeau
3105f891b7eSNickeau    }
3115f891b7eSNickeau
3125f891b7eSNickeau    /**
3135f891b7eSNickeau     * Render the output
3145f891b7eSNickeau     * @param string $mode
3155f891b7eSNickeau     * @param Doku_Renderer $renderer
3165f891b7eSNickeau     * @param array $data - what the function handle() return'ed
3175f891b7eSNickeau     * @return bool - rendered correctly (not used)
3185f891b7eSNickeau     *
3195f891b7eSNickeau     * The rendering process
3205f891b7eSNickeau     * @see DokuWiki_Syntax_Plugin::render()
3215f891b7eSNickeau     *
3225f891b7eSNickeau     */
3235f891b7eSNickeau    public function render($mode, Doku_Renderer $renderer, $data)
3245f891b7eSNickeau    {
3255f891b7eSNickeau        // The $data variable comes from the handle() function
3265f891b7eSNickeau        //
3275f891b7eSNickeau        // $mode = 'xhtml' means that we output html
3285f891b7eSNickeau        // There is other mode such as metadata where you can output data for the headers (Not 100% sure)
3295f891b7eSNickeau        if ($mode == 'xhtml') {
3305f891b7eSNickeau
3315f891b7eSNickeau
3325f891b7eSNickeau            /** @var Doku_Renderer_xhtml $renderer */
3335f891b7eSNickeau
3345f891b7eSNickeau            $state = $data[PluginUtility::STATE];
3355f891b7eSNickeau            switch ($state) {
3365f891b7eSNickeau
3375f891b7eSNickeau
3385f891b7eSNickeau                case DOKU_LEXER_UNMATCHED :
3395f891b7eSNickeau
34032b85071SNickeau                    $renderer->doc .= PluginUtility::renderUnmatched($data);
3415f891b7eSNickeau                    break;
3425f891b7eSNickeau
3435f891b7eSNickeau                case DOKU_LEXER_EXIT :
3445f891b7eSNickeau                    $codes = $data[self::CODES_ATTRIBUTE];
345531e725cSNickeau                    $callStackArray = $data[PluginUtility::ATTRIBUTES];
346531e725cSNickeau                    $iFrameAttributes = TagAttributes::createFromCallStackArray($callStackArray, self::TAG);
347531e725cSNickeau
3485f891b7eSNickeau                    // Create the real output of webcode
3495f891b7eSNickeau                    if (sizeof($codes) == 0) {
3505f891b7eSNickeau                        return false;
3515f891b7eSNickeau                    }
3525f891b7eSNickeau
353531e725cSNickeau                    // Credits bar
354531e725cSNickeau                    $bar = '<div class="webcode-bar">';
355531e725cSNickeau
3565f891b7eSNickeau
3575f891b7eSNickeau                    // Dokuwiki Code ?
358531e725cSNickeau                    if (array_key_exists(self::MARKI_LANG, $codes)) {
3595f891b7eSNickeau
360531e725cSNickeau                        $markiCode = $codes[self::MARKI_LANG];
361531e725cSNickeau                        /**
362531e725cSNickeau                         * By default, markup code
363531e725cSNickeau                         * is rendered inside the page
364531e725cSNickeau                         * We got less problem such as iframe overflow
365531e725cSNickeau                         * due to lazy loading, such as relative link, ...
366531e725cSNickeau                         *
367531e725cSNickeau                         */
368531e725cSNickeau                        if (!$iFrameAttributes->hasComponentAttribute("iframe")) {
369531e725cSNickeau                            $renderer->doc .= PluginUtility::render($markiCode);
370531e725cSNickeau                            return true;
371531e725cSNickeau                        }
372531e725cSNickeau
373531e725cSNickeau                        $queryParams = array(
374531e725cSNickeau                            'call' => action_plugin_combo_webcode::CALL_ID,
375531e725cSNickeau                            action_plugin_combo_webcode::MARKI_PARAM => $markiCode
376531e725cSNickeau                        );
377531e725cSNickeau                        $queryString = http_build_query($queryParams);
378531e725cSNickeau                        $url = Site::getAjaxUrl() . "?$queryString";
379531e725cSNickeau                        $iFrameAttributes->addHtmlAttributeValue("src", $url);
3805f891b7eSNickeau
3815f891b7eSNickeau                    } else {
3825f891b7eSNickeau
3835f891b7eSNickeau
3845f891b7eSNickeau                        // Js, Html, Css
385531e725cSNickeau                        $iframeSrcValue = '<html><head>';
386e55e4e71SNickeau                        $iframeSrcValue .= '<meta http-equiv="content-type" content="text/html; charset=UTF-8"/>';
387531e725cSNickeau                        $iframeSrcValue .= '<title>Made by WebCode</title>';
388e55e4e71SNickeau                        $iframeSrcValue .= '<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/normalize/3.0.3/normalize.min.css"/>';
3895f891b7eSNickeau
3905f891b7eSNickeau
3915f891b7eSNickeau                        // External Resources such as css stylesheet or js
392531e725cSNickeau                        $externalResources = [];
393531e725cSNickeau                        if ($iFrameAttributes->hasComponentAttribute(self::EXTERNAL_RESOURCES_ATTRIBUTE_KEY)) {
394531e725cSNickeau                            $resources = $iFrameAttributes->getValueAndRemove(self::EXTERNAL_RESOURCES_ATTRIBUTE_KEY);
395531e725cSNickeau                            $externalResources = explode(",", $resources);
3965f891b7eSNickeau                        }
3975f891b7eSNickeau
3985f891b7eSNickeau                        // Babel Preprocessor, if babel is used, add it to the external resources
3995f891b7eSNickeau                        if (array_key_exists('babel', $codes)) {
4005f891b7eSNickeau                            $babelMin = "https://unpkg.com/babel-standalone@6/babel.min.js";
4015f891b7eSNickeau                            // a load of babel invoke it (be sure to not have it twice
4025f891b7eSNickeau                            if (!(array_key_exists($babelMin, $externalResources))) {
4035f891b7eSNickeau                                $externalResources[] = $babelMin;
4045f891b7eSNickeau                            }
4055f891b7eSNickeau                        }
4065f891b7eSNickeau
4075f891b7eSNickeau                        // Add the external resources
4085f891b7eSNickeau                        foreach ($externalResources as $externalResource) {
4095f891b7eSNickeau                            $pathInfo = pathinfo($externalResource);
4105f891b7eSNickeau                            $fileExtension = $pathInfo['extension'];
4115f891b7eSNickeau                            switch ($fileExtension) {
4125f891b7eSNickeau                                case 'css':
413e55e4e71SNickeau                                    $iframeSrcValue .= '<link rel="stylesheet" type="text/css" href="' . $externalResource . '"/>';
4145f891b7eSNickeau                                    break;
4155f891b7eSNickeau                                case 'js':
416531e725cSNickeau                                    $iframeSrcValue .= '<script type="text/javascript" src="' . $externalResource . '"></script>';
4175f891b7eSNickeau                                    break;
4185f891b7eSNickeau                            }
4195f891b7eSNickeau                        }
4205f891b7eSNickeau
4215f891b7eSNickeau
4225f891b7eSNickeau                        // WebConsole style sheet
423531e725cSNickeau                        $iframeSrcValue .= '<link rel="stylesheet" type="text/css" href="' . PluginUtility::getResourceBaseUrl() . '/webcode/webcode-iframe.css?ver=' . self::WEB_CSS_VERSION . '"/>';
4245f891b7eSNickeau
42521913ab3SNickeau                        // A little margin to make it neater
42621913ab3SNickeau                        // that can be overwritten via cascade
427531e725cSNickeau                        $iframeSrcValue .= '<style>body { margin:10px } /* default margin */</style>';
42821913ab3SNickeau
42921913ab3SNickeau                        // The css
4305f891b7eSNickeau                        if (array_key_exists('css', $codes)) {
431531e725cSNickeau                            $iframeSrcValue .= '<!-- The CSS code -->';
432531e725cSNickeau                            $iframeSrcValue .= '<style>' . $codes['css'] . '</style>';
4335f891b7eSNickeau                        };
434531e725cSNickeau                        $iframeSrcValue .= '</head><body>';
4355f891b7eSNickeau                        if (array_key_exists('html', $codes)) {
436531e725cSNickeau                            $iframeSrcValue .= '<!-- The HTML code -->';
437531e725cSNickeau                            $iframeSrcValue .= $codes['html'];
4385f891b7eSNickeau                        }
4395f891b7eSNickeau                        // The javascript console area is based at the end of the HTML document
4405f891b7eSNickeau                        $useConsole = $data[self::USE_CONSOLE_ATTRIBUTE];
4415f891b7eSNickeau                        if ($useConsole) {
442531e725cSNickeau                            $iframeSrcValue .= '<!-- WebCode Console -->';
443e55e4e71SNickeau                            $iframeSrcValue .= '<div><p class="webConsoleTitle">Console Output:</p>';
444e55e4e71SNickeau                            $iframeSrcValue .= '<div id="webCodeConsole"></div>';
445e55e4e71SNickeau                            $iframeSrcValue .= '<script type="text/javascript" src="' . PluginUtility::getResourceBaseUrl() . '/webcode/webcode-console.js?ver=' . self::WEB_CONSOLE_JS_VERSION . '"></script>';
446531e725cSNickeau                            $iframeSrcValue .= '</div>';
4475f891b7eSNickeau                        }
4485f891b7eSNickeau                        // The javascript comes at the end because it may want to be applied on previous HTML element
4495f891b7eSNickeau                        // as the page load in the IO order, javascript must be placed at the end
4505f891b7eSNickeau                        if (array_key_exists('javascript', $codes)) {
451531e725cSNickeau                            $iframeSrcValue .= '<!-- The Javascript code -->';
452531e725cSNickeau                            $iframeSrcValue .= '<script type="text/javascript">' . $codes['javascript'] . '</script>';
4535f891b7eSNickeau                        }
4545f891b7eSNickeau                        if (array_key_exists('babel', $codes)) {
455531e725cSNickeau                            $iframeSrcValue .= '<!-- The Babel code -->';
456531e725cSNickeau                            $iframeSrcValue .= '<script type="text/babel">' . $codes['babel'] . '</script>';
4575f891b7eSNickeau                        }
458531e725cSNickeau                        $iframeSrcValue .= '</body></html>';
459e55e4e71SNickeau                        $iFrameAttributes->addHtmlAttributeValue("srcdoc", $iframeSrcValue);
4605f891b7eSNickeau
461531e725cSNickeau                        // Code bar with button
462531e725cSNickeau                        $bar .= '<div class="webcode-bar-item">' . PluginUtility::getUrl(self::TAG, "Rendered by WebCode", false) . '</div>';
463531e725cSNickeau                        $bar .= '<div class="webcode-bar-item">' . $this->addJsFiddleButton($codes, $externalResources, $useConsole, $iFrameAttributes->getValue("name")) . '</div>';
4645f891b7eSNickeau
465531e725cSNickeau
4665f891b7eSNickeau                    }
4675f891b7eSNickeau
468531e725cSNickeau                    /**
469531e725cSNickeau                     * If there is no height
470531e725cSNickeau                     */
471a6bf47aaSNickeau                    if (!$iFrameAttributes->hasComponentAttribute(Dimension::HEIGHT_KEY)) {
4725f891b7eSNickeau
473531e725cSNickeau                        /**
474531e725cSNickeau                         * Adjust the height attribute
475531e725cSNickeau                         * of the iframe element
476531e725cSNickeau                         * Any styling attribute would take over
477531e725cSNickeau                         */
478531e725cSNickeau                        PluginUtility::getSnippetManager()->attachJavascriptSnippetForBar(self::TAG);
479531e725cSNickeau                        /**
480531e725cSNickeau                         * CSS Height auto works when an image is loaded asynchronously but not
481531e725cSNickeau                         * when there is only text in the iframe
482531e725cSNickeau                         */
483531e725cSNickeau                        //$iFrameAttributes->addStyleDeclaration("height", "auto");
484531e725cSNickeau                        /**
485531e725cSNickeau                         * Due to margin at the bottom with css height=auto,
486531e725cSNickeau                         * we may see a scroll bar
487531e725cSNickeau                         * This block of code is to avoid scrolling,
488531e725cSNickeau                         * then scrolling = no if not set
489531e725cSNickeau                         */
490531e725cSNickeau                        if (!$iFrameAttributes->hasComponentAttribute("scrolling")) {
491531e725cSNickeau                            $iFrameAttributes->addHtmlAttributeValue("scrolling", "no");
4925f891b7eSNickeau                        }
4935f891b7eSNickeau
4945f891b7eSNickeau                    }
4955f891b7eSNickeau
496531e725cSNickeau
497531e725cSNickeau                    PluginUtility::getSnippetManager()->attachCssSnippetForBar(self::TAG);
498531e725cSNickeau
499e55e4e71SNickeau                    /**
500e55e4e71SNickeau                     * The iframe does not have any width
501e55e4e71SNickeau                     * By default, we set it to 100% and it can be
502e55e4e71SNickeau                     * constraint with the `width` attributes that will
503e55e4e71SNickeau                     * set a a max-width
504e55e4e71SNickeau                     */
505e55e4e71SNickeau                    $iFrameAttributes->addStyleDeclaration("width","100%");
506e55e4e71SNickeau
507531e725cSNickeau                    $iFrameHtml = $iFrameAttributes->toHtmlEnterTag("iframe") . '</iframe>';
508531e725cSNickeau                    $bar .= '</div>'; // close the bar
509531e725cSNickeau                    $renderer->doc .= "<div class=\"webcode-wrapper\">" . $iFrameHtml . $bar . '</div>';
510531e725cSNickeau
511531e725cSNickeau
5125f891b7eSNickeau                    break;
5135f891b7eSNickeau            }
5145f891b7eSNickeau
5155f891b7eSNickeau            return true;
5165f891b7eSNickeau        }
5175f891b7eSNickeau        return false;
5185f891b7eSNickeau    }
5195f891b7eSNickeau
5205f891b7eSNickeau    /**
5215f891b7eSNickeau     * @param array $codes the array containing the codes
522531e725cSNickeau     * @param array $externalResources the attributes of a call (for now the externalResources)
523531e725cSNickeau     * @param bool $useConsole
524531e725cSNickeau     * @param string $snippetTitle
5255f891b7eSNickeau     * @return string the HTML form code
5265f891b7eSNickeau     *
5275f891b7eSNickeau     * Specification, see http://doc.jsfiddle.net/api/post.html
5285f891b7eSNickeau     */
529531e725cSNickeau    public function addJsFiddleButton($codes, $externalResources, $useConsole = false, $snippetTitle = null)
5305f891b7eSNickeau    {
5315f891b7eSNickeau
5325f891b7eSNickeau        $postURL = "https://jsfiddle.net/api/post/library/pure/"; //No Framework
5335f891b7eSNickeau
5345f891b7eSNickeau
535531e725cSNickeau        if ($useConsole) {
5365f891b7eSNickeau            // If their is a console.log function, add the Firebug Lite support of JsFiddle
5375f891b7eSNickeau            // Seems to work only with the Edge version of jQuery
5385f891b7eSNickeau            // $postURL .= "edge/dependencies/Lite/";
5395f891b7eSNickeau            // The firebug logging is not working anymore because of 404
5405f891b7eSNickeau            // Adding them here
5415f891b7eSNickeau            $externalResources[] = 'The firebug resources for the console.log features';
5425f891b7eSNickeau            $externalResources[] = PluginUtility::getResourceBaseUrl() . '/firebug/firebug-lite.css';
5435f891b7eSNickeau            $externalResources[] = PluginUtility::getResourceBaseUrl() . '/firebug/firebug-lite-1.2.js';
5445f891b7eSNickeau        }
5455f891b7eSNickeau
5465f891b7eSNickeau        // The below code is to prevent this JsFiddle bug: https://github.com/jsfiddle/jsfiddle-issues/issues/726
5475f891b7eSNickeau        // The order of the resources is not guaranteed
5485f891b7eSNickeau        // We pass then the resources only if their is one resources
5495f891b7eSNickeau        // Otherwise we pass them as a script element in the HTML.
5505f891b7eSNickeau        if (count($externalResources) <= 1) {
551*9337a630SNickeau            $externalResourcesInput = '<input type="hidden" name="resources" value="' . implode(",", $externalResources) . '"/>';
5525f891b7eSNickeau        } else {
5535f891b7eSNickeau            $codes['html'] .= "\n\n\n\n\n<!-- The resources -->\n";
5545f891b7eSNickeau            $codes['html'] .= "<!-- They have been added here because their order is not guarantee through the API. -->\n";
5555f891b7eSNickeau            $codes['html'] .= "<!-- See: https://github.com/jsfiddle/jsfiddle-issues/issues/726 -->\n";
5565f891b7eSNickeau            foreach ($externalResources as $externalResource) {
5575f891b7eSNickeau                if ($externalResource != "") {
5585f891b7eSNickeau                    $extension = pathinfo($externalResource)['extension'];
5595f891b7eSNickeau                    switch ($extension) {
5605f891b7eSNickeau                        case "css":
5615f891b7eSNickeau                            $codes['html'] .= "<link href=\"" . $externalResource . "\" rel=\"stylesheet\">\n";
5625f891b7eSNickeau                            break;
5635f891b7eSNickeau                        case "js":
5645f891b7eSNickeau                            $codes['html'] .= "<script src=\"" . $externalResource . "\"></script>\n";
5655f891b7eSNickeau                            break;
5665f891b7eSNickeau                        default:
5675f891b7eSNickeau                            $codes['html'] .= "<!-- " . $externalResource . " -->\n";
5685f891b7eSNickeau                    }
5695f891b7eSNickeau                }
5705f891b7eSNickeau            }
5715f891b7eSNickeau        }
5725f891b7eSNickeau
5735f891b7eSNickeau        $jsCode = $codes['javascript'];
5745f891b7eSNickeau        $jsPanel = 0; // language for the js specific panel (0 = JavaScript)
5755f891b7eSNickeau        if (array_key_exists('babel', $codes)) {
5765f891b7eSNickeau            $jsCode = $codes['babel'];
5775f891b7eSNickeau            $jsPanel = 3; // 3 = Babel
5785f891b7eSNickeau        }
5795f891b7eSNickeau
5805f891b7eSNickeau        // Title and description
5815f891b7eSNickeau        global $ID;
5825f891b7eSNickeau        $pageTitle = tpl_pagetitle($ID, true);
583531e725cSNickeau        if (!$snippetTitle) {
5845f891b7eSNickeau
585531e725cSNickeau            $snippetTitle = "Code from " . $pageTitle;
5865f891b7eSNickeau        }
5875f891b7eSNickeau        $description = "Code from the page '" . $pageTitle . "' \n" . wl($ID, $absolute = true);
5885f891b7eSNickeau        return '<form  method="post" action="' . $postURL . '" target="_blank">' .
589*9337a630SNickeau            '<input type="hidden" name="title" value="' . htmlentities($snippetTitle) . '"/>' .
590*9337a630SNickeau            '<input type="hidden" name="description" value="' . htmlentities($description) . '"/>' .
591*9337a630SNickeau            '<input type="hidden" name="css" value="' . htmlentities($codes['css']) . '"/>' .
592*9337a630SNickeau            '<input type="hidden" name="html" value="' . htmlentities("<!-- The HTML -->" . $codes['html']) . '"/>' .
593*9337a630SNickeau            '<input type="hidden" name="js" value="' . htmlentities($jsCode) . '"/>' .
594*9337a630SNickeau            '<input type="hidden" name="panel_js" value="' . htmlentities($jsPanel) . '"/>' .
595*9337a630SNickeau            '<input type="hidden" name="wrap" value="b"/>' .  //javascript no wrap in body
5965f891b7eSNickeau            $externalResourcesInput .
5975f891b7eSNickeau            '<button>Try the code</button>' .
5985f891b7eSNickeau            '</form>';
5995f891b7eSNickeau
6005f891b7eSNickeau    }
6015f891b7eSNickeau
6025f891b7eSNickeau    /**
6035f891b7eSNickeau     * @param $codes the array containing the codes
6045f891b7eSNickeau     * @param $attributes the attributes of a call (for now the externalResources)
6055f891b7eSNickeau     * @return string the HTML form code
6065f891b7eSNickeau     */
6075f891b7eSNickeau    public function addCodePenButton($codes, $attributes)
6085f891b7eSNickeau    {
6095f891b7eSNickeau        // TODO
6105f891b7eSNickeau        // http://blog.codepen.io/documentation/api/prefill/
6115f891b7eSNickeau    }
6125f891b7eSNickeau
6135f891b7eSNickeau
6145f891b7eSNickeau}
615