|
Moodle
2.2.1
http://www.collinsharper.com
|
00001 <?php 00002 00003 // This file is part of Moodle - http://moodle.org/ 00004 // 00005 // Moodle is free software: you can redistribute it and/or modify 00006 // it under the terms of the GNU General Public License as published by 00007 // the Free Software Foundation, either version 3 of the License, or 00008 // (at your option) any later version. 00009 // 00010 // Moodle is distributed in the hope that it will be useful, 00011 // but WITHOUT ANY WARRANTY; without even the implied warranty of 00012 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 00013 // GNU General Public License for more details. 00014 // 00015 // You should have received a copy of the GNU General Public License 00016 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 00017 00030 defined('MOODLE_INTERNAL') || die(); 00031 00043 class renderer_base { 00045 protected $opencontainers; 00047 protected $page; 00049 protected $target; 00050 00056 public function __construct(moodle_page $page, $target) { 00057 $this->opencontainers = $page->opencontainers; 00058 $this->page = $page; 00059 $this->target = $target; 00060 } 00061 00067 public function render(renderable $widget) { 00068 $rendermethod = 'render_'.get_class($widget); 00069 if (method_exists($this, $rendermethod)) { 00070 return $this->$rendermethod($widget); 00071 } 00072 throw new coding_exception('Can not render widget, renderer method ('.$rendermethod.') not found.'); 00073 } 00074 00081 public function add_action_handler(component_action $action, $id=null) { 00082 if (!$id) { 00083 $id = html_writer::random_id($action->event); 00084 } 00085 $this->page->requires->event_handler("#$id", $action->event, $action->jsfunction, $action->jsfunctionargs); 00086 return $id; 00087 } 00088 00093 public function has_started() { 00094 return $this->page->state >= moodle_page::STATE_IN_BODY; 00095 } 00096 00102 public static function prepare_classes($classes) { 00103 if (is_array($classes)) { 00104 return implode(' ', array_unique($classes)); 00105 } 00106 return $classes; 00107 } 00108 00132 public function pix_url($imagename, $component = 'moodle') { 00133 return $this->page->theme->pix_url($imagename, $component); 00134 } 00135 } 00136 00137 00145 class plugin_renderer_base extends renderer_base { 00150 protected $output; 00151 00157 public function __construct(moodle_page $page, $target) { 00158 $this->output = $page->get_renderer('core', null, $target); 00159 parent::__construct($page, $target); 00160 } 00161 00167 public function render(renderable $widget) { 00168 $rendermethod = 'render_'.get_class($widget); 00169 if (method_exists($this, $rendermethod)) { 00170 return $this->$rendermethod($widget); 00171 } 00172 // pass to core renderer if method not found here 00173 return $this->output->render($widget); 00174 } 00175 00184 public function __call($method, $arguments) { 00185 if (method_exists('renderer_base', $method)) { 00186 throw new coding_exception('Protected method called against '.__CLASS__.' :: '.$method); 00187 } 00188 if (method_exists($this->output, $method)) { 00189 return call_user_func_array(array($this->output, $method), $arguments); 00190 } else { 00191 throw new coding_exception('Unknown method called against '.__CLASS__.' :: '.$method); 00192 } 00193 } 00194 } 00195 00196 00204 class core_renderer extends renderer_base { 00211 const MAIN_CONTENT_TOKEN = '[MAIN CONTENT GOES HERE]'; 00213 protected $contenttype; 00215 protected $metarefreshtag = ''; 00217 protected $unique_end_html_token; 00219 protected $unique_performance_info_token; 00221 protected $unique_main_content_token; 00222 00228 public function __construct(moodle_page $page, $target) { 00229 $this->opencontainers = $page->opencontainers; 00230 $this->page = $page; 00231 $this->target = $target; 00232 00233 $this->unique_end_html_token = '%%ENDHTML-'.sesskey().'%%'; 00234 $this->unique_performance_info_token = '%%PERFORMANCEINFO-'.sesskey().'%%'; 00235 $this->unique_main_content_token = '[MAIN CONTENT GOES HERE - '.sesskey().']'; 00236 } 00237 00243 public function doctype() { 00244 global $CFG; 00245 00246 $doctype = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">' . "\n"; 00247 $this->contenttype = 'text/html; charset=utf-8'; 00248 00249 if (empty($CFG->xmlstrictheaders)) { 00250 return $doctype; 00251 } 00252 00253 // We want to serve the page with an XML content type, to force well-formedness errors to be reported. 00254 $prolog = '<?xml version="1.0" encoding="utf-8"?>' . "\n"; 00255 if (isset($_SERVER['HTTP_ACCEPT']) && strpos($_SERVER['HTTP_ACCEPT'], 'application/xhtml+xml') !== false) { 00256 // Firefox and other browsers that can cope natively with XHTML. 00257 $this->contenttype = 'application/xhtml+xml; charset=utf-8'; 00258 00259 } else if (preg_match('/MSIE.*Windows NT/', $_SERVER['HTTP_USER_AGENT'])) { 00260 // IE can't cope with application/xhtml+xml, but it will cope if we send application/xml with an XSL stylesheet. 00261 $this->contenttype = 'application/xml; charset=utf-8'; 00262 $prolog .= '<?xml-stylesheet type="text/xsl" href="' . $CFG->httpswwwroot . '/lib/xhtml.xsl"?>' . "\n"; 00263 00264 } else { 00265 $prolog = ''; 00266 } 00267 00268 return $prolog . $doctype; 00269 } 00270 00276 public function htmlattributes() { 00277 return get_html_lang(true) . ' xmlns="http://www.w3.org/1999/xhtml"'; 00278 } 00279 00286 public function standard_head_html() { 00287 global $CFG, $SESSION; 00288 $output = ''; 00289 $output .= '<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />' . "\n"; 00290 $output .= '<meta name="keywords" content="moodle, ' . $this->page->title . '" />' . "\n"; 00291 if (!$this->page->cacheable) { 00292 $output .= '<meta http-equiv="pragma" content="no-cache" />' . "\n"; 00293 $output .= '<meta http-equiv="expires" content="0" />' . "\n"; 00294 } 00295 // This is only set by the {@link redirect()} method 00296 $output .= $this->metarefreshtag; 00297 00298 // Check if a periodic refresh delay has been set and make sure we arn't 00299 // already meta refreshing 00300 if ($this->metarefreshtag=='' && $this->page->periodicrefreshdelay!==null) { 00301 $output .= '<meta http-equiv="refresh" content="'.$this->page->periodicrefreshdelay.';url='.$this->page->url->out().'" />'; 00302 } 00303 00304 // flow player embedding support 00305 $this->page->requires->js_function_call('M.util.load_flowplayer'); 00306 00307 $this->page->requires->js_function_call('setTimeout', array('fix_column_widths()', 20)); 00308 00309 $focus = $this->page->focuscontrol; 00310 if (!empty($focus)) { 00311 if (preg_match("#forms\['([a-zA-Z0-9]+)'\].elements\['([a-zA-Z0-9]+)'\]#", $focus, $matches)) { 00312 // This is a horrifically bad way to handle focus but it is passed in 00313 // through messy formslib::moodleform 00314 $this->page->requires->js_function_call('old_onload_focus', array($matches[1], $matches[2])); 00315 } else if (strpos($focus, '.')!==false) { 00316 // Old style of focus, bad way to do it 00317 debugging('This code is using the old style focus event, Please update this code to focus on an element id or the moodleform focus method.', DEBUG_DEVELOPER); 00318 $this->page->requires->js_function_call('old_onload_focus', explode('.', $focus, 2)); 00319 } else { 00320 // Focus element with given id 00321 $this->page->requires->js_function_call('focuscontrol', array($focus)); 00322 } 00323 } 00324 00325 // Get the theme stylesheet - this has to be always first CSS, this loads also styles.css from all plugins; 00326 // any other custom CSS can not be overridden via themes and is highly discouraged 00327 $urls = $this->page->theme->css_urls($this->page); 00328 foreach ($urls as $url) { 00329 $this->page->requires->css_theme($url); 00330 } 00331 00332 // Get the theme javascript head and footer 00333 $jsurl = $this->page->theme->javascript_url(true); 00334 $this->page->requires->js($jsurl, true); 00335 $jsurl = $this->page->theme->javascript_url(false); 00336 $this->page->requires->js($jsurl); 00337 00338 // Get any HTML from the page_requirements_manager. 00339 $output .= $this->page->requires->get_head_code($this->page, $this); 00340 00341 // List alternate versions. 00342 foreach ($this->page->alternateversions as $type => $alt) { 00343 $output .= html_writer::empty_tag('link', array('rel' => 'alternate', 00344 'type' => $type, 'title' => $alt->title, 'href' => $alt->url)); 00345 } 00346 00347 if (!empty($CFG->additionalhtmlhead)) { 00348 $output .= "\n".$CFG->additionalhtmlhead; 00349 } 00350 00351 return $output; 00352 } 00353 00359 public function standard_top_of_body_html() { 00360 global $CFG; 00361 $output = $this->page->requires->get_top_of_body_code(); 00362 if (!empty($CFG->additionalhtmltopofbody)) { 00363 $output .= "\n".$CFG->additionalhtmltopofbody; 00364 } 00365 return $output; 00366 } 00367 00374 public function standard_footer_html() { 00375 global $CFG, $SCRIPT; 00376 00377 // This function is normally called from a layout.php file in {@link header()} 00378 // but some of the content won't be known until later, so we return a placeholder 00379 // for now. This will be replaced with the real content in {@link footer()}. 00380 $output = $this->unique_performance_info_token; 00381 if ($this->page->devicetypeinuse == 'legacy') { 00382 // The legacy theme is in use print the notification 00383 $output .= html_writer::tag('div', get_string('legacythemeinuse'), array('class'=>'legacythemeinuse')); 00384 } 00385 00386 // Get links to switch device types (only shown for users not on a default device) 00387 $output .= $this->theme_switch_links(); 00388 00389 if (!empty($CFG->debugpageinfo)) { 00390 $output .= '<div class="performanceinfo pageinfo">This page is: ' . $this->page->debug_summary() . '</div>'; 00391 } 00392 if (debugging(null, DEBUG_DEVELOPER) and has_capability('moodle/site:config', get_context_instance(CONTEXT_SYSTEM))) { // Only in developer mode 00393 // Add link to profiling report if necessary 00394 if (function_exists('profiling_is_running') && profiling_is_running()) { 00395 $txt = get_string('profiledscript', 'admin'); 00396 $title = get_string('profiledscriptview', 'admin'); 00397 $url = $CFG->wwwroot . '/admin/tool/profiling/index.php?script=' . urlencode($SCRIPT); 00398 $link= '<a title="' . $title . '" href="' . $url . '">' . $txt . '</a>'; 00399 $output .= '<div class="profilingfooter">' . $link . '</div>'; 00400 } 00401 $output .= '<div class="purgecaches"><a href="'.$CFG->wwwroot.'/admin/purgecaches.php?confirm=1&sesskey='.sesskey().'">'.get_string('purgecaches', 'admin').'</a></div>'; 00402 } 00403 if (!empty($CFG->debugvalidators)) { 00404 $output .= '<div class="validators"><ul> 00405 <li><a href="http://validator.w3.org/check?verbose=1&ss=1&uri=' . urlencode(qualified_me()) . '">Validate HTML</a></li> 00406 <li><a href="http://www.contentquality.com/mynewtester/cynthia.exe?rptmode=-1&url1=' . urlencode(qualified_me()) . '">Section 508 Check</a></li> 00407 <li><a href="http://www.contentquality.com/mynewtester/cynthia.exe?rptmode=0&warnp2n3e=1&url1=' . urlencode(qualified_me()) . '">WCAG 1 (2,3) Check</a></li> 00408 </ul></div>'; 00409 } 00410 if (!empty($CFG->additionalhtmlfooter)) { 00411 $output .= "\n".$CFG->additionalhtmlfooter; 00412 } 00413 return $output; 00414 } 00415 00421 public function main_content() { 00422 return $this->unique_main_content_token; 00423 } 00424 00430 public function standard_end_of_body_html() { 00431 // This function is normally called from a layout.php file in {@link header()} 00432 // but some of the content won't be known until later, so we return a placeholder 00433 // for now. This will be replaced with the real content in {@link footer()}. 00434 return $this->unique_end_html_token; 00435 } 00436 00442 public function login_info() { 00443 global $USER, $CFG, $DB, $SESSION; 00444 00445 if (during_initial_install()) { 00446 return ''; 00447 } 00448 00449 $loginapge = ((string)$this->page->url === get_login_url()); 00450 $course = $this->page->course; 00451 00452 if (session_is_loggedinas()) { 00453 $realuser = session_get_realuser(); 00454 $fullname = fullname($realuser, true); 00455 $realuserinfo = " [<a href=\"$CFG->wwwroot/course/loginas.php?id=$course->id&sesskey=".sesskey()."\">$fullname</a>] "; 00456 } else { 00457 $realuserinfo = ''; 00458 } 00459 00460 $loginurl = get_login_url(); 00461 00462 if (empty($course->id)) { 00463 // $course->id is not defined during installation 00464 return ''; 00465 } else if (isloggedin()) { 00466 $context = get_context_instance(CONTEXT_COURSE, $course->id); 00467 00468 $fullname = fullname($USER, true); 00469 // Since Moodle 2.0 this link always goes to the public profile page (not the course profile page) 00470 $username = "<a href=\"$CFG->wwwroot/user/profile.php?id=$USER->id\">$fullname</a>"; 00471 if (is_mnet_remote_user($USER) and $idprovider = $DB->get_record('mnet_host', array('id'=>$USER->mnethostid))) { 00472 $username .= " from <a href=\"{$idprovider->wwwroot}\">{$idprovider->name}</a>"; 00473 } 00474 if (isguestuser()) { 00475 $loggedinas = $realuserinfo.get_string('loggedinasguest'); 00476 if (!$loginapge) { 00477 $loggedinas .= " (<a href=\"$loginurl\">".get_string('login').'</a>)'; 00478 } 00479 } else if (is_role_switched($course->id)) { // Has switched roles 00480 $rolename = ''; 00481 if ($role = $DB->get_record('role', array('id'=>$USER->access['rsw'][$context->path]))) { 00482 $rolename = ': '.format_string($role->name); 00483 } 00484 $loggedinas = get_string('loggedinas', 'moodle', $username).$rolename. 00485 " (<a href=\"$CFG->wwwroot/course/view.php?id=$course->id&switchrole=0&sesskey=".sesskey()."\">".get_string('switchrolereturn').'</a>)'; 00486 } else { 00487 $loggedinas = $realuserinfo.get_string('loggedinas', 'moodle', $username).' '. 00488 " (<a href=\"$CFG->wwwroot/login/logout.php?sesskey=".sesskey()."\">".get_string('logout').'</a>)'; 00489 } 00490 } else { 00491 $loggedinas = get_string('loggedinnot', 'moodle'); 00492 if (!$loginapge) { 00493 $loggedinas .= " (<a href=\"$loginurl\">".get_string('login').'</a>)'; 00494 } 00495 } 00496 00497 $loggedinas = '<div class="logininfo">'.$loggedinas.'</div>'; 00498 00499 if (isset($SESSION->justloggedin)) { 00500 unset($SESSION->justloggedin); 00501 if (!empty($CFG->displayloginfailures)) { 00502 if (!isguestuser()) { 00503 if ($count = count_login_failures($CFG->displayloginfailures, $USER->username, $USER->lastlogin)) { 00504 $loggedinas .= ' <div class="loginfailures">'; 00505 if (empty($count->accounts)) { 00506 $loggedinas .= get_string('failedloginattempts', '', $count); 00507 } else { 00508 $loggedinas .= get_string('failedloginattemptsall', '', $count); 00509 } 00510 if (file_exists("$CFG->dirroot/report/log/index.php") and has_capability('report/log:view', get_context_instance(CONTEXT_SYSTEM))) { 00511 $loggedinas .= ' (<a href="'.$CFG->wwwroot.'/report/log/index.php'. 00512 '?chooselog=1&id=1&modid=site_errors">'.get_string('logs').'</a>)'; 00513 } 00514 $loggedinas .= '</div>'; 00515 } 00516 } 00517 } 00518 } 00519 00520 return $loggedinas; 00521 } 00522 00527 public function home_link() { 00528 global $CFG, $SITE; 00529 00530 if ($this->page->pagetype == 'site-index') { 00531 // Special case for site home page - please do not remove 00532 return '<div class="sitelink">' . 00533 '<a title="Moodle" href="http://moodle.org/">' . 00534 '<img style="width:100px;height:30px" src="' . $this->pix_url('moodlelogo') . '" alt="moodlelogo" /></a></div>'; 00535 00536 } else if (!empty($CFG->target_release) && $CFG->target_release != $CFG->release) { 00537 // Special case for during install/upgrade. 00538 return '<div class="sitelink">'. 00539 '<a title="Moodle" href="http://docs.moodle.org/en/Administrator_documentation" onclick="this.target=\'_blank\'">' . 00540 '<img style="width:100px;height:30px" src="' . $this->pix_url('moodlelogo') . '" alt="moodlelogo" /></a></div>'; 00541 00542 } else if ($this->page->course->id == $SITE->id || strpos($this->page->pagetype, 'course-view') === 0) { 00543 return '<div class="homelink"><a href="' . $CFG->wwwroot . '/">' . 00544 get_string('home') . '</a></div>'; 00545 00546 } else { 00547 return '<div class="homelink"><a href="' . $CFG->wwwroot . '/course/view.php?id=' . $this->page->course->id . '">' . 00548 format_string($this->page->course->shortname, true, array('context' => $this->page->context)) . '</a></div>'; 00549 } 00550 } 00551 00571 public function redirect_message($encodedurl, $message, $delay, $debugdisableredirect) { 00572 global $CFG; 00573 $url = str_replace('&', '&', $encodedurl); 00574 00575 switch ($this->page->state) { 00576 case moodle_page::STATE_BEFORE_HEADER : 00577 // No output yet it is safe to delivery the full arsenal of redirect methods 00578 if (!$debugdisableredirect) { 00579 // Don't use exactly the same time here, it can cause problems when both redirects fire at the same time. 00580 $this->metarefreshtag = '<meta http-equiv="refresh" content="'. $delay .'; url='. $encodedurl .'" />'."\n"; 00581 $this->page->requires->js_function_call('document.location.replace', array($url), false, ($delay + 3)); 00582 } 00583 $output = $this->header(); 00584 break; 00585 case moodle_page::STATE_PRINTING_HEADER : 00586 // We should hopefully never get here 00587 throw new coding_exception('You cannot redirect while printing the page header'); 00588 break; 00589 case moodle_page::STATE_IN_BODY : 00590 // We really shouldn't be here but we can deal with this 00591 debugging("You should really redirect before you start page output"); 00592 if (!$debugdisableredirect) { 00593 $this->page->requires->js_function_call('document.location.replace', array($url), false, $delay); 00594 } 00595 $output = $this->opencontainers->pop_all_but_last(); 00596 break; 00597 case moodle_page::STATE_DONE : 00598 // Too late to be calling redirect now 00599 throw new coding_exception('You cannot redirect after the entire page has been generated'); 00600 break; 00601 } 00602 $output .= $this->notification($message, 'redirectmessage'); 00603 $output .= '<div class="continuebutton">(<a href="'. $encodedurl .'">'. get_string('continue') .'</a>)</div>'; 00604 if ($debugdisableredirect) { 00605 $output .= '<p><strong>Error output, so disabling automatic redirect.</strong></p>'; 00606 } 00607 $output .= $this->footer(); 00608 return $output; 00609 } 00610 00625 public function header() { 00626 global $USER, $CFG; 00627 00628 if (session_is_loggedinas()) { 00629 $this->page->add_body_class('userloggedinas'); 00630 } 00631 00632 $this->page->set_state(moodle_page::STATE_PRINTING_HEADER); 00633 00634 // Find the appropriate page layout file, based on $this->page->pagelayout. 00635 $layoutfile = $this->page->theme->layout_file($this->page->pagelayout); 00636 // Render the layout using the layout file. 00637 $rendered = $this->render_page_layout($layoutfile); 00638 00639 // Slice the rendered output into header and footer. 00640 $cutpos = strpos($rendered, $this->unique_main_content_token); 00641 if ($cutpos === false) { 00642 $cutpos = strpos($rendered, self::MAIN_CONTENT_TOKEN); 00643 $token = self::MAIN_CONTENT_TOKEN; 00644 } else { 00645 $token = $this->unique_main_content_token; 00646 } 00647 00648 if ($cutpos === false) { 00649 throw new coding_exception('page layout file ' . $layoutfile . ' does not contain the main content placeholder, please include "<?php echo $OUTPUT->main_content() ?>" in theme layout file.'); 00650 } 00651 $header = substr($rendered, 0, $cutpos); 00652 $footer = substr($rendered, $cutpos + strlen($token)); 00653 00654 if (empty($this->contenttype)) { 00655 debugging('The page layout file did not call $OUTPUT->doctype()'); 00656 $header = $this->doctype() . $header; 00657 } 00658 00659 send_headers($this->contenttype, $this->page->cacheable); 00660 00661 $this->opencontainers->push('header/footer', $footer); 00662 $this->page->set_state(moodle_page::STATE_IN_BODY); 00663 00664 return $header . $this->skip_link_target('maincontent'); 00665 } 00666 00672 protected function render_page_layout($layoutfile) { 00673 global $CFG, $SITE, $USER; 00674 // The next lines are a bit tricky. The point is, here we are in a method 00675 // of a renderer class, and this object may, or may not, be the same as 00676 // the global $OUTPUT object. When rendering the page layout file, we want to use 00677 // this object. However, people writing Moodle code expect the current 00678 // renderer to be called $OUTPUT, not $this, so define a variable called 00679 // $OUTPUT pointing at $this. The same comment applies to $PAGE and $COURSE. 00680 $OUTPUT = $this; 00681 $PAGE = $this->page; 00682 $COURSE = $this->page->course; 00683 00684 ob_start(); 00685 include($layoutfile); 00686 $rendered = ob_get_contents(); 00687 ob_end_clean(); 00688 return $rendered; 00689 } 00690 00695 public function footer() { 00696 global $CFG, $DB; 00697 00698 $output = $this->container_end_all(true); 00699 00700 $footer = $this->opencontainers->pop('header/footer'); 00701 00702 if (debugging() and $DB and $DB->is_transaction_started()) { 00703 // TODO: MDL-20625 print warning - transaction will be rolled back 00704 } 00705 00706 // Provide some performance info if required 00707 $performanceinfo = ''; 00708 if (defined('MDL_PERF') || (!empty($CFG->perfdebug) and $CFG->perfdebug > 7)) { 00709 $perf = get_performance_info(); 00710 if (defined('MDL_PERFTOLOG') && !function_exists('register_shutdown_function')) { 00711 error_log("PERF: " . $perf['txt']); 00712 } 00713 if (defined('MDL_PERFTOFOOT') || debugging() || $CFG->perfdebug > 7) { 00714 $performanceinfo = $perf['html']; 00715 } 00716 } 00717 $footer = str_replace($this->unique_performance_info_token, $performanceinfo, $footer); 00718 00719 $footer = str_replace($this->unique_end_html_token, $this->page->requires->get_end_code(), $footer); 00720 00721 $this->page->set_state(moodle_page::STATE_DONE); 00722 00723 return $output . $footer; 00724 } 00725 00734 public function container_end_all($shouldbenone = false) { 00735 return $this->opencontainers->pop_all_but_last($shouldbenone); 00736 } 00737 00742 public function lang_menu() { 00743 global $CFG; 00744 00745 if (empty($CFG->langmenu)) { 00746 return ''; 00747 } 00748 00749 if ($this->page->course != SITEID and !empty($this->page->course->lang)) { 00750 // do not show lang menu if language forced 00751 return ''; 00752 } 00753 00754 $currlang = current_language(); 00755 $langs = get_string_manager()->get_list_of_translations(); 00756 00757 if (count($langs) < 2) { 00758 return ''; 00759 } 00760 00761 $s = new single_select($this->page->url, 'lang', $langs, $currlang, null); 00762 $s->label = get_accesshide(get_string('language')); 00763 $s->class = 'langmenu'; 00764 return $this->render($s); 00765 } 00766 00772 public function block_controls($controls) { 00773 if (empty($controls)) { 00774 return ''; 00775 } 00776 $controlshtml = array(); 00777 foreach ($controls as $control) { 00778 $controlshtml[] = html_writer::tag('a', 00779 html_writer::empty_tag('img', array('src' => $this->pix_url($control['icon'])->out(false), 'alt' => $control['caption'])), 00780 array('class' => 'icon','title' => $control['caption'], 'href' => $control['url'])); 00781 } 00782 return html_writer::tag('div', implode('', $controlshtml), array('class' => 'commands')); 00783 } 00784 00806 function block(block_contents $bc, $region) { 00807 $bc = clone($bc); // Avoid messing up the object passed in. 00808 if (empty($bc->blockinstanceid) || !strip_tags($bc->title)) { 00809 $bc->collapsible = block_contents::NOT_HIDEABLE; 00810 } 00811 if ($bc->collapsible == block_contents::HIDDEN) { 00812 $bc->add_class('hidden'); 00813 } 00814 if (!empty($bc->controls)) { 00815 $bc->add_class('block_with_controls'); 00816 } 00817 00818 $skiptitle = strip_tags($bc->title); 00819 if (empty($skiptitle)) { 00820 $output = ''; 00821 $skipdest = ''; 00822 } else { 00823 $output = html_writer::tag('a', get_string('skipa', 'access', $skiptitle), array('href' => '#sb-' . $bc->skipid, 'class' => 'skip-block')); 00824 $skipdest = html_writer::tag('span', '', array('id' => 'sb-' . $bc->skipid, 'class' => 'skip-block-to')); 00825 } 00826 00827 $output .= html_writer::start_tag('div', $bc->attributes); 00828 00829 $output .= $this->block_header($bc); 00830 $output .= $this->block_content($bc); 00831 00832 $output .= html_writer::end_tag('div'); 00833 00834 $output .= $this->block_annotation($bc); 00835 00836 $output .= $skipdest; 00837 00838 $this->init_block_hider_js($bc); 00839 return $output; 00840 } 00841 00848 protected function block_header(block_contents $bc) { 00849 00850 $title = ''; 00851 if ($bc->title) { 00852 $title = html_writer::tag('h2', $bc->title, null); 00853 } 00854 00855 $controlshtml = $this->block_controls($bc->controls); 00856 00857 $output = ''; 00858 if ($title || $controlshtml) { 00859 $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')); 00860 } 00861 return $output; 00862 } 00863 00870 protected function block_content(block_contents $bc) { 00871 $output = html_writer::start_tag('div', array('class' => 'content')); 00872 if (!$bc->title && !$this->block_controls($bc->controls)) { 00873 $output .= html_writer::tag('div', '', array('class'=>'block_action notitle')); 00874 } 00875 $output .= $bc->content; 00876 $output .= $this->block_footer($bc); 00877 $output .= html_writer::end_tag('div'); 00878 00879 return $output; 00880 } 00881 00888 protected function block_footer(block_contents $bc) { 00889 $output = ''; 00890 if ($bc->footer) { 00891 $output .= html_writer::tag('div', $bc->footer, array('class' => 'footer')); 00892 } 00893 return $output; 00894 } 00895 00902 protected function block_annotation(block_contents $bc) { 00903 $output = ''; 00904 if ($bc->annotation) { 00905 $output .= html_writer::tag('div', $bc->annotation, array('class' => 'blockannotation')); 00906 } 00907 return $output; 00908 } 00909 00915 protected function init_block_hider_js(block_contents $bc) { 00916 if (!empty($bc->attributes['id']) and $bc->collapsible != block_contents::NOT_HIDEABLE) { 00917 $config = new stdClass; 00918 $config->id = $bc->attributes['id']; 00919 $config->title = strip_tags($bc->title); 00920 $config->preference = 'block' . $bc->blockinstanceid . 'hidden'; 00921 $config->tooltipVisible = get_string('hideblocka', 'access', $config->title); 00922 $config->tooltipHidden = get_string('showblocka', 'access', $config->title); 00923 00924 $this->page->requires->js_init_call('M.util.init_block_hider', array($config)); 00925 user_preference_allow_ajax_update($config->preference, PARAM_BOOL); 00926 } 00927 } 00928 00935 public function list_block_contents($icons, $items) { 00936 $row = 0; 00937 $lis = array(); 00938 foreach ($items as $key => $string) { 00939 $item = html_writer::start_tag('li', array('class' => 'r' . $row)); 00940 if (!empty($icons[$key])) { //test if the content has an assigned icon 00941 $item .= html_writer::tag('div', $icons[$key], array('class' => 'icon column c0')); 00942 } 00943 $item .= html_writer::tag('div', $string, array('class' => 'column c1')); 00944 $item .= html_writer::end_tag('li'); 00945 $lis[] = $item; 00946 $row = 1 - $row; // Flip even/odd. 00947 } 00948 return html_writer::tag('ul', implode("\n", $lis), array('class' => 'unlist')); 00949 } 00950 00956 public function blocks_for_region($region) { 00957 $blockcontents = $this->page->blocks->get_content_for_region($region, $this); 00958 00959 $output = ''; 00960 foreach ($blockcontents as $bc) { 00961 if ($bc instanceof block_contents) { 00962 $output .= $this->block($bc, $region); 00963 } else if ($bc instanceof block_move_target) { 00964 $output .= $this->block_move_target($bc); 00965 } else { 00966 throw new coding_exception('Unexpected type of thing (' . get_class($bc) . ') found in list of block contents.'); 00967 } 00968 } 00969 return $output; 00970 } 00971 00977 public function block_move_target($target) { 00978 return html_writer::tag('a', html_writer::tag('span', $target->text, array('class' => 'accesshide')), array('href' => $target->url, 'class' => 'blockmovetarget')); 00979 } 00980 00990 public function action_link($url, $text, component_action $action = null, array $attributes=null) { 00991 if (!($url instanceof moodle_url)) { 00992 $url = new moodle_url($url); 00993 } 00994 $link = new action_link($url, $text, $action, $attributes); 00995 00996 return $this->render($link); 00997 } 00998 01004 protected function render_action_link(action_link $link) { 01005 global $CFG; 01006 01007 if ($link->text instanceof renderable) { 01008 $text = $this->render($link->text); 01009 } else { 01010 $text = $link->text; 01011 } 01012 01013 // A disabled link is rendered as formatted text 01014 if (!empty($link->attributes['disabled'])) { 01015 // do not use div here due to nesting restriction in xhtml strict 01016 return html_writer::tag('span', $text, array('class'=>'currentlink')); 01017 } 01018 01019 $attributes = $link->attributes; 01020 unset($link->attributes['disabled']); 01021 $attributes['href'] = $link->url; 01022 01023 if ($link->actions) { 01024 if (empty($attributes['id'])) { 01025 $id = html_writer::random_id('action_link'); 01026 $attributes['id'] = $id; 01027 } else { 01028 $id = $attributes['id']; 01029 } 01030 foreach ($link->actions as $action) { 01031 $this->add_action_handler($action, $id); 01032 } 01033 } 01034 01035 return html_writer::tag('a', $text, $attributes); 01036 } 01037 01038 01049 public function action_icon($url, pix_icon $pixicon, component_action $action = null, array $attributes = null, $linktext=false) { 01050 if (!($url instanceof moodle_url)) { 01051 $url = new moodle_url($url); 01052 } 01053 $attributes = (array)$attributes; 01054 01055 if (empty($attributes['class'])) { 01056 // let ppl override the class via $options 01057 $attributes['class'] = 'action-icon'; 01058 } 01059 01060 $icon = $this->render($pixicon); 01061 01062 if ($linktext) { 01063 $text = $pixicon->attributes['alt']; 01064 } else { 01065 $text = ''; 01066 } 01067 01068 return $this->action_link($url, $text.$icon, $action, $attributes); 01069 } 01070 01081 public function confirm($message, $continue, $cancel) { 01082 if ($continue instanceof single_button) { 01083 // ok 01084 } else if (is_string($continue)) { 01085 $continue = new single_button(new moodle_url($continue), get_string('continue'), 'post'); 01086 } else if ($continue instanceof moodle_url) { 01087 $continue = new single_button($continue, get_string('continue'), 'post'); 01088 } else { 01089 throw new coding_exception('The continue param to $OUTPUT->confirm() must be either a URL (string/moodle_url) or a single_button instance.'); 01090 } 01091 01092 if ($cancel instanceof single_button) { 01093 // ok 01094 } else if (is_string($cancel)) { 01095 $cancel = new single_button(new moodle_url($cancel), get_string('cancel'), 'get'); 01096 } else if ($cancel instanceof moodle_url) { 01097 $cancel = new single_button($cancel, get_string('cancel'), 'get'); 01098 } else { 01099 throw new coding_exception('The cancel param to $OUTPUT->confirm() must be either a URL (string/moodle_url) or a single_button instance.'); 01100 } 01101 01102 $output = $this->box_start('generalbox', 'notice'); 01103 $output .= html_writer::tag('p', $message); 01104 $output .= html_writer::tag('div', $this->render($continue) . $this->render($cancel), array('class' => 'buttons')); 01105 $output .= $this->box_end(); 01106 return $output; 01107 } 01108 01118 public function single_button($url, $label, $method='post', array $options=null) { 01119 if (!($url instanceof moodle_url)) { 01120 $url = new moodle_url($url); 01121 } 01122 $button = new single_button($url, $label, $method); 01123 01124 foreach ((array)$options as $key=>$value) { 01125 if (array_key_exists($key, $button)) { 01126 $button->$key = $value; 01127 } 01128 } 01129 01130 return $this->render($button); 01131 } 01132 01138 protected function render_single_button(single_button $button) { 01139 $attributes = array('type' => 'submit', 01140 'value' => $button->label, 01141 'disabled' => $button->disabled ? 'disabled' : null, 01142 'title' => $button->tooltip); 01143 01144 if ($button->actions) { 01145 $id = html_writer::random_id('single_button'); 01146 $attributes['id'] = $id; 01147 foreach ($button->actions as $action) { 01148 $this->add_action_handler($action, $id); 01149 } 01150 } 01151 01152 // first the input element 01153 $output = html_writer::empty_tag('input', $attributes); 01154 01155 // then hidden fields 01156 $params = $button->url->params(); 01157 if ($button->method === 'post') { 01158 $params['sesskey'] = sesskey(); 01159 } 01160 foreach ($params as $var => $val) { 01161 $output .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => $var, 'value' => $val)); 01162 } 01163 01164 // then div wrapper for xhtml strictness 01165 $output = html_writer::tag('div', $output); 01166 01167 // now the form itself around it 01168 if ($button->method === 'get') { 01169 $url = $button->url->out_omit_querystring(true); // url without params, the anchor part allowed 01170 } else { 01171 $url = $button->url->out_omit_querystring(); // url without params, the anchor part not allowed 01172 } 01173 if ($url === '') { 01174 $url = '#'; // there has to be always some action 01175 } 01176 $attributes = array('method' => $button->method, 01177 'action' => $url, 01178 'id' => $button->formid); 01179 $output = html_writer::tag('form', $output, $attributes); 01180 01181 // and finally one more wrapper with class 01182 return html_writer::tag('div', $output, array('class' => $button->class)); 01183 } 01184 01195 public function single_select($url, $name, array $options, $selected='', $nothing=array(''=>'choosedots'), $formid=null) { 01196 if (!($url instanceof moodle_url)) { 01197 $url = new moodle_url($url); 01198 } 01199 $select = new single_select($url, $name, $options, $selected, $nothing, $formid); 01200 01201 return $this->render($select); 01202 } 01203 01209 protected function render_single_select(single_select $select) { 01210 $select = clone($select); 01211 if (empty($select->formid)) { 01212 $select->formid = html_writer::random_id('single_select_f'); 01213 } 01214 01215 $output = ''; 01216 $params = $select->url->params(); 01217 if ($select->method === 'post') { 01218 $params['sesskey'] = sesskey(); 01219 } 01220 foreach ($params as $name=>$value) { 01221 $output .= html_writer::empty_tag('input', array('type'=>'hidden', 'name'=>$name, 'value'=>$value)); 01222 } 01223 01224 if (empty($select->attributes['id'])) { 01225 $select->attributes['id'] = html_writer::random_id('single_select'); 01226 } 01227 01228 if ($select->disabled) { 01229 $select->attributes['disabled'] = 'disabled'; 01230 } 01231 01232 if ($select->tooltip) { 01233 $select->attributes['title'] = $select->tooltip; 01234 } 01235 01236 if ($select->label) { 01237 $output .= html_writer::label($select->label, $select->attributes['id']); 01238 } 01239 01240 if ($select->helpicon instanceof help_icon) { 01241 $output .= $this->render($select->helpicon); 01242 } else if ($select->helpicon instanceof old_help_icon) { 01243 $output .= $this->render($select->helpicon); 01244 } 01245 01246 $output .= html_writer::select($select->options, $select->name, $select->selected, $select->nothing, $select->attributes); 01247 01248 $go = html_writer::empty_tag('input', array('type'=>'submit', 'value'=>get_string('go'))); 01249 $output .= html_writer::tag('noscript', html_writer::tag('div', $go), array('style'=>'inline')); 01250 01251 $nothing = empty($select->nothing) ? false : key($select->nothing); 01252 $this->page->requires->js_init_call('M.util.init_select_autosubmit', array($select->formid, $select->attributes['id'], $nothing)); 01253 01254 // then div wrapper for xhtml strictness 01255 $output = html_writer::tag('div', $output); 01256 01257 // now the form itself around it 01258 if ($select->method === 'get') { 01259 $url = $select->url->out_omit_querystring(true); // url without params, the anchor part allowed 01260 } else { 01261 $url = $select->url->out_omit_querystring(); // url without params, the anchor part not allowed 01262 } 01263 $formattributes = array('method' => $select->method, 01264 'action' => $url, 01265 'id' => $select->formid); 01266 $output = html_writer::tag('form', $output, $formattributes); 01267 01268 // and finally one more wrapper with class 01269 return html_writer::tag('div', $output, array('class' => $select->class)); 01270 } 01271 01280 public function url_select(array $urls, $selected, $nothing=array(''=>'choosedots'), $formid=null) { 01281 $select = new url_select($urls, $selected, $nothing, $formid); 01282 return $this->render($select); 01283 } 01284 01290 protected function render_url_select(url_select $select) { 01291 global $CFG; 01292 01293 $select = clone($select); 01294 if (empty($select->formid)) { 01295 $select->formid = html_writer::random_id('url_select_f'); 01296 } 01297 01298 if (empty($select->attributes['id'])) { 01299 $select->attributes['id'] = html_writer::random_id('url_select'); 01300 } 01301 01302 if ($select->disabled) { 01303 $select->attributes['disabled'] = 'disabled'; 01304 } 01305 01306 if ($select->tooltip) { 01307 $select->attributes['title'] = $select->tooltip; 01308 } 01309 01310 $output = ''; 01311 01312 if ($select->label) { 01313 $output .= html_writer::label($select->label, $select->attributes['id']); 01314 } 01315 01316 if ($select->helpicon instanceof help_icon) { 01317 $output .= $this->render($select->helpicon); 01318 } else if ($select->helpicon instanceof old_help_icon) { 01319 $output .= $this->render($select->helpicon); 01320 } 01321 01322 // For security reasons, the script course/jumpto.php requires URL starting with '/'. To keep 01323 // backward compatibility, we are removing heading $CFG->wwwroot from URLs here. 01324 $urls = array(); 01325 foreach ($select->urls as $k=>$v) { 01326 if (is_array($v)) { 01327 // optgroup structure 01328 foreach ($v as $optgrouptitle => $optgroupoptions) { 01329 foreach ($optgroupoptions as $optionurl => $optiontitle) { 01330 if (empty($optionurl)) { 01331 $safeoptionurl = ''; 01332 } else if (strpos($optionurl, $CFG->wwwroot.'/') === 0) { 01333 // debugging('URLs passed to url_select should be in local relative form - please fix the code.', DEBUG_DEVELOPER); 01334 $safeoptionurl = str_replace($CFG->wwwroot, '', $optionurl); 01335 } else if (strpos($optionurl, '/') !== 0) { 01336 debugging("Invalid url_select urls parameter inside optgroup: url '$optionurl' is not local relative url!"); 01337 continue; 01338 } else { 01339 $safeoptionurl = $optionurl; 01340 } 01341 $urls[$k][$optgrouptitle][$safeoptionurl] = $optiontitle; 01342 } 01343 } 01344 } else { 01345 // plain list structure 01346 if (empty($k)) { 01347 // nothing selected option 01348 } else if (strpos($k, $CFG->wwwroot.'/') === 0) { 01349 $k = str_replace($CFG->wwwroot, '', $k); 01350 } else if (strpos($k, '/') !== 0) { 01351 debugging("Invalid url_select urls parameter: url '$k' is not local relative url!"); 01352 continue; 01353 } 01354 $urls[$k] = $v; 01355 } 01356 } 01357 $selected = $select->selected; 01358 if (!empty($selected)) { 01359 if (strpos($select->selected, $CFG->wwwroot.'/') === 0) { 01360 $selected = str_replace($CFG->wwwroot, '', $selected); 01361 } else if (strpos($selected, '/') !== 0) { 01362 debugging("Invalid value of parameter 'selected': url '$selected' is not local relative url!"); 01363 } 01364 } 01365 01366 $output .= html_writer::empty_tag('input', array('type'=>'hidden', 'name'=>'sesskey', 'value'=>sesskey())); 01367 $output .= html_writer::select($urls, 'jump', $selected, $select->nothing, $select->attributes); 01368 01369 if (!$select->showbutton) { 01370 $go = html_writer::empty_tag('input', array('type'=>'submit', 'value'=>get_string('go'))); 01371 $output .= html_writer::tag('noscript', html_writer::tag('div', $go), array('style'=>'inline')); 01372 $nothing = empty($select->nothing) ? false : key($select->nothing); 01373 $output .= $this->page->requires->js_init_call('M.util.init_url_select', array($select->formid, $select->attributes['id'], $nothing)); 01374 } else { 01375 $output .= html_writer::empty_tag('input', array('type'=>'submit', 'value'=>$select->showbutton)); 01376 } 01377 01378 // then div wrapper for xhtml strictness 01379 $output = html_writer::tag('div', $output); 01380 01381 // now the form itself around it 01382 $formattributes = array('method' => 'post', 01383 'action' => new moodle_url('/course/jumpto.php'), 01384 'id' => $select->formid); 01385 $output = html_writer::tag('form', $output, $formattributes); 01386 01387 // and finally one more wrapper with class 01388 return html_writer::tag('div', $output, array('class' => $select->class)); 01389 } 01390 01398 public function doc_link($path, $text = '') { 01399 global $CFG; 01400 01401 $icon = $this->pix_icon('docs', $text, 'moodle', array('class'=>'iconhelp')); 01402 01403 $url = new moodle_url(get_docs_url($path)); 01404 01405 $attributes = array('href'=>$url); 01406 if (!empty($CFG->doctonewwindow)) { 01407 $attributes['id'] = $this->add_action_handler(new popup_action('click', $url)); 01408 } 01409 01410 return html_writer::tag('a', $icon.$text, $attributes); 01411 } 01412 01421 public function pix_icon($pix, $alt, $component='moodle', array $attributes = null) { 01422 $icon = new pix_icon($pix, $alt, $component, $attributes); 01423 return $this->render($icon); 01424 } 01425 01431 protected function render_pix_icon(pix_icon $icon) { 01432 $attributes = $icon->attributes; 01433 $attributes['src'] = $this->pix_url($icon->pix, $icon->component); 01434 return html_writer::empty_tag('img', $attributes); 01435 } 01436 01442 protected function render_pix_emoticon(pix_emoticon $emoticon) { 01443 $attributes = $emoticon->attributes; 01444 $attributes['src'] = $this->pix_url($emoticon->pix, $emoticon->component); 01445 return html_writer::empty_tag('img', $attributes); 01446 } 01447 01452 function render_rating(rating $rating) { 01453 global $CFG, $USER; 01454 01455 if ($rating->settings->aggregationmethod == RATING_AGGREGATE_NONE) { 01456 return null;//ratings are turned off 01457 } 01458 01459 $ratingmanager = new rating_manager(); 01460 // Initialise the JavaScript so ratings can be done by AJAX. 01461 $ratingmanager->initialise_rating_javascript($this->page); 01462 01463 $strrate = get_string("rate", "rating"); 01464 $ratinghtml = ''; //the string we'll return 01465 01466 // permissions check - can they view the aggregate? 01467 if ($rating->user_can_view_aggregate()) { 01468 01469 $aggregatelabel = $ratingmanager->get_aggregate_label($rating->settings->aggregationmethod); 01470 $aggregatestr = $rating->get_aggregate_string(); 01471 01472 $aggregatehtml = html_writer::tag('span', $aggregatestr, array('id' => 'ratingaggregate'.$rating->itemid, 'class' => 'ratingaggregate')).' '; 01473 if ($rating->count > 0) { 01474 $countstr = "({$rating->count})"; 01475 } else { 01476 $countstr = '-'; 01477 } 01478 $aggregatehtml .= html_writer::tag('span', $countstr, array('id'=>"ratingcount{$rating->itemid}", 'class' => 'ratingcount')).' '; 01479 01480 $ratinghtml .= html_writer::tag('span', $aggregatelabel, array('class'=>'rating-aggregate-label')); 01481 if ($rating->settings->permissions->viewall && $rating->settings->pluginpermissions->viewall) { 01482 01483 $nonpopuplink = $rating->get_view_ratings_url(); 01484 $popuplink = $rating->get_view_ratings_url(true); 01485 01486 $action = new popup_action('click', $popuplink, 'ratings', array('height' => 400, 'width' => 600)); 01487 $ratinghtml .= $this->action_link($nonpopuplink, $aggregatehtml, $action); 01488 } else { 01489 $ratinghtml .= $aggregatehtml; 01490 } 01491 } 01492 01493 $formstart = null; 01494 // if the item doesn't belong to the current user, the user has permission to rate 01495 // and we're within the assessable period 01496 if ($rating->user_can_rate()) { 01497 01498 $rateurl = $rating->get_rate_url(); 01499 $inputs = $rateurl->params(); 01500 01501 //start the rating form 01502 $formattrs = array( 01503 'id' => "postrating{$rating->itemid}", 01504 'class' => 'postratingform', 01505 'method' => 'post', 01506 'action' => $rateurl->out_omit_querystring() 01507 ); 01508 $formstart = html_writer::start_tag('form', $formattrs); 01509 $formstart .= html_writer::start_tag('div', array('class' => 'ratingform')); 01510 01511 // add the hidden inputs 01512 foreach ($inputs as $name => $value) { 01513 $attributes = array('type' => 'hidden', 'class' => 'ratinginput', 'name' => $name, 'value' => $value); 01514 $formstart .= html_writer::empty_tag('input', $attributes); 01515 } 01516 01517 if (empty($ratinghtml)) { 01518 $ratinghtml .= $strrate.': '; 01519 } 01520 $ratinghtml = $formstart.$ratinghtml; 01521 01522 $scalearray = array(RATING_UNSET_RATING => $strrate.'...') + $rating->settings->scale->scaleitems; 01523 $scaleattrs = array('class'=>'postratingmenu ratinginput','id'=>'menurating'.$rating->itemid); 01524 $ratinghtml .= html_writer::select($scalearray, 'rating', $rating->rating, false, $scaleattrs); 01525 01526 //output submit button 01527 $ratinghtml .= html_writer::start_tag('span', array('class'=>"ratingsubmit")); 01528 01529 $attributes = array('type' => 'submit', 'class' => 'postratingmenusubmit', 'id' => 'postratingsubmit'.$rating->itemid, 'value' => s(get_string('rate', 'rating'))); 01530 $ratinghtml .= html_writer::empty_tag('input', $attributes); 01531 01532 if (!$rating->settings->scale->isnumeric) { 01533 $ratinghtml .= $this->help_icon_scale($rating->settings->scale->courseid, $rating->settings->scale); 01534 } 01535 $ratinghtml .= html_writer::end_tag('span'); 01536 $ratinghtml .= html_writer::end_tag('div'); 01537 $ratinghtml .= html_writer::end_tag('form'); 01538 } 01539 01540 return $ratinghtml; 01541 } 01542 01543 /* 01544 * Centered heading with attached help button (same title text) 01545 * and optional icon attached 01546 * @param string $text A heading text 01547 * @param string $helpidentifier The keyword that defines a help page 01548 * @param string $component component name 01549 * @param string|moodle_url $icon 01550 * @param string $iconalt icon alt text 01551 * @return string HTML fragment 01552 */ 01553 public function heading_with_help($text, $helpidentifier, $component='moodle', $icon='', $iconalt='') { 01554 $image = ''; 01555 if ($icon) { 01556 $image = $this->pix_icon($icon, $iconalt, $component, array('class'=>'icon')); 01557 } 01558 01559 $help = ''; 01560 if ($helpidentifier) { 01561 $help = $this->help_icon($helpidentifier, $component); 01562 } 01563 01564 return $this->heading($image.$text.$help, 2, 'main help'); 01565 } 01566 01577 public function old_help_icon($helpidentifier, $title, $component = 'moodle', $linktext = '') { 01578 debugging('The method old_help_icon() is deprecated, please fix the code and use help_icon() method instead', DEBUG_DEVELOPER); 01579 $icon = new old_help_icon($helpidentifier, $title, $component); 01580 if ($linktext === true) { 01581 $icon->linktext = $title; 01582 } else if (!empty($linktext)) { 01583 $icon->linktext = $linktext; 01584 } 01585 return $this->render($icon); 01586 } 01587 01593 protected function render_old_help_icon(old_help_icon $helpicon) { 01594 global $CFG; 01595 01596 // first get the help image icon 01597 $src = $this->pix_url('help'); 01598 01599 if (empty($helpicon->linktext)) { 01600 $alt = $helpicon->title; 01601 } else { 01602 $alt = get_string('helpwiththis'); 01603 } 01604 01605 $attributes = array('src'=>$src, 'alt'=>$alt, 'class'=>'iconhelp'); 01606 $output = html_writer::empty_tag('img', $attributes); 01607 01608 // add the link text if given 01609 if (!empty($helpicon->linktext)) { 01610 // the spacing has to be done through CSS 01611 $output .= $helpicon->linktext; 01612 } 01613 01614 // now create the link around it - we need https on loginhttps pages 01615 $url = new moodle_url($CFG->httpswwwroot.'/help.php', array('component' => $helpicon->component, 'identifier' => $helpicon->helpidentifier, 'lang'=>current_language())); 01616 01617 // note: this title is displayed only if JS is disabled, otherwise the link will have the new ajax tooltip 01618 $title = get_string('helpprefix2', '', trim($helpicon->title, ". \t")); 01619 01620 $attributes = array('href'=>$url, 'title'=>$title); 01621 $id = html_writer::random_id('helpicon'); 01622 $attributes['id'] = $id; 01623 $output = html_writer::tag('a', $output, $attributes); 01624 01625 $this->page->requires->js_init_call('M.util.help_icon.add', array(array('id'=>$id, 'url'=>$url->out(false)))); 01626 01627 // and finally span 01628 return html_writer::tag('span', $output, array('class' => 'helplink')); 01629 } 01630 01639 public function help_icon($identifier, $component = 'moodle', $linktext = '') { 01640 $icon = new help_icon($identifier, $component); 01641 $icon->diag_strings(); 01642 if ($linktext === true) { 01643 $icon->linktext = get_string($icon->identifier, $icon->component); 01644 } else if (!empty($linktext)) { 01645 $icon->linktext = $linktext; 01646 } 01647 return $this->render($icon); 01648 } 01649 01655 protected function render_help_icon(help_icon $helpicon) { 01656 global $CFG; 01657 01658 // first get the help image icon 01659 $src = $this->pix_url('help'); 01660 01661 $title = get_string($helpicon->identifier, $helpicon->component); 01662 01663 if (empty($helpicon->linktext)) { 01664 $alt = get_string('helpprefix2', '', trim($title, ". \t")); 01665 } else { 01666 $alt = get_string('helpwiththis'); 01667 } 01668 01669 $attributes = array('src'=>$src, 'alt'=>$alt, 'class'=>'iconhelp'); 01670 $output = html_writer::empty_tag('img', $attributes); 01671 01672 // add the link text if given 01673 if (!empty($helpicon->linktext)) { 01674 // the spacing has to be done through CSS 01675 $output .= $helpicon->linktext; 01676 } 01677 01678 // now create the link around it - we need https on loginhttps pages 01679 $url = new moodle_url($CFG->httpswwwroot.'/help.php', array('component' => $helpicon->component, 'identifier' => $helpicon->identifier, 'lang'=>current_language())); 01680 01681 // note: this title is displayed only if JS is disabled, otherwise the link will have the new ajax tooltip 01682 $title = get_string('helpprefix2', '', trim($title, ". \t")); 01683 01684 $attributes = array('href'=>$url, 'title'=>$title); 01685 $id = html_writer::random_id('helpicon'); 01686 $attributes['id'] = $id; 01687 $output = html_writer::tag('a', $output, $attributes); 01688 01689 $this->page->requires->js_init_call('M.util.help_icon.add', array(array('id'=>$id, 'url'=>$url->out(false)))); 01690 01691 // and finally span 01692 return html_writer::tag('span', $output, array('class' => 'helplink')); 01693 } 01694 01702 public function help_icon_scale($courseid, stdClass $scale) { 01703 global $CFG; 01704 01705 $title = get_string('helpprefix2', '', $scale->name) .' ('.get_string('newwindow').')'; 01706 01707 $icon = $this->pix_icon('help', get_string('scales'), 'moodle', array('class'=>'iconhelp')); 01708 01709 $scaleid = abs($scale->id); 01710 01711 $link = new moodle_url('/course/scales.php', array('id' => $courseid, 'list' => true, 'scaleid' => $scaleid)); 01712 $action = new popup_action('click', $link, 'ratingscale'); 01713 01714 return html_writer::tag('span', $this->action_link($link, $icon, $action), array('class' => 'helplink')); 01715 } 01716 01724 public function spacer(array $attributes = null, $br = false) { 01725 $attributes = (array)$attributes; 01726 if (empty($attributes['width'])) { 01727 $attributes['width'] = 1; 01728 } 01729 if (empty($attributes['height'])) { 01730 $attributes['height'] = 1; 01731 } 01732 $attributes['class'] = 'spacer'; 01733 01734 $output = $this->pix_icon('spacer', '', 'moodle', $attributes); 01735 01736 if (!empty($br)) { 01737 $output .= '<br />'; 01738 } 01739 01740 return $output; 01741 } 01742 01772 public function user_picture(stdClass $user, array $options = null) { 01773 $userpicture = new user_picture($user); 01774 foreach ((array)$options as $key=>$value) { 01775 if (array_key_exists($key, $userpicture)) { 01776 $userpicture->$key = $value; 01777 } 01778 } 01779 return $this->render($userpicture); 01780 } 01781 01787 protected function render_user_picture(user_picture $userpicture) { 01788 global $CFG, $DB; 01789 01790 $user = $userpicture->user; 01791 01792 if ($userpicture->alttext) { 01793 if (!empty($user->imagealt)) { 01794 $alt = $user->imagealt; 01795 } else { 01796 $alt = get_string('pictureof', '', fullname($user)); 01797 } 01798 } else { 01799 $alt = ''; 01800 } 01801 01802 if (empty($userpicture->size)) { 01803 $size = 35; 01804 } else if ($userpicture->size === true or $userpicture->size == 1) { 01805 $size = 100; 01806 } else { 01807 $size = $userpicture->size; 01808 } 01809 01810 $class = $userpicture->class; 01811 01812 if ($user->picture != 1 && $user->picture != 2) { 01813 $class .= ' defaultuserpic'; 01814 } 01815 01816 $src = $userpicture->get_url($this->page, $this); 01817 01818 $attributes = array('src'=>$src, 'alt'=>$alt, 'title'=>$alt, 'class'=>$class, 'width'=>$size, 'height'=>$size); 01819 01820 // get the image html output fisrt 01821 $output = html_writer::empty_tag('img', $attributes);; 01822 01823 // then wrap it in link if needed 01824 if (!$userpicture->link) { 01825 return $output; 01826 } 01827 01828 if (empty($userpicture->courseid)) { 01829 $courseid = $this->page->course->id; 01830 } else { 01831 $courseid = $userpicture->courseid; 01832 } 01833 01834 if ($courseid == SITEID) { 01835 $url = new moodle_url('/user/profile.php', array('id' => $user->id)); 01836 } else { 01837 $url = new moodle_url('/user/view.php', array('id' => $user->id, 'course' => $courseid)); 01838 } 01839 01840 $attributes = array('href'=>$url); 01841 01842 if ($userpicture->popup) { 01843 $id = html_writer::random_id('userpicture'); 01844 $attributes['id'] = $id; 01845 $this->add_action_handler(new popup_action('click', $url), $id); 01846 } 01847 01848 return html_writer::tag('a', $output, $attributes); 01849 } 01850 01856 public function htmllize_file_tree($dir) { 01857 if (empty($dir['subdirs']) and empty($dir['files'])) { 01858 return ''; 01859 } 01860 $result = '<ul>'; 01861 foreach ($dir['subdirs'] as $subdir) { 01862 $result .= '<li>'.s($subdir['dirname']).' '.$this->htmllize_file_tree($subdir).'</li>'; 01863 } 01864 foreach ($dir['files'] as $file) { 01865 $filename = $file->get_filename(); 01866 $result .= '<li><span>'.html_writer::link($file->fileurl, $filename).'</span></li>'; 01867 } 01868 $result .= '</ul>'; 01869 01870 return $result; 01871 } 01889 public function file_picker($options) { 01890 $fp = new file_picker($options); 01891 return $this->render($fp); 01892 } 01898 public function render_file_picker(file_picker $fp) { 01899 global $CFG, $OUTPUT, $USER; 01900 $options = $fp->options; 01901 $client_id = $options->client_id; 01902 $strsaved = get_string('filesaved', 'repository'); 01903 $straddfile = get_string('openpicker', 'repository'); 01904 $strloading = get_string('loading', 'repository'); 01905 $icon_progress = $OUTPUT->pix_icon('i/loading_small', $strloading).''; 01906 01907 $currentfile = $options->currentfile; 01908 if (empty($currentfile)) { 01909 $currentfile = get_string('nofilesattached', 'repository'); 01910 } 01911 if ($options->maxbytes) { 01912 $size = $options->maxbytes; 01913 } else { 01914 $size = get_max_upload_file_size(); 01915 } 01916 if ($size == -1) { 01917 $maxsize = ''; 01918 } else { 01919 $maxsize = get_string('maxfilesize', 'moodle', display_size($size)); 01920 } 01921 if ($options->buttonname) { 01922 $buttonname = ' name="' . $options->buttonname . '"'; 01923 } else { 01924 $buttonname = ''; 01925 } 01926 $html = <<<EOD 01927 <div class="filemanager-loading mdl-align" id='filepicker-loading-{$client_id}'> 01928 $icon_progress 01929 </div> 01930 <div id="filepicker-wrapper-{$client_id}" class="mdl-left" style="display:none"> 01931 <div> 01932 <input type="button" id="filepicker-button-{$client_id}" value="{$straddfile}"{$buttonname}/> 01933 <span> $maxsize </span> 01934 </div> 01935 EOD; 01936 if ($options->env != 'url') { 01937 $html .= <<<EOD 01938 <div id="file_info_{$client_id}" class="mdl-left filepicker-filelist">$currentfile</div> 01939 EOD; 01940 } 01941 $html .= '</div>'; 01942 return $html; 01943 } 01944 01952 public function update_module_button($cmid, $modulename) { 01953 global $CFG; 01954 if (has_capability('moodle/course:manageactivities', get_context_instance(CONTEXT_MODULE, $cmid))) { 01955 $modulename = get_string('modulename', $modulename); 01956 $string = get_string('updatethis', '', $modulename); 01957 $url = new moodle_url("$CFG->wwwroot/course/mod.php", array('update' => $cmid, 'return' => true, 'sesskey' => sesskey())); 01958 return $this->single_button($url, $string); 01959 } else { 01960 return ''; 01961 } 01962 } 01963 01969 public function edit_button(moodle_url $url) { 01970 01971 $url->param('sesskey', sesskey()); 01972 if ($this->page->user_is_editing()) { 01973 $url->param('edit', 'off'); 01974 $editstring = get_string('turneditingoff'); 01975 } else { 01976 $url->param('edit', 'on'); 01977 $editstring = get_string('turneditingon'); 01978 } 01979 01980 return $this->single_button($url, $editstring); 01981 } 01982 01989 public function close_window_button($text='') { 01990 if (empty($text)) { 01991 $text = get_string('closewindow'); 01992 } 01993 $button = new single_button(new moodle_url('#'), $text, 'get'); 01994 $button->add_action(new component_action('click', 'close_window')); 01995 01996 return $this->container($this->render($button), 'closewindow'); 01997 } 01998 02005 public function error_text($message) { 02006 if (empty($message)) { 02007 return ''; 02008 } 02009 return html_writer::tag('span', $message, array('class' => 'error')); 02010 } 02011 02026 public function fatal_error($message, $moreinfourl, $link, $backtrace, $debuginfo = null) { 02027 global $CFG; 02028 02029 $output = ''; 02030 $obbuffer = ''; 02031 02032 if ($this->has_started()) { 02033 // we can not always recover properly here, we have problems with output buffering, 02034 // html tables, etc. 02035 $output .= $this->opencontainers->pop_all_but_last(); 02036 02037 } else { 02038 // It is really bad if library code throws exception when output buffering is on, 02039 // because the buffered text would be printed before our start of page. 02040 // NOTE: this hack might be behave unexpectedly in case output buffering is enabled in PHP.ini 02041 error_reporting(0); // disable notices from gzip compression, etc. 02042 while (ob_get_level() > 0) { 02043 $buff = ob_get_clean(); 02044 if ($buff === false) { 02045 break; 02046 } 02047 $obbuffer .= $buff; 02048 } 02049 error_reporting($CFG->debug); 02050 02051 // Header not yet printed 02052 if (isset($_SERVER['SERVER_PROTOCOL'])) { 02053 // server protocol should be always present, because this render 02054 // can not be used from command line or when outputting custom XML 02055 @header($_SERVER['SERVER_PROTOCOL'] . ' 404 Not Found'); 02056 } 02057 $this->page->set_context(null); // ugly hack - make sure page context is set to something, we do not want bogus warnings here 02058 $this->page->set_url('/'); // no url 02059 //$this->page->set_pagelayout('base'); //TODO: MDL-20676 blocks on error pages are weird, unfortunately it somehow detect the pagelayout from URL :-( 02060 $this->page->set_title(get_string('error')); 02061 $this->page->set_heading($this->page->course->fullname); 02062 $output .= $this->header(); 02063 } 02064 02065 $message = '<p class="errormessage">' . $message . '</p>'. 02066 '<p class="errorcode"><a href="' . $moreinfourl . '">' . 02067 get_string('moreinformation') . '</a></p>'; 02068 if (empty($CFG->rolesactive)) { 02069 $message .= '<p class="errormessage">' . get_string('installproblem', 'error') . '</p>'; 02070 //It is usually not possible to recover from errors triggered during installation, you may need to create a new database or use a different database prefix for new installation. 02071 } 02072 $output .= $this->box($message, 'errorbox'); 02073 02074 if (debugging('', DEBUG_DEVELOPER)) { 02075 if (!empty($debuginfo)) { 02076 $debuginfo = s($debuginfo); // removes all nasty JS 02077 $debuginfo = str_replace("\n", '<br />', $debuginfo); // keep newlines 02078 $output .= $this->notification('<strong>Debug info:</strong> '.$debuginfo, 'notifytiny'); 02079 } 02080 if (!empty($backtrace)) { 02081 $output .= $this->notification('<strong>Stack trace:</strong> '.format_backtrace($backtrace), 'notifytiny'); 02082 } 02083 if ($obbuffer !== '' ) { 02084 $output .= $this->notification('<strong>Output buffer:</strong> '.s($obbuffer), 'notifytiny'); 02085 } 02086 } 02087 02088 if (empty($CFG->rolesactive)) { 02089 // continue does not make much sense if moodle is not installed yet because error is most probably not recoverable 02090 } else if (!empty($link)) { 02091 $output .= $this->continue_button($link); 02092 } 02093 02094 $output .= $this->footer(); 02095 02096 // Padding to encourage IE to display our error page, rather than its own. 02097 $output .= str_repeat(' ', 512); 02098 02099 return $output; 02100 } 02101 02110 public function notification($message, $classes = 'notifyproblem') { 02111 return html_writer::tag('div', clean_text($message), array('class' => renderer_base::prepare_classes($classes))); 02112 } 02113 02120 public function continue_button($url) { 02121 if (!($url instanceof moodle_url)) { 02122 $url = new moodle_url($url); 02123 } 02124 $button = new single_button($url, get_string('continue'), 'get'); 02125 $button->class = 'continuebutton'; 02126 02127 return $this->render($button); 02128 } 02129 02140 public function paging_bar($totalcount, $page, $perpage, $baseurl, $pagevar = 'page') { 02141 $pb = new paging_bar($totalcount, $page, $perpage, $baseurl, $pagevar); 02142 return $this->render($pb); 02143 } 02144 02150 protected function render_paging_bar(paging_bar $pagingbar) { 02151 $output = ''; 02152 $pagingbar = clone($pagingbar); 02153 $pagingbar->prepare($this, $this->page, $this->target); 02154 02155 if ($pagingbar->totalcount > $pagingbar->perpage) { 02156 $output .= get_string('page') . ':'; 02157 02158 if (!empty($pagingbar->previouslink)) { 02159 $output .= ' (' . $pagingbar->previouslink . ') '; 02160 } 02161 02162 if (!empty($pagingbar->firstlink)) { 02163 $output .= ' ' . $pagingbar->firstlink . ' ...'; 02164 } 02165 02166 foreach ($pagingbar->pagelinks as $link) { 02167 $output .= "  $link"; 02168 } 02169 02170 if (!empty($pagingbar->lastlink)) { 02171 $output .= ' ...' . $pagingbar->lastlink . ' '; 02172 } 02173 02174 if (!empty($pagingbar->nextlink)) { 02175 $output .= '  (' . $pagingbar->nextlink . ')'; 02176 } 02177 } 02178 02179 return html_writer::tag('div', $output, array('class' => 'paging')); 02180 } 02181 02187 public function skip_link_target($id = null) { 02188 return html_writer::tag('span', '', array('id' => $id)); 02189 } 02190 02199 public function heading($text, $level = 2, $classes = 'main', $id = null) { 02200 $level = (integer) $level; 02201 if ($level < 1 or $level > 6) { 02202 throw new coding_exception('Heading level must be an integer between 1 and 6.'); 02203 } 02204 return html_writer::tag('h' . $level, $text, array('id' => $id, 'class' => renderer_base::prepare_classes($classes))); 02205 } 02206 02214 public function box($contents, $classes = 'generalbox', $id = null) { 02215 return $this->box_start($classes, $id) . $contents . $this->box_end(); 02216 } 02217 02224 public function box_start($classes = 'generalbox', $id = null) { 02225 $this->opencontainers->push('box', html_writer::end_tag('div')); 02226 return html_writer::start_tag('div', array('id' => $id, 02227 'class' => 'box ' . renderer_base::prepare_classes($classes))); 02228 } 02229 02234 public function box_end() { 02235 return $this->opencontainers->pop('box'); 02236 } 02237 02245 public function container($contents, $classes = null, $id = null) { 02246 return $this->container_start($classes, $id) . $contents . $this->container_end(); 02247 } 02248 02255 public function container_start($classes = null, $id = null) { 02256 $this->opencontainers->push('container', html_writer::end_tag('div')); 02257 return html_writer::start_tag('div', array('id' => $id, 02258 'class' => renderer_base::prepare_classes($classes))); 02259 } 02260 02265 public function container_end() { 02266 return $this->opencontainers->pop('container'); 02267 } 02268 02289 function tree_block_contents($items, $attrs=array()) { 02290 // exit if empty, we don't want an empty ul element 02291 if (empty($items)) { 02292 return ''; 02293 } 02294 // array of nested li elements 02295 $lis = array(); 02296 foreach ($items as $item) { 02297 // this applies to the li item which contains all child lists too 02298 $content = $item->content($this); 02299 $liclasses = array($item->get_css_type()); 02300 if (!$item->forceopen || (!$item->forceopen && $item->collapse) || ($item->children->count()==0 && $item->nodetype==navigation_node::NODETYPE_BRANCH)) { 02301 $liclasses[] = 'collapsed'; 02302 } 02303 if ($item->isactive === true) { 02304 $liclasses[] = 'current_branch'; 02305 } 02306 $liattr = array('class'=>join(' ',$liclasses)); 02307 // class attribute on the div item which only contains the item content 02308 $divclasses = array('tree_item'); 02309 if ($item->children->count()>0 || $item->nodetype==navigation_node::NODETYPE_BRANCH) { 02310 $divclasses[] = 'branch'; 02311 } else { 02312 $divclasses[] = 'leaf'; 02313 } 02314 if (!empty($item->classes) && count($item->classes)>0) { 02315 $divclasses[] = join(' ', $item->classes); 02316 } 02317 $divattr = array('class'=>join(' ', $divclasses)); 02318 if (!empty($item->id)) { 02319 $divattr['id'] = $item->id; 02320 } 02321 $content = html_writer::tag('p', $content, $divattr) . $this->tree_block_contents($item->children); 02322 if (!empty($item->preceedwithhr) && $item->preceedwithhr===true) { 02323 $content = html_writer::empty_tag('hr') . $content; 02324 } 02325 $content = html_writer::tag('li', $content, $liattr); 02326 $lis[] = $content; 02327 } 02328 return html_writer::tag('ul', implode("\n", $lis), $attrs); 02329 } 02330 02335 public function navbar() { 02336 $items = $this->page->navbar->get_items(); 02337 02338 $htmlblocks = array(); 02339 // Iterate the navarray and display each node 02340 $itemcount = count($items); 02341 $separator = get_separator(); 02342 for ($i=0;$i < $itemcount;$i++) { 02343 $item = $items[$i]; 02344 $item->hideicon = true; 02345 if ($i===0) { 02346 $content = html_writer::tag('li', $this->render($item)); 02347 } else { 02348 $content = html_writer::tag('li', $separator.$this->render($item)); 02349 } 02350 $htmlblocks[] = $content; 02351 } 02352 02353 //accessibility: heading for navbar list (MDL-20446) 02354 $navbarcontent = html_writer::tag('span', get_string('pagepath'), array('class'=>'accesshide')); 02355 $navbarcontent .= html_writer::tag('ul', join('', $htmlblocks)); 02356 // XHTML 02357 return $navbarcontent; 02358 } 02359 02360 protected function render_navigation_node(navigation_node $item) { 02361 $content = $item->get_content(); 02362 $title = $item->get_title(); 02363 if ($item->icon instanceof renderable && !$item->hideicon) { 02364 $icon = $this->render($item->icon); 02365 $content = $icon.$content; // use CSS for spacing of icons 02366 } 02367 if ($item->helpbutton !== null) { 02368 $content = trim($item->helpbutton).html_writer::tag('span', $content, array('class'=>'clearhelpbutton', 'tabindex'=>'0')); 02369 } 02370 if ($content === '') { 02371 return ''; 02372 } 02373 if ($item->action instanceof action_link) { 02374 $link = $item->action; 02375 if ($item->hidden) { 02376 $link->add_class('dimmed'); 02377 } 02378 $link->text = $content.$link->text; // add help icon 02379 $content = $this->render($link); 02380 } else if ($item->action instanceof moodle_url) { 02381 $attributes = array(); 02382 if ($title !== '') { 02383 $attributes['title'] = $title; 02384 } 02385 if ($item->hidden) { 02386 $attributes['class'] = 'dimmed_text'; 02387 } 02388 $content = html_writer::link($item->action, $content, $attributes); 02389 02390 } else if (is_string($item->action) || empty($item->action)) { 02391 $attributes = array('tabindex'=>'0'); //add tab support to span but still maintain character stream sequence. 02392 if ($title !== '') { 02393 $attributes['title'] = $title; 02394 } 02395 if ($item->hidden) { 02396 $attributes['class'] = 'dimmed_text'; 02397 } 02398 $content = html_writer::tag('span', $content, $attributes); 02399 } 02400 return $content; 02401 } 02402 02412 public function rarrow() { 02413 return $this->page->theme->rarrow; 02414 } 02415 02425 public function larrow() { 02426 return $this->page->theme->larrow; 02427 } 02428 02438 public function custom_menu() { 02439 global $CFG; 02440 if (empty($CFG->custommenuitems)) { 02441 return ''; 02442 } 02443 $custommenu = new custom_menu($CFG->custommenuitems, current_language()); 02444 return $this->render_custom_menu($custommenu); 02445 } 02446 02457 protected function render_custom_menu(custom_menu $menu) { 02458 static $menucount = 0; 02459 // If the menu has no children return an empty string 02460 if (!$menu->has_children()) { 02461 return ''; 02462 } 02463 // Increment the menu count. This is used for ID's that get worked with 02464 // in JavaScript as is essential 02465 $menucount++; 02466 // Initialise this custom menu (the custom menu object is contained in javascript-static 02467 $jscode = js_writer::function_call_with_Y('M.core_custom_menu.init', array('custom_menu_'.$menucount)); 02468 $jscode = "(function(){{$jscode}})"; 02469 $this->page->requires->yui_module('node-menunav', $jscode); 02470 // Build the root nodes as required by YUI 02471 $content = html_writer::start_tag('div', array('id'=>'custom_menu_'.$menucount, 'class'=>'yui3-menu yui3-menu-horizontal javascript-disabled')); 02472 $content .= html_writer::start_tag('div', array('class'=>'yui3-menu-content')); 02473 $content .= html_writer::start_tag('ul'); 02474 // Render each child 02475 foreach ($menu->get_children() as $item) { 02476 $content .= $this->render_custom_menu_item($item); 02477 } 02478 // Close the open tags 02479 $content .= html_writer::end_tag('ul'); 02480 $content .= html_writer::end_tag('div'); 02481 $content .= html_writer::end_tag('div'); 02482 // Return the custom menu 02483 return $content; 02484 } 02485 02498 protected function render_custom_menu_item(custom_menu_item $menunode) { 02499 // Required to ensure we get unique trackable id's 02500 static $submenucount = 0; 02501 if ($menunode->has_children()) { 02502 // If the child has menus render it as a sub menu 02503 $submenucount++; 02504 $content = html_writer::start_tag('li'); 02505 if ($menunode->get_url() !== null) { 02506 $url = $menunode->get_url(); 02507 } else { 02508 $url = '#cm_submenu_'.$submenucount; 02509 } 02510 $content .= html_writer::link($url, $menunode->get_text(), array('class'=>'yui3-menu-label', 'title'=>$menunode->get_title())); 02511 $content .= html_writer::start_tag('div', array('id'=>'cm_submenu_'.$submenucount, 'class'=>'yui3-menu custom_menu_submenu')); 02512 $content .= html_writer::start_tag('div', array('class'=>'yui3-menu-content')); 02513 $content .= html_writer::start_tag('ul'); 02514 foreach ($menunode->get_children() as $menunode) { 02515 $content .= $this->render_custom_menu_item($menunode); 02516 } 02517 $content .= html_writer::end_tag('ul'); 02518 $content .= html_writer::end_tag('div'); 02519 $content .= html_writer::end_tag('div'); 02520 $content .= html_writer::end_tag('li'); 02521 } else { 02522 // The node doesn't have children so produce a final menuitem 02523 $content = html_writer::start_tag('li', array('class'=>'yui3-menuitem')); 02524 if ($menunode->get_url() !== null) { 02525 $url = $menunode->get_url(); 02526 } else { 02527 $url = '#'; 02528 } 02529 $content .= html_writer::link($url, $menunode->get_text(), array('class'=>'yui3-menuitem-content', 'title'=>$menunode->get_title())); 02530 $content .= html_writer::end_tag('li'); 02531 } 02532 // Return the sub menu 02533 return $content; 02534 } 02535 02541 protected function theme_switch_links() { 02542 02543 $actualdevice = get_device_type(); 02544 $currentdevice = $this->page->devicetypeinuse; 02545 $switched = ($actualdevice != $currentdevice); 02546 02547 if (!$switched && $currentdevice == 'default' && $actualdevice == 'default') { 02548 // The user is using the a default device and hasn't switched so don't shown the switch 02549 // device links. 02550 return ''; 02551 } 02552 02553 if ($switched) { 02554 $linktext = get_string('switchdevicerecommended'); 02555 $devicetype = $actualdevice; 02556 } else { 02557 $linktext = get_string('switchdevicedefault'); 02558 $devicetype = 'default'; 02559 } 02560 $linkurl = new moodle_url('/theme/switchdevice.php', array('url' => $this->page->url, 'device' => $devicetype, 'sesskey' => sesskey())); 02561 02562 $content = html_writer::start_tag('div', array('id' => 'theme_switch_link')); 02563 $content .= html_writer::link($linkurl, $linktext); 02564 $content .= html_writer::end_tag('div'); 02565 02566 return $content; 02567 } 02568 } 02569 02571 02581 class core_renderer_cli extends core_renderer { 02586 public function header() { 02587 return $this->page->heading . "\n"; 02588 } 02589 02598 public function heading($text, $level = 2, $classes = 'main', $id = null) { 02599 $text .= "\n"; 02600 switch ($level) { 02601 case 1: 02602 return '=>' . $text; 02603 case 2: 02604 return '-->' . $text; 02605 default: 02606 return $text; 02607 } 02608 } 02609 02619 public function fatal_error($message, $moreinfourl, $link, $backtrace, $debuginfo = null) { 02620 $output = "!!! $message !!!\n"; 02621 02622 if (debugging('', DEBUG_DEVELOPER)) { 02623 if (!empty($debuginfo)) { 02624 $output .= $this->notification($debuginfo, 'notifytiny'); 02625 } 02626 if (!empty($backtrace)) { 02627 $output .= $this->notification('Stack trace: ' . format_backtrace($backtrace, true), 'notifytiny'); 02628 } 02629 } 02630 02631 return $output; 02632 } 02633 02640 public function notification($message, $classes = 'notifyproblem') { 02641 $message = clean_text($message); 02642 if ($classes === 'notifysuccess') { 02643 return "++ $message ++\n"; 02644 } 02645 return "!! $message !!\n"; 02646 } 02647 } 02648 02649 02660 class core_renderer_ajax extends core_renderer { 02670 public function fatal_error($message, $moreinfourl, $link, $backtrace, $debuginfo = null) { 02671 global $CFG; 02672 02673 $this->page->set_context(null); // ugly hack - make sure page context is set to something, we do not want bogus warnings here 02674 02675 $e = new stdClass(); 02676 $e->error = $message; 02677 $e->stacktrace = NULL; 02678 $e->debuginfo = NULL; 02679 $e->reproductionlink = NULL; 02680 if (!empty($CFG->debug) and $CFG->debug >= DEBUG_DEVELOPER) { 02681 $e->reproductionlink = $link; 02682 if (!empty($debuginfo)) { 02683 $e->debuginfo = $debuginfo; 02684 } 02685 if (!empty($backtrace)) { 02686 $e->stacktrace = format_backtrace($backtrace, true); 02687 } 02688 } 02689 $this->header(); 02690 return json_encode($e); 02691 } 02692 02693 public function notification($message, $classes = 'notifyproblem') { 02694 } 02695 02696 public function redirect_message($encodedurl, $message, $delay, $debugdisableredirect) { 02697 } 02698 02699 public function header() { 02700 // unfortunately YUI iframe upload does not support application/json 02701 if (!empty($_FILES)) { 02702 @header('Content-type: text/plain; charset=utf-8'); 02703 } else { 02704 @header('Content-type: application/json; charset=utf-8'); 02705 } 02706 02708 @header('Cache-Control: no-store, no-cache, must-revalidate'); 02709 @header('Cache-Control: post-check=0, pre-check=0', false); 02710 @header('Pragma: no-cache'); 02711 @header('Expires: Mon, 20 Aug 1969 09:23:00 GMT'); 02712 @header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT'); 02713 @header('Accept-Ranges: none'); 02714 } 02715 02716 public function footer() { 02717 } 02718 02719 public function heading($text, $level = 2, $classes = 'main', $id = null) { 02720 } 02721 } 02722