608 lines
21 KiB
PHP
608 lines
21 KiB
PHP
<?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/>.
|
|
|
|
/**
|
|
* Renderers to align Moodle's HTML with that expected by Bootstrap
|
|
*
|
|
* @package theme_bootstrapbase
|
|
* @copyright 2012 Bas Brands, www.basbrands.nl
|
|
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
|
*/
|
|
|
|
class theme_bootstrapbase_core_renderer extends core_renderer {
|
|
|
|
/** @var custom_menu_item language The language menu if created */
|
|
protected $language = null;
|
|
|
|
/**
|
|
* The standard tags that should be included in the <head> tag
|
|
* including a meta description for the front page
|
|
*
|
|
* @return string HTML fragment.
|
|
*/
|
|
public function standard_head_html() {
|
|
global $SITE, $PAGE;
|
|
|
|
$output = parent::standard_head_html();
|
|
|
|
// Setup help icon overlays.
|
|
$this->page->requires->yui_module('moodle-core-popuphelp', 'M.core.init_popuphelp');
|
|
$this->page->requires->strings_for_js(array(
|
|
'morehelp',
|
|
'loadinghelp',
|
|
), 'moodle');
|
|
|
|
if ($PAGE->pagelayout == 'frontpage') {
|
|
$summary = s(strip_tags(format_text($SITE->summary, FORMAT_HTML)));
|
|
if (!empty($summary)) {
|
|
$output .= "<meta name=\"description\" content=\"$summary\" />\n";
|
|
}
|
|
}
|
|
|
|
return $output;
|
|
}
|
|
|
|
/*
|
|
* This renders the navbar.
|
|
* Uses bootstrap compatible html.
|
|
*/
|
|
public function navbar() {
|
|
$items = $this->page->navbar->get_items();
|
|
if (empty($items)) {
|
|
return '';
|
|
}
|
|
|
|
$breadcrumbs = array();
|
|
foreach ($items as $item) {
|
|
$item->hideicon = true;
|
|
$breadcrumbs[] = $this->render($item);
|
|
}
|
|
$divider = '<span class="divider">'.get_separator().'</span>';
|
|
$list_items = '<li>'.join(" $divider</li><li>", $breadcrumbs).'</li>';
|
|
$title = '<span class="accesshide" id="navbar-label">'.get_string('pagepath').'</span>';
|
|
return $title . '<nav aria-labelledby="navbar-label"><ul class="breadcrumb">' .
|
|
$list_items . '</ul></nav>';
|
|
}
|
|
|
|
/**
|
|
* This code renders the navbar button to control the display of the custom menu
|
|
* on smaller screens.
|
|
*
|
|
* Do not display the button if the menu is empty.
|
|
*
|
|
* @return string HTML fragment
|
|
*/
|
|
protected function navbar_button() {
|
|
global $CFG;
|
|
|
|
if (empty($CFG->custommenuitems) && $this->lang_menu() == '') {
|
|
return '';
|
|
}
|
|
|
|
$iconbar = html_writer::tag('span', '', array('class' => 'icon-bar'));
|
|
$button = html_writer::tag('a', $iconbar . "\n" . $iconbar. "\n" . $iconbar, array(
|
|
'class' => 'btn btn-navbar',
|
|
'data-toggle' => 'collapse',
|
|
'data-target' => '.nav-collapse'
|
|
));
|
|
return $button;
|
|
}
|
|
|
|
/*
|
|
* Overriding the custom_menu function ensures the custom menu is
|
|
* always shown, even if no menu items are configured in the global
|
|
* theme settings page.
|
|
*/
|
|
public function custom_menu($custommenuitems = '') {
|
|
global $CFG;
|
|
|
|
if (empty($custommenuitems) && !empty($CFG->custommenuitems)) {
|
|
$custommenuitems = $CFG->custommenuitems;
|
|
}
|
|
$custommenu = new custom_menu($custommenuitems, current_language());
|
|
return $this->render_custom_menu($custommenu);
|
|
}
|
|
|
|
/*
|
|
* This renders the bootstrap top menu.
|
|
*
|
|
* This renderer is needed to enable the Bootstrap style navigation.
|
|
*/
|
|
protected function render_custom_menu(custom_menu $menu) {
|
|
global $CFG;
|
|
|
|
$langs = get_string_manager()->get_list_of_translations();
|
|
$haslangmenu = $this->lang_menu() != '';
|
|
|
|
if (!$menu->has_children() && !$haslangmenu) {
|
|
return '';
|
|
}
|
|
|
|
if ($haslangmenu) {
|
|
$strlang = get_string('language');
|
|
$currentlang = current_language();
|
|
if (isset($langs[$currentlang])) {
|
|
$currentlang = $langs[$currentlang];
|
|
} else {
|
|
$currentlang = $strlang;
|
|
}
|
|
$this->language = $menu->add($currentlang, new moodle_url(''), $strlang, 10000);
|
|
foreach ($langs as $langtype => $langname) {
|
|
$this->language->add($langname, new moodle_url($this->page->url, array('lang' => $langtype)), $langname);
|
|
}
|
|
}
|
|
|
|
$content = '<ul class="nav">';
|
|
foreach ($menu->get_children() as $item) {
|
|
$content .= $this->render_custom_menu_item($item, 1);
|
|
}
|
|
|
|
return $content.'</ul>';
|
|
}
|
|
|
|
/*
|
|
* This code renders the custom menu items for the
|
|
* bootstrap dropdown menu.
|
|
*/
|
|
protected function render_custom_menu_item(custom_menu_item $menunode, $level = 0 ) {
|
|
static $submenucount = 0;
|
|
|
|
$content = '';
|
|
if ($menunode->has_children()) {
|
|
|
|
if ($level == 1) {
|
|
$class = 'dropdown';
|
|
} else {
|
|
$class = 'dropdown-submenu';
|
|
}
|
|
|
|
if ($menunode === $this->language) {
|
|
$class .= ' langmenu';
|
|
}
|
|
$content = html_writer::start_tag('li', array('class' => $class));
|
|
// If the child has menus render it as a sub menu.
|
|
$submenucount++;
|
|
if ($menunode->get_url() !== null) {
|
|
$url = $menunode->get_url();
|
|
} else {
|
|
$url = '#cm_submenu_'.$submenucount;
|
|
}
|
|
$content .= html_writer::start_tag('a', array('href'=>$url, 'class'=>'dropdown-toggle', 'data-toggle'=>'dropdown', 'title'=>$menunode->get_title()));
|
|
$content .= $menunode->get_text();
|
|
if ($level == 1) {
|
|
$content .= '<b class="caret"></b>';
|
|
}
|
|
$content .= '</a>';
|
|
$content .= '<ul class="dropdown-menu">';
|
|
foreach ($menunode->get_children() as $menunode) {
|
|
$content .= $this->render_custom_menu_item($menunode, 0);
|
|
}
|
|
$content .= '</ul>';
|
|
} else {
|
|
// The node doesn't have children so produce a final menuitem.
|
|
// Also, if the node's text matches '####', add a class so we can treat it as a divider.
|
|
if (preg_match("/^#+$/", $menunode->get_text())) {
|
|
// This is a divider.
|
|
$content = '<li class="divider"> </li>';
|
|
} else {
|
|
$content = '<li>';
|
|
if ($menunode->get_url() !== null) {
|
|
$url = $menunode->get_url();
|
|
} else {
|
|
$url = '#';
|
|
}
|
|
$content .= html_writer::link($url, $menunode->get_text(), array('title' => $menunode->get_title()));
|
|
$content .= '</li>';
|
|
}
|
|
}
|
|
return $content;
|
|
}
|
|
|
|
/**
|
|
* Renders tabtree
|
|
*
|
|
* @param tabtree $tabtree
|
|
* @return string
|
|
*/
|
|
protected function render_tabtree(tabtree $tabtree) {
|
|
if (empty($tabtree->subtree)) {
|
|
return '';
|
|
}
|
|
$firstrow = $secondrow = '';
|
|
foreach ($tabtree->subtree as $tab) {
|
|
$firstrow .= $this->render($tab);
|
|
if (($tab->selected || $tab->activated) && !empty($tab->subtree) && $tab->subtree !== array()) {
|
|
$secondrow = $this->tabtree($tab->subtree);
|
|
}
|
|
}
|
|
return html_writer::tag('ul', $firstrow, array('class' => 'nav nav-tabs')) . $secondrow;
|
|
}
|
|
|
|
/**
|
|
* Renders tabobject (part of tabtree)
|
|
*
|
|
* This function is called from {@link core_renderer::render_tabtree()}
|
|
* and also it calls itself when printing the $tabobject subtree recursively.
|
|
*
|
|
* @param tabobject $tabobject
|
|
* @return string HTML fragment
|
|
*/
|
|
protected function render_tabobject(tabobject $tab) {
|
|
if (($tab->selected and (!$tab->linkedwhenselected)) or $tab->activated) {
|
|
return html_writer::tag('li', html_writer::tag('a', $tab->text), array('class' => 'active'));
|
|
} else if ($tab->inactive) {
|
|
return html_writer::tag('li', html_writer::tag('a', $tab->text), array('class' => 'disabled'));
|
|
} else {
|
|
if (!($tab->link instanceof moodle_url)) {
|
|
// backward compartibility when link was passed as quoted string
|
|
$link = "<a href=\"$tab->link\" title=\"$tab->title\">$tab->text</a>";
|
|
} else {
|
|
$link = html_writer::link($tab->link, $tab->text, array('title' => $tab->title));
|
|
}
|
|
$params = $tab->selected ? array('class' => 'active') : null;
|
|
return html_writer::tag('li', $link, $params);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Wrapper for header elements.
|
|
*
|
|
* @return string HTML to display the main header.
|
|
*/
|
|
public function full_header() {
|
|
$html = html_writer::start_tag('header', array('id' => 'page-header', 'class' => 'clearfix'));
|
|
$html .= $this->context_header();
|
|
$html .= html_writer::start_div('clearfix', array('id' => 'page-navbar'));
|
|
$html .= html_writer::tag('div', $this->navbar(), array('class' => 'breadcrumb-nav'));
|
|
$html .= html_writer::div($this->page_heading_button(), 'breadcrumb-button');
|
|
$html .= html_writer::end_div();
|
|
$html .= html_writer::tag('div', $this->course_header(), array('id' => 'course-header'));
|
|
$html .= html_writer::end_tag('header');
|
|
return $html;
|
|
}
|
|
|
|
/**
|
|
* Get the compact logo URL.
|
|
*
|
|
* @return string
|
|
*/
|
|
public function get_compact_logo_url($maxwidth = 100, $maxheight = 100) {
|
|
return parent::get_compact_logo_url(null, 70);
|
|
}
|
|
|
|
/**
|
|
* Prints a nice side block with an optional header.
|
|
*
|
|
* The content is described
|
|
* by a {@link core_renderer::block_contents} object.
|
|
*
|
|
* <div id="inst{$instanceid}" class="block_{$blockname} block">
|
|
* <div class="header"></div>
|
|
* <div class="content">
|
|
* ...CONTENT...
|
|
* <div class="footer">
|
|
* </div>
|
|
* </div>
|
|
* <div class="annotation">
|
|
* </div>
|
|
* </div>
|
|
*
|
|
* @param block_contents $bc HTML for the content
|
|
* @param string $region the region the block is appearing in.
|
|
* @return string the HTML to be output.
|
|
*/
|
|
public function block(block_contents $bc, $region) {
|
|
$bc = clone($bc); // Avoid messing up the object passed in.
|
|
if (empty($bc->blockinstanceid) || !strip_tags($bc->title)) {
|
|
$bc->collapsible = block_contents::NOT_HIDEABLE;
|
|
}
|
|
if (!empty($bc->blockinstanceid)) {
|
|
$bc->attributes['data-instanceid'] = $bc->blockinstanceid;
|
|
}
|
|
$skiptitle = strip_tags($bc->title);
|
|
if ($bc->blockinstanceid && !empty($skiptitle)) {
|
|
$bc->attributes['aria-labelledby'] = 'instance-'.$bc->blockinstanceid.'-header';
|
|
} else if (!empty($bc->arialabel)) {
|
|
$bc->attributes['aria-label'] = $bc->arialabel;
|
|
}
|
|
if ($bc->dockable) {
|
|
$bc->attributes['data-dockable'] = 1;
|
|
}
|
|
if ($bc->collapsible == block_contents::HIDDEN) {
|
|
$bc->add_class('hidden');
|
|
}
|
|
if (!empty($bc->controls)) {
|
|
$bc->add_class('block_with_controls');
|
|
}
|
|
|
|
|
|
if (empty($skiptitle)) {
|
|
$output = '';
|
|
$skipdest = '';
|
|
} else {
|
|
$output = html_writer::link('#sb-'.$bc->skipid, get_string('skipa', 'access', $skiptitle),
|
|
array('class' => 'skip skip-block', 'id' => 'fsb-' . $bc->skipid));
|
|
$skipdest = html_writer::span('', 'skip-block-to',
|
|
array('id' => 'sb-' . $bc->skipid));
|
|
}
|
|
|
|
$output .= html_writer::start_tag('div', $bc->attributes);
|
|
|
|
$output .= $this->block_header($bc);
|
|
$output .= $this->block_content($bc);
|
|
|
|
$output .= html_writer::end_tag('div');
|
|
|
|
$output .= $this->block_annotation($bc);
|
|
|
|
$output .= $skipdest;
|
|
|
|
$this->init_block_hider_js($bc);
|
|
return $output;
|
|
}
|
|
|
|
/**
|
|
* Produces a header for a block
|
|
*
|
|
* @param block_contents $bc
|
|
* @return string
|
|
*/
|
|
protected function block_header(block_contents $bc) {
|
|
|
|
$title = '';
|
|
if ($bc->title) {
|
|
$attributes = array();
|
|
if ($bc->blockinstanceid) {
|
|
$attributes['id'] = 'instance-'.$bc->blockinstanceid.'-header';
|
|
}
|
|
$title = html_writer::tag('h2', $bc->title, $attributes);
|
|
}
|
|
|
|
$blockid = null;
|
|
if (isset($bc->attributes['id'])) {
|
|
$blockid = $bc->attributes['id'];
|
|
}
|
|
$controlshtml = $this->block_controls($bc->controls, $blockid);
|
|
|
|
$output = '';
|
|
if ($title || $controlshtml) {
|
|
$output .= html_writer::tag('div', html_writer::tag('div', html_writer::tag('div', '', array('class'=>'block_action')). $title . $controlshtml, array('class' => 'title')), array('class' => 'header'));
|
|
}
|
|
return $output;
|
|
}
|
|
|
|
/**
|
|
* Produces the content area for a block
|
|
*
|
|
* @param block_contents $bc
|
|
* @return string
|
|
*/
|
|
protected function block_content(block_contents $bc) {
|
|
$output = html_writer::start_tag('div', array('class' => 'content'));
|
|
if (!$bc->title && !$this->block_controls($bc->controls)) {
|
|
$output .= html_writer::tag('div', '', array('class'=>'block_action notitle'));
|
|
}
|
|
$output .= $bc->content;
|
|
$output .= $this->block_footer($bc);
|
|
$output .= html_writer::end_tag('div');
|
|
|
|
return $output;
|
|
}
|
|
|
|
/**
|
|
* Produces the footer for a block
|
|
*
|
|
* @param block_contents $bc
|
|
* @return string
|
|
*/
|
|
protected function block_footer(block_contents $bc) {
|
|
$output = '';
|
|
if ($bc->footer) {
|
|
$output .= html_writer::tag('div', $bc->footer, array('class' => 'footer'));
|
|
}
|
|
return $output;
|
|
}
|
|
|
|
/**
|
|
* Produces the annotation for a block
|
|
*
|
|
* @param block_contents $bc
|
|
* @return string
|
|
*/
|
|
protected function block_annotation(block_contents $bc) {
|
|
$output = '';
|
|
if ($bc->annotation) {
|
|
$output .= html_writer::tag('div', $bc->annotation, array('class' => 'blockannotation'));
|
|
}
|
|
return $output;
|
|
}
|
|
|
|
/**
|
|
* Calls the JS require function to hide a block.
|
|
*
|
|
* @param block_contents $bc A block_contents object
|
|
*/
|
|
protected function init_block_hider_js(block_contents $bc) {
|
|
if (!empty($bc->attributes['id']) and $bc->collapsible != block_contents::NOT_HIDEABLE) {
|
|
$config = new stdClass;
|
|
$config->id = $bc->attributes['id'];
|
|
$config->title = strip_tags($bc->title);
|
|
$config->preference = 'block' . $bc->blockinstanceid . 'hidden';
|
|
$config->tooltipVisible = get_string('hideblocka', 'access', $config->title);
|
|
$config->tooltipHidden = get_string('showblocka', 'access', $config->title);
|
|
|
|
$this->page->requires->js_init_call('M.util.init_block_hider', array($config));
|
|
user_preference_allow_ajax_update($config->preference, PARAM_BOOL);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns the CSS classes to apply to the body tag.
|
|
*
|
|
* @since Moodle 2.5.1 2.6
|
|
* @param array $additionalclasses Any additional classes to apply.
|
|
* @return string
|
|
*/
|
|
public function body_css_classes(array $additionalclasses = array()) {
|
|
// Add a class for each block region on the page.
|
|
// We use the block manager here because the theme object makes get_string calls.
|
|
$usedregions = array();
|
|
foreach ($this->page->blocks->get_regions() as $region) {
|
|
$additionalclasses[] = 'has-region-'.$region;
|
|
if ($this->page->blocks->region_has_content($region, $this)) {
|
|
$additionalclasses[] = 'used-region-'.$region;
|
|
$usedregions[] = $region;
|
|
} else {
|
|
$additionalclasses[] = 'empty-region-'.$region;
|
|
}
|
|
if ($this->page->blocks->region_completely_docked($region, $this)) {
|
|
$additionalclasses[] = 'docked-region-'.$region;
|
|
}
|
|
}
|
|
if (!$usedregions) {
|
|
// No regions means there is only content, add 'content-only' class.
|
|
$additionalclasses[] = 'content-only';
|
|
} else if (count($usedregions) === 1) {
|
|
// Add the -only class for the only used region.
|
|
$region = array_shift($usedregions);
|
|
$additionalclasses[] = $region . '-only';
|
|
}
|
|
foreach ($this->page->layout_options as $option => $value) {
|
|
if ($value) {
|
|
$additionalclasses[] = 'layout-option-'.$option;
|
|
}
|
|
}
|
|
$css = $this->page->bodyclasses .' '. join(' ', $additionalclasses);
|
|
return $css;
|
|
}
|
|
|
|
/**
|
|
* Renders preferences groups.
|
|
*
|
|
* @param preferences_groups $renderable The renderable
|
|
* @return string The output.
|
|
*/
|
|
public function render_preferences_groups(preferences_groups $renderable) {
|
|
$html = '';
|
|
$html .= html_writer::start_div('row-fluid');
|
|
$html .= html_writer::start_tag('div', array('class' => 'span12 preferences-groups'));
|
|
$i = 0;
|
|
$open = false;
|
|
foreach ($renderable->groups as $group) {
|
|
if ($i == 0 || $i % 3 == 0) {
|
|
if ($open) {
|
|
$html .= html_writer::end_tag('div');
|
|
}
|
|
$html .= html_writer::start_tag('div', array('class' => 'row-fluid'));
|
|
$open = true;
|
|
}
|
|
$html .= $this->render($group);
|
|
$i++;
|
|
}
|
|
|
|
$html .= html_writer::end_tag('div');
|
|
|
|
$html .= html_writer::end_tag('ul');
|
|
$html .= html_writer::end_tag('div');
|
|
$html .= html_writer::end_div();
|
|
return $html;
|
|
}
|
|
|
|
/**
|
|
* Renders an action menu component.
|
|
*
|
|
* ARIA references:
|
|
* - http://www.w3.org/WAI/GL/wiki/Using_ARIA_menus
|
|
* - http://stackoverflow.com/questions/12279113/recommended-wai-aria-implementation-for-navigation-bar-menu
|
|
*
|
|
* @param action_menu $menu
|
|
* @return string HTML
|
|
*/
|
|
public function render_action_menu(action_menu $menu) {
|
|
$context = $menu->export_for_template($this);
|
|
return $this->render_from_template('core/action_menu', $context);
|
|
}
|
|
|
|
/**
|
|
* Renders a single button widget.
|
|
*
|
|
* This will return HTML to display a form containing a single button.
|
|
*
|
|
* @param single_button $button
|
|
* @return string HTML fragment
|
|
*/
|
|
protected function render_single_button(single_button $button) {
|
|
$attributes = array('type' => 'submit',
|
|
'value' => $button->label,
|
|
'disabled' => $button->disabled ? 'disabled' : null,
|
|
'title' => $button->tooltip);
|
|
|
|
if ($button->actions) {
|
|
$id = html_writer::random_id('single_button');
|
|
$attributes['id'] = $id;
|
|
foreach ($button->actions as $action) {
|
|
$this->add_action_handler($action, $id);
|
|
}
|
|
}
|
|
|
|
// first the input element
|
|
$output = html_writer::empty_tag('input', $attributes);
|
|
|
|
// then hidden fields
|
|
$params = $button->url->params();
|
|
if ($button->method === 'post') {
|
|
$params['sesskey'] = sesskey();
|
|
}
|
|
foreach ($params as $var => $val) {
|
|
$output .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => $var, 'value' => $val));
|
|
}
|
|
|
|
// then div wrapper for xhtml strictness
|
|
$output = html_writer::tag('div', $output);
|
|
|
|
// now the form itself around it
|
|
if ($button->method === 'get') {
|
|
$url = $button->url->out_omit_querystring(true); // url without params, the anchor part allowed
|
|
} else {
|
|
$url = $button->url->out_omit_querystring(); // url without params, the anchor part not allowed
|
|
}
|
|
if ($url === '') {
|
|
$url = '#'; // there has to be always some action
|
|
}
|
|
$attributes = array('method' => $button->method,
|
|
'action' => $url,
|
|
'id' => $button->formid);
|
|
$output = html_writer::tag('form', $output, $attributes);
|
|
|
|
// and finally one more wrapper with class
|
|
return html_writer::tag('div', $output, array('class' => $button->class));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Overridden core maintenance renderer.
|
|
*
|
|
* This renderer gets used instead of the standard core_renderer during maintenance
|
|
* tasks such as installation and upgrade.
|
|
* We override it in order to style those scenarios consistently with the regular
|
|
* bootstrap look and feel.
|
|
*
|
|
* @package theme_bootstrapbase
|
|
* @copyright 2014 Sam Hemelryk
|
|
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
|
*/
|
|
class theme_bootstrapbase_core_renderer_maintenance extends core_renderer_maintenance {
|
|
}
|