xref: /plugin/combo/syntax/webcode.php (revision 5f891b7e09648e05e78f5882f3fdde1e9df9b0f1)
1*5f891b7eSNickeau<?php
2*5f891b7eSNickeau/**
3*5f891b7eSNickeau * Copyright (c) 2021. ComboStrap, Inc. and its affiliates. All Rights Reserved.
4*5f891b7eSNickeau *
5*5f891b7eSNickeau * This source code is licensed under the GPL license found in the
6*5f891b7eSNickeau * COPYING  file in the root directory of this source tree.
7*5f891b7eSNickeau *
8*5f891b7eSNickeau * @license  GPL 3 (https://www.gnu.org/licenses/gpl-3.0.en.html)
9*5f891b7eSNickeau * @author   ComboStrap <support@combostrap.com>
10*5f891b7eSNickeau *
11*5f891b7eSNickeau */
12*5f891b7eSNickeau
13*5f891b7eSNickeau/**
14*5f891b7eSNickeau * Plugin Webcode: Show webcode (Css, HTML) in a iframe
15*5f891b7eSNickeau *
16*5f891b7eSNickeau * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
17*5f891b7eSNickeau * @author     Nicolas GERARD
18*5f891b7eSNickeau */
19*5f891b7eSNickeau
20*5f891b7eSNickeau// must be run within Dokuwiki
21*5f891b7eSNickeauuse ComboStrap\SnippetManager;
22*5f891b7eSNickeauuse ComboStrap\LogUtility;
23*5f891b7eSNickeauuse ComboStrap\PluginUtility;
24*5f891b7eSNickeauuse ComboStrap\Tag;
25*5f891b7eSNickeau
26*5f891b7eSNickeauif (!defined('DOKU_INC')) die();
27*5f891b7eSNickeau
28*5f891b7eSNickeau/**
29*5f891b7eSNickeau * Webcode
30*5f891b7eSNickeau */
31*5f891b7eSNickeauclass syntax_plugin_combo_webcode extends DokuWiki_Syntax_Plugin
32*5f891b7eSNickeau{
33*5f891b7eSNickeau
34*5f891b7eSNickeau    const EXTERNAL_RESOURCES_ATTRIBUTE_DISPLAY = 'externalResources'; // In the action bar
35*5f891b7eSNickeau    const EXTERNAL_RESOURCES_ATTRIBUTE_KEY = 'externalresources'; // In the code
36*5f891b7eSNickeau
37*5f891b7eSNickeau    // Simple cache bursting implementation for the webCodeConsole.(js|css) file
38*5f891b7eSNickeau    // They must be incremented manually when they changed
39*5f891b7eSNickeau    const WEB_CSS_VERSION = 1.1;
40*5f891b7eSNickeau    const WEB_CONSOLE_JS_VERSION = 2.1;
41*5f891b7eSNickeau
42*5f891b7eSNickeau    const TAG = 'webcode';
43*5f891b7eSNickeau
44*5f891b7eSNickeau    /**
45*5f891b7eSNickeau     * The tag that have codes
46*5f891b7eSNickeau     */
47*5f891b7eSNickeau    const CODE_TAGS = array("code", "plugin_combo_code");
48*5f891b7eSNickeau
49*5f891b7eSNickeau    /**
50*5f891b7eSNickeau     * The attribute names in the array
51*5f891b7eSNickeau     */
52*5f891b7eSNickeau    const CODES_ATTRIBUTE = "codes";
53*5f891b7eSNickeau    const USE_CONSOLE_ATTRIBUTE = "useConsole";
54*5f891b7eSNickeau
55*5f891b7eSNickeau    /**
56*5f891b7eSNickeau     * @var array that holds the iframe attributes
57*5f891b7eSNickeau     */
58*5f891b7eSNickeau    private $attributes = array();
59*5f891b7eSNickeau
60*5f891b7eSNickeau
61*5f891b7eSNickeau    /**
62*5f891b7eSNickeau     * Syntax Type.
63*5f891b7eSNickeau     *
64*5f891b7eSNickeau     * Needs to return one of the mode types defined in $PARSER_MODES in parser.php
65*5f891b7eSNickeau     * @see https://www.dokuwiki.org/devel:syntax_plugins#syntax_types
66*5f891b7eSNickeau     *
67*5f891b7eSNickeau     * container because it may contain header in case of how to
68*5f891b7eSNickeau     */
69*5f891b7eSNickeau    public function getType()
70*5f891b7eSNickeau    {
71*5f891b7eSNickeau        return 'container';
72*5f891b7eSNickeau    }
73*5f891b7eSNickeau
74*5f891b7eSNickeau    /**
75*5f891b7eSNickeau     * @return array
76*5f891b7eSNickeau     * Allow which kind of plugin inside
77*5f891b7eSNickeau     *
78*5f891b7eSNickeau     * array('container', 'baseonly','formatting', 'substition', 'protected', 'disabled', 'paragraphs')
79*5f891b7eSNickeau     *
80*5f891b7eSNickeau     */
81*5f891b7eSNickeau    public function getAllowedTypes()
82*5f891b7eSNickeau    {
83*5f891b7eSNickeau        return array('container', 'baseonly', 'formatting', 'substition', 'protected', 'disabled', 'paragraphs');
84*5f891b7eSNickeau    }
85*5f891b7eSNickeau
86*5f891b7eSNickeau
87*5f891b7eSNickeau    public function accepts($mode)
88*5f891b7eSNickeau    {
89*5f891b7eSNickeau        if (!$this->getConf(syntax_plugin_combo_preformatted::CONF_PREFORMATTED_ENABLE)) {
90*5f891b7eSNickeau            return PluginUtility::disablePreformatted($mode);
91*5f891b7eSNickeau        } else {
92*5f891b7eSNickeau            return true;
93*5f891b7eSNickeau        }
94*5f891b7eSNickeau    }
95*5f891b7eSNickeau
96*5f891b7eSNickeau    /**
97*5f891b7eSNickeau     * @see Doku_Parser_Mode::getSort()
98*5f891b7eSNickeau     * The mode (plugin) with the lowest sort number will win out
99*5f891b7eSNickeau     *
100*5f891b7eSNickeau     * See {@link Doku_Parser_Mode_code}
101*5f891b7eSNickeau     */
102*5f891b7eSNickeau    public function getSort()
103*5f891b7eSNickeau    {
104*5f891b7eSNickeau        return 99;
105*5f891b7eSNickeau    }
106*5f891b7eSNickeau
107*5f891b7eSNickeau    /**
108*5f891b7eSNickeau     * Called before any calls to ConnectTo
109*5f891b7eSNickeau     * @return void
110*5f891b7eSNickeau     */
111*5f891b7eSNickeau    function preConnect()
112*5f891b7eSNickeau    {
113*5f891b7eSNickeau    }
114*5f891b7eSNickeau
115*5f891b7eSNickeau    /**
116*5f891b7eSNickeau     * Create a pattern that will called this plugin
117*5f891b7eSNickeau     *
118*5f891b7eSNickeau     * @param string $mode
119*5f891b7eSNickeau     *
120*5f891b7eSNickeau     * All dokuwiki mode can be seen in the parser.php file
121*5f891b7eSNickeau     * @see Doku_Parser_Mode::connectTo()
122*5f891b7eSNickeau     */
123*5f891b7eSNickeau    public function connectTo($mode)
124*5f891b7eSNickeau    {
125*5f891b7eSNickeau
126*5f891b7eSNickeau        $pattern = PluginUtility::getContainerTagPattern(self::TAG);
127*5f891b7eSNickeau        $this->Lexer->addEntryPattern($pattern, $mode, PluginUtility::getModeForComponent($this->getPluginComponent()));
128*5f891b7eSNickeau
129*5f891b7eSNickeau    }
130*5f891b7eSNickeau
131*5f891b7eSNickeau
132*5f891b7eSNickeau    // This where the addPattern and addExitPattern are defined
133*5f891b7eSNickeau    public function postConnect()
134*5f891b7eSNickeau    {
135*5f891b7eSNickeau        $this->Lexer->addExitPattern('</' . self::TAG . '>', PluginUtility::getModeForComponent($this->getPluginComponent()));
136*5f891b7eSNickeau    }
137*5f891b7eSNickeau
138*5f891b7eSNickeau
139*5f891b7eSNickeau    /**
140*5f891b7eSNickeau     * Handle the match
141*5f891b7eSNickeau     * You get the match for each pattern in the $match variable
142*5f891b7eSNickeau     * $state says if it's an entry, exit or match pattern
143*5f891b7eSNickeau     *
144*5f891b7eSNickeau     * This is an instruction block and is cached apart from the rendering output
145*5f891b7eSNickeau     * There is two caches levels
146*5f891b7eSNickeau     * This cache may be suppressed with the url parameters ?purge=true
147*5f891b7eSNickeau     *
148*5f891b7eSNickeau     * The returned values are cached in an array that will be passed to the render method
149*5f891b7eSNickeau     * The handle function goal is to parse the matched syntax through the pattern function
150*5f891b7eSNickeau     * and to return the result for use in the renderer
151*5f891b7eSNickeau     * This result is always cached until the page is modified.
152*5f891b7eSNickeau     * @param string $match
153*5f891b7eSNickeau     * @param int $state
154*5f891b7eSNickeau     * @param int $pos
155*5f891b7eSNickeau     * @param Doku_Handler $handler
156*5f891b7eSNickeau     * @return array|bool
157*5f891b7eSNickeau     * @throws Exception
158*5f891b7eSNickeau     * @see DokuWiki_Syntax_Plugin::handle()
159*5f891b7eSNickeau     *
160*5f891b7eSNickeau     */
161*5f891b7eSNickeau    public function handle($match, $state, $pos, Doku_Handler $handler)
162*5f891b7eSNickeau    {
163*5f891b7eSNickeau        switch ($state) {
164*5f891b7eSNickeau
165*5f891b7eSNickeau            case DOKU_LEXER_ENTER :
166*5f891b7eSNickeau
167*5f891b7eSNickeau                // We got the first webcode tag and its attributes
168*5f891b7eSNickeau
169*5f891b7eSNickeau                $match = substr($match, 8, -1); //9 = strlen("<webcode")
170*5f891b7eSNickeau
171*5f891b7eSNickeau                // Reset of the attributes
172*5f891b7eSNickeau                // With some framework the php object may be still persisted in memory
173*5f891b7eSNickeau                // And you may get some attributes from other page
174*5f891b7eSNickeau                $attributes = array();
175*5f891b7eSNickeau                $attributes['frameborder'] = 1;
176*5f891b7eSNickeau                $attributes['width'] = '100%';
177*5f891b7eSNickeau
178*5f891b7eSNickeau                $renderingModeKey = 'renderingmode';
179*5f891b7eSNickeau                $attributes[$renderingModeKey] = 'story';
180*5f891b7eSNickeau
181*5f891b7eSNickeau                // config Parameters will get their value in lowercase
182*5f891b7eSNickeau                $configAttributes = [$renderingModeKey];
183*5f891b7eSNickeau
184*5f891b7eSNickeau                // /i not case sensitive
185*5f891b7eSNickeau                $attributePattern = "\s*(\w+)\s*=\s*\"?([^\"\s]+)\"?\\s*";
186*5f891b7eSNickeau                $result = preg_match_all('/' . $attributePattern . '/i', $match, $matches);
187*5f891b7eSNickeau
188*5f891b7eSNickeau
189*5f891b7eSNickeau                if ($result != 0) {
190*5f891b7eSNickeau                    foreach ($matches[1] as $key => $lang) {
191*5f891b7eSNickeau                        $attributeKey = strtolower($lang);
192*5f891b7eSNickeau                        $attributeValue = $matches[2][$key];
193*5f891b7eSNickeau                        if (in_array($attributeKey, $configAttributes)) {
194*5f891b7eSNickeau                            $attributeValue = strtolower($attributeValue);
195*5f891b7eSNickeau                        }
196*5f891b7eSNickeau                        $attributes[$attributeKey] = $attributeValue;
197*5f891b7eSNickeau                    }
198*5f891b7eSNickeau                }
199*5f891b7eSNickeau
200*5f891b7eSNickeau                // We set the attributes on a class scope
201*5f891b7eSNickeau                // to be used in the DOKU_LEXER_UNMATCHED step
202*5f891b7eSNickeau                $this->attributes = $attributes;
203*5f891b7eSNickeau
204*5f891b7eSNickeau                // Cache the values to be used by the render method
205*5f891b7eSNickeau                return array(
206*5f891b7eSNickeau                    PluginUtility::STATE => $state,
207*5f891b7eSNickeau                    PluginUtility::ATTRIBUTES => $attributes
208*5f891b7eSNickeau                );
209*5f891b7eSNickeau
210*5f891b7eSNickeau
211*5f891b7eSNickeau            case DOKU_LEXER_UNMATCHED :
212*5f891b7eSNickeau
213*5f891b7eSNickeau                // Cache the values
214*5f891b7eSNickeau                return array(
215*5f891b7eSNickeau                    PluginUtility::STATE => $state,
216*5f891b7eSNickeau                    PluginUtility::PAYLOAD => $match
217*5f891b7eSNickeau                );
218*5f891b7eSNickeau
219*5f891b7eSNickeau            case DOKU_LEXER_EXIT:
220*5f891b7eSNickeau
221*5f891b7eSNickeau                /**
222*5f891b7eSNickeau                 * Capture all codes
223*5f891b7eSNickeau                 */
224*5f891b7eSNickeau                $codes = array();
225*5f891b7eSNickeau                /**
226*5f891b7eSNickeau                 * Does the javascript contains a console statement
227*5f891b7eSNickeau                 */
228*5f891b7eSNickeau                $useConsole = false;
229*5f891b7eSNickeau                $exitTag = new Tag(self::TAG, array(), $state, $handler);
230*5f891b7eSNickeau                $openingTag = $exitTag->getOpeningTag();
231*5f891b7eSNickeau                if ($openingTag->hasDescendants()) {
232*5f891b7eSNickeau                    $tags = $openingTag->getDescendants();
233*5f891b7eSNickeau                    /**
234*5f891b7eSNickeau                     * Mime and code content are in two differents
235*5f891b7eSNickeau                     * tag. To be able to set the content to the good type
236*5f891b7eSNickeau                     * we keep a trace of it
237*5f891b7eSNickeau                     */
238*5f891b7eSNickeau                    $actualCodeType = "";
239*5f891b7eSNickeau                    foreach ($tags as $tag) {
240*5f891b7eSNickeau                        if (in_array($tag->getName(), self::CODE_TAGS)) {
241*5f891b7eSNickeau
242*5f891b7eSNickeau                            if ($tag->getState() == DOKU_LEXER_ENTER) {
243*5f891b7eSNickeau                                // Get the code (The content between the code nodes)
244*5f891b7eSNickeau                                // We ltrim because the match gives us the \n at the beginning and at the end
245*5f891b7eSNickeau                                $actualCodeType = strtolower(trim($tag->getType()));
246*5f891b7eSNickeau
247*5f891b7eSNickeau                                // Xml is html
248*5f891b7eSNickeau                                if ($actualCodeType == 'xml') {
249*5f891b7eSNickeau                                    $actualCodeType = 'html';
250*5f891b7eSNickeau                                }
251*5f891b7eSNickeau                                // The code for a language may be scattered in mutliple block
252*5f891b7eSNickeau                                if (!isset($codes[$actualCodeType])) {
253*5f891b7eSNickeau                                    $codes[$actualCodeType] = "";
254*5f891b7eSNickeau                                }
255*5f891b7eSNickeau                                continue;
256*5f891b7eSNickeau                            }
257*5f891b7eSNickeau
258*5f891b7eSNickeau                            if ($tag->getState() == DOKU_LEXER_UNMATCHED) {
259*5f891b7eSNickeau
260*5f891b7eSNickeau                                $codeContent = $tag->getData()[PluginUtility::PAYLOAD];
261*5f891b7eSNickeau
262*5f891b7eSNickeau                                if (empty($actualCodeType)) {
263*5f891b7eSNickeau                                    LogUtility::msg("The type of the code should not be null for the code content " . $codeContent, LogUtility::LVL_MSG_WARNING, self::TAG);
264*5f891b7eSNickeau                                    continue;
265*5f891b7eSNickeau                                }
266*5f891b7eSNickeau
267*5f891b7eSNickeau                                // Append it
268*5f891b7eSNickeau                                $codes[$actualCodeType] = $codes[$actualCodeType] . $codeContent;
269*5f891b7eSNickeau
270*5f891b7eSNickeau                                // Check if a javascript console function is used, only if the flag is not set to true
271*5f891b7eSNickeau                                if (!$useConsole == true) {
272*5f891b7eSNickeau                                    if (in_array($actualCodeType, array('babel', 'javascript', 'html', 'xml'))) {
273*5f891b7eSNickeau                                        // if the code contains 'console.'
274*5f891b7eSNickeau                                        $result = preg_match('/' . 'console\.' . '/is', $codeContent);
275*5f891b7eSNickeau                                        if ($result) {
276*5f891b7eSNickeau                                            $useConsole = true;
277*5f891b7eSNickeau                                        }
278*5f891b7eSNickeau                                    }
279*5f891b7eSNickeau                                }
280*5f891b7eSNickeau                                // Reset
281*5f891b7eSNickeau                                $actualCodeType = "";
282*5f891b7eSNickeau                            }
283*5f891b7eSNickeau                        }
284*5f891b7eSNickeau                    }
285*5f891b7eSNickeau                }
286*5f891b7eSNickeau                return array(
287*5f891b7eSNickeau                    PluginUtility::STATE => $state,
288*5f891b7eSNickeau                    self::CODES_ATTRIBUTE => $codes,
289*5f891b7eSNickeau                    self::USE_CONSOLE_ATTRIBUTE => $useConsole
290*5f891b7eSNickeau                );
291*5f891b7eSNickeau
292*5f891b7eSNickeau        }
293*5f891b7eSNickeau        return false;
294*5f891b7eSNickeau
295*5f891b7eSNickeau    }
296*5f891b7eSNickeau
297*5f891b7eSNickeau    /**
298*5f891b7eSNickeau     * Render the output
299*5f891b7eSNickeau     * @param string $mode
300*5f891b7eSNickeau     * @param Doku_Renderer $renderer
301*5f891b7eSNickeau     * @param array $data - what the function handle() return'ed
302*5f891b7eSNickeau     * @return bool - rendered correctly (not used)
303*5f891b7eSNickeau     *
304*5f891b7eSNickeau     * The rendering process
305*5f891b7eSNickeau     * @see DokuWiki_Syntax_Plugin::render()
306*5f891b7eSNickeau     *
307*5f891b7eSNickeau     */
308*5f891b7eSNickeau    public function render($mode, Doku_Renderer $renderer, $data)
309*5f891b7eSNickeau    {
310*5f891b7eSNickeau        // The $data variable comes from the handle() function
311*5f891b7eSNickeau        //
312*5f891b7eSNickeau        // $mode = 'xhtml' means that we output html
313*5f891b7eSNickeau        // There is other mode such as metadata where you can output data for the headers (Not 100% sure)
314*5f891b7eSNickeau        if ($mode == 'xhtml') {
315*5f891b7eSNickeau
316*5f891b7eSNickeau
317*5f891b7eSNickeau            /** @var Doku_Renderer_xhtml $renderer */
318*5f891b7eSNickeau
319*5f891b7eSNickeau            $state = $data[PluginUtility::STATE];
320*5f891b7eSNickeau            switch ($state) {
321*5f891b7eSNickeau
322*5f891b7eSNickeau                case DOKU_LEXER_ENTER :
323*5f891b7eSNickeau
324*5f891b7eSNickeau                    PluginUtility::getSnippetManager()->addJavascriptSnippetIfNeeded(self::TAG);
325*5f891b7eSNickeau
326*5f891b7eSNickeau                    // The extracted data are the attribute of the webcode tag
327*5f891b7eSNickeau                    // We put in a class variable so that we can use in the last step (DOKU_LEXER_EXIT)
328*5f891b7eSNickeau                    $this->attributes = $data[PluginUtility::ATTRIBUTES];
329*5f891b7eSNickeau
330*5f891b7eSNickeau                    break;
331*5f891b7eSNickeau
332*5f891b7eSNickeau                case DOKU_LEXER_UNMATCHED :
333*5f891b7eSNickeau
334*5f891b7eSNickeau                    // Render and escape
335*5f891b7eSNickeau                    $renderer->doc .= $renderer->_xmlEntities($data[PluginUtility::PAYLOAD]);
336*5f891b7eSNickeau                    break;
337*5f891b7eSNickeau
338*5f891b7eSNickeau                case DOKU_LEXER_EXIT :
339*5f891b7eSNickeau                    $codes = $data[self::CODES_ATTRIBUTE];
340*5f891b7eSNickeau                    // Create the real output of webcode
341*5f891b7eSNickeau                    if (sizeof($codes) == 0) {
342*5f891b7eSNickeau                        return false;
343*5f891b7eSNickeau                    }
344*5f891b7eSNickeau
345*5f891b7eSNickeau                    PluginUtility::getSnippetManager()->addCssSnippetOnlyOnce(self::TAG);
346*5f891b7eSNickeau
347*5f891b7eSNickeau                    // Dokuwiki Code ?
348*5f891b7eSNickeau                    if (array_key_exists('dw', $codes)) {
349*5f891b7eSNickeau
350*5f891b7eSNickeau                        $renderer->doc .= PluginUtility::render($codes['dw']);
351*5f891b7eSNickeau
352*5f891b7eSNickeau                    } else {
353*5f891b7eSNickeau
354*5f891b7eSNickeau
355*5f891b7eSNickeau                        // Js, Html, Css
356*5f891b7eSNickeau                        $iframeHtml = '<html><head>';
357*5f891b7eSNickeau                        $iframeHtml .= '<meta http-equiv="content-type" content="text/html; charset=UTF-8">';
358*5f891b7eSNickeau                        $iframeHtml .= '<title>Made by Webcode</title>';
359*5f891b7eSNickeau                        $iframeHtml .= '<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/normalize/3.0.3/normalize.min.css">';
360*5f891b7eSNickeau
361*5f891b7eSNickeau
362*5f891b7eSNickeau                        // External Resources such as css stylesheet or js
363*5f891b7eSNickeau                        $externalResources = array();
364*5f891b7eSNickeau                        if (array_key_exists(self::EXTERNAL_RESOURCES_ATTRIBUTE_KEY, $this->attributes)) {
365*5f891b7eSNickeau                            $externalResources = explode(",", $this->attributes[self::EXTERNAL_RESOURCES_ATTRIBUTE_KEY]);
366*5f891b7eSNickeau                        }
367*5f891b7eSNickeau
368*5f891b7eSNickeau                        // Babel Preprocessor, if babel is used, add it to the external resources
369*5f891b7eSNickeau                        if (array_key_exists('babel', $codes)) {
370*5f891b7eSNickeau                            $babelMin = "https://unpkg.com/babel-standalone@6/babel.min.js";
371*5f891b7eSNickeau                            // a load of babel invoke it (be sure to not have it twice
372*5f891b7eSNickeau                            if (!(array_key_exists($babelMin, $externalResources))) {
373*5f891b7eSNickeau                                $externalResources[] = $babelMin;
374*5f891b7eSNickeau                            }
375*5f891b7eSNickeau                        }
376*5f891b7eSNickeau
377*5f891b7eSNickeau                        // Add the external resources
378*5f891b7eSNickeau                        foreach ($externalResources as $externalResource) {
379*5f891b7eSNickeau                            $pathInfo = pathinfo($externalResource);
380*5f891b7eSNickeau                            $fileExtension = $pathInfo['extension'];
381*5f891b7eSNickeau                            switch ($fileExtension) {
382*5f891b7eSNickeau                                case 'css':
383*5f891b7eSNickeau                                    $iframeHtml .= '<link rel="stylesheet" type="text/css" href="' . $externalResource . '">';
384*5f891b7eSNickeau                                    break;
385*5f891b7eSNickeau                                case 'js':
386*5f891b7eSNickeau                                    $iframeHtml .= '<script type="text/javascript" src="' . $externalResource . '"></script>';
387*5f891b7eSNickeau                                    break;
388*5f891b7eSNickeau                            }
389*5f891b7eSNickeau                        }
390*5f891b7eSNickeau
391*5f891b7eSNickeau
392*5f891b7eSNickeau                        // WebConsole style sheet
393*5f891b7eSNickeau                        $iframeHtml .= '<link rel="stylesheet" type="text/css" href="' . PluginUtility::getResourceBaseUrl() . '/webcode/webcode-iframe.css?ver=' . self::WEB_CSS_VERSION . '"/>';
394*5f891b7eSNickeau
395*5f891b7eSNickeau                        if (array_key_exists('css', $codes)) {
396*5f891b7eSNickeau                            $iframeHtml .= '<!-- The CSS code -->';
397*5f891b7eSNickeau                            $iframeHtml .= '<style>' . $codes['css'] . '</style>';
398*5f891b7eSNickeau                        };
399*5f891b7eSNickeau                        $iframeHtml .= '</head><body style="margin:10px">';
400*5f891b7eSNickeau                        if (array_key_exists('html', $codes)) {
401*5f891b7eSNickeau                            $iframeHtml .= '<!-- The HTML code -->';
402*5f891b7eSNickeau                            $iframeHtml .= $codes['html'];
403*5f891b7eSNickeau                        }
404*5f891b7eSNickeau                        // The javascript console area is based at the end of the HTML document
405*5f891b7eSNickeau                        $useConsole = $data[self::USE_CONSOLE_ATTRIBUTE];
406*5f891b7eSNickeau                        if ($useConsole) {
407*5f891b7eSNickeau                            $iframeHtml .= '<!-- WebCode Console -->';
408*5f891b7eSNickeau                            $iframeHtml .= '<div><p class=\'webConsoleTitle\'>Console Output:</p>';
409*5f891b7eSNickeau                            $iframeHtml .= '<div id=\'webCodeConsole\'></div>';
410*5f891b7eSNickeau                            $iframeHtml .= '<script type=\'text/javascript\' src=\'' . PluginUtility::getResourceBaseUrl() . '/webcode/webcode-console.js?ver=' . self::WEB_CONSOLE_JS_VERSION . '\'></script>';
411*5f891b7eSNickeau                            $iframeHtml .= '</div>';
412*5f891b7eSNickeau                        }
413*5f891b7eSNickeau                        // The javascript comes at the end because it may want to be applied on previous HTML element
414*5f891b7eSNickeau                        // as the page load in the IO order, javascript must be placed at the end
415*5f891b7eSNickeau                        if (array_key_exists('javascript', $codes)) {
416*5f891b7eSNickeau                            $iframeHtml .= '<!-- The Javascript code -->';
417*5f891b7eSNickeau                            $iframeHtml .= '<script type="text/javascript">' . $codes['javascript'] . '</script>';
418*5f891b7eSNickeau                        }
419*5f891b7eSNickeau                        if (array_key_exists('babel', $codes)) {
420*5f891b7eSNickeau                            $iframeHtml .= '<!-- The Babel code -->';
421*5f891b7eSNickeau                            $iframeHtml .= '<script type="text/babel">' . $codes['babel'] . '</script>';
422*5f891b7eSNickeau                        }
423*5f891b7eSNickeau                        $iframeHtml .= '</body></html>';
424*5f891b7eSNickeau
425*5f891b7eSNickeau                        // Here the magic from the plugin happens
426*5f891b7eSNickeau                        // We add the Iframe and the JsFiddleButton
427*5f891b7eSNickeau                        $iFrameHtml = '<iframe ';
428*5f891b7eSNickeau
429*5f891b7eSNickeau                        // We add the name HTML attribute
430*5f891b7eSNickeau                        $name = "WebCode iFrame";
431*5f891b7eSNickeau                        if (array_key_exists('name', $this->attributes)) {
432*5f891b7eSNickeau                            $name .= ' ' . $this->attributes['name'];
433*5f891b7eSNickeau                        }
434*5f891b7eSNickeau                        $iFrameHtml .= ' name="' . $name . '" ';
435*5f891b7eSNickeau
436*5f891b7eSNickeau                        // The class to be able to select them
437*5f891b7eSNickeau                        $iFrameHtml .= ' class="webCode" ';
438*5f891b7eSNickeau
439*5f891b7eSNickeau                        // We add the others HTML attributes
440*5f891b7eSNickeau                        $iFrameHtmlAttributes = array('width', 'height', 'frameborder', 'scrolling');
441*5f891b7eSNickeau                        foreach ($this->attributes as $attribute => $value) {
442*5f891b7eSNickeau                            if (in_array($attribute, $iFrameHtmlAttributes)) {
443*5f891b7eSNickeau                                $iFrameHtml .= ' ' . $attribute . '=' . $value;
444*5f891b7eSNickeau                            }
445*5f891b7eSNickeau                        }
446*5f891b7eSNickeau                        $iFrameHtml .= ' srcdoc="' . htmlentities($iframeHtml) . '" ></iframe>';//
447*5f891b7eSNickeau
448*5f891b7eSNickeau                        // Credits bar
449*5f891b7eSNickeau                        $bar = '<div class="webcode-bar">';
450*5f891b7eSNickeau                        $bar .= '<div class="webcode-bar-item">' . PluginUtility::getUrl(self::TAG, "Rendered by Webcode",false) . '</div>';
451*5f891b7eSNickeau                        $bar .= '<div class="webcode-bar-item">' . $this->addJsFiddleButton($codes, $this->attributes) . '</div>';
452*5f891b7eSNickeau                        $bar .= '</div>';
453*5f891b7eSNickeau                        $renderer->doc .= '<div class="webcode">' . $iFrameHtml . $bar . '</div>';
454*5f891b7eSNickeau                    }
455*5f891b7eSNickeau
456*5f891b7eSNickeau                    break;
457*5f891b7eSNickeau            }
458*5f891b7eSNickeau
459*5f891b7eSNickeau            return true;
460*5f891b7eSNickeau        }
461*5f891b7eSNickeau        return false;
462*5f891b7eSNickeau    }
463*5f891b7eSNickeau
464*5f891b7eSNickeau    /**
465*5f891b7eSNickeau     * @param array $codes the array containing the codes
466*5f891b7eSNickeau     * @param array $attributes the attributes of a call (for now the externalResources)
467*5f891b7eSNickeau     * @return string the HTML form code
468*5f891b7eSNickeau     *
469*5f891b7eSNickeau     * Specification, see http://doc.jsfiddle.net/api/post.html
470*5f891b7eSNickeau     */
471*5f891b7eSNickeau    public function addJsFiddleButton($codes, $attributes)
472*5f891b7eSNickeau    {
473*5f891b7eSNickeau
474*5f891b7eSNickeau        $postURL = "https://jsfiddle.net/api/post/library/pure/"; //No Framework
475*5f891b7eSNickeau
476*5f891b7eSNickeau        $externalResources = array();
477*5f891b7eSNickeau        if (array_key_exists(self::EXTERNAL_RESOURCES_ATTRIBUTE_KEY, $attributes)) {
478*5f891b7eSNickeau            $externalResources = explode(",", $attributes[self::EXTERNAL_RESOURCES_ATTRIBUTE_KEY]);
479*5f891b7eSNickeau        }
480*5f891b7eSNickeau
481*5f891b7eSNickeau
482*5f891b7eSNickeau        if ($this->useConsole) {
483*5f891b7eSNickeau            // If their is a console.log function, add the Firebug Lite support of JsFiddle
484*5f891b7eSNickeau            // Seems to work only with the Edge version of jQuery
485*5f891b7eSNickeau            // $postURL .= "edge/dependencies/Lite/";
486*5f891b7eSNickeau            // The firebug logging is not working anymore because of 404
487*5f891b7eSNickeau            // Adding them here
488*5f891b7eSNickeau            $externalResources[] = 'The firebug resources for the console.log features';
489*5f891b7eSNickeau            $externalResources[] = PluginUtility::getResourceBaseUrl() . '/firebug/firebug-lite.css';
490*5f891b7eSNickeau            $externalResources[] = PluginUtility::getResourceBaseUrl() . '/firebug/firebug-lite-1.2.js';
491*5f891b7eSNickeau        }
492*5f891b7eSNickeau
493*5f891b7eSNickeau        // The below code is to prevent this JsFiddle bug: https://github.com/jsfiddle/jsfiddle-issues/issues/726
494*5f891b7eSNickeau        // The order of the resources is not guaranteed
495*5f891b7eSNickeau        // We pass then the resources only if their is one resources
496*5f891b7eSNickeau        // Otherwise we pass them as a script element in the HTML.
497*5f891b7eSNickeau        if (count($externalResources) <= 1) {
498*5f891b7eSNickeau            $externalResourcesInput = '<input type="hidden" name="resources" value="' . implode(",", $externalResources) . '">';
499*5f891b7eSNickeau        } else {
500*5f891b7eSNickeau            $codes['html'] .= "\n\n\n\n\n<!-- The resources -->\n";
501*5f891b7eSNickeau            $codes['html'] .= "<!-- They have been added here because their order is not guarantee through the API. -->\n";
502*5f891b7eSNickeau            $codes['html'] .= "<!-- See: https://github.com/jsfiddle/jsfiddle-issues/issues/726 -->\n";
503*5f891b7eSNickeau            foreach ($externalResources as $externalResource) {
504*5f891b7eSNickeau                if ($externalResource != "") {
505*5f891b7eSNickeau                    $extension = pathinfo($externalResource)['extension'];
506*5f891b7eSNickeau                    switch ($extension) {
507*5f891b7eSNickeau                        case "css":
508*5f891b7eSNickeau                            $codes['html'] .= "<link href=\"" . $externalResource . "\" rel=\"stylesheet\">\n";
509*5f891b7eSNickeau                            break;
510*5f891b7eSNickeau                        case "js":
511*5f891b7eSNickeau                            $codes['html'] .= "<script src=\"" . $externalResource . "\"></script>\n";
512*5f891b7eSNickeau                            break;
513*5f891b7eSNickeau                        default:
514*5f891b7eSNickeau                            $codes['html'] .= "<!-- " . $externalResource . " -->\n";
515*5f891b7eSNickeau                    }
516*5f891b7eSNickeau                }
517*5f891b7eSNickeau            }
518*5f891b7eSNickeau        }
519*5f891b7eSNickeau
520*5f891b7eSNickeau        $jsCode = $codes['javascript'];
521*5f891b7eSNickeau        $jsPanel = 0; // language for the js specific panel (0 = JavaScript)
522*5f891b7eSNickeau        if (array_key_exists('babel', $codes)) {
523*5f891b7eSNickeau            $jsCode = $codes['babel'];
524*5f891b7eSNickeau            $jsPanel = 3; // 3 = Babel
525*5f891b7eSNickeau        }
526*5f891b7eSNickeau
527*5f891b7eSNickeau        // Title and description
528*5f891b7eSNickeau        global $ID;
529*5f891b7eSNickeau        $title = $attributes['name'];
530*5f891b7eSNickeau        $pageTitle = tpl_pagetitle($ID, true);
531*5f891b7eSNickeau        if (!$title) {
532*5f891b7eSNickeau
533*5f891b7eSNickeau            $title = "Code from " . $pageTitle;
534*5f891b7eSNickeau        }
535*5f891b7eSNickeau        $description = "Code from the page '" . $pageTitle . "' \n" . wl($ID, $absolute = true);
536*5f891b7eSNickeau        return '<form  method="post" action="' . $postURL . '" target="_blank">' .
537*5f891b7eSNickeau            '<input type="hidden" name="title" value="' . htmlentities($title) . '">' .
538*5f891b7eSNickeau            '<input type="hidden" name="description" value="' . htmlentities($description) . '">' .
539*5f891b7eSNickeau            '<input type="hidden" name="css" value="' . htmlentities($codes['css']) . '">' .
540*5f891b7eSNickeau            '<input type="hidden" name="html" value="' . htmlentities("<!-- The HTML -->" . $codes['html']) . '">' .
541*5f891b7eSNickeau            '<input type="hidden" name="js" value="' . htmlentities($jsCode) . '">' .
542*5f891b7eSNickeau            '<input type="hidden" name="panel_js" value="' . htmlentities($jsPanel) . '">' .
543*5f891b7eSNickeau            '<input type="hidden" name="wrap" value="b">' .  //javascript no wrap in body
544*5f891b7eSNickeau            $externalResourcesInput .
545*5f891b7eSNickeau            '<button>Try the code</button>' .
546*5f891b7eSNickeau            '</form>';
547*5f891b7eSNickeau
548*5f891b7eSNickeau    }
549*5f891b7eSNickeau
550*5f891b7eSNickeau    /**
551*5f891b7eSNickeau     * @param $codes the array containing the codes
552*5f891b7eSNickeau     * @param $attributes the attributes of a call (for now the externalResources)
553*5f891b7eSNickeau     * @return string the HTML form code
554*5f891b7eSNickeau     */
555*5f891b7eSNickeau    public function addCodePenButton($codes, $attributes)
556*5f891b7eSNickeau    {
557*5f891b7eSNickeau        // TODO
558*5f891b7eSNickeau        // http://blog.codepen.io/documentation/api/prefill/
559*5f891b7eSNickeau    }
560*5f891b7eSNickeau
561*5f891b7eSNickeau
562*5f891b7eSNickeau}
563