. /** * Format tiles external API * * @package format_tiles * @copyright 2018 David Watson {@link http://evolutioncode.uk} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ use format_tiles\tile_photo; defined('MOODLE_INTERNAL') || die; global $CFG; require_once("$CFG->libdir/externallib.php"); require_once($CFG->dirroot . '/course/format/tiles/locallib.php'); /** * Format tiles external functions * * @package format_tiles * @category external * @copyright 2018 David Watson {@link http://evolutioncode.uk} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * @since Moodle 3.3 */ class format_tiles_external extends external_api { /** * Teacher is changing the icon for a course section or whole course using AJAX * @param Integer $courseid the id of this course * @param Integer $sectionid the number of the section in this course - zero if whole course * @param String $filename the icon filename or photo filename for this tile. * @param string $imagetype whether it's a tile icon or a background photo. * @param int $sourcecontextid the context id of the source photo or icon. * @param int $sourceitemid the item id of the course photo or icon. * @return [] status and image URL if applicable. * @throws dml_exception * @throws invalid_parameter_exception * @throws moodle_exception * @throws required_capability_exception * @throws restricted_context_exception */ public static function set_image( $courseid, $sectionid, $filename, $imagetype = 'tileicon', $sourcecontextid = 0, $sourceitemid = 0 ) { global $DB; $data = self::validate_parameters(self::set_image_parameters(), array( 'courseid' => $courseid, 'sectionid' => $sectionid, 'image' => $filename, 'sourcecontextid' => $sourcecontextid, 'sourceitemid' => $sourceitemid, 'imagetype' => $imagetype ) ); // Section id of zero means we are changing the course icon. Otherwise check sec id is valid. if ($data['sectionid'] !== 0 && $DB->get_record('course_sections', array('course' => $data['courseid'], 'id' => $data['sectionid'])) === false) { throw new invalid_parameter_exception('Invalid course and section id combination'); } $context = context_course::instance($data['courseid']); self::validate_context($context); require_capability('moodle/course:viewhiddenactivities', $context); // This allows non-editing teachers for the course. switch ($data['imagetype']) { case 'tileicon': $result = self::set_tile_icon($data); break; case 'tilephoto': if (!get_config('format_tiles', 'allowphototiles')) { throw new invalid_parameter_exception("Photo tiles are disabled by site admin"); } $result = self::set_tile_photo($data); break; case 'draftfile': $result = self::set_tile_photo_from_draftfile($data); break; default: throw new invalid_parameter_exception('Image type is invalid ' . $data['imagetype']); } return $result; } /** * Given a draft file uploaded by user, save top this plugin's file area. * @param [] $data * @return array * @throws dml_exception * @throws file_exception * @throws invalid_parameter_exception * @throws moodle_exception * @throws required_capability_exception * @throws stored_file_creation_exception */ private static function set_tile_photo_from_draftfile($data) { if (!$data['sourcecontextid'] || !$data['sourceitemid']) { throw new invalid_parameter_exception("Invalid source context id or source item id"); } $tilephoto = new tile_photo($data['courseid'], $data['sectionid']); $fs = get_file_storage(); $sourcefile = $fs->get_file( $data['sourcecontextid'], 'user', 'draft', $data['sourceitemid'], '/', $data['image'] ); $newfile = $tilephoto->set_file_from_stored_file($sourcefile, $data['image']); if ($newfile) { return array( 'status' => true, 'imageurl' => $tilephoto->get_image_url() ); } else { return array( 'status' => false, 'imageurl' => '' ); } } /** * Given the data describing the photo we want and the tile to apply it to, set the tile to use that photo. * @param [] $data * @return array * @throws coding_exception * @throws dml_exception * @throws file_exception * @throws invalid_parameter_exception * @throws moodle_exception * @throws required_capability_exception * @throws stored_file_creation_exception */ private static function set_tile_photo($data) { $sourcecontext = context::instance_by_id($data['sourcecontextid']); $issettingsampleimage = $sourcecontext->contextlevel == CONTEXT_SYSTEM && $data['sourceitemid'] == 0 & $data['image'] == 'sample_image.jpg'; if (!$data['sourcecontextid'] || (!$data['sourceitemid'] && !$issettingsampleimage)) { throw new invalid_parameter_exception("Invalid source context id or source item id"); } if ($sourcecontext->contextlevel !== CONTEXT_COURSE && !$issettingsampleimage) { throw new InvalidArgumentException("Invalid context level"); } if ($data['sourcecontextid'] &&!$issettingsampleimage) { // Arguably we don't need to do this as the only files the user will see are those they posted themselves. // This is thanks to the database query which generates the files list. So they could see them once. require_capability('moodle/course:viewhiddenactivities', $sourcecontext); } $courseid = $sourcecontext->instanceid; if ($issettingsampleimage) { $sourcefile = tile_photo::get_sample_image_file(); } else { $sourcephoto = new tile_photo($courseid, $data['sourceitemid']); $sourcefile = $sourcephoto->get_file(); } $tilephoto = new tile_photo($data['courseid'], $data['sectionid']); $file = $tilephoto->set_file_from_stored_file($sourcefile, $data['image']); if ($file) { return array( 'status' => true, 'imageurl' => $tilephoto->get_image_url() ); } else { return array( 'status' => false, 'imageurl' => '' ); } } /** * Given the data describing the icon we want and the tile to apply it to, set the tile to use that icon * @param [] $data * @return array * @throws coding_exception * @throws dml_exception * @throws invalid_parameter_exception */ private static function set_tile_icon($data) { global $DB; $availableicons = (new \format_tiles\icon_set)->available_tile_icons($data['courseid']); if (!isset($availableicons[$data['image']])) { throw new invalid_parameter_exception('Icon is invalid'); } if ($data['sectionid'] === 0) { $optionname = 'defaulttileicon'; // All default icon for whole course. } else { $optionname = 'tileicon'; // Icon for just this tile. } $existingicon = $DB->get_record( 'course_format_options', ['format' => 'tiles', 'name' => $optionname, 'courseid' => $data['courseid'], 'sectionid' => $data['sectionid']] ); if (!isset($existingicon->value)) { // No icon is presently stored for this so we need to insert new record. $record = new stdClass(); $record->format = 'tiles'; $record->courseid = $data['courseid']; $record->sectionid = $data['sectionid']; $record->name = $optionname; $record->value = $data['image']; $result = $DB->insert_record('course_format_options', $record); } else if ($data['sectionid'] != 0) { // We are dealing with a tile icon for one particular section, so check if user has picked the course default. $defaulticonthiscourse = $DB->get_record( 'course_format_options', ['format' => 'tiles', 'name' => 'defaulttileicon', 'courseid' => $data['courseid'], 'sectionid' => 0] )->value; if ($data['image'] == $defaulticonthiscourse) { // Using default icon for a tile do don't store anything in database = default. $result = $DB->delete_records( 'course_format_options', ['format' => 'tiles', 'name' => 'tileicon', 'courseid' => $data['courseid'], 'sectionid' => $data['sectionid']] ); } else { // User has not picked default and there is an existing record so update it. $existingicon->value = $data['image']; $result = $DB->update_record('course_format_options', $existingicon); } } else { // Updating existing course icon record. $existingicon->value = $data['image']; $result = $DB->update_record('course_format_options', $existingicon); } if ($data['sectionid'] !== 0) { // If there is a tile photo attached to this tile, clear it. $tilephoto = new tile_photo($data['courseid'], $data['sectionid']); $tilephoto->clear(); } return array( 'status' => $result ? true : false, 'imageurl' => '' ); } /** * Returns description of get_instance_info() parameters. * * @return external_function_parameters */ public static function set_image_parameters() { return new external_function_parameters( array( 'courseid' => new external_value(PARAM_INT, 'Course id whose icon/image we are setting'), 'sectionid' => new external_value( PARAM_INT, 'Section id whose icon/imasge we are setting (zero means whole course not just one section)' ), 'image' => new external_value(PARAM_RAW, 'File name for the image picked'), 'imagetype' => new external_value(PARAM_RAW, 'Image type for image picked (tileicon, tilephoto, draftfile)'), 'sourcecontextid' => new external_value( PARAM_INT, 'File table context id for the photo file picked (0 if unused)', VALUE_DEFAULT, 0 ), 'sourceitemid' => new external_value( PARAM_INT, 'File table item id for the photo file picked (0 if unused)', VALUE_DEFAULT, 0 ) ) ); } /** * Returns description of method result value * @return external_description */ public static function set_image_returns() { return new external_single_structure(array( 'status' => new external_value(PARAM_BOOL, 'Whether the image was set'), 'imageurl' => new external_value(PARAM_RAW, 'Image URL if background photo set (not used for icons)'), )); } /** * Get the HTML for a single section page for a course * (i.e. the list of activities and resources comprising the contents of a tile) * Intended to be called from AJAX so that the result can be added to the multi * tiles page by JS * * The method returns the HTML rather than the underlying course data to save making * another round trip to the server to render the HTML from the data, via the mustache * template. This would have been another way of doing it, and would be easy to achieve * by calling the template from JS. * * @param int $courseid * @param int $sectionid we want to display * @param boolean $setjsusedsession whether to set the session jsenabled flag to true * @return array of warnings and status result * @since Moodle 3.0 * @throws moodle_exception */ public static function get_single_section_page_html($courseid, $sectionid, $setjsusedsession = false) { global $PAGE, $SESSION; $params = self::validate_parameters( self::get_single_section_page_html_parameters(), array( 'courseid' => $courseid, 'sectionid' => $sectionid, 'setjsusedsession' => $setjsusedsession ) ); // Request and permission validation. // Ensure user has access to course context. // validate_context() below ends up calling require_login($courseid). $context = context_course::instance($params['courseid']); self::validate_context($context); $course = get_course($params['courseid']); $renderer = $PAGE->get_renderer('format_tiles'); $templateable = new \format_tiles\output\course_output($course, true, $params['sectionid']); $data = $templateable->export_for_template($renderer); $result = array( 'html' => $renderer->render_from_template('format_tiles/single_section', $data) ); // This session var is used later, when user revisits main course page, or a single section, for a course using this format. // If set to true, the page can safely be rendered from PHP in the javascript friendly format. // (A