"success", "warning" => "warning-msg", "error" => "error", "message" => "message", "static" => "static-msg"]; private static function getCore() { return $GLOBALS['core']; } # Auth check public static function check($permissions) { $core = self::getCore(); if ($core->blog && $core->auth->check($permissions, $core->blog->id)) { return; } if (session_id()) { $core->session->destroy(); } http::redirect(DC_AUTH_PAGE); } # Check super admin public static function checkSuper() { $core = self::getCore(); if (!$core->auth->isSuperAdmin()) { if (session_id()) { $core->session->destroy(); } http::redirect(DC_AUTH_PAGE); } } # Top of admin page public static function open($title = '', $head = '', $breadcrumb = '', $options = []) { $core = self::getCore(); $js = []; # List of user's blogs if ($core->auth->getBlogCount() == 1 || $core->auth->getBlogCount() > 20) { $blog_box = '

' . __('Blog:') . ' ' . html::escapeHTML($core->blog->name) . ''; if ($core->auth->getBlogCount() > 20) { $blog_box .= ' - ' . __('Change blog') . ''; } $blog_box .= '

'; } else { $rs_blogs = $core->getBlogs(['order' => 'LOWER(blog_name)', 'limit' => 20]); $blogs = []; while ($rs_blogs->fetch()) { $blogs[html::escapeHTML($rs_blogs->blog_name . ' - ' . $rs_blogs->blog_url)] = $rs_blogs->blog_id; } $blog_box = '

' . $core->formNonce() . form::combo('switchblog', $blogs, $core->blog->id) . '

