weekly release 3.2dev
[moodle.git] / theme / boost / classes / output / core_renderer.php
CommitLineData
536f0460
DW
1<?php
2// This file is part of Moodle - http://moodle.org/
3//
4// Moodle is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// Moodle is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
16
2ede86d7 17namespace theme_boost\output;
536f0460 18
da4d9eb7 19use coding_exception;
536f0460
DW
20use html_writer;
21use tabobject;
22use tabtree;
23use custom_menu_item;
24use custom_menu;
d7fbf722
DW
25use block_contents;
26use stdClass;
f130c411 27use moodle_url;
3f0544b8
DW
28use preferences_groups;
29use action_menu;
b7e95263 30use help_icon;
d7c65752 31use single_button;
bf7f35e9 32use single_select;
b0da86e0 33use paging_bar;
f1b34660 34use url_select;
b71c82ad 35use context_course;
132093f0 36use pix_icon;
536f0460
DW
37
38defined('MOODLE_INTERNAL') || die;
39
40/**
41 * Renderers to align Moodle's HTML with that expected by Bootstrap
42 *
2ede86d7 43 * @package theme_boost
536f0460
DW
44 * @copyright 2012 Bas Brands, www.basbrands.nl
45 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
46 */
47
48class core_renderer extends \core_renderer {
49
50 /** @var custom_menu_item language The language menu if created */
51 protected $language = null;
52
7fcfc073
FM
53 /**
54 * Outputs the opening section of a box.
55 *
56 * @param string $classes A space-separated list of CSS classes
57 * @param string $id An optional ID
58 * @param array $attributes An array of other attributes to give the box.
59 * @return string the HTML to output.
60 */
61 public function box_start($classes = 'generalbox', $id = null, $attributes = array()) {
63e4df60
DW
62 if (is_array($classes)) {
63 $classes = implode(' ', $classes);
64 }
7fcfc073
FM
65 return parent::box_start($classes . ' p-a-1', $id, $attributes);
66 }
67
1ecc3daf
FM
68 /**
69 * Wrapper for header elements.
70 *
71 * @return string HTML to display the main header.
72 */
73 public function full_header() {
74 $html = html_writer::start_tag('header', array('id' => 'page-header', 'class' => 'row'));
75 $html .= html_writer::start_div('col-xs-12 p-t-1 p-b-1');
76 $html .= $this->context_header();
77 $html .= html_writer::start_div('clearfix', array('id' => 'page-navbar'));
78 $html .= html_writer::tag('div', $this->navbar(), array('class' => 'breadcrumb-nav'));
79 $html .= html_writer::div($this->page_heading_button(), 'breadcrumb-button');
80 $html .= html_writer::end_div();
81 $html .= html_writer::tag('div', $this->course_header(), array('id' => 'course-header'));
82 $html .= html_writer::end_div();
83 $html .= html_writer::end_tag('header');
84 return $html;
85 }
86
536f0460
DW
87 /**
88 * The standard tags that should be included in the <head> tag
89 * including a meta description for the front page
90 *
91 * @return string HTML fragment.
92 */
93 public function standard_head_html() {
94 global $SITE, $PAGE;
95
96 $output = parent::standard_head_html();
97 if ($PAGE->pagelayout == 'frontpage') {
98 $summary = s(strip_tags(format_text($SITE->summary, FORMAT_HTML)));
99 if (!empty($summary)) {
100 $output .= "<meta name=\"description\" content=\"$summary\" />\n";
101 }
102 }
103
104 return $output;
105 }
106
107 /*
108 * This renders the navbar.
109 * Uses bootstrap compatible html.
110 */
111 public function navbar() {
13d07a01 112 return $this->render_from_template('core/navbar', $this->page->navbar);
536f0460
DW
113 }
114
b71c82ad
FM
115 /**
116 * Override to inject the logo.
117 *
118 * @param array $headerinfo The header info.
119 * @param int $headinglevel What level the 'h' tag will be.
120 * @return string HTML for the header bar.
121 */
122 public function context_header($headerinfo = null, $headinglevel = 1) {
123 global $SITE;
124
125 if ($this->should_display_main_logo($headinglevel)) {
126 $sitename = format_string($SITE->fullname, true, array('context' => context_course::instance(SITEID)));
127 return html_writer::div(html_writer::empty_tag('img', [
128 'src' => $this->get_logo_url(null, 75), 'alt' => $sitename]), 'logo');
129 }
130
131 return parent::context_header($headerinfo, $headinglevel);
132 }
133
134 /**
135 * Get the compact logo URL.
136 *
137 * @return string
138 */
139 public function get_compact_logo_url($maxwidth = 100, $maxheight = 100) {
137c289a 140 return parent::get_compact_logo_url(null, 35);
b71c82ad
FM
141 }
142
143 /**
144 * Whether we should display the main logo.
145 *
146 * @return bool
147 */
148 public function should_display_main_logo($headinglevel = 1) {
149 global $PAGE;
150
151 // Only render the logo if we're on the front page or login page and the we have a logo.
152 $logo = $this->get_logo_url();
153 if ($headinglevel == 1 && !empty($logo)) {
154 if ($PAGE->pagelayout == 'frontpage' || $PAGE->pagelayout == 'login') {
155 return true;
156 }
157 }
158
159 return false;
160 }
161 /**
162 * Whether we should display the logo in the navbar.
163 *
164 * We will when there are no main logos, and we have compact logo.
165 *
166 * @return bool
167 */
168 public function should_display_navbar_logo() {
169 $logo = $this->get_compact_logo_url();
170 return !empty($logo) && !$this->should_display_main_logo();
171 }
172
536f0460
DW
173 /*
174 * Overriding the custom_menu function ensures the custom menu is
175 * always shown, even if no menu items are configured in the global
176 * theme settings page.
177 */
178 public function custom_menu($custommenuitems = '') {
179 global $CFG;
180
181 if (empty($custommenuitems) && !empty($CFG->custommenuitems)) {
182 $custommenuitems = $CFG->custommenuitems;
183 }
184 $custommenu = new custom_menu($custommenuitems, current_language());
185 return $this->render_custom_menu($custommenu);
186 }
187
188 /*
189 * This renders the bootstrap top menu.
190 *
191 * This renderer is needed to enable the Bootstrap style navigation.
192 */
193 protected function render_custom_menu(custom_menu $menu) {
194 global $CFG;
195
196 $langs = get_string_manager()->get_list_of_translations();
197 $haslangmenu = $this->lang_menu() != '';
198
199 if (!$menu->has_children() && !$haslangmenu) {
200 return '';
201 }
202
203 if ($haslangmenu) {
f130c411 204 $strlang = get_string('language');
536f0460
DW
205 $currentlang = current_language();
206 if (isset($langs[$currentlang])) {
207 $currentlang = $langs[$currentlang];
208 } else {
209 $currentlang = $strlang;
210 }
211 $this->language = $menu->add($currentlang, new moodle_url('#'), $strlang, 10000);
212 foreach ($langs as $langtype => $langname) {
213 $this->language->add($langname, new moodle_url($this->page->url, array('lang' => $langtype)), $langname);
214 }
215 }
216
f130c411 217 $content = '';
536f0460 218 foreach ($menu->get_children() as $item) {
f130c411
DW
219 $context = $item->export_for_template($this);
220 $content .= $this->render_from_template('core/custom_menu_item', $context);
536f0460
DW
221 }
222
536f0460
DW
223 return $content;
224 }
225
226 /**
227 * This code renders the navbar button to control the display of the custom menu
228 * on smaller screens.
229 *
230 * Do not display the button if the menu is empty.
231 *
232 * @return string HTML fragment
233 */
234 public function navbar_button() {
235 global $CFG;
236
237 if (empty($CFG->custommenuitems) && $this->lang_menu() == '') {
238 return '';
239 }
240
241 $iconbar = html_writer::tag('span', '', array('class' => 'icon-bar'));
242 $button = html_writer::tag('a', $iconbar . "\n" . $iconbar. "\n" . $iconbar, array(
243 'class' => 'btn btn-navbar',
244 'data-toggle' => 'collapse',
245 'data-target' => '.nav-collapse'
246 ));
247 return $button;
248 }
249
250 /**
251 * Renders tabtree
252 *
253 * @param tabtree $tabtree
254 * @return string
255 */
256 protected function render_tabtree(tabtree $tabtree) {
257 if (empty($tabtree->subtree)) {
258 return '';
259 }
da4d9eb7
FM
260 $data = $tabtree->export_for_template($this);
261 return $this->render_from_template('core/tabtree', $data);
536f0460
DW
262 }
263
264 /**
265 * Renders tabobject (part of tabtree)
266 *
267 * This function is called from {@link core_renderer::render_tabtree()}
268 * and also it calls itself when printing the $tabobject subtree recursively.
269 *
270 * @param tabobject $tabobject
271 * @return string HTML fragment
272 */
273 protected function render_tabobject(tabobject $tab) {
da4d9eb7 274 throw new coding_exception('Tab objects should not be directly rendered.');
536f0460 275 }
d7fbf722
DW
276
277 /**
278 * Prints a nice side block with an optional header.
279 *
280 * @param block_contents $bc HTML for the content
281 * @param string $region the region the block is appearing in.
282 * @return string the HTML to be output.
283 */
284 public function block(block_contents $bc, $region) {
285 $bc = clone($bc); // Avoid messing up the object passed in.
286 if (empty($bc->blockinstanceid) || !strip_tags($bc->title)) {
287 $bc->collapsible = block_contents::NOT_HIDEABLE;
288 }
d7fbf722
DW
289
290 $id = !empty($bc->attributes['id']) ? $bc->attributes['id'] : uniqid('block-');
291 $context = new stdClass();
292 $context->skipid = $bc->skipid;
293 $context->blockinstanceid = $bc->blockinstanceid;
294 $context->dockable = $bc->dockable;
295 $context->id = $id;
296 $context->hidden = $bc->collapsible == block_contents::HIDDEN;
297 $context->skiptitle = strip_tags($bc->title);
298 $context->showskiplink = !empty($context->skiptitle);
299 $context->arialabel = $bc->arialabel;
300 $context->ariarole = !empty($bc->attributes['role']) ? $bc->attributes['role'] : 'complementary';
301 $context->type = $bc->attributes['data-block'];
302 $context->title = $bc->title;
303 $context->content = $bc->content;
304 $context->annotation = $bc->annotation;
305 $context->footer = $bc->footer;
306 $context->hascontrols = !empty($bc->controls);
307 if ($context->hascontrols) {
308 $context->controls = $this->block_controls($bc->controls, $id);
309 }
310
311 return $this->render_from_template('core/block', $context);
312 }
313
314 /**
315 * Returns the CSS classes to apply to the body tag.
316 *
317 * @since Moodle 2.5.1 2.6
318 * @param array $additionalclasses Any additional classes to apply.
319 * @return string
320 */
321 public function body_css_classes(array $additionalclasses = array()) {
322 return $this->page->bodyclasses;
323 }
3f0544b8
DW
324
325 /**
326 * Renders preferences groups.
327 *
328 * @param preferences_groups $renderable The renderable
329 * @return string The output.
330 */
331 public function render_preferences_groups(preferences_groups $renderable) {
332 return $this->render_from_template('core/preferences_groups', $renderable);
333 }
334
3f0544b8
DW
335 /**
336 * Renders an action menu component.
337 *
338 * @param action_menu $menu
339 * @return string HTML
340 */
341 public function render_action_menu(action_menu $menu) {
132093f0
FM
342
343 // We don't want the class icon there!
344 foreach ($menu->get_secondary_actions() as $action) {
345 if ($action instanceof \action_menu_link && $action->has_class('icon')) {
346 $action->attributes['class'] = preg_replace('/(^|\s+)icon(\s+|$)/i', '', $action->attributes['class']);
347 }
348 }
349
350 $context = $menu->export_for_template($this);
351
352 // We do not want the icon with the caret, the caret is added by Bootstrap.
353 if (empty($context->primary->menutrigger)) {
354 $newurl = $this->pix_url('t/edit', 'moodle');
355 $context->primary->icon['attributes'] = array_reduce($context->primary->icon['attributes'],
356 function($carry, $item) use ($newurl) {
357 if ($item['name'] === 'src') {
358 $item['value'] = $newurl->out(false);
359 }
360 $carry[] = $item;
361 return $carry;
362 }, []
363 );
364 }
365
366 return $this->render_from_template('core/action_menu', $context);
3f0544b8 367 }
ac4a389e 368
b7e95263
DW
369 /**
370 * Implementation of user image rendering.
371 *
372 * @param help_icon $helpicon A help icon instance
373 * @return string HTML fragment
374 */
375 protected function render_help_icon(help_icon $helpicon) {
376 $context = $helpicon->export_for_template($this);
377 return $this->render_from_template('core/help_icon', $context);
378 }
bf7f35e9 379
d7c65752
FM
380 /**
381 * Renders a single button widget.
382 *
383 * This will return HTML to display a form containing a single button.
384 *
385 * @param single_button $button
386 * @return string HTML fragment
387 */
388 protected function render_single_button(single_button $button) {
389 return $this->render_from_template('core/single_button', $button->export_for_template($this));
390 }
391
bf7f35e9
FM
392 /**
393 * Renders a single select.
394 *
395 * @param single_select $select The object.
396 * @return string HTML
397 */
398 protected function render_single_select(single_select $select) {
399 return $this->render_from_template('core/single_select', $select->export_for_template($this));
400 }
401
b0da86e0
FM
402 /**
403 * Renders a paging bar.
404 *
405 * @param paging_bar $pagingbar The object.
406 * @return string HTML
407 */
408 protected function render_paging_bar(paging_bar $pagingbar) {
8a47abcd
DW
409 // Any more than 10 is not usable and causes wierd wrapping of the pagination in this theme.
410 $pagingbar->maxdisplay = 10;
b0da86e0
FM
411 return $this->render_from_template('core/paging_bar', $pagingbar->export_for_template($this));
412 }
413
f1b34660
FM
414 /**
415 * Renders a url select.
416 *
417 * @param url_select $select The object.
418 * @return string HTML
419 */
420 protected function render_url_select(url_select $select) {
421 return $this->render_from_template('core/url_select', $select->export_for_template($this));
422 }
63e4df60
DW
423
424 /**
425 * Renders a pix_icon widget and returns the HTML to display it.
426 *
427 * @param pix_icon $icon
428 * @return string HTML fragment
429 */
430 protected function render_pix_icon(pix_icon $icon) {
431 $data = $icon->export_for_template($this);
432 foreach ($data['attributes'] as $key => $item) {
433 $name = $item['name'];
434 $value = $item['value'];
435 if ($name == 'class') {
436 $data['extraclasses'] = $value;
437 unset($data['attributes'][$key]);
438 $data['attributes'] = array_values($data['attributes']);
439 break;
440 }
441 }
442 return $this->render_from_template('core/pix_icon', $data);
443 }
536f0460 444}