Differences Between: [Versions 310 and 311] [Versions 311 and 400] [Versions 311 and 401] [Versions 311 and 402] [Versions 311 and 403] [Versions 39 and 311]
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('icon', '', $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 global $CFG; 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 = array(); 642 643 // First add in progress asynchronous backups. 644 // Only if asynchronous backups are enabled. 645 // Also only render async status in correct area. Courese OR activity (not both). 646 if ($async 647 && (($viewer->filearea == 'course' && $viewer->currentcontext->contextlevel == CONTEXT_COURSE) 648 || ($viewer->filearea == 'activity' && $viewer->currentcontext->contextlevel == CONTEXT_MODULE)) 649 ) { 650 $table->data = \async_helper::get_async_backups($this, $viewer->currentcontext->instanceid); 651 } 652 653 // Add completed backups. 654 foreach ($files as $file) { 655 if ($file->is_directory()) { 656 continue; 657 } 658 $fileurl = moodle_url::make_pluginfile_url( 659 $file->get_contextid(), 660 $file->get_component(), 661 $file->get_filearea(), 662 null, 663 $file->get_filepath(), 664 $file->get_filename(), 665 true 666 ); 667 $params = array(); 668 $params['action'] = 'choosebackupfile'; 669 $params['filename'] = $file->get_filename(); 670 $params['filepath'] = $file->get_filepath(); 671 $params['component'] = $file->get_component(); 672 $params['filearea'] = $file->get_filearea(); 673 $params['filecontextid'] = $file->get_contextid(); 674 $params['contextid'] = $viewer->currentcontext->id; 675 $params['itemid'] = $file->get_itemid(); 676 $restoreurl = new moodle_url('/backup/restorefile.php', $params); 677 $restorelink = html_writer::link($restoreurl, get_string('restore')); 678 $downloadlink = html_writer::link($fileurl, get_string('download')); 679 680 // Conditional display of the restore and download links, initially only for the 'automated' filearea. 681 if ($params['filearea'] == 'automated') { 682 if (!has_capability('moodle/restore:viewautomatedfilearea', $viewer->currentcontext)) { 683 $restorelink = ''; 684 } 685 if (!can_download_from_backup_filearea($params['filearea'], $viewer->currentcontext)) { 686 $downloadlink = ''; 687 } 688 } 689 $tabledata = array( 690 $file->get_filename(), 691 userdate ($file->get_timemodified()), 692 display_size ($file->get_filesize()), 693 $downloadlink, 694 $restorelink 695 ); 696 if ($async) { 697 $tabledata[] = $this->get_status_display(backup::STATUS_FINISHED_OK, null); 698 } 699 700 $table->data[] = $tabledata; 701 } 702 703 $html = html_writer::table($table); 704 705 // For automated backups, the ability to manage backup files is controlled by the ability to download them. 706 // All files must be from the same file area in a backup_files_viewer. 707 $canmanagebackups = true; 708 if ($viewer->filearea == 'automated') { 709 if (!can_download_from_backup_filearea($viewer->filearea, $viewer->currentcontext)) { 710 $canmanagebackups = false; 711 } 712 } 713 714 if ($canmanagebackups) { 715 $html .= $this->output->single_button( 716 new moodle_url('/backup/backupfilesedit.php', array( 717 'currentcontext' => $viewer->currentcontext->id, 718 'contextid' => $viewer->filecontext->id, 719 'filearea' => $viewer->filearea, 720 'component' => $viewer->component, 721 'returnurl' => $this->page->url->out()) 722 ), 723 get_string('managefiles', 'backup'), 724 'post' 725 ); 726 } 727 728 return $html; 729 } 730 731 /** 732 * Renders a restore course search object 733 * 734 * @param restore_course_search $component 735 * @return string 736 */ 737 public function render_restore_course_search(restore_course_search $component) { 738 $output = html_writer::start_tag('div', array('class' => 'restore-course-search mb-1')); 739 $output .= html_writer::start_tag('div', array('class' => 'rcs-results table-sm w-75')); 740 741 $table = new html_table(); 742 $table->head = array('', get_string('shortnamecourse'), get_string('fullnamecourse')); 743 $table->data = array(); 744 if ($component->get_count() !== 0) { 745 foreach ($component->get_results() as $course) { 746 $row = new html_table_row(); 747 $row->attributes['class'] = 'rcs-course'; 748 if (!$course->visible) { 749 $row->attributes['class'] .= ' dimmed'; 750 } 751 $id = $this->make_unique_id('restore-course'); 752 $row->cells = [ 753 html_writer::empty_tag('input', ['type' => 'radio', 'name' => 'targetid', 'value' => $course->id, 754 'id' => $id]), 755 html_writer::label( 756 format_string($course->shortname, true, ['context' => context_course::instance($course->id)]), 757 $id, 758 true, 759 ['class' => 'd-block'] 760 ), 761 format_string($course->fullname, true, ['context' => context_course::instance($course->id)]) 762 ]; 763 $table->data[] = $row; 764 } 765 if ($component->has_more_results()) { 766 $cell = new html_table_cell(get_string('moreresults', 'backup')); 767 $cell->colspan = 3; 768 $cell->attributes['class'] = 'notifyproblem'; 769 $row = new html_table_row(array($cell)); 770 $row->attributes['class'] = 'rcs-course'; 771 $table->data[] = $row; 772 } 773 } else { 774 $cell = new html_table_cell(get_string('nomatchingcourses', 'backup')); 775 $cell->colspan = 3; 776 $cell->attributes['class'] = 'notifyproblem'; 777 $row = new html_table_row(array($cell)); 778 $row->attributes['class'] = 'rcs-course'; 779 $table->data[] = $row; 780 } 781 $output .= html_writer::table($table); 782 $output .= html_writer::end_tag('div'); 783 784 $data = [ 785 'inform' => true, 786 'extraclasses' => 'rcs-search mb-3 w-25', 787 'inputname' => restore_course_search::$VAR_SEARCH, 788 'searchstring' => get_string('searchcourses'), 789 'query' => $component->get_search(), 790 ]; 791 $output .= $this->output->render_from_template('core/search_input', $data); 792 793 $output .= html_writer::end_tag('div'); 794 return $output; 795 } 796 797 /** 798 * Renders an import course search object 799 * 800 * @param import_course_search $component 801 * @return string 802 */ 803 public function render_import_course_search(import_course_search $component) { 804 $output = html_writer::start_tag('div', array('class' => 'import-course-search')); 805 if ($component->get_count() === 0) { 806 $output .= $this->output->notification(get_string('nomatchingcourses', 'backup')); 807 808 $output .= html_writer::start_tag('div', array('class' => 'ics-search form-inline')); 809 $attrs = array( 810 'type' => 'text', 811 'name' => restore_course_search::$VAR_SEARCH, 812 'value' => $component->get_search(), 813 'aria-label' => get_string('searchcourses'), 814 'placeholder' => get_string('searchcourses'), 815 'class' => 'form-control' 816 ); 817 $output .= html_writer::empty_tag('input', $attrs); 818 $attrs = array( 819 'type' => 'submit', 820 'name' => 'searchcourses', 821 'value' => get_string('search'), 822 'class' => 'btn btn-secondary ml-1' 823 ); 824 $output .= html_writer::empty_tag('input', $attrs); 825 $output .= html_writer::end_tag('div'); 826 827 $output .= html_writer::end_tag('div'); 828 return $output; 829 } 830 831 $countstr = ''; 832 if ($component->has_more_results()) { 833 $countstr = get_string('morecoursesearchresults', 'backup', $component->get_count()); 834 } else { 835 $countstr = get_string('totalcoursesearchresults', 'backup', $component->get_count()); 836 } 837 838 $output .= html_writer::tag('div', $countstr, array('class' => 'ics-totalresults')); 839 $output .= html_writer::start_tag('div', array('class' => 'ics-results')); 840 841 $table = new html_table(); 842 $table->head = array('', get_string('shortnamecourse'), get_string('fullnamecourse')); 843 $table->data = array(); 844 foreach ($component->get_results() as $course) { 845 $row = new html_table_row(); 846 $row->attributes['class'] = 'ics-course'; 847 if (!$course->visible) { 848 $row->attributes['class'] .= ' dimmed'; 849 } 850 $id = $this->make_unique_id('import-course'); 851 $row->cells = [ 852 html_writer::empty_tag('input', ['type' => 'radio', 'name' => 'importid', 'value' => $course->id, 853 'id' => $id]), 854 html_writer::label( 855 format_string($course->shortname, true, ['context' => context_course::instance($course->id)]), 856 $id, 857 true, 858 ['class' => 'd-block'] 859 ), 860 format_string($course->fullname, true, ['context' => context_course::instance($course->id)]) 861 ]; 862 $table->data[] = $row; 863 } 864 if ($component->has_more_results()) { 865 $cell = new html_table_cell(get_string('moreresults', 'backup')); 866 $cell->colspan = 3; 867 $cell->attributes['class'] = 'notifyproblem'; 868 $row = new html_table_row(array($cell)); 869 $row->attributes['class'] = 'rcs-course'; 870 $table->data[] = $row; 871 } 872 $output .= html_writer::table($table); 873 $output .= html_writer::end_tag('div'); 874 875 $output .= html_writer::start_tag('div', array('class' => 'ics-search form-inline')); 876 $attrs = array( 877 'type' => 'text', 878 'name' => restore_course_search::$VAR_SEARCH, 879 'value' => $component->get_search(), 880 'aria-label' => get_string('searchcourses'), 881 'placeholder' => get_string('searchcourses'), 882 'class' => 'form-control'); 883 $output .= html_writer::empty_tag('input', $attrs); 884 $attrs = array( 885 'type' => 'submit', 886 'name' => 'searchcourses', 887 'value' => get_string('search'), 888 'class' => 'btn btn-secondary ml-1' 889 ); 890 $output .= html_writer::empty_tag('input', $attrs); 891 $output .= html_writer::end_tag('div'); 892 893 $output .= html_writer::end_tag('div'); 894 return $output; 895 } 896 897 /** 898 * Renders a restore category search object 899 * 900 * @param restore_category_search $component 901 * @return string 902 */ 903 public function render_restore_category_search(restore_category_search $component) { 904 $output = html_writer::start_tag('div', array('class' => 'restore-course-search mb-1')); 905 $output .= html_writer::start_tag('div', array('class' => 'rcs-results table-sm w-75')); 906 907 $table = new html_table(); 908 $table->head = array('', get_string('name'), get_string('description')); 909 $table->data = array(); 910 911 if ($component->get_count() !== 0) { 912 foreach ($component->get_results() as $category) { 913 $row = new html_table_row(); 914 $row->attributes['class'] = 'rcs-course'; 915 if (!$category->visible) { 916 $row->attributes['class'] .= ' dimmed'; 917 } 918 $context = context_coursecat::instance($category->id); 919 $id = $this->make_unique_id('restore-category'); 920 $row->cells = [ 921 html_writer::empty_tag('input', ['type' => 'radio', 'name' => 'targetid', 'value' => $category->id, 922 'id' => $id]), 923 html_writer::label( 924 format_string($category->name, true, ['context' => context_coursecat::instance($category->id)]), 925 $id, 926 true, 927 ['class' => 'd-block'] 928 ), 929 format_text(file_rewrite_pluginfile_urls($category->description, 'pluginfile.php', $context->id, 930 'coursecat', 'description', null), $category->descriptionformat, ['overflowdiv' => true]) 931 ]; 932 $table->data[] = $row; 933 } 934 if ($component->has_more_results()) { 935 $cell = new html_table_cell(get_string('moreresults', 'backup')); 936 $cell->attributes['class'] = 'notifyproblem'; 937 $cell->colspan = 3; 938 $row = new html_table_row(array($cell)); 939 $row->attributes['class'] = 'rcs-course'; 940 $table->data[] = $row; 941 } 942 } else { 943 $cell = new html_table_cell(get_string('nomatchingcourses', 'backup')); 944 $cell->colspan = 3; 945 $cell->attributes['class'] = 'notifyproblem'; 946 $row = new html_table_row(array($cell)); 947 $row->attributes['class'] = 'rcs-course'; 948 $table->data[] = $row; 949 } 950 $output .= html_writer::table($table); 951 $output .= html_writer::end_tag('div'); 952 953 $data = [ 954 'inform' => true, 955 'extraclasses' => 'rcs-search mb-3 w-25', 956 'inputname' => restore_category_search::$VAR_SEARCH, 957 'searchstring' => get_string('searchcoursecategories'), 958 'query' => $component->get_search(), 959 ]; 960 $output .= $this->output->render_from_template('core/search_input', $data); 961 962 $output .= html_writer::end_tag('div'); 963 return $output; 964 } 965 966 /** 967 * Get markup to render table for all of a users async 968 * in progress restores. 969 * 970 * @param int $userid The Moodle user id. 971 * @param \context $context The Moodle context for these restores. 972 * @return string $html The table HTML. 973 */ 974 public function restore_progress_viewer ($userid, $context) { 975 $tablehead = array(get_string('course'), get_string('time'), get_string('status', 'backup')); 976 977 $table = new html_table(); 978 $table->attributes['class'] = 'backup-files-table generaltable'; 979 $table->head = $tablehead; 980 $tabledata = array(); 981 982 // Get all in progress async restores for this user. 983 $restores = \async_helper::get_async_restores($userid); 984 985 // For each backup get, new item name, time restore created and progress. 986 foreach ($restores as $restore) { 987 988 $restorename = \async_helper::get_restore_name($context); 989 $timecreated = $restore->timecreated; 990 $status = $this->get_status_display($restore->status, $restore->backupid, $restore->backupid, null, 'restore'); 991 992 $tablerow = array($restorename, userdate($timecreated), $status); 993 $tabledata[] = $tablerow; 994 } 995 996 $table->data = $tabledata; 997 $html = html_writer::table($table); 998 999 return $html; 1000 } 1001 1002 /** 1003 * Get markup to render table for all of a users course copies. 1004 * 1005 * @param int $userid The Moodle user id. 1006 * @param int $courseid The id of the course to get the backups for. 1007 * @return string $html The table HTML. 1008 */ 1009 public function copy_progress_viewer(int $userid, int $courseid): string { 1010 $tablehead = array( 1011 get_string('copysource', 'backup'), 1012 get_string('copydest', 'backup'), 1013 get_string('time'), 1014 get_string('copyop', 'backup'), 1015 get_string('status', 'backup') 1016 ); 1017 1018 $table = new html_table(); 1019 $table->attributes['class'] = 'backup-files-table generaltable'; 1020 $table->head = $tablehead; 1021 1022 $tabledata = array(); 1023 1024 // Get all in progress course copies for this user. 1025 $copies = \copy_helper::get_copies($userid, $courseid); 1026 1027 foreach ($copies as $copy) { 1028 $sourceurl = new \moodle_url('/course/view.php', array('id' => $copy->sourceid)); 1029 1030 $tablerow = array( 1031 html_writer::link($sourceurl, $copy->source), 1032 $copy->destination, 1033 userdate($copy->timecreated), 1034 get_string($copy->operation), 1035 $this->get_status_display($copy->status, $copy->backupid, $copy->restoreid, $copy->operation) 1036 ); 1037 $tabledata[] = $tablerow; 1038 } 1039 1040 $table->data = $tabledata; 1041 $html = html_writer::table($table); 1042 1043 return $html; 1044 } 1045 } 1046 1047 /** 1048 * Data structure representing backup files viewer 1049 * 1050 * @copyright 2010 Dongsheng Cai 1051 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 1052 * @since Moodle 2.0 1053 */ 1054 class backup_files_viewer implements renderable { 1055 1056 /** 1057 * @var array 1058 */ 1059 public $files; 1060 1061 /** 1062 * @var context 1063 */ 1064 public $filecontext; 1065 1066 /** 1067 * @var string 1068 */ 1069 public $component; 1070 1071 /** 1072 * @var string 1073 */ 1074 public $filearea; 1075 1076 /** 1077 * @var context 1078 */ 1079 public $currentcontext; 1080 1081 /** 1082 * Constructor of backup_files_viewer class 1083 * @param array $options 1084 */ 1085 public function __construct(array $options = null) { 1086 global $CFG, $USER; 1087 $fs = get_file_storage(); 1088 $this->currentcontext = $options['currentcontext']; 1089 $this->filecontext = $options['filecontext']; 1090 $this->component = $options['component']; 1091 $this->filearea = $options['filearea']; 1092 $files = $fs->get_area_files($this->filecontext->id, $this->component, $this->filearea, false, 'timecreated'); 1093 $this->files = array_reverse($files); 1094 } 1095 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body