MDL-25394 Improved support for mobile and device themes

This commit is contained in:
Anthony Forth 2011-05-31 14:25:52 +08:00 committed by Sam Hemelryk
parent 16b5541dd2
commit 37959dd471
11 changed files with 534 additions and 70 deletions

View file

@ -7307,7 +7307,193 @@ class admin_setting_configcolourpicker extends admin_setting {
$content .= html_writer::end_tag('div');
return format_admin_setting($this, $this->visiblename, $content, $this->description, false, '', $this->get_defaultsetting(), $query);
}
}
/**
* Administration interface for user specified regular expressions for device detection.
*
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class admin_setting_devicedetectregex extends admin_setting {
/**
* Calls parent::__construct with specific args
*/
public function __construct($name, $visiblename, $description, $defaultsetting = '') {
global $CFG;
parent::__construct($name, $visiblename, $description, $defaultsetting);
}
/**
* Return the current setting(s)
*
* @return array Current settings array
*/
public function get_setting() {
global $CFG;
$config = $this->config_read($this->name);
if (is_null($config)) {
return null;
}
return $this->prepare_form_data($config);
}
/**
* Save selected settings
*
* @param array $data Array of settings to save
* @return bool
*/
public function write_setting($data) {
if (empty($data)) {
$data = array();
}
if ($this->config_write($this->name, $this->process_form_data($data))) {
return ''; // success
} else {
return get_string('errorsetting', 'admin') . $this->visiblename . html_writer::empty_tag('br');
}
}
/**
* Return XHTML field(s) for regexes
*
* @param array $data Array of options to set in HTML
* @return string XHTML string for the fields and wrapping div(s)
*/
public function output_html($data, $query='') {
global $OUTPUT;
$out = html_writer::start_tag('table', array('border' => 1, 'class' => 'generaltable'));
$out .= html_writer::start_tag('thead');
$out .= html_writer::start_tag('tr');
$out .= html_writer::tag('th', get_string('devicedetectregexexpression', 'admin'));
$out .= html_writer::tag('th', get_string('devicedetectregexvalue', 'admin'));
$out .= html_writer::end_tag('tr');
$out .= html_writer::end_tag('thead');
$out .= html_writer::start_tag('tbody');
if (empty($data)) {
$looplimit = 1;
} else {
$looplimit = (count($data)/2)+1;
}
for ($i=0; $i<$looplimit; $i++) {
$out .= html_writer::start_tag('tr');
$expressionname = 'expression'.$i;
if (!empty($data[$expressionname])){
$expression = $data[$expressionname];
} else {
$expression = '';
}
$out .= html_writer::tag('td',
html_writer::empty_tag('input',
array(
'type' => 'text',
'class' => 'form-text',
'name' => $this->get_full_name().'[expression'.$i.']',
'value' => $expression,
)
), array('class' => 'c'.$i)
);
$valuename = 'value'.$i;
if (!empty($data[$valuename])){
$value = $data[$valuename];
} else {
$value= '';
}
$out .= html_writer::tag('td',
html_writer::empty_tag('input',
array(
'type' => 'text',
'class' => 'form-text',
'name' => $this->get_full_name().'[value'.$i.']',
'value' => $value,
)
), array('class' => 'c'.$i)
);
$out .= html_writer::end_tag('tr');
}
$out .= html_writer::end_tag('tbody');
$out .= html_writer::end_tag('table');
return format_admin_setting($this, $this->visiblename, $out, $this->description, false, '', null, $query);
}
/**
* Converts the string of regexes
*
* @see self::process_form_data()
* @param $regexes string of regexes
* @return array of form fields and their values
*/
protected function prepare_form_data($regexes) {
$regexes = json_decode($regexes);
$form = array();
$i = 0;
foreach ($regexes as $value => $regex) {
$expressionname = 'expression'.$i;
$valuename = 'value'.$i;
$form[$expressionname] = $regex;
$form[$valuename] = $value;
$i++;
}
return $form;
}
/**
* Converts the data from admin settings form into a string of regexes
*
* @see self::prepare_form_data()
* @param array $data array of admin form fields and values
* @return false|string of regexes
*/
protected function process_form_data(array $form) {
$count = count($form); // number of form field values
if ($count % 2) {
// we must get five fields per expression
return false;
}
$regexes = array();
for ($i = 0; $i < $count / 2; $i++) {
$expressionname = "expression".$i;
$valuename = "value".$i;
$expression = trim($form['expression'.$i]);
$value = trim($form['value'.$i]);
if (empty($expression)){
continue;
}
$regexes[$value] = $expression;
}
$regexes = json_encode($regexes);
return $regexes;
}
}

