See Release Notes
Long Term Support Release
Differences Between: [Versions 310 and 401] [Versions 311 and 401] [Versions 39 and 401] [Versions 400 and 401] [Versions 401 and 402] [Versions 401 and 403]
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 17 /** 18 * This file contains backup and restore output renderers 19 * 20 * @package core_backup 21 * @copyright 2010 Sam Hemelryk 22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 */ 24 25 defined('MOODLE_INTERNAL') || die; 26 27 global $CFG; 28 require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php'); 29 require_once($CFG->dirroot . '/backup/util/includes/restore_includes.php'); 30 require_once($CFG->dirroot . '/backup/moodle2/backup_plan_builder.class.php'); 31 32 /** 33 * The primary renderer for the backup. 34 * 35 * Can be retrieved with the following code: 36 * <?php 37 * $renderer = $PAGE->get_renderer('core', 'backup'); 38 * ?> 39 * 40 * @package core_backup 41 * @copyright 2010 Sam Hemelryk 42 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 43 */ 44 class core_backup_renderer extends plugin_renderer_base { 45 46 /** 47 * Same site notification display. 48 * 49 * @var string 50 */ 51 private $samesitenotification = ''; 52 53 /** 54 * Renderers a progress bar for the backup or restore given the items that make it up. 55 * 56 * @param array $items An array of items 57 * @return string 58 */ 59 public function progress_bar(array $items) { 60 foreach ($items as &$item) { 61 $text = $item['text']; 62 unset($item['text']); 63 if (array_key_exists('link', $item)) { 64 $link = $item['link']; 65 unset($item['link']); 66 $item = html_writer::link($link, $text, $item); 67 } else { 68 $item = html_writer::tag('span', $text, $item); 69 } 70 } 71 return html_writer::tag('div', join(get_separator(), $items), array('class' => 'backup_progress clearfix')); 72 } 73 74 /** 75 * The backup and restore pages may display a log (if any) in a scrolling box. 76 * 77 * @param string $loghtml Log content in HTML format 78 * @return string HTML content that shows the log 79 */ 80 public function log_display($loghtml) { 81 $out = html_writer::start_div('backup_log'); 82 $out .= $this->output->heading(get_string('backuplog', 'backup')); 83 $out .= html_writer::start_div('backup_log_contents'); 84 $out .= $loghtml; 85 $out .= html_writer::end_div(); 86 $out .= html_writer::end_div(); 87 return $out; 88 } 89 90 /** 91 * Set the same site backup notification. 92 * 93 */ 94 public function set_samesite_notification() { 95 $this->samesitenotification = $this->output->notification(get_string('samesitenotification', 'backup'), 'info'); 96 } 97 98 /** 99 * Get the same site backup notification. 100 * 101 */ 102 public function get_samesite_notification() { 103 return $this->samesitenotification; 104 } 105 106 /** 107 * Prints a dependency notification 108 * 109 * @param string $message 110 * @return string 111 */ 112 public function dependency_notification($message) { 113 return html_writer::tag('div', $message, array('class' => 'notification dependencies_enforced')); 114 } 115 116 /** 117 * Displays the details of a backup file 118 * 119 * @param stdClass $details 120 * @param moodle_url $nextstageurl 121 * @return string 122 */ 123 public function backup_details($details, $nextstageurl) { 124 $yestick = $this->output->pix_icon('i/valid', get_string('yes')); 125 $notick = $this->output->pix_icon('i/invalid', get_string('no')); 126 127 $html = html_writer::start_tag('div', array('class' => 'backup-restore')); 128 129 $html .= html_writer::start_tag('div', ['class' => 'backup-section', 130 'role' => 'table', 'aria-labelledby' => 'backupdetailsheader']); 131 $html .= $this->output->heading(get_string('backupdetails', 'backup'), 2, 'header', 'backupdetailsheader'); 132 $html .= $this->backup_detail_pair(get_string('backuptype', 'backup'), get_string('backuptype'.$details->type, 'backup')); 133 $html .= $this->backup_detail_pair(get_string('backupformat', 'backup'), get_string('backupformat'.$details->format, 'backup')); 134 $html .= $this->backup_detail_pair(get_string('backupmode', 'backup'), get_string('backupmode'.$details->mode, 'backup')); 135 $html .= $this->backup_detail_pair(get_string('backupdate', 'backup'), userdate($details->backup_date)); 136 $html .= $this->backup_detail_pair(get_string('moodleversion', 'backup'), 137 html_writer::tag('span', $details->moodle_release, array('class' => 'moodle_release')). 138 html_writer::tag('span', '['.$details->moodle_version.']', array('class' => 'moodle_version sub-detail'))); 139 $html .= $this->backup_detail_pair(get_string('backupversion', 'backup'), 140 html_writer::tag('span', $details->backup_release, array('class' => 'moodle_release')). 141 html_writer::tag('span', '['.$details->backup_version.']', array('class' => 'moodle_version sub-detail'))); 142 $html .= $this->backup_detail_pair(get_string('originalwwwroot', 'backup'), 143 html_writer::tag('span', $details->original_wwwroot, array('class' => 'originalwwwroot')). 144 html_writer::tag('span', '['.$details->original_site_identifier_hash.']', array('class' => 'sitehash sub-detail'))); 145 if (!empty($details->include_file_references_to_external_content)) { 146 $message = ''; 147 if (backup_general_helper::backup_is_samesite($details)) { 148 $message = $yestick . ' ' . get_string('filereferencessamesite', 'backup'); 149 } else { 150 $message = $notick . ' ' . get_string('filereferencesnotsamesite', 'backup'); 151 } 152 $html .= $this->backup_detail_pair(get_string('includefilereferences', 'backup'), $message); 153 } 154 155 $html .= html_writer::end_tag('div'); 156 157 $html .= html_writer::start_tag('div', ['class' => 'backup-section settings-section', 158 'role' => 'table', 'aria-labelledby' => 'backupsettingsheader']); 159 $html .= $this->output->heading(get_string('backupsettings', 'backup'), 2, 'header', 'backupsettingsheader'); 160 foreach ($details->root_settings as $label => $value) { 161 if ($label == 'filename' or $label == 'user_files') { 162 continue; 163 } 164 $html .= $this->backup_detail_pair(get_string('rootsetting'.str_replace('_', '', $label), 'backup'), $value ? $yestick : $notick); 165 } 166 $html .= html_writer::end_tag('div'); 167 168 if ($details->type === 'course') { 169 $html .= html_writer::start_tag('div', ['class' => 'backup-section', 170 'role' => 'table', 'aria-labelledby' => 'backupcoursedetailsheader']); 171 $html .= $this->output->heading(get_string('backupcoursedetails', 'backup'), 2, 'header', 'backupcoursedetailsheader'); 172 $html .= $this->backup_detail_pair(get_string('coursetitle', 'backup'), $details->course->title); 173 $html .= $this->backup_detail_pair(get_string('courseid', 'backup'), $details->course->courseid); 174 175 // Warning users about front page backups. 176 if ($details->original_course_format === 'site') { 177 $html .= $this->backup_detail_pair(get_string('type_format', 'plugin'), get_string('sitecourseformatwarning', 'backup')); 178 } 179 $html .= html_writer::start_tag('div', array('class' => 'backup-sub-section')); 180 $html .= $this->output->heading(get_string('backupcoursesections', 'backup'), 3, array('class' => 'subheader')); 181 foreach ($details->sections as $key => $section) { 182 $included = $key.'_included'; 183 $userinfo = $key.'_userinfo'; 184 if ($section->settings[$included] && $section->settings[$userinfo]) { 185 $value = get_string('sectionincanduser', 'backup'); 186 } else if ($section->settings[$included]) { 187 $value = get_string('sectioninc', 'backup'); 188 } else { 189 continue; 190 } 191 $html .= $this->backup_detail_pair(get_string('backupcoursesection', 'backup', $section->title), $value); 192 $table = null; 193 foreach ($details->activities as $activitykey => $activity) { 194 if ($activity->sectionid != $section->sectionid) { 195 continue; 196 } 197 if (empty($table)) { 198 $table = new html_table(); 199 $table->head = array(get_string('module', 'backup'), get_string('title', 'backup'), get_string('userinfo', 'backup')); 200 $table->colclasses = array('modulename', 'moduletitle', 'userinfoincluded'); 201 $table->align = array('left', 'left', 'center'); 202 $table->attributes = array('class' => 'activitytable generaltable'); 203 $table->data = array(); 204 } 205 $name = get_string('pluginname', $activity->modulename); 206 $icon = new image_icon('monologo', '', $activity->modulename, ['class' => 'iconlarge icon-pre']); 207 $table->data[] = array( 208 $this->output->render($icon).$name, 209 $activity->title, 210 ($activity->settings[$activitykey.'_userinfo']) ? $yestick : $notick, 211 ); 212 } 213 if (!empty($table)) { 214 $html .= $this->backup_detail_pair(get_string('sectionactivities', 'backup'), html_writer::table($table)); 215 } 216 217 } 218 $html .= html_writer::end_tag('div'); 219 $html .= html_writer::end_tag('div'); 220 } 221 222 $html .= $this->continue_button($nextstageurl, 'post'); 223 $html .= html_writer::end_tag('div'); 224 225 return $html; 226 } 227 228 /** 229 * Displays the general information about a backup file with non-standard format 230 * 231 * @param moodle_url $nextstageurl URL to send user to 232 * @param array $details basic info about the file (format, type) 233 * @return string HTML code to display 234 */ 235 public function backup_details_nonstandard($nextstageurl, array $details) { 236 237 $html = html_writer::start_tag('div', array('class' => 'backup-restore nonstandardformat')); 238 $html .= html_writer::start_tag('div', array('class' => 'backup-section')); 239 $html .= $this->output->heading(get_string('backupdetails', 'backup'), 2, 'header'); 240 $html .= $this->output->box(get_string('backupdetailsnonstandardinfo', 'backup'), 'noticebox'); 241 $html .= $this->backup_detail_pair( 242 get_string('backupformat', 'backup'), 243 get_string('backupformat'.$details['format'], 'backup')); 244 $html .= $this->backup_detail_pair( 245 get_string('backuptype', 'backup'), 246 get_string('backuptype'.$details['type'], 'backup')); 247 $html .= html_writer::end_tag('div'); 248 $html .= $this->continue_button($nextstageurl, 'post'); 249 $html .= html_writer::end_tag('div'); 250 251 return $html; 252 } 253 254 /** 255 * Displays the general information about a backup file with unknown format 256 * 257 * @param moodle_url $nextstageurl URL to send user to 258 * @return string HTML code to display 259 */ 260 public function backup_details_unknown(moodle_url $nextstageurl) { 261 262 $html = html_writer::start_div('unknownformat'); 263 $html .= $this->output->heading(get_string('errorinvalidformat', 'backup'), 2); 264 $html .= $this->output->notification(get_string('errorinvalidformatinfo', 'backup'), 'notifyproblem'); 265 $html .= $this->continue_button($nextstageurl, 'post'); 266 $html .= html_writer::end_div(); 267 268 return $html; 269 } 270 271 /** 272 * Displays a course selector for restore 273 * 274 * @param moodle_url $nextstageurl 275 * @param bool $wholecourse true if we are restoring whole course (as with backup::TYPE_1COURSE), false otherwise 276 * @param restore_category_search $categories 277 * @param restore_course_search $courses 278 * @param int $currentcourse 279 * @return string 280 */ 281 public function course_selector(moodle_url $nextstageurl, $wholecourse = true, restore_category_search $categories = null, 282 restore_course_search $courses = null, $currentcourse = null) { 283 global $CFG; 284 require_once($CFG->dirroot.'/course/lib.php'); 285 286 // These variables are used to check if the form using this function was submitted. 287 $target = optional_param('target', false, PARAM_INT); 288 $targetid = optional_param('targetid', null, PARAM_INT); 289 290 // Check if they submitted the form but did not provide all the data we need. 291 $missingdata = false; 292 if ($target and is_null($targetid)) { 293 $missingdata = true; 294 } 295 296 $nextstageurl->param('sesskey', sesskey()); 297 298 $form = html_writer::start_tag('form', array('method' => 'post', 'action' => $nextstageurl->out_omit_querystring(), 299 'class' => 'mform')); 300 foreach ($nextstageurl->params() as $key => $value) { 301 $form .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => $key, 'value' => $value)); 302 } 303 304 $hasrestoreoption = false; 305 306 $html = html_writer::start_tag('div', array('class' => 'backup-course-selector backup-restore')); 307 if ($wholecourse && !empty($categories) && ($categories->get_count() > 0 || $categories->get_search())) { 308 // New course. 309 $hasrestoreoption = true; 310 $html .= $form; 311 $html .= html_writer::start_tag('div', array('class' => 'bcs-new-course backup-section')); 312 $html .= $this->output->heading(get_string('restoretonewcourse', 'backup'), 2, array('class' => 'header')); 313 $html .= $this->backup_detail_input(get_string('restoretonewcourse', 'backup'), 'radio', 'target', 314 backup::TARGET_NEW_COURSE, array('checked' => 'checked')); 315 $selectacategoryhtml = $this->backup_detail_pair(get_string('selectacategory', 'backup'), $this->render($categories)); 316 // Display the category selection as required if the form was submitted but this data was not supplied. 317 if ($missingdata && $target == backup::TARGET_NEW_COURSE) { 318 $html .= html_writer::span(get_string('required'), 'error'); 319 $html .= html_writer::start_tag('fieldset', array('class' => 'error')); 320 $html .= $selectacategoryhtml; 321 $html .= html_writer::end_tag('fieldset'); 322 } else { 323 $html .= $selectacategoryhtml; 324 } 325 $attrs = array('type' => 'submit', 'value' => get_string('continue'), 'class' => 'btn btn-primary'); 326 $html .= $this->backup_detail_pair('', html_writer::empty_tag('input', $attrs)); 327 $html .= html_writer::end_tag('div'); 328 $html .= html_writer::end_tag('form'); 329 } 330 331 if ($wholecourse && !empty($currentcourse)) { 332 // Current course. 333 $hasrestoreoption = true; 334 $html .= $form; 335 $html .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => 'targetid', 'value' => $currentcourse)); 336 $html .= html_writer::start_tag('div', array('class' => 'bcs-current-course backup-section')); 337 $html .= $this->output->heading(get_string('restoretocurrentcourse', 'backup'), 2, array('class' => 'header')); 338 $html .= $this->backup_detail_input(get_string('restoretocurrentcourseadding', 'backup'), 'radio', 'target', 339 backup::TARGET_CURRENT_ADDING, array('checked' => 'checked')); 340 $html .= $this->backup_detail_input(get_string('restoretocurrentcoursedeleting', 'backup'), 'radio', 'target', 341 backup::TARGET_CURRENT_DELETING); 342 $attrs = array('type' => 'submit', 'value' => get_string('continue'), 'class' => 'btn btn-primary'); 343 $html .= $this->backup_detail_pair('', html_writer::empty_tag('input', $attrs)); 344 $html .= html_writer::end_tag('div'); 345 $html .= html_writer::end_tag('form'); 346 } 347 348 // If we are restoring an activity, then include the current course. 349 if (!$wholecourse) { 350 $courses->invalidate_results(); // Clean list of courses. 351 $courses->set_include_currentcourse(); 352 } 353 if (!empty($courses) && ($courses->get_count() > 0 || $courses->get_search())) { 354 // Existing course. 355 $hasrestoreoption = true; 356 $html .= $form; 357 $html .= html_writer::start_tag('div', array('class' => 'bcs-existing-course backup-section')); 358 $html .= $this->output->heading(get_string('restoretoexistingcourse', 'backup'), 2, array('class' => 'header')); 359 if ($wholecourse) { 360 $html .= $this->backup_detail_input(get_string('restoretoexistingcourseadding', 'backup'), 'radio', 'target', 361 backup::TARGET_EXISTING_ADDING, array('checked' => 'checked')); 362 $html .= $this->backup_detail_input(get_string('restoretoexistingcoursedeleting', 'backup'), 'radio', 'target', 363 backup::TARGET_EXISTING_DELETING); 364 } else { 365 $html .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => 'target', 'value' => backup::TARGET_EXISTING_ADDING)); 366 } 367 $selectacoursehtml = $this->backup_detail_pair(get_string('selectacourse', 'backup'), $this->render($courses)); 368 // Display the course selection as required if the form was submitted but this data was not supplied. 369 if ($missingdata && $target == backup::TARGET_EXISTING_ADDING) { 370 $html .= html_writer::span(get_string('required'), 'error'); 371 $html .= html_writer::start_tag('fieldset', array('class' => 'error')); 372 $html .= $selectacoursehtml; 373 $html .= html_writer::end_tag('fieldset'); 374 } else { 375 $html .= $selectacoursehtml; 376 } 377 $attrs = array('type' => 'submit', 'value' => get_string('continue'), 'class' => 'btn btn-primary'); 378 $html .= $this->backup_detail_pair('', html_writer::empty_tag('input', $attrs)); 379 $html .= html_writer::end_tag('div'); 380 $html .= html_writer::end_tag('form'); 381 } 382 383 if (!$hasrestoreoption) { 384 echo $this->output->notification(get_string('norestoreoptions', 'backup')); 385 } 386 387 $html .= html_writer::end_tag('div'); 388 return $html; 389 } 390 391 /** 392 * Displays the import course selector 393 * 394 * @param moodle_url $nextstageurl 395 * @param import_course_search $courses 396 * @return string 397 */ 398 public function import_course_selector(moodle_url $nextstageurl, import_course_search $courses = null) { 399 $html = html_writer::start_tag('div', array('class' => 'import-course-selector backup-restore')); 400 $html .= html_writer::start_tag('form', array('method' => 'post', 'action' => $nextstageurl->out_omit_querystring())); 401 foreach ($nextstageurl->params() as $key => $value) { 402 $html .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => $key, 'value' => $value)); 403 } 404 // We only allow import adding for now. Enforce it here. 405 $html .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => 'target', 'value' => backup::TARGET_CURRENT_ADDING)); 406 $html .= html_writer::start_tag('div', array('class' => 'ics-existing-course backup-section')); 407 $html .= $this->output->heading(get_string('importdatafrom'), 2, array('class' => 'header')); 408 $html .= $this->backup_detail_pair(get_string('selectacourse', 'backup'), $this->render($courses)); 409 $attrs = array('type' => 'submit', 'value' => get_string('continue'), 'class' => 'btn btn-primary'); 410 $html .= html_writer::start_tag('div', array('class' => 'mt-3')); 411 $html .= $this->backup_detail_pair('', html_writer::empty_tag('input', $attrs)); 412 $html .= html_writer::end_tag('div'); 413 $html .= html_writer::end_tag('div'); 414 $html .= html_writer::end_tag('form'); 415 $html .= html_writer::end_tag('div'); 416 return $html; 417 } 418 419 /** 420 * Creates a detailed pairing (key + value) 421 * 422 * @staticvar int $count 423 * @param string $label 424 * @param string $value 425 * @return string 426 */ 427 protected function backup_detail_pair($label, $value) { 428 static $count = 0; 429 $count ++; 430 $html = html_writer::start_tag('div', ['class' => 'detail-pair', 'role' => 'row']); 431 $html .= html_writer::tag('div', $label, ['class' => 'detail-pair-label mb-2', 'role' => 'cell']); 432 $html .= html_writer::tag('div', $value, ['class' => 'detail-pair-value pl-2', 'role' => 'cell']); 433 $html .= html_writer::end_tag('div'); 434 return $html; 435 } 436 437 /** 438 * Creates a unique id string by appending an incremental number to the prefix. 439 * 440 * @param string $prefix To be used as the left part of the id string. 441 * @return string 442 */ 443 protected function make_unique_id(string $prefix): string { 444 static $count = 0; 445 446 return $prefix . '-' . $count++; 447 } 448 449 /** 450 * Created a detailed pairing with an input 451 * 452 * @param string $label 453 * @param string $type 454 * @param string $name 455 * @param string $value 456 * @param array $attributes 457 * @param string|null $description 458 * @return string 459 */ 460 protected function backup_detail_input($label, $type, $name, $value, array $attributes = array(), $description = null) { 461 if (!empty($description)) { 462 $description = html_writer::tag('span', $description, array('class' => 'description')); 463 } else { 464 $description = ''; 465 } 466 $id = $this->make_unique_id('detail-pair-value'); 467 return $this->backup_detail_pair( 468 html_writer::label($label, $id), 469 html_writer::empty_tag('input', $attributes + ['id' => $id, 'name' => $name, 'type' => $type, 'value' => $value]) . 470 $description 471 ); 472 } 473 474 /** 475 * Creates a detailed pairing with a select 476 * 477 * @param string $label 478 * @param string $name 479 * @param array $options 480 * @param string $selected 481 * @param bool $nothing 482 * @param array $attributes 483 * @param string|null $description 484 * @return string 485 */ 486 protected function backup_detail_select($label, $name, $options, $selected = '', $nothing = false, array $attributes = array(), $description = null) { 487 if (!empty ($description)) { 488 $description = html_writer::tag('span', $description, array('class' => 'description')); 489 } else { 490 $description = ''; 491 } 492 return $this->backup_detail_pair($label, html_writer::select($options, $name, $selected, false, $attributes).$description); 493 } 494 495 /** 496 * Displays precheck notices 497 * 498 * @param array $results 499 * @return string 500 */ 501 public function precheck_notices($results) { 502 $output = html_writer::start_tag('div', array('class' => 'restore-precheck-notices')); 503 if (array_key_exists('errors', $results)) { 504 foreach ($results['errors'] as $error) { 505 $output .= $this->output->notification($error); 506 } 507 } 508 if (array_key_exists('warnings', $results)) { 509 foreach ($results['warnings'] as $warning) { 510 $output .= $this->output->notification($warning, 'notifyproblem'); 511 } 512 } 513 return $output.html_writer::end_tag('div'); 514 } 515 516 /** 517 * Displays substage buttons 518 * 519 * @param bool $haserrors 520 * @return string 521 */ 522 public function substage_buttons($haserrors) { 523 $output = html_writer::start_tag('div', array('continuebutton')); 524 if (!$haserrors) { 525 $attrs = array('type' => 'submit', 'value' => get_string('continue'), 'class' => 'btn btn-primary'); 526 $output .= html_writer::empty_tag('input', $attrs); 527 } 528 $attrs = array('type' => 'submit', 'name' => 'cancel', 'value' => get_string('cancel'), 'class' => 'btn btn-secondary'); 529 $output .= html_writer::empty_tag('input', $attrs); 530 $output .= html_writer::end_tag('div'); 531 return $output; 532 } 533 534 /** 535 * Displays a role mapping interface 536 * 537 * @param array $rolemappings 538 * @param array $roles 539 * @return string 540 */ 541 public function role_mappings($rolemappings, $roles) { 542 $roles[0] = get_string('none'); 543 $output = html_writer::start_tag('div', array('class' => 'restore-rolemappings')); 544 $output .= $this->output->heading(get_string('restorerolemappings', 'backup'), 2); 545 foreach ($rolemappings as $id => $mapping) { 546 $label = $mapping->name; 547 $name = 'mapping'.$id; 548 $selected = $mapping->targetroleid; 549 $output .= $this->backup_detail_select($label, $name, $roles, $mapping->targetroleid, false, array(), $mapping->description); 550 } 551 $output .= html_writer::end_tag('div'); 552 return $output; 553 } 554 555 /** 556 * Displays a continue button, overriding core renderer method of the same in order 557 * to override submission method of the button form 558 * 559 * @param string|moodle_url $url 560 * @param string $method 561 * @return string 562 */ 563 public function continue_button($url, $method = 'post') { 564 if (!($url instanceof moodle_url)) { 565 $url = new moodle_url($url); 566 } 567 if ($method != 'post') { 568 $method = 'get'; 569 } 570 $button = new single_button($url, get_string('continue'), $method, true); 571 $button->class = 'continuebutton'; 572 return $this->render($button); 573 } 574 /** 575 * Print a backup files tree 576 * @param array $options 577 * @return string 578 */ 579 public function backup_files_viewer(array $options = null) { 580 $files = new backup_files_viewer($options); 581 return $this->render($files); 582 } 583 584 /** 585 * Generate the status indicator markup for display in the 586 * backup restore file area UI. 587 * 588 * @param int $statuscode The status code of the backup. 589 * @param string $backupid The backup record id. 590 * @return string|boolean $status The status indicator for the operation. 591 */ 592 public function get_status_display($statuscode, $backupid, $restoreid=null, $operation='backup') { 593 if ($statuscode == backup::STATUS_AWAITING 594 || $statuscode == backup::STATUS_EXECUTING 595 || $statuscode == backup::STATUS_REQUIRE_CONV) { // In progress. 596 $progresssetup = array( 597 'backupid' => $backupid, 598 'restoreid' => $restoreid, 599 'operation' => $operation, 600 'width' => '100' 601 ); 602 $status = $this->render_from_template('core/async_backup_progress', $progresssetup); 603 } else if ($statuscode == backup::STATUS_FINISHED_ERR) { // Error. 604 $icon = $this->output->render(new \pix_icon('i/delete', get_string('failed', 'backup'))); 605 $status = \html_writer::span($icon, 'action-icon'); 606 } else if ($statuscode == backup::STATUS_FINISHED_OK) { // Complete. 607 $icon = $this->output->render(new \pix_icon('i/checked', get_string('successful', 'backup'))); 608 $status = \html_writer::span($icon, 'action-icon'); 609 } 610 611 return $status; 612 } 613 614 /** 615 * Displays a backup files viewer 616 * 617 * @global stdClass $USER 618 * @param backup_files_viewer $viewer 619 * @return string 620 */ 621 public function render_backup_files_viewer(backup_files_viewer $viewer) { 622 623 $files = $viewer->files; 624 625 $async = \async_helper::is_async_enabled(); 626 627 $tablehead = array( 628 get_string('filename', 'backup'), 629 get_string('time'), 630 get_string('size'), 631 get_string('download'), 632 get_string('restore')); 633 if ($async) { 634 $tablehead[] = get_string('status', 'backup'); 635 } 636 637 $table = new html_table(); 638 $table->attributes['class'] = 'backup-files-table generaltable'; 639 $table->head = $tablehead; 640 $table->width = '100%'; 641 $table->data = []; 642 643 // First add in progress asynchronous backups. 644 // Only if asynchronous backups are enabled. 645 if ($async) { 646 $tabledata = []; 647 $backups = \async_helper::get_async_backups($viewer->filearea, $viewer->filecontext->instanceid); 648 // For each backup get, new item name, time restore created and progress. 649 foreach ($backups as $backup) { 650 $status = $this->get_status_display($backup->status, $backup->backupid); 651 $timecreated = $backup->timecreated; 652 $tablerow = [$backup->filename, userdate($timecreated), '-', '-', '-', $status]; 653 $tabledata[] = $tablerow; 654 } 655 $table->data = $tabledata; 656 } 657 658 // Add completed backups. 659 foreach ($files as $file) { 660 if ($file->is_directory()) { 661 continue; 662 } 663 $fileurl = moodle_url::make_pluginfile_url( 664 $file->get_contextid(), 665 $file->get_component(), 666 $file->get_filearea(), 667 null, 668 $file->get_filepath(), 669 $file->get_filename(), 670 true 671 ); 672 $params = array(); 673 $params['action'] = 'choosebackupfile'; 674 $params['filename'] = $file->get_filename(); 675 $params['filepath'] = $file->get_filepath(); 676 $params['component'] = $file->get_component(); 677 $params['filearea'] = $file->get_filearea(); 678 $params['filecontextid'] = $file->get_contextid(); 679 $params['contextid'] = $viewer->currentcontext->id; 680 $params['itemid'] = $file->get_itemid(); 681 $restoreurl = new moodle_url('/backup/restorefile.php', $params); 682 $restorelink = html_writer::link($restoreurl, get_string('restore')); 683 $downloadlink = html_writer::link($fileurl, get_string('download')); 684 685 // Conditional display of the restore and download links, initially only for the 'automated' filearea. 686 if ($params['filearea'] == 'automated') { 687 if (!has_capability('moodle/restore:viewautomatedfilearea', $viewer->currentcontext)) { 688 $restorelink = ''; 689 } 690 if (!can_download_from_backup_filearea($params['filearea'], $viewer->currentcontext)) { 691 $downloadlink = ''; 692 } 693 } 694 $tabledata = array( 695 $file->get_filename(), 696 userdate ($file->get_timemodified()), 697 display_size ($file->get_filesize()), 698 $downloadlink, 699 $restorelink 700 ); 701 if ($async) { 702 $tabledata[] = $this->get_status_display(backup::STATUS_FINISHED_OK, null); 703 } 704 705 $table->data[] = $tabledata; 706 } 707 708 $html = html_writer::table($table); 709 710 // For automated backups, the ability to manage backup files is controlled by the ability to download them. 711 // All files must be from the same file area in a backup_files_viewer. 712 $canmanagebackups = true; 713 if ($viewer->filearea == 'automated') { 714 if (!can_download_from_backup_filearea($viewer->filearea, $viewer->currentcontext)) { 715 $canmanagebackups = false; 716 } 717 } 718 719 if ($canmanagebackups) { 720 $html .= $this->output->single_button( 721 new moodle_url('/backup/backupfilesedit.php', array( 722 'currentcontext' => $viewer->currentcontext->id, 723 'contextid' => $viewer->filecontext->id, 724 'filearea' => $viewer->filearea, 725 'component' => $viewer->component, 726 'returnurl' => $this->page->url->out()) 727 ), 728 get_string('managefiles', 'backup'), 729 'post' 730 ); 731 } 732 733 return $html; 734 } 735 736 /** 737 * Renders a restore course search object 738 * 739 * @param restore_course_search $component 740 * @return string 741 */ 742 public function render_restore_course_search(restore_course_search $component) { 743 $output = html_writer::start_tag('div', array('class' => 'restore-course-search mb-1')); 744 $output .= html_writer::start_tag('div', array('class' => 'rcs-results table-sm w-75')); 745 746 $table = new html_table(); 747 $table->head = array('', get_string('shortnamecourse'), get_string('fullnamecourse')); 748 $table->data = array(); 749 if ($component->get_count() !== 0) { 750 foreach ($component->get_results() as $course) { 751 $row = new html_table_row(); 752 $row->attributes['class'] = 'rcs-course'; 753 if (!$course->visible) { 754 $row->attributes['class'] .= ' dimmed'; 755 } 756 $id = $this->make_unique_id('restore-course'); 757 $row->cells = [ 758 html_writer::empty_tag('input', ['type' => 'radio', 'name' => 'targetid', 'value' => $course->id, 759 'id' => $id]), 760 html_writer::label( 761 format_string($course->shortname, true, ['context' => context_course::instance($course->id)]), 762 $id, 763 true, 764 ['class' => 'd-block'] 765 ), 766 format_string($course->fullname, true, ['context' => context_course::instance($course->id)]) 767 ]; 768 $table->data[] = $row; 769 } 770 if ($component->has_more_results()) { 771 $cell = new html_table_cell(get_string('moreresults', 'backup')); 772 $cell->colspan = 3; 773 $cell->attributes['class'] = 'notifyproblem'; 774 $row = new html_table_row(array($cell)); 775 $row->attributes['class'] = 'rcs-course'; 776 $table->data[] = $row; 777 } 778 } else { 779 $cell = new html_table_cell(get_string('nomatchingcourses', 'backup')); 780 $cell->colspan = 3; 781 $cell->attributes['class'] = 'notifyproblem'; 782 $row = new html_table_row(array($cell)); 783 $row->attributes['class'] = 'rcs-course'; 784 $table->data[] = $row; 785 } 786 $output .= html_writer::table($table); 787 $output .= html_writer::end_tag('div'); 788 789 $data = [ 790 'inform' => true, 791 'extraclasses' => 'rcs-search mb-3 w-25', 792 'inputname' => restore_course_search::$VAR_SEARCH, 793 'searchstring' => get_string('searchcourses'), 794 'buttonattributes' => [ 795 (object) ['key' => 'name', 'value' => 'searchcourses'], 796 (object) ['key' => 'value', 'value' => 1], 797 ], 798 'query' => $component->get_search(), 799 ]; 800 $output .= $this->output->render_from_template('core/search_input', $data); 801 802 $output .= html_writer::end_tag('div'); 803 return $output; 804 } 805 806 /** 807 * Renders an import course search object 808 * 809 * @param import_course_search $component 810 * @return string 811 */ 812 public function render_import_course_search(import_course_search $component) { 813 $output = html_writer::start_tag('div', array('class' => 'import-course-search')); 814 if ($component->get_count() === 0) { 815 $output .= $this->output->notification(get_string('nomatchingcourses', 'backup')); 816 817 $output .= html_writer::start_tag('div', array('class' => 'ics-search form-inline')); 818 $attrs = array( 819 'type' => 'text', 820 'name' => restore_course_search::$VAR_SEARCH, 821 'value' => $component->get_search(), 822 'aria-label' => get_string('searchcourses'), 823 'placeholder' => get_string('searchcourses'), 824 'class' => 'form-control' 825 ); 826 $output .= html_writer::empty_tag('input', $attrs); 827 $attrs = array( 828 'type' => 'submit', 829 'name' => 'searchcourses', 830 'value' => get_string('search'), 831 'class' => 'btn btn-secondary ml-1' 832 ); 833 $output .= html_writer::empty_tag('input', $attrs); 834 $output .= html_writer::end_tag('div'); 835 836 $output .= html_writer::end_tag('div'); 837 return $output; 838 } 839 840 $countstr = ''; 841 if ($component->has_more_results()) { 842 $countstr = get_string('morecoursesearchresults', 'backup', $component->get_count()); 843 } else { 844 $countstr = get_string('totalcoursesearchresults', 'backup', $component->get_count()); 845 } 846 847 $output .= html_writer::tag('div', $countstr, array('class' => 'ics-totalresults')); 848 $output .= html_writer::start_tag('div', array('class' => 'ics-results')); 849 850 $table = new html_table(); 851 $table->head = array('', get_string('shortnamecourse'), get_string('fullnamecourse')); 852 $table->data = array(); 853 foreach ($component->get_results() as $course) { 854 $row = new html_table_row(); 855 $row->attributes['class'] = 'ics-course'; 856 if (!$course->visible) { 857 $row->attributes['class'] .= ' dimmed'; 858 } 859 $id = $this->make_unique_id('import-course'); 860 $row->cells = [ 861 html_writer::empty_tag('input', ['type' => 'radio', 'name' => 'importid', 'value' => $course->id, 862 'id' => $id]), 863 html_writer::label( 864 format_string($course->shortname, true, ['context' => context_course::instance($course->id)]), 865 $id, 866 true, 867 ['class' => 'd-block'] 868 ), 869 format_string($course->fullname, true, ['context' => context_course::instance($course->id)]) 870 ]; 871 $table->data[] = $row; 872 } 873 if ($component->has_more_results()) { 874 $cell = new html_table_cell(get_string('moreresults', 'backup')); 875 $cell->colspan = 3; 876 $cell->attributes['class'] = 'notifyproblem'; 877 $row = new html_table_row(array($cell)); 878 $row->attributes['class'] = 'rcs-course'; 879 $table->data[] = $row; 880 } 881 $output .= html_writer::table($table); 882 $output .= html_writer::end_tag('div'); 883 884 $output .= html_writer::start_tag('div', array('class' => 'ics-search form-inline')); 885 $attrs = array( 886 'type' => 'text', 887 'name' => restore_course_search::$VAR_SEARCH, 888 'value' => $component->get_search(), 889 'aria-label' => get_string('searchcourses'), 890 'placeholder' => get_string('searchcourses'), 891 'class' => 'form-control'); 892 $output .= html_writer::empty_tag('input', $attrs); 893 $attrs = array( 894 'type' => 'submit', 895 'name' => 'searchcourses', 896 'value' => get_string('search'), 897 'class' => 'btn btn-secondary ml-1' 898 ); 899 $output .= html_writer::empty_tag('input', $attrs); 900 $output .= html_writer::end_tag('div'); 901 902 $output .= html_writer::end_tag('div'); 903 return $output; 904 } 905 906 /** 907 * Renders a restore category search object 908 * 909 * @param restore_category_search $component 910 * @return string 911 */ 912 public function render_restore_category_search(restore_category_search $component) { 913 $output = html_writer::start_tag('div', array('class' => 'restore-course-search mb-1')); 914 $output .= html_writer::start_tag('div', array('class' => 'rcs-results table-sm w-75')); 915 916 $table = new html_table(); 917 $table->head = array('', get_string('name'), get_string('description')); 918 $table->data = array(); 919 920 if ($component->get_count() !== 0) { 921 foreach ($component->get_results() as $category) { 922 $row = new html_table_row(); 923 $row->attributes['class'] = 'rcs-course'; 924 if (!$category->visible) { 925 $row->attributes['class'] .= ' dimmed'; 926 } 927 $context = context_coursecat::instance($category->id); 928 $id = $this->make_unique_id('restore-category'); 929 $row->cells = [ 930 html_writer::empty_tag('input', ['type' => 'radio', 'name' => 'targetid', 'value' => $category->id, 931 'id' => $id]), 932 html_writer::label( 933 format_string($category->name, true, ['context' => context_coursecat::instance($category->id)]), 934 $id, 935 true, 936 ['class' => 'd-block'] 937 ), 938 format_text(file_rewrite_pluginfile_urls($category->description, 'pluginfile.php', $context->id, 939 'coursecat', 'description', null), $category->descriptionformat, ['overflowdiv' => true]) 940 ]; 941 $table->data[] = $row; 942 } 943 if ($component->has_more_results()) { 944 $cell = new html_table_cell(get_string('moreresults', 'backup')); 945 $cell->attributes['class'] = 'notifyproblem'; 946 $cell->colspan = 3; 947 $row = new html_table_row(array($cell)); 948 $row->attributes['class'] = 'rcs-course'; 949 $table->data[] = $row; 950 } 951 } else { 952 $cell = new html_table_cell(get_string('nomatchingcourses', 'backup')); 953 $cell->colspan = 3; 954 $cell->attributes['class'] = 'notifyproblem'; 955 $row = new html_table_row(array($cell)); 956 $row->attributes['class'] = 'rcs-course'; 957 $table->data[] = $row; 958 } 959 $output .= html_writer::table($table); 960 $output .= html_writer::end_tag('div'); 961 962 $data = [ 963 'inform' => true, 964 'extraclasses' => 'rcs-search mb-3 w-25', 965 'inputname' => restore_category_search::$VAR_SEARCH, 966 'searchstring' => get_string('searchcoursecategories'), 967 'buttonattributes' => [ 968 (object) ['key' => 'name', 'value' => 'searchcourses'], 969 (object) ['key' => 'value', 'value' => 1], 970 ], 971 'query' => $component->get_search(), 972 ]; 973 $output .= $this->output->render_from_template('core/search_input', $data); 974 975 $output .= html_writer::end_tag('div'); 976 return $output; 977 } 978 979 /** 980 * Get markup to render table for all of a users async 981 * in progress restores. 982 * 983 * @param int $userid The Moodle user id. 984 * @param \context $context The Moodle context for these restores. 985 * @return string $html The table HTML. 986 */ 987 public function restore_progress_viewer ($userid, $context) { 988 $tablehead = array(get_string('course'), get_string('time'), get_string('status', 'backup')); 989 990 $table = new html_table(); 991 $table->attributes['class'] = 'backup-files-table generaltable'; 992 $table->head = $tablehead; 993 $tabledata = array(); 994 995 // Get all in progress async restores for this user. 996 $restores = \async_helper::get_async_restores($userid); 997 998 // For each backup get, new item name, time restore created and progress. 999 foreach ($restores as $restore) { 1000 1001 $restorename = \async_helper::get_restore_name($context); 1002 $timecreated = $restore->timecreated; 1003 $status = $this->get_status_display($restore->status, $restore->backupid, $restore->backupid, null, 'restore'); 1004 1005 $tablerow = array($restorename, userdate($timecreated), $status); 1006 $tabledata[] = $tablerow; 1007 } 1008 1009 $table->data = $tabledata; 1010 $html = html_writer::table($table); 1011 1012 return $html; 1013 } 1014 1015 /** 1016 * Get markup to render table for all of a users course copies. 1017 * 1018 * @param int $userid The Moodle user id. 1019 * @param int $courseid The id of the course to get the backups for. 1020 * @return string $html The table HTML. 1021 */ 1022 public function copy_progress_viewer(int $userid, int $courseid): string { 1023 $tablehead = array( 1024 get_string('copysource', 'backup'), 1025 get_string('copydest', 'backup'), 1026 get_string('time'), 1027 get_string('copyop', 'backup'), 1028 get_string('status', 'backup') 1029 ); 1030 1031 $table = new html_table(); 1032 $table->attributes['class'] = 'backup-files-table generaltable'; 1033 $table->head = $tablehead; 1034 1035 $tabledata = array(); 1036 1037 // Get all in progress course copies for this user. 1038 $copies = \copy_helper::get_copies($userid, $courseid); 1039 1040 foreach ($copies as $copy) { 1041 $sourceurl = new \moodle_url('/course/view.php', array('id' => $copy->sourceid)); 1042 1043 $tablerow = array( 1044 html_writer::link($sourceurl, $copy->source), 1045 $copy->destination, 1046 userdate($copy->timecreated), 1047 get_string($copy->operation), 1048 $this->get_status_display($copy->status, $copy->backupid, $copy->restoreid, $copy->operation) 1049 ); 1050 $tabledata[] = $tablerow; 1051 } 1052 1053 $table->data = $tabledata; 1054 $html = html_writer::table($table); 1055 1056 return $html; 1057 } 1058 } 1059 1060 /** 1061 * Data structure representing backup files viewer 1062 * 1063 * @copyright 2010 Dongsheng Cai 1064 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 1065 * @since Moodle 2.0 1066 */ 1067 class backup_files_viewer implements renderable { 1068 1069 /** 1070 * @var array 1071 */ 1072 public $files; 1073 1074 /** 1075 * @var context 1076 */ 1077 public $filecontext; 1078 1079 /** 1080 * @var string 1081 */ 1082 public $component; 1083 1084 /** 1085 * @var string 1086 */ 1087 public $filearea; 1088 1089 /** 1090 * @var context 1091 */ 1092 public $currentcontext; 1093 1094 /** 1095 * Constructor of backup_files_viewer class 1096 * @param array $options 1097 */ 1098 public function __construct(array $options = null) { 1099 global $CFG, $USER; 1100 $fs = get_file_storage(); 1101 $this->currentcontext = $options['currentcontext']; 1102 $this->filecontext = $options['filecontext']; 1103 $this->component = $options['component']; 1104 $this->filearea = $options['filearea']; 1105 $files = $fs->get_area_files($this->filecontext->id, $this->component, $this->filearea, false, 'timecreated'); 1106 $this->files = array_reverse($files); 1107 } 1108 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body