Dotclear

source: inc/admin/lib.dc.page.php @ 3926:199b1f662109

Revision 3926:199b1f662109, 42.2 KB checked in by franck <carnet.franck.paul@…>, 7 years ago (diff)

Switching from inline JS variables to JSON script. Filter controls are on the way

Line 
1<?php
2/**
3 * @package Dotclear
4 * @subpackage Backend
5 *
6 * @copyright Olivier Meunier & Association Dotclear
7 * @copyright GPL-2.0-only
8 */
9
10if (!defined('DC_RC_PATH')) {return;}
11
12define('DC_AUTH_PAGE', 'auth.php');
13
14class dcPage
15{
16    private static $loaded_js     = [];
17    private static $loaded_css    = [];
18    private static $xframe_loaded = false;
19
20    private static function getCore()
21    {
22        return $GLOBALS['core'];
23    }
24
25    # Auth check
26    public static function check($permissions)
27    {
28        $core = self::getCore();
29
30        if ($core->blog && $core->auth->check($permissions, $core->blog->id)) {
31            return;
32        }
33
34        if (session_id()) {
35            $core->session->destroy();
36        }
37        http::redirect(DC_AUTH_PAGE);
38    }
39
40    # Check super admin
41    public static function checkSuper()
42    {
43        $core = self::getCore();
44
45        if (!$core->auth->isSuperAdmin()) {
46            if (session_id()) {
47                $core->session->destroy();
48            }
49            http::redirect(DC_AUTH_PAGE);
50        }
51    }
52
53    # Top of admin page
54    public static function open($title = '', $head = '', $breadcrumb = '', $options = [])
55    {
56        $core = self::getCore();
57        $js   = [];
58
59        # List of user's blogs
60        if ($core->auth->getBlogCount() == 1 || $core->auth->getBlogCount() > 20) {
61            $blog_box =
62            '<p>' . __('Blog:') . ' <strong title="' . html::escapeHTML($core->blog->url) . '">' .
63            html::escapeHTML($core->blog->name) . '</strong>';
64
65            if ($core->auth->getBlogCount() > 20) {
66                $blog_box .= ' - <a href="' . $core->adminurl->get("admin.blogs") . '">' . __('Change blog') . '</a>';
67            }
68            $blog_box .= '</p>';
69        } else {
70            $rs_blogs = $core->getBlogs(['order' => 'LOWER(blog_name)', 'limit' => 20]);
71            $blogs    = [];
72            while ($rs_blogs->fetch()) {
73                $blogs[html::escapeHTML($rs_blogs->blog_name . ' - ' . $rs_blogs->blog_url)] = $rs_blogs->blog_id;
74            }
75            $blog_box = '<p><label for="switchblog" class="classic">' . __('Blogs:') . '</label> ' .
76            $core->formNonce() . form::combo('switchblog', $blogs, $core->blog->id) .
77            '<input type="submit" value="' . __('ok') . '" class="hidden-if-js" /></p>';
78        }
79
80        $safe_mode = isset($_SESSION['sess_safe_mode']) && $_SESSION['sess_safe_mode'];
81
82        # Display
83        $headers = new ArrayObject([]);
84
85        # Content-Type
86        $headers['content-type'] = 'Content-Type: text/html; charset=UTF-8';
87
88        # Referrer Policy for admin pages
89        $headers['referrer'] = 'Referrer-Policy: strict-origin';
90
91        # Prevents Clickjacking as far as possible
92        if (isset($options['x-frame-allow'])) {
93            self::setXFrameOptions($headers, $options['x-frame-allow']);
94        } else {
95            self::setXFrameOptions($headers);
96        }
97
98        # Content-Security-Policy (only if safe mode if not active, it may help)
99        if (!$safe_mode && $core->blog->settings->system->csp_admin_on) {
100            // Get directives from settings if exist, else set defaults
101            $csp = new ArrayObject([]);
102
103                                                                                // SQlite Clearbricks driver does not allow using single quote at beginning or end of a field value
104                                                                                // so we have to use neutral values (localhost and 127.0.0.1) for some CSP directives
105            $csp_prefix = $core->con->syntax() == 'sqlite' ? 'localhost ' : ''; // Hack for SQlite Clearbricks syntax
106            $csp_suffix = $core->con->syntax() == 'sqlite' ? ' 127.0.0.1' : ''; // Hack for SQlite Clearbricks syntax
107
108            $csp['default-src'] = $core->blog->settings->system->csp_admin_default ?:
109            $csp_prefix . "'self'" . $csp_suffix;
110            $csp['script-src'] = $core->blog->settings->system->csp_admin_script ?:
111            $csp_prefix . "'self' 'unsafe-inline' 'unsafe-eval'" . $csp_suffix;
112            $csp['style-src'] = $core->blog->settings->system->csp_admin_style ?:
113            $csp_prefix . "'self' 'unsafe-inline'" . $csp_suffix;
114            $csp['img-src'] = $core->blog->settings->system->csp_admin_img ?:
115            $csp_prefix . "'self' data: http://media.dotaddict.org blob:";
116
117            # Cope with blog post preview (via public URL in iframe)
118            if (!is_null($core->blog->host)) {
119                $csp['default-src'] .= ' ' . parse_url($core->blog->host, PHP_URL_HOST);
120                $csp['script-src'] .= ' ' . parse_url($core->blog->host, PHP_URL_HOST);
121                $csp['style-src'] .= ' ' . parse_url($core->blog->host, PHP_URL_HOST);
122            }
123            # Cope with media display in media manager (via public URL)
124            if (!is_null($core->media)) {
125                $csp['img-src'] .= ' ' . parse_url($core->media->root_url, PHP_URL_HOST);
126            } elseif (!is_null($core->blog->host)) {
127                // Let's try with the blog URL
128                $csp['img-src'] .= ' ' . parse_url($core->blog->host, PHP_URL_HOST);
129            }
130            # Allow everything in iframe (used by editors to preview public content)
131            $csp['child-src'] = "*";
132
133            # --BEHAVIOR-- adminPageHTTPHeaderCSP
134            $core->callBehavior('adminPageHTTPHeaderCSP', $csp);
135
136            // Construct CSP header
137            $directives = [];
138            foreach ($csp as $key => $value) {
139                if ($value) {
140                    $directives[] = $key . ' ' . $value;
141                }
142            }
143            if (count($directives)) {
144                if (version_compare(phpversion(), '5.4', '>=')) {
145                    // csp_report.php needs PHP ≥ 5.4
146                    $directives[] = "report-uri " . DC_ADMIN_URL . "csp_report.php";
147                }
148                $report_only    = ($core->blog->settings->system->csp_admin_report_only) ? '-Report-Only' : '';
149                $headers['csp'] = "Content-Security-Policy" . $report_only . ": " . implode(" ; ", $directives);
150            }
151        }
152
153        # --BEHAVIOR-- adminPageHTTPHeaders
154        $core->callBehavior('adminPageHTTPHeaders', $headers);
155        foreach ($headers as $key => $value) {
156            header($value);
157        }
158
159        echo
160        '<!DOCTYPE html>' .
161        '<html lang="' . $core->auth->getInfo('user_lang') . '">' . "\n" .
162        "<head>\n" .
163        '  <meta charset="UTF-8" />' . "\n" .
164        '  <meta name="ROBOTS" content="NOARCHIVE,NOINDEX,NOFOLLOW" />' . "\n" .
165        '  <meta name="GOOGLEBOT" content="NOSNIPPET" />' . "\n" .
166        '  <meta name="viewport" content="width=device-width, initial-scale=1.0" />' . "\n" .
167        '  <title>' . $title . ' - ' . html::escapeHTML($core->blog->name) . ' - ' . html::escapeHTML(DC_VENDOR_NAME) . ' - ' . DC_VERSION . '</title>' . "\n";
168
169        if ($core->auth->user_prefs->interface->darkmode) {
170            $js['darkMode'] = 1;
171            echo self::cssLoad('style/default-dark.css');
172        } else {
173            $js['darkMode'] = 0;
174            echo self::cssLoad('style/default.css');
175        }
176        if (l10n::getTextDirection($GLOBALS['_lang']) == 'rtl') {
177            echo self::cssLoad('style/default-rtl.css');
178        }
179
180        $core->auth->user_prefs->addWorkspace('interface');
181        if (!$core->auth->user_prefs->interface->hide_std_favicon) {
182            echo
183                '<link rel="icon" type="image/png" href="images/favicon96-login.png" />' . "\n" .
184                '<link rel="shortcut icon" href="images/favicon.ico" type="image/x-icon" />' . "\n";
185        }
186        if ($core->auth->user_prefs->interface->htmlfontsize) {
187            $js['htmlFontSize'] = $core->auth->user_prefs->interface->htmlfontsize;
188        }
189        $js['hideMoreInfo']   = (boolean) $core->auth->user_prefs->interface->hidemoreinfo;
190        $js['showAjaxLoader'] = (boolean) $core->auth->user_prefs->interface->showajaxloader;
191
192        $core->auth->user_prefs->addWorkspace('accessibility');
193        $js['noDragDrop'] = (boolean) $core->auth->user_prefs->accessibility->nodragdrop;
194
195        // Set some JSON data
196        echo dcUtils::jsJson('dotclear_init', $js);
197
198        echo
199        self::jsCommon() .
200        self::jsToggles() .
201            $head;
202
203        # --BEHAVIOR-- adminPageHTMLHead
204        $core->callBehavior('adminPageHTMLHead');
205
206        echo
207        "</head>\n" .
208        '<body id="dotclear-admin' .
209        ($safe_mode ? ' safe-mode' : '') . '" class="no-js' .
210        ($core->auth->user_prefs->interface->dynfontsize ? ' responsive-font' : '') . '">' . "\n" .
211
212        '<ul id="prelude">' .
213        '<li><a href="#content">' . __('Go to the content') . '</a></li>' .
214        '<li><a href="#main-menu">' . __('Go to the menu') . '</a></li>' .
215        '<li><a href="#help">' . __('Go to help') . '</a></li>' .
216        '</ul>' . "\n" .
217        '<header id="header" role="banner">' .
218        '<h1><a href="' . $core->adminurl->get("admin.home") . '"><span class="hidden">' . DC_VENDOR_NAME . '</span></a></h1>' . "\n";
219
220        echo
221        '<form action="' . $core->adminurl->get("admin.home") . '" method="post" id="top-info-blog">' .
222        $blog_box .
223        '<p><a href="' . $core->blog->url . '" class="outgoing" title="' . __('Go to site') .
224        '">' . __('Go to site') . '<img src="images/outgoing-link.svg" alt="" /></a>' .
225        '</p></form>' .
226        '<ul id="top-info-user">' .
227        '<li><a class="' . (preg_match('/' . preg_quote($core->adminurl->get('admin.home')) . '$/', $_SERVER['REQUEST_URI']) ? ' active' : '') . '" href="' . $core->adminurl->get("admin.home") . '">' . __('My dashboard') . '</a></li>' .
228        '<li><a class="smallscreen' . (preg_match('/' . preg_quote($core->adminurl->get('admin.user.preferences')) . '(\?.*)?$/', $_SERVER['REQUEST_URI']) ? ' active' : '') .
229        '" href="' . $core->adminurl->get("admin.user.preferences") . '">' . __('My preferences') . '</a></li>' .
230        '<li><a href="' . $core->adminurl->get("admin.home", ['logout' => 1]) . '" class="logout"><span class="nomobile">' . sprintf(__('Logout %s'), $core->auth->userID()) .
231            '</span><img src="images/logout.png" alt="" /></a></li>' .
232            '</ul>' .
233            '</header>'; // end header
234
235        echo
236        '<div id="wrapper" class="clearfix">' . "\n" .
237        '<div class="hidden-if-no-js collapser-box"><button type="button" id="collapser" class="void-btn">' .
238        '<img class="collapse-mm visually-hidden" src="images/collapser-hide.png" alt="' . __('Hide main menu') . '" />' .
239        '<img class="expand-mm visually-hidden" src="images/collapser-show.png" alt="' . __('Show main menu') . '" />' .
240            '</button></div>' .
241            '<main id="main" role="main">' . "\n" .
242            '<div id="content" class="clearfix">' . "\n";
243
244        # Safe mode
245        if ($safe_mode) {
246            echo
247            '<div class="warning" role="alert"><h3>' . __('Safe mode') . '</h3>' .
248            '<p>' . __('You are in safe mode. All plugins have been temporarily disabled. Remind to log out then log in again normally to get back all functionalities') . '</p>' .
249                '</div>';
250        }
251
252        // Display breadcrumb (if given) before any error messages
253        echo $breadcrumb;
254
255        // Display notices and errors
256        echo $core->notices->getNotices();
257    }
258
259    /**
260    @deprecated Please use $core->notices->getNotices()
261     **/
262    public static function notices()
263    {
264        $core = self::getCore();
265        return $core->notices->getNotices();
266    }
267
268    /**
269    @deprecated Please use $core->notices->addNotice()
270     **/
271    public static function addNotice($type, $message, $options = [])
272    {
273        $core = self::getCore();
274        $core->notices->addNotice($type, $message, $options);
275    }
276
277    /**
278    @deprecated Please use $core->notices->addSuccessNotice()
279     **/
280    public static function addSuccessNotice($message, $options = [])
281    {
282        self::addNotice("success", $message, $options);
283    }
284
285    /**
286    @deprecated Please use $core->notices->addWarningNotice()
287     **/
288    public static function addWarningNotice($message, $options = [])
289    {
290        self::addNotice("warning", $message, $options);
291    }
292
293    /**
294    @deprecated Please use $core->notices->addErrorNotice()
295     **/
296    public static function addErrorNotice($message, $options = [])
297    {
298        self::addNotice("error", $message, $options);
299    }
300
301    public static function close()
302    {
303        $core = self::getCore();
304
305        if (!$GLOBALS['__resources']['ctxhelp']) {
306            if (!$core->auth->user_prefs->interface->hidehelpbutton) {
307                echo
308                '<p id="help-button"><a href="' . $core->adminurl->get("admin.help") . '" class="outgoing" title="' .
309                __('Global help') . '">' . __('Global help') . '</a></p>';
310            }
311        }
312
313        $menu = &$GLOBALS['_menu'];
314
315        echo
316        "</div>\n" .  // End of #content
317        "</main>\n" . // End of #main
318
319        '<nav id="main-menu" role="navigation">' . "\n" .
320
321        '<form id="search-menu" action="' . $core->adminurl->get("admin.search") . '" method="get" role="search">' .
322        '<p><label for="qx" class="hidden">' . __('Search:') . ' </label>' . form::field('qx', 30, 255, '') .
323        '<input type="submit" value="' . __('OK') . '" /></p>' .
324            '</form>';
325
326        foreach ($menu as $k => $v) {
327            echo $menu[$k]->draw();
328        }
329
330        $text = sprintf(__('Thank you for using %s.'), 'Dotclear ' . DC_VERSION);
331
332        # --BEHAVIOR-- adminPageFooter
333        $textAlt = $core->callBehavior('adminPageFooter', $core, $text);
334        if ($textAlt != '') {
335            $text = $textAlt;
336        }
337        $text = html::escapeHTML($text);
338
339        echo
340        '</nav>' . "\n" . // End of #main-menu
341        "</div>\n";       // End of #wrapper
342
343        echo '<p id="gototop"><a href="#wrapper">' . __('Page top') . '</a></p>' . "\n";
344
345        $figure = "
346  ♥‿♥
347              |    |    |
348             )_)  )_)  )_)
349            )___))___))___)\
350           )____)____)_____)\\
351         _____|____|____|____\\\__
352---------\                   /---------
353  ^^^^^ ^^^^^^^^^^^^^^^^^^^^^
354    ^^^^      ^^^^     ^^^    ^^
355         ^^^^      ^^^
356  ";
357
358        echo
359            '<footer id="footer" role="contentinfo">' .
360            '<a href="http://dotclear.org/" title="' . $text . '">' .
361            '<img src="style/dc_logos/w-dotclear90.png" alt="' . $text . '" /></a></footer>' . "\n" .
362            "<!-- " . "\n" .
363            $figure .
364            " -->" . "\n";
365
366        if (defined('DC_DEV') && DC_DEV === true) {
367            echo self::debugInfo();
368        }
369
370        echo
371            '</body></html>';
372    }
373
374    public static function openPopup($title = '', $head = '', $breadcrumb = '')
375    {
376        $core = self::getCore();
377        $js   = [];
378
379        # Display
380        header('Content-Type: text/html; charset=UTF-8');
381
382        # Referrer Policy for admin pages
383        header('Referrer-Policy: strict-origin');
384
385        # Prevents Clickjacking as far as possible
386        header('X-Frame-Options: SAMEORIGIN'); // FF 3.6.9+ Chrome 4.1+ IE 8+ Safari 4+ Opera 10.5+
387
388        echo
389        '<!DOCTYPE html>' .
390        '<html lang="' . $core->auth->getInfo('user_lang') . '">' . "\n" .
391        "<head>\n" .
392        '  <meta charset="UTF-8" />' . "\n" .
393        '  <meta name="viewport" content="width=device-width, initial-scale=1.0" />' . "\n" .
394        '  <title>' . $title . ' - ' . html::escapeHTML($core->blog->name) . ' - ' . html::escapeHTML(DC_VENDOR_NAME) . ' - ' . DC_VERSION . '</title>' . "\n" .
395            '  <meta name="ROBOTS" content="NOARCHIVE,NOINDEX,NOFOLLOW" />' . "\n" .
396            '  <meta name="GOOGLEBOT" content="NOSNIPPET" />' . "\n";
397
398        if ($core->auth->user_prefs->interface->darkmode) {
399            $js['darkMode'] = 1;
400            echo self::cssLoad('style/default-dark.css');
401        } else {
402            $js['darkMode'] = 0;
403            echo self::cssLoad('style/default.css');
404        }
405        if (l10n::getTextDirection($GLOBALS['_lang']) == 'rtl') {
406            echo self::cssLoad('style/default-rtl.css');
407        }
408
409        $core->auth->user_prefs->addWorkspace('interface');
410        if ($core->auth->user_prefs->interface->htmlfontsize) {
411            $js['htmlFontSize'] = $core->auth->user_prefs->interface->htmlfontsize;
412        }
413        $js['hideMoreInfo']   = (boolean) $core->auth->user_prefs->interface->hidemoreinfo;
414        $js['showAjaxLoader'] = (boolean) $core->auth->user_prefs->interface->showajaxloader;
415
416        $core->auth->user_prefs->addWorkspace('accessibility');
417        $js['noDragDrop'] = (boolean) $core->auth->user_prefs->accessibility->nodragdrop;
418
419        // Set JSON data
420        echo dcUtils::jsJson('dotclear_init', $js);
421
422        echo
423        self::jsCommon() .
424        self::jsToggles() .
425            $head;
426
427        # --BEHAVIOR-- adminPageHTMLHead
428        $core->callBehavior('adminPageHTMLHead');
429
430        echo
431            "</head>\n" .
432            '<body id="dotclear-admin" class="popup' .
433            ($core->auth->user_prefs->interface->dynfontsize ? ' responsive-font' : '') . '">' . "\n" .
434
435            '<h1>' . DC_VENDOR_NAME . '</h1>' . "\n";
436
437        echo
438            '<div id="wrapper">' . "\n" .
439            '<main id="main" role="main">' . "\n" .
440            '<div id="content">' . "\n";
441
442        // display breadcrumb if given
443        echo $breadcrumb;
444
445        // Display notices and errors
446        echo $core->notices->getNotices();
447    }
448
449    public static function closePopup()
450    {
451        echo
452        "</div>\n" .  // End of #content
453        "</main>\n" . // End of #main
454        "</div>\n" .  // End of #wrapper
455
456        '<p id="gototop"><a href="#wrapper">' . __('Page top') . '</a></p>' . "\n" .
457
458            '<footer id="footer" role="contentinfo"><p>&nbsp;</p></footer>' . "\n" .
459            '</body></html>';
460    }
461
462    public static function breadcrumb($elements = null, $options = [])
463    {
464        $core = self::getCore();
465
466        $with_home_link = isset($options['home_link']) ? $options['home_link'] : true;
467        $hl             = isset($options['hl']) ? $options['hl'] : true;
468        $hl_pos         = isset($options['hl_pos']) ? $options['hl_pos'] : -1;
469        // First item of array elements should be blog's name, System or Plugins
470        $res = '<h2>' . ($with_home_link ?
471            '<a class="go_home" href="' . $core->adminurl->get("admin.home") . '"><img src="style/dashboard.png" alt="' . __('Go to dashboard') . '" /></a>' :
472            '<img src="style/dashboard-alt.png" alt="" />');
473        $index = 0;
474        if ($hl_pos < 0) {
475            $hl_pos = count($elements) + $hl_pos;
476        }
477        foreach ($elements as $element => $url) {
478            if ($hl && $index == $hl_pos) {
479                $element = sprintf('<span class="page-title">%s</span>', $element);
480            }
481            $res .= ($with_home_link ? ($index == 1 ? ' : ' : ' &rsaquo; ') : ($index == 0 ? ' ' : ' &rsaquo; ')) .
482                ($url ? '<a href="' . $url . '">' : '') . $element . ($url ? '</a>' : '');
483            $index++;
484        }
485        $res .= '</h2>';
486        return $res;
487    }
488
489    /**
490    @deprecated Please use $core->notices->message()
491     **/
492    public static function message($msg, $timestamp = true, $div = false, $echo = true, $class = 'message')
493    {
494        $core = self::getCore();
495        return $core->notices->message($msg, $timestamp, $div, $echo, $class);
496    }
497
498    /**
499    @deprecated Please use $core->notices->success()
500     **/
501    public static function success($msg, $timestamp = true, $div = false, $echo = true)
502    {
503        return self::message($msg, $timestamp, $div, $echo, "success");
504    }
505
506    /**
507    @deprecated Please use $core->notices->warning()
508     **/
509    public static function warning($msg, $timestamp = true, $div = false, $echo = true)
510    {
511        return self::message($msg, $timestamp, $div, $echo, "warning-msg");
512    }
513
514    private static function debugInfo()
515    {
516        $global_vars = implode(', ', array_keys($GLOBALS));
517
518        $res =
519        '<div id="debug"><div>' .
520        '<p>memory usage: ' . memory_get_usage() . ' (' . files::size(memory_get_usage()) . ')</p>';
521
522        if (function_exists('xdebug_get_profiler_filename')) {
523            $res .= '<p>Elapsed time: ' . xdebug_time_index() . ' seconds</p>';
524
525            $prof_file = xdebug_get_profiler_filename();
526            if ($prof_file) {
527                $res .= '<p>Profiler file : ' . xdebug_get_profiler_filename() . '</p>';
528            } else {
529                $prof_url = http::getSelfURI();
530                $prof_url .= (strpos($prof_url, '?') === false) ? '?' : '&';
531                $prof_url .= 'XDEBUG_PROFILE';
532                $res .= '<p><a href="' . html::escapeURL($prof_url) . '">Trigger profiler</a></p>';
533            }
534
535            /* xdebug configuration:
536        zend_extension = /.../xdebug.so
537        xdebug.auto_trace = On
538        xdebug.trace_format = 0
539        xdebug.trace_options = 1
540        xdebug.show_mem_delta = On
541        xdebug.profiler_enable = 0
542        xdebug.profiler_enable_trigger = 1
543        xdebug.profiler_output_dir = /tmp
544        xdebug.profiler_append = 0
545        xdebug.profiler_output_name = timestamp
546         */
547        }
548
549        $res .=
550            '<p>Global vars: ' . $global_vars . '</p>' .
551            '</div></div>';
552
553        return $res;
554    }
555
556    public static function help($page, $index = '')
557    {
558        # Deprecated but we keep this for plugins.
559    }
560
561    public static function helpBlock(...$params)
562    {
563        $core = self::getCore();
564
565        if ($core->auth->user_prefs->interface->hidehelpbutton) {
566            return;
567        }
568
569        $args = new ArrayObject($params);
570
571        # --BEHAVIOR-- adminPageHelpBlock
572        $core->callBehavior('adminPageHelpBlock', $args);
573
574        if (empty($args)) {
575            return;
576        };
577
578        global $__resources;
579        if (empty($__resources['help'])) {
580            return;
581        }
582
583        $content = '';
584        foreach ($args as $v) {
585            if (is_object($v) && isset($v->content)) {
586                $content .= $v->content;
587                continue;
588            }
589
590            if (!isset($__resources['help'][$v])) {
591                continue;
592            }
593            $f = $__resources['help'][$v];
594            if (!file_exists($f) || !is_readable($f)) {
595                continue;
596            }
597
598            $fc = file_get_contents($f);
599            if (preg_match('|<body[^>]*?>(.*?)</body>|ms', $fc, $matches)) {
600                $content .= $matches[1];
601            } else {
602                $content .= $fc;
603            }
604        }
605
606        if (trim($content) == '') {
607            return;
608        }
609
610        // Set contextual help global flag
611        $GLOBALS['__resources']['ctxhelp'] = true;
612
613        echo
614        '<div id="help"><hr /><div class="help-content clear"><h3>' . __('Help about this page') . '</h3>' .
615        $content .
616        '</div>' .
617        '<div id="helplink"><hr />' .
618        '<p>' .
619        sprintf(__('See also %s'), sprintf('<a href="' . $core->adminurl->get("admin.help") . '">%s</a>', __('the global help'))) .
620            '.</p>' .
621            '</div></div>';
622    }
623
624    public static function cssLoad($src, $media = 'screen', $v = '')
625    {
626        $escaped_src = html::escapeHTML($src);
627        if (!isset(self::$loaded_css[$escaped_src])) {
628            self::$loaded_css[$escaped_src] = true;
629            $escaped_src                    = self::appendVersion($escaped_src, $v);
630
631            return '<link rel="stylesheet" href="' . $escaped_src . '" type="text/css" media="' . $media . '" />' . "\n";
632        }
633    }
634
635    public static function jsLoad($src, $v = '')
636    {
637        $escaped_src = html::escapeHTML($src);
638        if (!isset(self::$loaded_js[$escaped_src])) {
639            self::$loaded_js[$escaped_src] = true;
640            $escaped_src                   = self::appendVersion($escaped_src, $v);
641            return '<script type="text/javascript" src="' . $escaped_src . '"></script>' . "\n";
642        }
643    }
644
645    private static function appendVersion($src, $v = '')
646    {
647        $src .= (strpos($src, '?') === false ? '?' : '&amp;') . 'v=';
648        if (defined('DC_DEV') && DC_DEV === true) {
649            $src .= md5(uniqid());
650        } else {
651            $src .= ($v === '' ? DC_VERSION : $v);
652        }
653        return $src;
654    }
655
656    public static function jsVar($n, $v)
657    {
658        return $n . " = '" . html::escapeJS($v) . "';\n";
659    }
660
661    public static function jsVars($vars)
662    {
663        $ret = '<script type="text/javascript">' . "\n";
664        foreach ($vars as $var => $value) {
665            $ret .= $var . ' = ' . (is_string($value) ? "'" . html::escapeJS($value) . "'" : $value) . ';' . "\n";
666        }
667        $ret .= "</script>\n";
668
669        return $ret;
670    }
671
672    public static function jsJson($id, $vars)
673    {
674        return dcUtils::jsJson($id, $vars);
675    }
676
677    public static function jsToggles()
678    {
679        $core = self::getCore();
680
681        if ($core->auth->user_prefs->toggles) {
682            $unfolded_sections = explode(',', $core->auth->user_prefs->toggles->unfolded_sections);
683            foreach ($unfolded_sections as $k => &$v) {
684                if ($v == '') {
685                    unset($unfolded_sections[$k]);
686                } else {
687                    $v = "'" . html::escapeJS($v) . "':true";
688                }
689            }
690        } else {
691            $unfolded_sections = [];
692        }
693        return '<script type="text/javascript">' . "\n" .
694        'dotclear.unfolded_sections = {' . join(",", $unfolded_sections) . "};\n" .
695            "</script>\n";
696    }
697
698    public static function jsCommon()
699    {
700        $core = self::getCore();
701
702        $mute_or_no = '';
703        if (empty($core->blog) || $core->blog->settings->system->jquery_migrate_mute) {
704            $mute_or_no .=
705                '<script type="text/javascript">' . "\n" .
706                'jQuery.migrateMute = true;' . "\n" .
707                "</script>\n";
708        }
709
710        return
711        self::jsLoad('js/jquery/jquery.js') .
712        $mute_or_no .
713        self::jsLoad('js/jquery/jquery-migrate.js') .
714        self::jsLoad('js/jquery/jquery.biscuit.js') .
715        self::jsLoad('js/common.js') .
716        self::jsLoad('js/services.js') .
717        self::jsLoad('js/prelude.js') .
718
719        '<script type="text/javascript">' . "\n" .
720        'jsToolBar = {}, jsToolBar.prototype = { elements : {} };' . "\n" .
721
722        self::jsVar('dotclear.nonce', $core->getNonce()) .
723
724        self::jsVar('dotclear.img_plus_src', 'images/expand.png') .
725        self::jsVar('dotclear.img_plus_txt', '►') .
726        self::jsVar('dotclear.img_plus_alt', __('uncover')) .
727        self::jsVar('dotclear.img_minus_src', 'images/hide.png') .
728        self::jsVar('dotclear.img_minus_txt', '▼') .
729        self::jsVar('dotclear.img_minus_alt', __('hide')) .
730        self::jsVar('dotclear.img_menu_on', 'images/menu_on.png') .
731        self::jsVar('dotclear.img_menu_off', 'images/menu_off.png') .
732
733        self::jsVar('dotclear.img_plus_theme_src', 'images/plus-theme.png') .
734        self::jsVar('dotclear.img_plus_theme_txt', '►') .
735        self::jsVar('dotclear.img_plus_theme_alt', __('uncover')) .
736        self::jsVar('dotclear.img_minus_theme_src', 'images/minus-theme.png') .
737        self::jsVar('dotclear.img_minus_theme_txt', '▼') .
738        self::jsVar('dotclear.img_minus_theme_alt', __('hide')) .
739
740        self::jsVar('dotclear.msg.help',
741            __('Need help?')) .
742        self::jsVar('dotclear.msg.new_window',
743            __('new window')) .
744        self::jsVar('dotclear.msg.help_hide',
745            __('Hide')) .
746        self::jsVar('dotclear.msg.to_select',
747            __('Select:')) .
748        self::jsVar('dotclear.msg.no_selection',
749            __('no selection')) .
750        self::jsVar('dotclear.msg.select_all',
751            __('select all')) .
752        self::jsVar('dotclear.msg.invert_sel',
753            __('Invert selection')) .
754        self::jsVar('dotclear.msg.website',
755            __('Web site:')) .
756        self::jsVar('dotclear.msg.email',
757            __('Email:')) .
758        self::jsVar('dotclear.msg.ip_address',
759            __('IP address:')) .
760        self::jsVar('dotclear.msg.error',
761            __('Error:')) .
762        self::jsVar('dotclear.msg.entry_created',
763            __('Entry has been successfully created.')) .
764        self::jsVar('dotclear.msg.edit_entry',
765            __('Edit entry')) .
766        self::jsVar('dotclear.msg.view_entry',
767            __('view entry')) .
768        self::jsVar('dotclear.msg.confirm_delete_posts',
769            __("Are you sure you want to delete selected entries (%s)?")) .
770        self::jsVar('dotclear.msg.confirm_delete_medias',
771            __("Are you sure you want to delete selected medias (%d)?")) .
772        self::jsVar('dotclear.msg.confirm_delete_categories',
773            __("Are you sure you want to delete selected categories (%s)?")) .
774        self::jsVar('dotclear.msg.confirm_delete_post',
775            __("Are you sure you want to delete this entry?")) .
776        self::jsVar('dotclear.msg.click_to_unlock',
777            __("Click here to unlock the field")) .
778        self::jsVar('dotclear.msg.confirm_spam_delete',
779            __('Are you sure you want to delete all spams?')) .
780        self::jsVar('dotclear.msg.confirm_delete_comments',
781            __('Are you sure you want to delete selected comments (%s)?')) .
782        self::jsVar('dotclear.msg.confirm_delete_comment',
783            __('Are you sure you want to delete this comment?')) .
784        self::jsVar('dotclear.msg.cannot_delete_users',
785            __('Users with posts cannot be deleted.')) .
786        self::jsVar('dotclear.msg.confirm_delete_user',
787            __('Are you sure you want to delete selected users (%s)?')) .
788        self::jsVar('dotclear.msg.confirm_delete_blog',
789            __('Are you sure you want to delete selected blogs (%s)?')) .
790        self::jsVar('dotclear.msg.confirm_delete_category',
791            __('Are you sure you want to delete category "%s"?')) .
792        self::jsVar('dotclear.msg.confirm_reorder_categories',
793            __('Are you sure you want to reorder all categories?')) .
794        self::jsVar('dotclear.msg.confirm_delete_media',
795            __('Are you sure you want to remove media "%s"?')) .
796        self::jsVar('dotclear.msg.confirm_delete_directory',
797            __('Are you sure you want to remove directory "%s"?')) .
798        self::jsVar('dotclear.msg.confirm_extract_current',
799            __('Are you sure you want to extract archive in current directory?')) .
800        self::jsVar('dotclear.msg.confirm_remove_attachment',
801            __('Are you sure you want to remove attachment "%s"?')) .
802        self::jsVar('dotclear.msg.confirm_delete_lang',
803            __('Are you sure you want to delete "%s" language?')) .
804        self::jsVar('dotclear.msg.confirm_delete_plugin',
805            __('Are you sure you want to delete "%s" plugin?')) .
806        self::jsVar('dotclear.msg.confirm_delete_plugins',
807            __('Are you sure you want to delete selected plugins?')) .
808        self::jsVar('dotclear.msg.use_this_theme',
809            __('Use this theme')) .
810        self::jsVar('dotclear.msg.remove_this_theme',
811            __('Remove this theme')) .
812        self::jsVar('dotclear.msg.confirm_delete_theme',
813            __('Are you sure you want to delete "%s" theme?')) .
814        self::jsVar('dotclear.msg.confirm_delete_themes',
815            __('Are you sure you want to delete selected themes?')) .
816        self::jsVar('dotclear.msg.confirm_delete_backup',
817            __('Are you sure you want to delete this backup?')) .
818        self::jsVar('dotclear.msg.confirm_revert_backup',
819            __('Are you sure you want to revert to this backup?')) .
820        self::jsVar('dotclear.msg.zip_file_content',
821            __('Zip file content')) .
822        self::jsVar('dotclear.msg.xhtml_validator',
823            __('XHTML markup validator')) .
824        self::jsVar('dotclear.msg.xhtml_valid',
825            __('XHTML content is valid.')) .
826        self::jsVar('dotclear.msg.xhtml_not_valid',
827            __('There are XHTML markup errors.')) .
828        self::jsVar('dotclear.msg.warning_validate_no_save_content',
829            __('Attention: an audit of a content not yet registered.')) .
830        self::jsVar('dotclear.msg.confirm_change_post_format',
831            __('You have unsaved changes. Switch post format will loose these changes. Proceed anyway?')) .
832        self::jsVar('dotclear.msg.confirm_change_post_format_noconvert',
833            __("Warning: post format change will not convert existing content. You will need to apply new format by yourself. Proceed anyway?")) .
834        self::jsVar('dotclear.msg.load_enhanced_uploader',
835            __('Loading enhanced uploader, please wait.')) .
836
837        self::jsVar('dotclear.msg.module_author',
838            __('Author:')) .
839        self::jsVar('dotclear.msg.module_details',
840            __('Details')) .
841        self::jsVar('dotclear.msg.module_support',
842            __('Support')) .
843        self::jsVar('dotclear.msg.module_help',
844            __('Help:')) .
845        self::jsVar('dotclear.msg.module_section',
846            __('Section:')) .
847        self::jsVar('dotclear.msg.module_tags',
848            __('Tags:')) .
849
850        self::jsVar('dotclear.msg.close_notice',
851            __('Hide this notice')) .
852
853            "\n" .
854            "</script>\n";
855    }
856
857    /**
858    @deprecated since version 2.11
859     */
860    public static function jsLoadIE7()
861    {
862        return '';
863    }
864
865    public static function jsConfirmClose(...$args)
866    {
867        if (count($args) > 0) {
868            foreach ($args as $k => $v) {
869                $args[$k] = "'" . html::escapeJS($v) . "'";
870            }
871            $params = implode(',', $args);
872        } else {
873            $params = '';
874        }
875
876        return
877        self::jsLoad('js/confirm-close.js') .
878        '<script type="text/javascript">' . "\n" .
879        "confirmClosePage = new confirmClose(" . $params . "); " .
880        "confirmClose.prototype.prompt = '" . html::escapeJS(__('You have unsaved changes.')) . "'; " .
881            "</script>\n";
882    }
883
884    public static function jsPageTabs($default = null)
885    {
886        if ($default) {
887            $default = "'" . html::escapeJS($default) . "'";
888        }
889
890        return
891        self::jsLoad('js/jquery/jquery.pageTabs.js') .
892            '<script type="text/javascript">' . "\n" .
893            '$(function() {' . "\n" .
894            '   $.pageTabs(' . $default . ');' . "\n" .
895            '});' .
896            "</script>\n";
897    }
898
899    public static function jsModal()
900    {
901        return
902        self::jsLoad('js/jquery/jquery.magnific-popup.js');
903    }
904
905    public static function jsColorPicker()
906    {
907        return
908        self::cssLoad('style/farbtastic/farbtastic.css') .
909        self::jsLoad('js/jquery/jquery.farbtastic.js') .
910        self::jsLoad('js/color-picker.js');
911    }
912
913    public static function jsDatePicker()
914    {
915        $js = [
916            'months'    => [
917                __('January'),
918                __('February'),
919                __('March'),
920                __('April'),
921                __('May'),
922                __('June'),
923                __('July'),
924                __('August'),
925                __('September'),
926                __('October'),
927                __('November'),
928                __('December')
929            ],
930            'days'      => [
931                __('Monday'),
932                __('Tuesday'),
933                __('Wednesday'),
934                __('Thursday'),
935                __('Friday'),
936                __('Saturday'),
937                __('Sunday')
938            ],
939            'img_src'   => 'images/date-picker.png',
940            'img_alt'   => __('Choose date'),
941            'close_msg' => __('close'),
942            'now_msg'   => __('now')
943        ];
944        return
945        self::cssLoad('style/date-picker.css') .
946        self::jsJson('date_picker', $js) .
947        self::jsLoad('js/date-picker.js');
948    }
949
950    public static function jsToolBar()
951    {
952        # Deprecated but we keep this for plugins.
953    }
954
955    public static function jsUpload($params = [], $base_url = null)
956    {
957        $core = self::getCore();
958
959        if (!$base_url) {
960            $base_url = path::clean(dirname(preg_replace('/(\?.*$)?/', '', $_SERVER['REQUEST_URI']))) . '/';
961        }
962
963        $params = array_merge($params, [
964            'sess_id=' . session_id(),
965            'sess_uid=' . $_SESSION['sess_browser_uid'],
966            'xd_check=' . $core->getNonce()
967        ]);
968
969        return
970        '<script type="text/javascript">' . "\n" .
971        "dotclear.jsUpload = {};\n" .
972        "dotclear.jsUpload.msg = {};\n" .
973        self::jsVar('dotclear.msg.enhanced_uploader_activate', __('Temporarily activate enhanced uploader')) .
974        self::jsVar('dotclear.msg.enhanced_uploader_disable', __('Temporarily disable enhanced uploader')) .
975        self::jsVar('dotclear.jsUpload.msg.limit_exceeded', __('Limit exceeded.')) .
976        self::jsVar('dotclear.jsUpload.msg.size_limit_exceeded', __('File size exceeds allowed limit.')) .
977        self::jsVar('dotclear.jsUpload.msg.canceled', __('Canceled.')) .
978        self::jsVar('dotclear.jsUpload.msg.http_error', __('HTTP Error:')) .
979        self::jsVar('dotclear.jsUpload.msg.error', __('Error:')) .
980        self::jsVar('dotclear.jsUpload.msg.choose_file', __('Choose file')) .
981        self::jsVar('dotclear.jsUpload.msg.choose_files', __('Choose files')) .
982        self::jsVar('dotclear.jsUpload.msg.cancel', __('Cancel')) .
983        self::jsVar('dotclear.jsUpload.msg.clean', __('Clean')) .
984        self::jsVar('dotclear.jsUpload.msg.upload', __('Upload')) .
985        self::jsVar('dotclear.jsUpload.msg.send', __('Send')) .
986        self::jsVar('dotclear.jsUpload.msg.file_successfully_uploaded', __('File successfully uploaded.')) .
987        self::jsVar('dotclear.jsUpload.msg.no_file_in_queue', __('No file in queue.')) .
988        self::jsVar('dotclear.jsUpload.msg.file_in_queue', __('1 file in queue.')) .
989        self::jsVar('dotclear.jsUpload.msg.files_in_queue', __('%d files in queue.')) .
990        self::jsVar('dotclear.jsUpload.msg.queue_error', __('Queue error:')) .
991        self::jsVar('dotclear.jsUpload.base_url', $base_url) .
992        "</script>\n" .
993
994        self::jsLoad('js/jquery/jquery-ui.custom.js') .
995        self::jsLoad('js/jsUpload/tmpl.js') .
996        self::jsLoad('js/jsUpload/template-upload.js') .
997        self::jsLoad('js/jsUpload/template-download.js') .
998        self::jsLoad('js/jsUpload/load-image.js') .
999        self::jsLoad('js/jsUpload/jquery.iframe-transport.js') .
1000        self::jsLoad('js/jsUpload/jquery.fileupload.js') .
1001        self::jsLoad('js/jsUpload/jquery.fileupload-process.js') .
1002        self::jsLoad('js/jsUpload/jquery.fileupload-resize.js') .
1003        self::jsLoad('js/jsUpload/jquery.fileupload-ui.js');
1004    }
1005
1006    public static function jsMetaEditor()
1007    {
1008        return self::jsLoad('js/meta-editor.js');
1009    }
1010
1011    public static function jsFilterControl($show = true)
1012    {
1013        $js = [
1014            'show_filters' => (boolean) $show,
1015            'filter_posts_list' => __('Show filters and display options'),
1016            'cancel_the_filter' => __('Cancel filters and display options')
1017        ];
1018        return
1019        self::jsJson('filter_controls', $js).
1020        self::jsLoad('js/filter-controls.js');
1021    }
1022
1023    public static function jsLoadCodeMirror($theme = '', $multi = true, $modes = ['css', 'htmlmixed', 'javascript', 'php', 'xml'])
1024    {
1025        $ret =
1026        self::cssLoad('js/codemirror/lib/codemirror.css') .
1027        self::jsLoad('js/codemirror/lib/codemirror.js');
1028        if ($multi) {
1029            $ret .= self::jsLoad('js/codemirror/addon/mode/multiplex.js');
1030        }
1031        foreach ($modes as $mode) {
1032            $ret .= self::jsLoad('js/codemirror/mode/' . $mode . '/' . $mode . '.js');
1033        }
1034        $ret .=
1035        self::jsLoad('js/codemirror/addon/edit/closebrackets.js') .
1036        self::jsLoad('js/codemirror/addon/edit/matchbrackets.js') .
1037        self::cssLoad('js/codemirror/addon/display/fullscreen.css') .
1038        self::jsLoad('js/codemirror/addon/display/fullscreen.js');
1039        if ($theme != '') {
1040            $ret .= self::cssLoad('js/codemirror/theme/' . $theme . '.css');
1041        }
1042        return $ret;
1043    }
1044
1045    public static function jsRunCodeMirror($name, $id, $mode, $theme = '')
1046    {
1047        $ret =
1048            '<script type="text/javascript">' . "\n" .
1049            'var ' . $name . ' = CodeMirror.fromTextArea(' . $id . ',{' . "\n" .
1050            '   mode: "' . $mode . '",' . "\n" .
1051            '   tabMode: "indent",' . "\n" .
1052            '   lineWrapping: 1,' . "\n" .
1053            '   lineNumbers: 1,' . "\n" .
1054            '   matchBrackets: 1,' . "\n" .
1055            '   autoCloseBrackets: 1,' . "\n" .
1056            '   extraKeys: {"F11": function(cm) {cm.setOption("fullScreen",!cm.getOption("fullScreen"));}}';
1057        if ($theme) {
1058            $ret .=
1059                ',' . "\n" .
1060                '   theme: "' . $theme . '"';
1061        }
1062        $ret .= "\n" .
1063            '});' . "\n" .
1064            '</script>';
1065        return $ret;
1066    }
1067
1068    public static function getCodeMirrorThemes()
1069    {
1070        $themes      = [];
1071        $themes_root = dirname(__FILE__) . '/../../admin' . '/js/codemirror/theme/';
1072        if (is_dir($themes_root) && is_readable($themes_root)) {
1073            if (($d = @dir($themes_root)) !== false) {
1074                while (($entry = $d->read()) !== false) {
1075                    if ($entry != '.' && $entry != '..' && substr($entry, 0, 1) != '.' && is_readable($themes_root . '/' . $entry)) {
1076                        $themes[] = substr($entry, 0, -4); // remove .css extension
1077                    }
1078                }
1079                sort($themes);
1080            }
1081        }
1082        return $themes;
1083    }
1084
1085    public static function getPF($file)
1086    {
1087        $core = self::getCore();
1088        return $core->adminurl->get('load.plugin.file', ['pf' => $file]);
1089    }
1090
1091    public static function getVF($file)
1092    {
1093        $core = self::getCore();
1094        return $core->adminurl->get('load.var.file', ['vf' => $file]);
1095    }
1096
1097    public static function setXFrameOptions($headers, $origin = null)
1098    {
1099        if (self::$xframe_loaded) {
1100            return;
1101        }
1102
1103        if ($origin !== null) {
1104            $url                        = parse_url($origin);
1105            $headers['x-frame-options'] = sprintf('X-Frame-Options: %s', is_array($url) && isset($url['host']) ?
1106                ("ALLOW-FROM " . (isset($url['scheme']) ? $url['scheme'] . ':' : '') . '//' . $url['host']) :
1107                'SAMEORIGIN');
1108        } else {
1109            $headers['x-frame-options'] = 'X-Frame-Options: SAMEORIGIN'; // FF 3.6.9+ Chrome 4.1+ IE 8+ Safari 4+ Opera 10.5+
1110        }
1111        self::$xframe_loaded = true;
1112    }
1113}
Note: See TracBrowser for help on using the repository browser.

Sites map