View file

@ -6112,6 +6112,12 @@ WHERE gradeitemid IS NOT NULL AND grademax IS NOT NULL");
upgrade_main_savepoint(true, 2011052300.02);
}
//set enable theme detection to the new themes setting.
if ($oldversion < 2011052500.00) {
set_config('enabledevicedetection', 1);
upgrade_main_savepoint(true, 2011052500.00);
}
return true;
}

View file

@ -7603,6 +7603,181 @@ function check_php_version($version='5.2.4') {
return false;
}
/**
* Returns whether a device/browser combination is mobile, tablet, legacy, default or the result of
* an optional admin specified regular expression. If enabledevicedetection is set to no or not set
* it returns default
*
* @return string device type
*/
function get_device_type() {
global $CFG;
if (empty($CFG->enabledevicedetection) || empty($_SERVER['HTTP_USER_AGENT'])) {
return 'default';
}
$useragent = $_SERVER['HTTP_USER_AGENT'];
if (!empty($CFG->devicedetectregex)) {
$regexes = json_decode($CFG->devicedetectregex);
foreach ($regexes as $value=>$regex) {
if (preg_match($regex, $useragent)) {
return $value;
}
}
}
//mobile detection PHP direct copy from open source detectmobilebrowser.com
$phonesregex = '/android|avantgo|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|symbian|treo|up\.(browser|link)|vodafone|wap|windows (ce|phone)|xda|xiino/i';
$modelsregex = '/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|e\-|e\/|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(di|rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|xda(\-|2|g)|yas\-|your|zeto|zte\-/i';
if (preg_match($phonesregex,$useragent)||preg_match($modelsregex,substr($useragent,0,4))){
return 'mobile';
}
$tabletregex = '/Tablet browser|iPad|iProd|GT-P1000|GT-I9000|SHW-M180S|SGH-T849|SCH-I800|Build\/ERE27|sholest/i';
if (preg_match($tabletregex, $useragent)) {
return 'tablet';
}
if (strpos($_SERVER['HTTP_USER_AGENT'], 'MSIE 6.') !== false) {
return 'legacy';
}
return 'default';
}
/**
* Returns a list of the device types supporting by Moodle
* @param boolean incusertypes includes types specified using the devicedetectregex admin setting
* @return array $types
*/
function get_device_type_list($incusertypes = true) {
global $CFG;
$types = array('default', 'legacy', 'mobile', 'tablet');
if ($incusertypes && !empty($CFG->devicedetectregex)) {
$regexes = json_decode($CFG->devicedetectregex);
foreach ($regexes as $value => $regex) {
$types[] = $value;
}
}
return $types;
}
/**
* Returns the theme selected for a particular device or false if none selected.
* @param string $themes
* @param string $devicetype
* @return string $theme or boolean false
*/
function get_selected_theme_for_device_type($devicetype = null) {
global $CFG;
if (empty($devicetype)) {
$devicetype = get_device_type();
//check if the user has switched theme, change $devicetype to default.
$switched = get_user_switched_device($devicetype);
if ($switched) {
$devicetype = $switched;
}
}
$themevarname = get_device_cfg_var_name($devicetype);
if (empty($CFG->$themevarname)) {
return false;
}
//prevent problems if a user installs themes
if (!is_dir($CFG->dirroot.'/theme/'.$CFG->$themevarname)) {
if ($devicetype == 'default') {
return 'standard';
} else {
return false;
}
}
return $CFG->$themevarname;
}
/**
* Returns the name of the device type theme var in $CFG (because there is not a standard convention to allow backwards compatability
* @param string $devicetype
*/
function get_device_cfg_var_name($devicetype = null) {
if ($devicetype == 'default' || empty($devicetype)) {
return 'theme';
}
return 'theme' . $devicetype;
}
/**
* Returns device type or false if the user has switched theme to default for a device type
* @param string $devicetype
*/
function get_user_switched_device($devicetype = null) {
global $USER;
if (empty($USER)) {
return false;
}
if (empty($devicetype)) {
$devicetype = get_device_type();
}
$switchdevice = get_user_preferences('switchdevice'.$devicetype);
if (empty($switchdevice)) {
return false;
}
return $switchdevice;
}
function switch_device($devicetype = null) {
global $USER;
if (is_null($devicetype)) {
$devicetype = get_device_type();
}
$switcheddevice = get_user_preferences('switchdevice'.$devicetype);
if (!empty($switcheddevice)) {
$switcheddevice = false;
} else {
$pref = optional_param('switchdevice', '', PARAM_TEXT);
if (!empty($pref)) {
$switcheddevice = $pref;
} else if ($devicetype == 'default') {
$switcheddevice = 'mobile';
} else {
$switcheddevice = 'default';
}
}
set_user_preference('switchdevice'.$devicetype, $switcheddevice);
}
/**
* Returns one or several CSS class names that match the user's browser. These can be put
* in the body tag of the page to apply browser-specific rules without relying on CSS hacks

View file

@ -212,6 +212,8 @@ class core_renderer extends renderer_base {
protected $contenttype;
/** @var string used by {@link redirect_message()} method to communicate with {@link header()}. */
protected $metarefreshtag = '';
/** @var set if the theme links function has been called **/
protected $switchlinkdisplayed;
/**
* Get the DOCTYPE declaration that should be used with this page. Designed to
@ -362,10 +364,13 @@ class core_renderer extends renderer_base {
// but some of the content won't be known until later, so we return a placeholder
// for now. This will be replaced with the real content in {@link footer()}.
$output = self::PERFORMANCE_INFO_TOKEN;
if ($this->page->legacythemeinuse) {
if ($this->page->devicetypeinuse == 'legacy'){
// The legacy theme is in use print the notification
$output .= html_writer::tag('div', get_string('legacythemeinuse'), array('class'=>'legacythemeinuse'));
}
$output .= $this->theme_switch_links();
if (!empty($CFG->debugpageinfo)) {
$output .= '<div class="performanceinfo pageinfo">This page is: ' . $this->page->debug_summary() . '</div>';
}
@ -2496,6 +2501,42 @@ EOD;
// Return the sub menu
return $content;
}
/*
* Renders theme links for switching between default and other themes.
*/
protected function theme_switch_links() {
if ($this->switchlinkdisplayed) {
return '';
}
global $USER;
$type = get_device_type();
$this->switchlinkdisplayed = true;
if ($type == 'default') {
return '';
}
$switched = get_user_switched_device();
if ($switched) {
$linktext = get_string('switchdevicerecommended');
} else {
$linktext = get_string('switchdevicedefault');
}
$content = html_writer::start_tag('div', array('id'=>'theme_switch_link'));
$linkurl = new moodle_url('/theme/switch.php', array('url' => $this->page->url));
$content .= html_writer::link($linkurl, $linktext);
$content .= html_writer::end_tag('div');
return $content;
}
}