'; } $safe_mode = isset($_SESSION['sess_safe_mode']) && $_SESSION['sess_safe_mode']; # Display $headers = new ArrayObject([]); # Content-Type $headers['content-type'] = 'Content-Type: text/html; charset=UTF-8'; # Referrer Policy for admin pages $headers['referrer'] = 'Referrer-Policy: strict-origin'; # Prevents Clickjacking as far as possible if (isset($options['x-frame-allow'])) { self::setXFrameOptions($headers, $options['x-frame-allow']); } else { self::setXFrameOptions($headers); } # Content-Security-Policy (only if safe mode if not active, it may help) if (!$safe_mode && $core->blog->settings->system->csp_admin_on) { // Get directives from settings if exist, else set defaults $csp = new ArrayObject([]); // SQlite Clearbricks driver does not allow using single quote at beginning or end of a field value // so we have to use neutral values (localhost and 127.0.0.1) for some CSP directives $csp_prefix = $core->con->driver() == 'sqlite' ? 'localhost ' : ''; // Hack for SQlite Clearbricks driver $csp_suffix = $core->con->driver() == 'sqlite' ? ' 127.0.0.1' : ''; // Hack for SQlite Clearbricks driver $csp['default-src'] = $core->blog->settings->system->csp_admin_default ?: $csp_prefix . "'self'" . $csp_suffix; $csp['script-src'] = $core->blog->settings->system->csp_admin_script ?: $csp_prefix . "'self' 'unsafe-inline' 'unsafe-eval'" . $csp_suffix; $csp['style-src'] = $core->blog->settings->system->csp_admin_style ?: $csp_prefix . "'self' 'unsafe-inline'" . $csp_suffix; $csp['img-src'] = $core->blog->settings->system->csp_admin_img ?: $csp_prefix . "'self' data: http://media.dotaddict.org blob:"; # Cope with blog post preview (via public URL in iframe) if (!is_null($core->blog->host)) { $csp['default-src'] .= ' ' . parse_url($core->blog->host, PHP_URL_HOST); $csp['script-src'] .= ' ' . parse_url($core->blog->host, PHP_URL_HOST); $csp['style-src'] .= ' ' . parse_url($core->blog->host, PHP_URL_HOST); } # Cope with media display in media manager (via public URL) if (!is_null($core->media)) { $csp['img-src'] .= ' ' . parse_url($core->media->root_url, PHP_URL_HOST); } # Allow everything in iframe (used by editors to preview public content) $csp['child-src'] = "*"; # --BEHAVIOR-- adminPageHTTPHeaderCSP $core->callBehavior('adminPageHTTPHeaderCSP', $csp); // Construct CSP header $directives = []; foreach ($csp as $key => $value) { if ($value) { $directives[] = $key . ' ' . $value; } } if (count($directives)) { if (version_compare(phpversion(), '5.4', '>=')) { // csp_report.php needs PHP ≥ 5.4 $directives[] = "report-uri " . DC_ADMIN_URL . "csp_report.php"; } $report_only = ($core->blog->settings->system->csp_admin_report_only) ? '-Report-Only' : ''; $headers['csp'] = "Content-Security-Policy" . $report_only . ": " . implode(" ; ", $directives); } } # --BEHAVIOR-- adminPageHTTPHeaders $core->callBehavior('adminPageHTTPHeaders', $headers); foreach ($headers as $key => $value) { header($value); } echo '' . '' . "\n" . "\n" . ' ' . "\n" . ' ' . "\n" . ' ' . "\n" . ' ' . "\n" . ' ' . $title . ' - ' . html::escapeHTML($core->blog->name) . ' - ' . html::escapeHTML(DC_VENDOR_NAME) . ' - ' . DC_VERSION . '' . "\n"; echo self::jsUtil(); if ($core->auth->user_prefs->interface->darkmode) { $js['darkMode'] = 1; echo self::cssLoad('style/default-dark.css'); } else { $js['darkMode'] = 0; echo self::cssLoad('style/default.css'); } if (l10n::getTextDirection($GLOBALS['_lang']) == 'rtl') { echo self::cssLoad('style/default-rtl.css'); } $core->auth->user_prefs->addWorkspace('interface'); if (!$core->auth->user_prefs->interface->hide_std_favicon) { echo '' . "\n" . '' . "\n"; } if ($core->auth->user_prefs->interface->htmlfontsize) { $js['htmlFontSize'] = $core->auth->user_prefs->interface->htmlfontsize; } $js['hideMoreInfo'] = (boolean) $core->auth->user_prefs->interface->hidemoreinfo; $js['showAjaxLoader'] = (boolean) $core->auth->user_prefs->interface->showajaxloader; $core->auth->user_prefs->addWorkspace('accessibility'); $js['noDragDrop'] = (boolean) $core->auth->user_prefs->accessibility->nodragdrop; // Set some JSON data echo dcUtils::jsJson('dotclear_init', $js); echo self::jsCommon() . self::jsToggles() . $head; # --BEHAVIOR-- adminPageHTMLHead $core->callBehavior('adminPageHTMLHead'); echo "\n" . '' . "\n" . '' . "\n" . ''; // end header echo '
' . "\n" . '
' . '
' . "\n" . '
' . "\n"; # Safe mode if ($safe_mode) { echo ''; } // Display breadcrumb (if given) before any error messages echo $breadcrumb; // Display notices and errors echo self::notices(); } public static function notices() { $core = self::getCore(); static $error_displayed = false; $res = ''; // return error messages if any if ($core->error->flag() && !$error_displayed) { # --BEHAVIOR-- adminPageNotificationError $notice_error = $core->callBehavior('adminPageNotificationError', $core, $core->error); if (isset($notice_error) && !empty($notice_error)) { $res .= $notice_error; } else { $res .= '

' . '' . (count($core->error->getErrors()) > 1 ? __('Errors:') : __('Error:')) . '' . '

