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