View file

@ -64,13 +64,13 @@ defined('MOODLE_INTERNAL') || die();
* @property-read object $course The current course that we are inside - a row from the
* course table. (Also available as $COURSE global.) If we are not inside
* an actual course, this will be the site course.
* @property-read bool $devicetypeinuse name of the device type in use
* @property-read string $docspath The path to the Moodle docs for this page.
* @property-read string $focuscontrol The id of the HTML element to be focused when the page has loaded.
* @property-read bool $headerprinted
* @property-read string $heading The main heading that should be displayed at the top of the <body>.
* @property-read string $headingmenu The menu (or actions) to display in the heading
* @property-read array $layout_options Returns arrays with options for layout file.
* @property-read bool $legacythemeinuse Returns true if the legacy theme is being used.
* @property-read navbar $navbar Returns the navbar object used to display the navbar
* @property-read global_navigation $navigation Returns the global navigation structure
* @property-read xml_container_stack $opencontainers Tracks XHTML tags on this page that have been opened but not closed.
@ -217,10 +217,10 @@ class moodle_page {
protected $_legacybrowsers = array('MSIE' => 6.0);
/**
* Is set to true if the chosen legacy theme is in use. False by default.
* @var bool
* Is set to the name of the device type in use.
* @var string
*/
protected $_legacythemeinuse = false;
protected $_devicetypeinuse = 'default';
protected $_https_login_required = false;
@ -523,11 +523,11 @@ class moodle_page {
}
/**
* Please do not call this method directly, use the ->legacythemeinuse syntax. {@link __get()}.
* Please do not call this method directly, use the ->devicetypeinuse syntax. {@link __get()}.
* @return bool
*/
protected function magic_get_legacythemeinuse() {
return ($this->_legacythemeinuse);
protected function magic_get_devicetypeinuse() {
return ($this->_devicetypeinuse);
}
/**
@ -1280,16 +1280,19 @@ class moodle_page {
}
}
$devicetype = get_device_type();
$theme = '';
foreach ($themeorder as $themetype) {
switch ($themetype) {
case 'course':
if (!empty($CFG->allowcoursethemes) and !empty($this->course->theme)) {
return $this->course->theme;
if (!empty($CFG->allowcoursethemes) and !empty($this->_course->theme) and $devicetype == 'default') {
return $this->_course->theme;
}
case 'category':
if (!empty($CFG->allowcategorythemes)) {
if (!empty($CFG->allowcategorythemes) and $devicetype == 'default') {
$categories = $this->categories;
foreach ($categories as $category) {
if (!empty($category->theme)) {
@ -1304,7 +1307,7 @@ class moodle_page {
}
case 'user':
if (!empty($CFG->allowuserthemes) and !empty($USER->theme)) {
if (!empty($CFG->allowuserthemes) and !empty($USER->theme) && $devicetype == 'default') {
if ($mnetpeertheme) {
return $mnetpeertheme;
} else {
@ -1315,33 +1318,15 @@ class moodle_page {
case 'site':
if ($mnetpeertheme) {
return $mnetpeertheme;
} else if(!empty($CFG->themelegacy) && $this->browser_is_outdated()) {
$this->_legacythemeinuse = true;
return $CFG->themelegacy;
} else {
return $CFG->theme;
}
$this->_devicetypeinuse = $devicetype;
return get_selected_theme_for_device_type();
}
}
}
/**
* Determines whether the current browser should
* default to the admin-selected legacy theme
*
* @return true if legacy theme should be used, otherwise false
*
*/
protected function browser_is_outdated() {
foreach($this->_legacybrowsers as $browser => $version) {
// Check the browser is valid first then that its version is suitable
if(check_browser_version($browser, '0') &&
!check_browser_version($browser, $version)) {
return true;
}
}
return false;
}
/**
* Sets ->pagetype from the script name. For example, if the script that was
@ -1448,8 +1433,8 @@ class moodle_page {
$this->add_body_class('drag');
}
if ($this->_legacythemeinuse) {
$this->add_body_class('legacytheme');
if ($this->_devicetypeinuse != 'default') {
$this->add_body_class($this->_devicetypeinuse . 'theme');
}
}