' . $core->error->toHTML() . '
'; } $error_displayed = true; } // return notices if any if (isset($_SESSION['notifications'])) { foreach ($_SESSION['notifications'] as $notification) { # --BEHAVIOR-- adminPageNotification $notice = $core->callBehavior('adminPageNotification', $core, $notification); $res .= (isset($notice) && !empty($notice) ? $notice : self::getNotification($notification)); } unset($_SESSION['notifications']); } return $res; } public static function addNotice($type, $message, $options = []) { if (isset(self::$N_TYPES[$type])) { $class = self::$N_TYPES[$type]; } else { $class = $type; } if (isset($_SESSION['notifications']) && is_array($_SESSION['notifications'])) { $notifications = $_SESSION['notifications']; } else { $notifications = []; } $n = array_merge($options, ['class' => $class, 'ts' => time(), 'text' => $message]); if ($type != "static") { $notifications[] = $n; } else { array_unshift($notifications, $n); } $_SESSION['notifications'] = $notifications; } public static function addSuccessNotice($message, $options = []) { self::addNotice("success", $message, $options); } public static function addWarningNotice($message, $options = []) { self::addNotice("warning", $message, $options); } public static function addErrorNotice($message, $options = []) { self::addNotice("error", $message, $options); } protected static function getNotification($n) { $core = self::getCore(); $tag = (isset($n['divtag']) && $n['divtag']) ? 'div' : 'p'; $ts = ''; if (!isset($n['with_ts']) || ($n['with_ts'] == true)) { $ts = dt::str(__('[%H:%M:%S]'), $n['ts'], $core->auth->getInfo('user_tz')) . ' '; } $res = '<' . $tag . ' class="' . $n['class'] . '" role="alert">' . $ts . $n['text'] . ''; return $res; } public static function close() { $core = self::getCore(); if (!$GLOBALS['__resources']['ctxhelp']) { if (!$core->auth->user_prefs->interface->hidehelpbutton) { echo '

' . __('Global help') . '

'; } } $menu = &$GLOBALS['_menu']; echo "
\n" . // End of #content "
\n" . // End of #main '' . "\n" . // End of #main-menu "
\n"; // End of #wrapper echo '

' . __('Page top') . '

' . "\n"; $figure = " ♥‿♥ | | | )_) )_) )_) )___))___))___)\ )____)____)_____)\\ _____|____|____|____\\\__ ---------\ /--------- ^^^^^ ^^^^^^^^^^^^^^^^^^^^^ ^^^^ ^^^^ ^^^ ^^ ^^^^ ^^^ "; echo '' . "\n" . "" . "\n"; if (defined('DC_DEV') && DC_DEV === true) { echo self::debugInfo(); } echo ''; } public static function openPopup($title = '', $head = '', $breadcrumb = '') { $core = self::getCore(); $js = []; # Display header('Content-Type: text/html; charset=UTF-8'); # Referrer Policy for admin pages header('Referrer-Policy: strict-origin'); # Prevents Clickjacking as far as possible header('X-Frame-Options: SAMEORIGIN'); // FF 3.6.9+ Chrome 4.1+ IE 8+ Safari 4+ Opera 10.5+ echo '' . '' . "\n" . "\n" . ' ' . "\n" . ' ' . "\n" . ' ' . $title . ' - ' . html::escapeHTML($core->blog->name) . ' - ' . html::escapeHTML(DC_VENDOR_NAME) . ' - ' . DC_VERSION . '' . "\n" . ' ' . "\n" . ' ' . "\n"; echo self::jsUtil(); if ($core->auth->user_prefs->interface->darkmode) { $js['darkMode'] = 1; echo self::cssLoad('style/default-dark.css'); } else { $js['darkMode'] = 0; echo self::cssLoad('style/default.css'); } if (l10n::getTextDirection($GLOBALS['_lang']) == 'rtl') { echo self::cssLoad('style/default-rtl.css'); } $core->auth->user_prefs->addWorkspace('interface'); if ($core->auth->user_prefs->interface->htmlfontsize) { $js['htmlFontSize'] = $core->auth->user_prefs->interface->htmlfontsize; } $js['hideMoreInfo'] = (boolean) $core->auth->user_prefs->interface->hidemoreinfo; $js['showAjaxLoader'] = (boolean) $core->auth->user_prefs->interface->showajaxloader; $core->auth->user_prefs->addWorkspace('accessibility'); $js['noDragDrop'] = (boolean) $core->auth->user_prefs->accessibility->nodragdrop; // Set JSON data echo dcUtils::jsJson('dotclear_init', $js); echo self::jsCommon() . self::jsToggles() . $head; # --BEHAVIOR-- adminPageHTMLHead $core->callBehavior('adminPageHTMLHead'); echo "\n" . '' . "\n" . '

