Dotclear

source: inc/admin/lib.dc.page.php @ 3912:326f9c53acae

Revision 3912:326f9c53acae, 46.1 KB checked in by franck <carnet.franck.paul@…>, 7 years ago (diff)

Switching from inline JS variables to JSON script. More to come…

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

Sites map