Commit b7009474 authored by tjhunt's avatar tjhunt
Browse files

themes: MDL-19077 change how the theme is initialised and CSS is served.

This is part of http://docs.moodle.org/en/Development:Theme_engines_for_Moodle%3F

$THEME is now initialised at the same time as $OUTPUT. Old functions like
theme_setup are deprecated in favour of methods on $PAGE. There is a new
theme_config class in outputlib.php that deals with loading the theme config.php file.

CSS used to be served by themes styles.php files calling a function in weblib.php.
Now it works by each theme's styles.php file doing
$themename = basename(dirname(__FILE__));
require_once(dirname(__FILE__) . '/../../theme/styles.php');
which is less code to be copied into each theme. (Old-style styles.php files still
work thanks to some code in deprecatedlib.php.)

Admin UI for choosing a theme cleaned up.

A couple of theme-specific hard-coded hacks like $THEME->cssconstants and
$THEME->CSSEdit have been replaced by a more generic $THEME->customcssoutputfunction
hook. See examples at the end of outputlib.php

Also:
* Fix setting the theme in the URL, which seems to have been broken since 1.9.
* Fix up errors on a few pages caused by the new initialisation order.
* MDL-19097 moodle_page::set_course should not set $COURSE unless it is $PAGE.
* httpsrequired() from moodlelib.php moved to $PAGE->https_required().
* Move has_started() method to the renderer base class.
* Further fixes to display of early errors.
* Remove print_header/footer_old from weblib. I did not mean to commit them before.
parent 0456fc1a
......@@ -11,6 +11,7 @@ if ($hassiteconfig) { // speedup for non-admins, add all caps used on this page
$temp->add(new admin_setting_configcheckbox('allowuserthemes', get_string('allowuserthemes', 'admin'), get_string('configallowuserthemes', 'admin'), 0));
$temp->add(new admin_setting_configcheckbox('allowcoursethemes', get_string('allowcoursethemes', 'admin'), get_string('configallowcoursethemes', 'admin'), 0));
$temp->add(new admin_setting_configcheckbox('allowcategorythemes', get_string('allowcategorythemes', 'admin'), get_string('configallowcategorythemes', 'admin'), 0));
$temp->add(new admin_setting_configcheckbox('allowthemechangeonurl', get_string('allowthemechangeonurl', 'admin'), get_string('configallowthemechangeonurl', 'admin'), 0));
$temp->add(new admin_setting_configcheckbox('allowuserblockhiding', get_string('allowuserblockhiding', 'admin'), get_string('configallowuserblockhiding', 'admin'), 1));
$temp->add(new admin_setting_configcheckbox('showblocksonmodpages', get_string('showblocksonmodpages', 'admin'), get_string('configshowblocksonmodpages', 'admin'), 0));
$temp->add(new admin_setting_configselect('hideactivitytypenavlink', get_string('hideactivitytypenavlink', 'admin'), get_string('confighideactivitytypenavlink', 'admin'), 0,
......
......@@ -291,8 +291,8 @@ $CFG->admin = 'admin';
// Set the priority of themes from highest to lowest. This is useful (for
// example) in sites where the user theme should override all other theme
// settings for accessibility reasons. You can also disable types of themes
// by removing them from the array. The default setting is:
// $CFG->themeorder = array('page', 'course', 'category', 'session', 'user', 'site');
// (other than site) by removing them from the array. The default setting is:
// $CFG->themeorder = array('course', 'category', 'session', 'user', 'site');
// NOTE: course, category, session, user themes still require the
// respective settings to be enabled
//
......
......@@ -63,11 +63,6 @@
}
}
if(!empty($CFG->allowcategorythemes) && isset($category->theme)) {
// specifying theme here saves us some dbqs
theme_setup($category->theme);
}
/// Print headings
$numcategories = $DB->count_records('course_categories');
......
......@@ -12,6 +12,7 @@ $string['allowediplist'] = 'Allowed IP list';
$string['allowemailaddresses'] = 'Allowed email domains';
$string['allowobjectembed'] = 'Allow EMBED and OBJECT tags';
$string['allowrenames'] = 'Allow renames';
$string['allowthemechangeonurl'] = 'Allow theme changes in the URL';
$string['allowuserblockhiding'] = 'Allow users to hide blocks';
$string['allowusermailcharset'] = 'Allow user to select character set';
$string['allowuserswitchrolestheycantassign'] = 'Allow users without the assign roles capability to switch roles';
......@@ -82,6 +83,7 @@ $string['configallowobjectembed'] = 'As a default security measure, normal users
$string['configallowoverride'] = 'You can allow people with the roles on the left side to override some of the column roles';
$string['configallowoverride2'] = 'Select which role(s) can be overridden by each role in the left column.<br />Note that these settings only apply to users who have either the capability moodle/role:override or the capability moodle/role:safeoverride allowed.';
$string['configallowswitch'] = 'Select which roles a user may switch to, based on which roles they already have. In addition to an entry in this table, a user must also have the moodle/role:switchroles capability to be able to switch.<br />Note that it is only possible to switch to roles that have the moodle/course:view capability, and that do not have the moodle/site:doanything capability, so some columns in this table are disabled.';
$string['configallowthemechangeonurl'] = 'If you turn this setting on, then the theme can by changed by adding theme={themename}&amp;sesskey={sesskey} to any Moodle URL.';
$string['configallowunenroll'] = 'If this is set \'Yes\', then students are allowed to unenrol themselves from courses whenever they like. Otherwise they are not allowed, and this process will be solely controlled by the teachers and administrators.';
$string['configallowuserblockhiding'] = 'Do you want to allow users to hide/show side blocks throughout this site? This feature uses Javascript and cookies to remember the state of each collapsible block, and only affects the user\'s own view.';
$string['configallowusermailcharset'] = 'Enabling this, every user in the site will be able to specify his own charset for email.';
......
......@@ -5205,9 +5205,6 @@ function admin_get_root($reload=false, $requirefulltree=true) {
$ADMIN->purge_children($requirefulltree);
}
// Some parts of the tree require $CFG->pixpath.
$OUTPUT->initialise_deprecated_cfg_pixpath();
if (!$ADMIN->loaded) {
// we process this file first to create categories first and in correct order
require($CFG->dirroot.'/'.$CFG->admin.'/settings/top.php');
......@@ -5275,7 +5272,7 @@ function admin_apply_default_settings($node=NULL, $unconditional=true) {
* @return int number of changed settings
*/
function admin_write_settings($formdata) {
global $CFG, $SITE, $PAGE, $DB;
global $CFG, $SITE, $DB;
$olddbsessions = !empty($CFG->dbsessions);
$formdata = (array)$formdata;
......@@ -5314,9 +5311,12 @@ function admin_write_settings($formdata) {
require_logout();
}
// now update $SITE - it might have been changed
$SITE = $DB->get_record('course', array('id'=>$SITE->id));
$PAGE->set_course($SITE);
// Now update $SITE - just update the fields, in case other people have a
// a reference to it (e.g. $PAGE, $COURSE).
$newsite = $DB->get_record('course', array('id'=>$SITE->id));
foreach (get_object_vars($newsite) as $field => $value) {
$SITE->$field = $value;
}
// now reload all settings - some of them might depend on the changed
admin_get_root(true);
......
......@@ -329,6 +329,10 @@ class block_manager implements ArrayAccess {
return;
}
if (!isset($this->defaultregion)) {
$this->page->initialise_theme_and_output();
}
if (is_null($includeinvisible)) {
$includeinvisible = $this->page->user_is_editing();
}
......
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Plug in constants/variables - See MDL-6798 for details
*
* Information from Urs Hunkler:
*
*
* More flexible themes with CSS constants: An option for Moodle retro themes and easy colour palette variants.
*
* I adopted Shaun Inman's "CSS Server-side Constants" to Moodle: http://www.shauninman.com/post/heap/2005/08/09/css_constants
*
* With setting "cssconstants" to true in "config.php" you activate the CSS constants. If "cssconstants" is missing or set to "false" the
* replacement function is not used.
*
* $THEME->cssconstants = true;
* By setting this to true, you will be able to use CSS constants
*
* The constant definitions are written into a separate CSS file named like "constants.css" and loaded first in config.php. You can use constants for any CSS properties. The constant definition looks like:
* <code>
* \@server constants {
* fontColor: #3a2830;
* aLink: #116699;
* aVisited: #AA2200;
* aHover: #779911;
* pageBackground: #FFFFFF;
* backgroundColor: #EEEEEE;
* backgroundSideblockHeader: #a8a4e9;
* fontcolorSideblockHeader: #222222;
* color1: #98818b;
* color2: #bd807b;
* color3: #f9d1d7;
* color4: #e8d4d8;
* }
* </code>
*
* The lines in the CSS files using CSS constants look like:
* <code>
* body {
* font-size: 100%;
* background-color: pageBackground;
* color: fontColor;
* font-family: 'Bitstream Vera Serif', georgia, times, serif;
* margin: 0;
* padding: 0;
* }
* div#page {
* margin: 0 10px;
* padding-top: 5px;
* border-top-width: 10px;
* border-top-style: solid;
* border-top-color: color3;
* }
* div.clearer {
* clear: both;
* }
* a:link {
* color: aLink;
* }
* </code>
*
* @package moodlecore
* @copyright 1999 onwards Martin Dougiamas http://dougiamas.com
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
/**
* Replaces CSS Constants within CSS string
*
* @param string $css
* @return string
*/
function replace_cssconstants($css) {
if (preg_match_all("/@server\s+(?:variables|constants)\s*\{\s*([^\}]+)\s*\}\s*/i",$css,$matches)) {
$variables = array();
foreach ($matches[0] as $key=>$server) {
$css = str_replace($server,'',$css);
preg_match_all("/([^:\}\s]+)\s*:\s*([^;\}]+);/",$matches[1][$key],$vars);
foreach ($vars[1] as $var=>$value) {
$variables[$value] = $vars[2][$var];
}
}
$css = str_replace(array_keys($variables),array_values($variables),$css);
}
return ($css);
}
?>
......@@ -1765,6 +1765,55 @@ class custom_corners_renderer_factory extends standard_renderer_factory {
}
/**
* Used to be used for setting up the theme. No longer used by core code, and
* should not have been used elsewhere.
*
* The theme is now automatically initialised before it is first used. If you really need
* to force this to happen, just reference $PAGE->theme.
*
* To force a particular theme on a particular page, you can use $PAGE->force_theme(...).
* However, I can't think of any valid reason to do that outside the theme selector UI.
*
* @deprecated
* @param string $theme The theme to use defaults to current theme
* @param array $params An array of parameters to use
*/
function theme_setup($theme = '', $params=NULL) {
throw new coding_exception('The function theme_setup is no longer required, and should no longer be used. ' .
'The current theme gets initialised automatically before it is first used.');
}
/**
* @deprecated use $PAGE->theme->name instead.
* @return string the name of the current theme.
*/
function current_theme() {
global $PAGE;
// TODO, uncomment this once we have eliminated all references to current_theme in core code.
// debugging('current_theme is deprecated, use $PAGE->theme->name instead', DEBUG_DEVELOPER);
return $PAGE->theme->name;
}
/**
* This used to be the thing that theme styles.php files used to do all the work.
* This is now handled differently. You should copy theme/standard/styes.php
* into your theme.
*
* @deprecated
* @param int $lastmodified Always gets set to now
* @param int $lifetime The max-age header setting (seconds) defaults to 300
* @param string $themename The name of the theme to use (optional) defaults to current theme
* @param string $forceconfig Force a particular theme config (optional)
* @param string $lang Load styles for the specified language (optional)
*/
function style_sheet_setup($lastmodified=0, $lifetime=300, $themename='', $forceconfig='', $lang='') {
global $CFG, $PAGE, $THEME, $showdeprecatedstylesheetsetupwarning;
$showdeprecatedstylesheetsetupwarning = true;
include($CFG->dirroot . '/theme/styles.php');
exit;
}
/**
* Prints some red text using echo
*
......
......@@ -8208,26 +8208,10 @@ function address_in_subnet($addr, $subnetstr) {
*
* By using this function properly, we can ensure 100% https-ized pages
* at our entire discretion (login, forgot_password, change_password)
*
* @global object
* @global bool
*/
function httpsrequired() {
global $CFG, $HTTPSPAGEREQUIRED;
if (!empty($CFG->loginhttps)) {
$HTTPSPAGEREQUIRED = true;
$CFG->httpswwwroot = str_replace('http:', 'https:', $CFG->wwwroot);
$CFG->httpsthemewww = str_replace('http:', 'https:', $CFG->themewww);
// change theme URLs to https
theme_setup();
} else {
$CFG->httpswwwroot = $CFG->wwwroot;
$CFG->httpsthemewww = $CFG->themewww;
}
global $PAGE;
$PAGE->https_required();
}
/**
......
This diff is collapsed.
......@@ -125,6 +125,14 @@ class moodle_page {
protected $_button = '';
protected $_theme = null;
/**
* Then the theme is initialsed, we save the stack trace, for use in error messages.
* @var array stack trace.
*/
protected $_wherethemewasinitialised = null;
/**
* Sets the page to refresh after a given delay (in seconds) using meta refresh
* in {@link standard_head_html()} in outputlib.php
......@@ -346,15 +354,12 @@ class moodle_page {
public function get_blocks() {
global $CFG, $THEME;
if (is_null($this->_blocks)) {
initialise_theme_and_output();
if (!empty($CFG->blockmanagerclass)) {
$classname = $CFG->blockmanagerclass;
} else {
$classname = 'block_manager';
}
$this->_blocks = new $classname($this);
$this->_blocks->add_regions($THEME->blockregions);
$this->_blocks->set_default_region($THEME->defaultblockregion);
}
return $this->_blocks;
}
......@@ -395,6 +400,17 @@ class moodle_page {
return $this->_button;
}
/**
* Please do not call this method directly, use the ->theme syntax. {@link __get()}.
* @return string the initialised theme for this page.
*/
public function get_theme() {
if (is_null($this->_theme)) {
$this->initialise_theme_and_output();
}
return $this->_theme;
}
/**
* Please do not call this method directly use the ->periodicrefreshdelay syntax
* {@link __get()}
......@@ -482,28 +498,28 @@ class moodle_page {
* @param object the course to set as the global course.
*/
public function set_course($course) {
global $COURSE;
global $COURSE, $PAGE;
if (empty($course->id)) {
throw new coding_exception('$course passed to moodle_page::set_course does not look like a proper course object.');
}
if ($this->_state > self::STATE_BEFORE_HEADER) {
throw new coding_exception('Cannot call moodle_page::set_course after output has been started.');
}
$this->ensure_theme_not_set();
if (!empty($this->_course->id) && $this->_course->id != $course->id) {
$this->_categories = null;
}
$this->_course = clone($course);
$COURSE = $this->_course;
if ($this === $PAGE) {
$COURSE = $this->_course;
moodle_setlocale();
}
if (!$this->_context) {
$this->set_context(get_context_instance(CONTEXT_COURSE, $this->_course->id));
}
moodle_setlocale();
}
/**
......@@ -646,6 +662,7 @@ class moodle_page {
if (is_array($this->_categories)) {
throw new coding_exception('Course category has already been set. You are not allowed to change it.');
}
$this->ensure_theme_not_set();
$this->set_course($SITE);
$this->load_category($categoryid);
$this->set_context(get_context_instance(CONTEXT_COURSECAT, $categoryid));
......@@ -771,6 +788,46 @@ class moodle_page {
}
}
/**
* Force this page to use a particular theme.
*
* Please use this cautiously. It is only intended to be used by the themes selector
* admin page, and theme/styles.php.
*
* @param $themename the name of the theme to use.
*/
public function force_theme($themename) {
global $PAGE, $THEME;
$this->ensure_theme_not_set();
$this->_theme = theme_config::load($themename);
if ($this === $PAGE) {
$THEME = $this->_theme;
}
}
/**
* This function sets the $HTTPSPAGEREQUIRED global
* (used in some parts of moodle to change some links)
* and calculate the proper wwwroot to be used
*
* By using this function properly, we can ensure 100% https-ized pages
* at our entire discretion (login, forgot_password, change_password)
*/
public function https_required() {
global $CFG, $HTTPSPAGEREQUIRED;
$this->ensure_theme_not_set();
if (!empty($CFG->loginhttps)) {
$HTTPSPAGEREQUIRED = true;
$CFG->httpswwwroot = str_replace('http:', 'https:', $CFG->wwwroot);
$CFG->httpsthemewww = str_replace('http:', 'https:', $CFG->themewww);
} else {
$CFG->httpswwwroot = $CFG->wwwroot;
$CFG->httpsthemewww = $CFG->themewww;
}
}
/// Initialisation methods =====================================================
/// These set various things up in a default way.
......@@ -794,6 +851,129 @@ class moodle_page {
$this->initialise_standard_body_classes();
$this->blocks->load_blocks();
// Add any stylesheets required using the horrible legacy mechanism.
if (!empty($CFG->stylesheets)) {
debugging('Some code on this page is using the horrible legacy mechanism $CFG->stylesheets to include links to ' .
'extra stylesheets. This is deprecated. Please use $PAGE->requires->css(...) instead.', DEBUG_DEVELOPER);
foreach ($CFG->stylesheets as $stylesheet) {
$this->page->requires->css($stylesheet, true);
}
}
// Require theme stylesheets.
$stylesheets = $this->theme->get_stylesheet_urls();
foreach ($stylesheets as $stylesheet) {
$this->requires->css($stylesheet, true);
}
}
/**
* Method for use by Moodle core to set up the theme. Do not
* use this in your own code.
*
* Make sure the right theme for this page is loaded. Tell our
* blocks_manager about the theme block regions, and then, if
* we are $PAGE, set up the globals $THEME and $OUTPUT.
*/
public function initialise_theme_and_output() {
global $OUTPUT, $PAGE, $SITE, $THEME;
if (!$this->_course) {
$this->set_course($SITE);
}
if (is_null($this->_theme)) {
$themename = $this->resolve_theme();
$this->_theme = theme_config::load($themename);
}
$this->blocks->add_regions($this->_theme->blockregions);
$this->blocks->set_default_region($this->_theme->defaultblockregion);
if ($this === $PAGE) {
$THEME = $this->_theme;
$this->_theme->setup_cfg_paths();
if (CLI_SCRIPT) {
$classname = 'cli_renderer_factory';
} else {
$classname = $this->_theme->rendererfactory;
}
$rendererfactory = new $classname($this->_theme, $this);
$OUTPUT = $rendererfactory->get_renderer('core');
}
$this->_wherethemewasinitialised = debug_backtrace();
}
/**
* Work out the theme this page should use.
*
* This depends on numerous $CFG settings, and the properties of this page.
*
* @return string the name of the theme that should be used on this page.
*/
protected function resolve_theme() {
global $CFG, $USER, $SESSION;
if (empty($CFG->themeorder)) {
$themeorder = array('course', 'category', 'session', 'user', 'site');
} else {
$themeorder = $CFG->themeorder;
// Just in case, make sure we always use the site theme if nothing else matched.
$themeorder[] = 'site';
}
$mnetpeertheme = '';
if (isloggedin() and isset($CFG->mnet_localhost_id) and $USER->mnethostid != $CFG->mnet_localhost_id) {
require_once($CFG->dirroot.'/mnet/peer.php');
$mnetpeer = new mnet_peer();
$mnetpeer->set_id($USER->mnethostid);
if ($mnetpeer->force_theme == 1 && $mnetpeer->theme != '') {
$mnetpeertheme = $mnetpeer->theme;
}
}
$theme = '';
foreach ($themeorder as $themetype) {
switch ($themetype) {
case 'course':
if (!empty($CFG->allowcoursethemes) and !empty($this->course->theme)) {
return $this->course->theme;
}
case 'category':
if (!empty($CFG->allowcategorythemes)) {
$categories = $this->categories;
foreach ($categories as $category) {
if (!empty($category->theme)) {
return $category->theme;
}
}
}
case 'session':
if (!empty($SESSION->theme)) {
return $SESSION->theme;
}
case 'user':
if (!empty($CFG->allowuserthemes) and !empty($USER->theme)) {
if ($mnetpeertheme) {
return $mnetpeertheme;
} else {
return $USER->theme;
}
}
case 'site':
if ($mnetpeertheme) {
return $mnetpeertheme;
} else {
return $CFG->theme;
}
}
}
}
/**
......@@ -835,7 +1015,7 @@ class moodle_page {
}
protected function initialise_standard_body_classes() {
global $CFG;
global $CFG, $USER;
$pagetype = $this->pagetype;
if ($pagetype == 'site-index') {
......@@ -948,6 +1128,15 @@ class moodle_page {
}
}
protected function ensure_theme_not_set() {
if (!is_null($this->_theme)) {
throw new coding_exception('The theme has already been set up for this page ready for output. ' .
'Therefore, you can no longer change the theme, or anything that might affect what ' .
'the current theme is, for example, the course.',
'Stack trace when the theme was set up: ' . format_backtrace($this->_wherethemewasinitialised));
}
}
protected function url_to_class_name($url) {
$bits = parse_url($url);
$class = str_replace('.', '-', $bits['host']);
......
......@@ -126,7 +126,7 @@ global $MCACHE;
* A global to define if the page being displayed must run under HTTPS.
*
* Its primary goal is to allow 100% HTTPS pages when $CFG->loginhttps is enabled. Default to false.
* Its enabled only by the httpsrequired() function and used in some pages to update some URLs
* Its enabled only by the $PAGE->https_required() function and used in some pages to update some URLs
*
* @global bool $HTTPSPAGEREQUIRED
* @name $HTTPSPAGEREQUIRED
......@@ -188,13 +188,10 @@ global $SCRIPT;
}
/// store settings from config.php in array in $CFG - we can use it later to detect problems and overrides
/// Store settings from config.php in array in $CFG - we can use it later to detect problems and overrides
$CFG->config_php_settings = (array)$CFG;
/// Set httpswwwroot default value (this variable will replace $CFG->wwwroot
/// inside some URLs used in HTTPSPAGEREQUIRED pages.
$CFG->httpswwwroot = $CFG->wwwroot;
/// Set up some paths.
$CFG->libdir = $CFG->dirroot .'/lib';
if (!isset($CFG->themedir)) {
......@@ -202,6 +199,11 @@ global $SCRIPT;
$CFG->themewww = $CFG->wwwroot.'/theme';
}
/// Set httpswwwroot default value (this variable will replace $CFG->wwwroot
/// inside some URLs used in HTTPSPAGEREQUIRED pages.
$CFG->httpswwwroot = $CFG->wwwroot;
$CFG->httpsthemewww = $CFG->themewww;
require_once($CFG->libdir .'/setuplib.php'); // Functions that MUST be loaded first
/// Time to start counting
......@@ -524,20 +526,18 @@ global $SCRIPT;
$SESSION = &$_SESSION['SESSION'];
$USER = &$_SESSION['USER'];
/// Load up theme variables (colours etc)
$CFG->httpsthemewww = $CFG->themewww;
if (isset($_GET['theme'])) {
if ($CFG->allowthemechangeonurl || confirm_sesskey()) {
$themename = clean_param($_GET['theme'], PARAM_SAFEDIR);