' . DC_VENDOR_NAME . '

' . "\n"; echo '
' . "\n" . '
' . "\n" . '
' . "\n"; // display breadcrumb if given echo $breadcrumb; // Display notices and errors echo self::notices(); } public static function closePopup() { echo "
\n" . // End of #content "
\n" . // End of #main "
\n" . // End of #wrapper '

' . __('Page top') . '

' . "\n" . '' . "\n" . ''; } public static function breadcrumb($elements = null, $options = []) { $core = self::getCore(); $with_home_link = isset($options['home_link']) ? $options['home_link'] : true; $hl = isset($options['hl']) ? $options['hl'] : true; $hl_pos = isset($options['hl_pos']) ? $options['hl_pos'] : -1; // First item of array elements should be blog's name, System or Plugins $res = '

' . ($with_home_link ? '' . __('Go to dashboard') . '' : ''); $index = 0; if ($hl_pos < 0) { $hl_pos = count($elements) + $hl_pos; } foreach ($elements as $element => $url) { if ($hl && $index == $hl_pos) { $element = sprintf('%s', $element); } $res .= ($with_home_link ? ($index == 1 ? ' : ' : ' › ') : ($index == 0 ? ' ' : ' › ')) . ($url ? '' : '') . $element . ($url ? '' : ''); $index++; } $res .= '

'; return $res; } public static function message($msg, $timestamp = true, $div = false, $echo = true, $class = 'message') { $core = self::getCore(); $res = ''; if ($msg != '') { $res = ($div ? '
' : '') . '' . ($timestamp ? dt::str(__('[%H:%M:%S]'), null, $core->auth->getInfo('user_tz')) . ' ' : '') . $msg . '

' . ($div ? '
' : ''); if ($echo) { echo $res; } } return $res; } public static function success($msg, $timestamp = true, $div = false, $echo = true) { return self::message($msg, $timestamp, $div, $echo, "success"); } public static function warning($msg, $timestamp = true, $div = false, $echo = true) { return self::message($msg, $timestamp, $div, $echo, "warning-msg"); } private static function debugInfo() { $global_vars = implode(', ', array_keys($GLOBALS)); $res = '
' . '

memory usage: ' . memory_get_usage() . ' (' . files::size(memory_get_usage()) . ')

'; if (function_exists('xdebug_get_profiler_filename')) { $res .= '

Elapsed time: ' . xdebug_time_index() . ' seconds

'; $prof_file = xdebug_get_profiler_filename(); if ($prof_file) { $res .= '

Profiler file : ' . xdebug_get_profiler_filename() . '

'; } else { $prof_url = http::getSelfURI(); $prof_url .= (strpos($prof_url, '?') === false) ? '?' : '&'; $prof_url .= 'XDEBUG_PROFILE'; $res .= '

Trigger profiler

'; } /* xdebug configuration: zend_extension = /.../xdebug.so xdebug.auto_trace = On xdebug.trace_format = 0 xdebug.trace_options = 1 xdebug.show_mem_delta = On xdebug.profiler_enable = 0 xdebug.profiler_enable_trigger = 1 xdebug.profiler_output_dir = /tmp xdebug.profiler_append = 0 xdebug.profiler_output_name = timestamp */ } $res .= '

Global vars: ' . $global_vars . '

' . '
'; return $res; } public static function help($page, $index = '') { # Deprecated but we keep this for plugins. } public static function helpBlock(...$params) { $core = self::getCore(); if ($core->auth->user_prefs->interface->hidehelpbutton) { return; } $args = new ArrayObject($params); # --BEHAVIOR-- adminPageHelpBlock $core->callBehavior('adminPageHelpBlock', $args); if (empty($args)) { return; }; global $__resources; if (empty($__resources['help'])) { return; } $content = ''; foreach ($args as $v) { if (is_object($v) && isset($v->content)) { $content .= $v->content; continue; } if (!isset($__resources['help'][$v])) { continue; } $f = $__resources['help'][$v]; if (!file_exists($f) || !is_readable($f)) { continue; } $fc = file_get_contents($f); if (preg_match('|]*?>(.*?)|ms', $fc, $matches)) { $content .= $matches[1]; } else { $content .= $fc; } } if (trim($content) == '') { return; } // Set contextual help global flag $GLOBALS['__resources']['ctxhelp'] = true; echo '

' . __('Help about this page') . '

' . $content . '
' . '
'; } public static function cssLoad($src, $media = 'screen', $v = '') { $escaped_src = html::escapeHTML($src); if (!isset(self::$loaded_css[$escaped_src])) { self::$loaded_css[$escaped_src] = true; $escaped_src = self::appendVersion($escaped_src, $v); return '' . "\n"; } } public static function jsLoad($src, $v = '') { $escaped_src = html::escapeHTML($src); if (!isset(self::$loaded_js[$escaped_src])) { self::$loaded_js[$escaped_src] = true; $escaped_src = self::appendVersion($escaped_src, $v); return '' . "\n"; } } private static function appendVersion($src, $v = '') { $src .= (strpos($src, '?') === false ? '?' : '&') . 'v='; if (defined('DC_DEV') && DC_DEV === true) { $src .= md5(uniqid()); } else { $src .= ($v === '' ? DC_VERSION : $v); } return $src; } public static function jsVar($n, $v) { return $n . " = '" . html::escapeJS($v) . "';\n"; } public static function jsVars($vars) { $ret = '\n"; return $ret; } public static function jsUtil() { $core = self::getCore(); return self::jsLoad($core->blog->getPF('util.js')); } public static function jsToggles() { $core = self::getCore(); if ($core->auth->user_prefs->toggles) { $unfolded_sections = explode(',', $core->auth->user_prefs->toggles->unfolded_sections); foreach ($unfolded_sections as $k => &$v) { if ($v == '') { unset($unfolded_sections[$k]); } else { $v = "'" . html::escapeJS($v) . "':true"; } } } else { $unfolded_sections = []; } return '\n"; } public static function jsCommon() { $core = self::getCore(); $mute_or_no = ''; if (empty($core->blog) || $core->blog->settings->system->jquery_migrate_mute) { $mute_or_no .= '\n"; } return self::jsLoad('js/jquery/jquery.js') . $mute_or_no . self::jsLoad('js/jquery/jquery-migrate.js') . self::jsLoad('js/jquery/jquery.biscuit.js') . self::jsLoad('js/common.js') . self::jsLoad('js/services.js') . self::jsLoad('js/prelude.js') . '\n"; } /** @deprecated since version 2.11 */ public static function jsLoadIE7() { return ''; } public static function jsConfirmClose(...$args) { if (count($args) > 0) { foreach ($args as $k => $v) { $args[$k] = "'" . html::escapeJS($v) . "'"; } $params = implode(',', $args); } else { $params = ''; } return self::jsLoad('js/confirm-close.js') . '\n"; } public static function jsPageTabs($default = null) { if ($default) { $default = "'" . html::escapeJS($default) . "'"; } return self::jsLoad('js/jquery/jquery.pageTabs.js') . '\n"; } public static function jsModal() { return self::jsLoad('js/jquery/jquery.magnific-popup.js'); } public static function jsColorPicker() { return self::cssLoad('style/farbtastic/farbtastic.css') . self::jsLoad('js/jquery/jquery.farbtastic.js') . self::jsLoad('js/color-picker.js'); } public static function jsDatePicker() { return self::cssLoad('style/date-picker.css') . self::jsLoad('js/date-picker.js') . '\n"; } public static function jsToolBar() { # Deprecated but we keep this for plugins. } public static function jsUpload($params = [], $base_url = null) { $core = self::getCore(); if (!$base_url) { $base_url = path::clean(dirname(preg_replace('/(\?.*$)?/', '', $_SERVER['REQUEST_URI']))) . '/'; } $params = array_merge($params, [ 'sess_id=' . session_id(), 'sess_uid=' . $_SESSION['sess_browser_uid'], 'xd_check=' . $core->getNonce() ]); return '\n" . self::jsLoad('js/jquery/jquery-ui.custom.js') . self::jsLoad('js/jsUpload/tmpl.js') . self::jsLoad('js/jsUpload/template-upload.js') . self::jsLoad('js/jsUpload/template-download.js') . self::jsLoad('js/jsUpload/load-image.js') . self::jsLoad('js/jsUpload/jquery.iframe-transport.js') . self::jsLoad('js/jsUpload/jquery.fileupload.js') . self::jsLoad('js/jsUpload/jquery.fileupload-process.js') . self::jsLoad('js/jsUpload/jquery.fileupload-resize.js') . self::jsLoad('js/jsUpload/jquery.fileupload-ui.js'); } public static function jsMetaEditor() { return self::jsLoad('js/meta-editor.js'); } public static function jsFilterControl($show = true) { return self::jsLoad('js/filter-controls.js') . '"; } public static function jsLoadCodeMirror($theme = '', $multi = true, $modes = ['css', 'htmlmixed', 'javascript', 'php', 'xml']) { $ret = self::cssLoad('js/codemirror/lib/codemirror.css') . self::jsLoad('js/codemirror/lib/codemirror.js'); if ($multi) { $ret .= self::jsLoad('js/codemirror/addon/mode/multiplex.js'); } foreach ($modes as $mode) { $ret .= self::jsLoad('js/codemirror/mode/' . $mode . '/' . $mode . '.js'); } $ret .= self::jsLoad('js/codemirror/addon/edit/closebrackets.js') . self::jsLoad('js/codemirror/addon/edit/matchbrackets.js') . self::cssLoad('js/codemirror/addon/display/fullscreen.css') . self::jsLoad('js/codemirror/addon/display/fullscreen.js'); if ($theme != '') { $ret .= self::cssLoad('js/codemirror/theme/' . $theme . '.css'); } return $ret; } public static function jsRunCodeMirror($name, $id, $mode, $theme = '') { $ret = ''; return $ret; } public static function getCodeMirrorThemes() { $themes = []; $themes_root = dirname(__FILE__) . '/../../admin' . '/js/codemirror/theme/'; if (is_dir($themes_root) && is_readable($themes_root)) { if (($d = @dir($themes_root)) !== false) { while (($entry = $d->read()) !== false) { if ($entry != '.' && $entry != '..' && substr($entry, 0, 1) != '.' && is_readable($themes_root . '/' . $entry)) { $themes[] = substr($entry, 0, -4); // remove .css extension } } sort($themes); } } return $themes; } public static function getPF($file) { $core = self::getCore(); return $core->adminurl->get('load.plugin.file', ['pf' => $file]); } public static function getVF($file) { $core = self::getCore(); return $core->adminurl->get('load.var.file', ['vf' => $file]); } public static function setXFrameOptions($headers, $origin = null) { if (self::$xframe_loaded) { return; } if ($origin !== null) { $url = parse_url($origin); $headers['x-frame-options'] = sprintf('X-Frame-Options: %s', is_array($url) && isset($url['host']) ? ("ALLOW-FROM " . (isset($url['scheme']) ? $url['scheme'] . ':' : '') . '//' . $url['host']) : 'SAMEORIGIN'); } else { $headers['x-frame-options'] = 'X-Frame-Options: SAMEORIGIN'; // FF 3.6.9+ Chrome 4.1+ IE 8+ Safari 4+ Opera 10.5+ } self::$xframe_loaded = true; } }