dirroot.'/repository/lib.php'); use local_treestudyplan\aggregator; use local_treestudyplan\studyplan; use local_treestudyplan\premium; use local_treestudyplan\debug; use local_treestudyplan\studyplanservice; use local_treestudyplan\courseservice; use local_treestudyplan\form\text_integer; use moodle_exception; use stdClass; /** * Moodleform class for the studyplan editor. A Moodleform is used here to facilitate a rich editor * in the studyplan description */ class studyplan_editform extends formbase { /** * Capability required to edit study plans * @var string */ const CAP_EDIT = "local/treestudyplan:editstudyplan"; /** * Translate parameters into customdata. * * @param object $params The parameters for form initialization * @return array Form data based on parameters */ public static function init_customdata(object $params) { $customdata = new stdClass; $customdata->create = $params->mode=='create' ? true : false; if ($customdata->create) { $customdata->context = \context::instance_by_id($params->contextid); } else { $customdata->plan = studyplan::find_by_id($params->studyplan_id); $customdata->context = $customdata->plan->context(); $customdata->simplemodel = $customdata->plan->simple_model(); } $customdata->editoroptions = [ 'trusttext' => true, 'subdirs' => true, 'maxfiles' => 20, 'maxbytes' => 20*1024*1024, 'context' => \context_system::instance(), // Keep the files in system context ]; $customdata->fileoptions = [ 'subdirs' => 0, 'maxbytes' => 10*1024*1024, // Max 10MiB should be sufficient for a picture. 'areamaxbytes' => 10485760, 'maxfiles' => 1, // Just one file 'accepted_types' => ['.jpg', '.png', '.jpeg', '.svg', '.svgz'], 'return_types' => \FILE_INTERNAL | \FILE_EXTERNAL, ]; return $customdata; } /** * Validate security access for this form based on the customdata generated by init_customdata * Return true if validation passes, false or throw an exception if it does not. * * @param object $customdata The customdata for this form * @return bool True if security validation passes. * @throws \moodle_exception if access denied for a specific reason. */ public static function check_security(object $customdata) { /*webservicehelper::require_capabilities(self::CAP_EDIT, $customdata->context); */ } /** * Generate form data from parameters * Also validate parameters and access permissions here * * @param object $customdata The parameters for form initialization * @return array Form data based on parameters */ public function init_formdata(object $customdata) { global $DB; /* Use direct database retrieval to avoid our abstractions causing trouble with existing moodle code assumptions. The form API does seem needlessly convoluted in it's use, but we need the editor... */ if ($customdata->create) { $entry = new stdClass; $entry->context_id = $customdata->context->id; $entry->aggregation = get_config("local_treestudyplan", "aggregation_mode"); $agcfg = json_decode(aggregator::create($entry->aggregation, "")->config_string(), true); // Determine the next august 1st for default value purposes. $august = strtotime("first day of august this year"); if ($august < time()) { $august = strtotime("first day of august next year"); } $entry->startdate = $august; $entry->enddate = $august + (364*24*60*60); // Not bothering about leap years here. $entry->periods = 4; } else { $entry = $DB->get_record(studyplan::TABLE, ['id' => $customdata->plan->id()]); $entry->startdate = strtotime($customdata->simplemodel['pages'][0]['startdate']); $entry->enddate = strtotime($customdata->simplemodel['pages'][0]['enddate']); $entry->periods = $customdata->simplemodel['pages'][0]['periods']; $agcfg = json_decode($customdata->plan->aggregator()->config_string(), true); } // Prepare the editor $entry = file_prepare_standard_editor( $entry, 'description', $customdata->editoroptions, \context_system::instance(), 'local_treestudyplan', 'studyplan', ($customdata->create) ? null : $customdata->plan->id() ); // Prepare file area for the icon // Get an unused draft itemid which will be used for this form. $draftitemid = file_get_submitted_draft_itemid('icon'); file_prepare_draft_area( // The $draftitemid is the target location. $draftitemid, // The combination of contextid / component / filearea / itemid // form the virtual bucket that files are currently stored in // and will be copied from. \context_system::instance()->id, 'local_treestudyplan', 'icon', ($customdata->create) ? null : $customdata->plan->id(), $customdata->fileoptions ); $entry->icon = $draftitemid; // Add aggregation configs to entry. foreach ($agcfg as $key => $val) { $entrykey = $entry->aggregation."_".$key; $entry->$entrykey = $val; } return $entry; } /** * Set up the form definition */ public function definition() { $mform = $this->_form; $customdata = (object)$this->_customdata; // Register integer type text_integer::Register(); // Define the form $field = 'name'; $mform->addElement('text', $field, get_string('studyplan_name', 'local_treestudyplan'), []); $mform->addRule($field, null, 'required', null, 'client'); $field = 'shortname'; $mform->addElement('text', $field, get_string('studyplan_shortname', 'local_treestudyplan'), []); $mform->addRule($field, null, 'required', null, 'client'); $field = 'idnumber'; $mform->addElement('text', $field, get_string('studyplan_idnumber', 'local_treestudyplan'), []); $contextlist = []; foreach(courseservice::list_available_categories('edit') as $c) { $contextlist[$c['context_id']] = implode(" / ", $c['category']['path']); } $mform->addElement('autocomplete', 'context_id', get_string('studyplan_context', 'local_treestudyplan'), $contextlist); $mform->addRule('context_id', null, 'required', null, 'client'); $mform->addElement( 'filemanager', 'icon', get_string('studyplan_icon', 'local_treestudyplan'), null, $customdata->fileoptions ); if ($customdata->create) { $timeless = \get_config("local_treestudyplan", "timelessperiods"); if ( !$timeless) { // Only add these fields if a new studyplan is created, to easily initialize the first page $field = 'startdate'; $mform->addElement('date_selector', $field, get_string('studyplan_startdate', 'local_treestudyplan'), []); $mform->addRule($field, null, 'required', null, 'client'); $field = 'enddate'; $mform->addElement('date_selector', $field, get_string('studyplan_enddate', 'local_treestudyplan'), []); $mform->addRule($field, null, 'required', null, 'client'); } $field = 'periods'; $mform->addElement('text_integer', $field, get_string('studyplan_slots', 'local_treestudyplan'), ["unsigned" => true]); $mform->setType($field, PARAM_INT); $mform->addRule($field, null, 'required', null, 'client'); } else { // These fields are only relevant if the studyplan is edited $field = 'suspended'; $mform->addElement('advcheckbox', $field, get_string('studyplan_suspend', 'local_treestudyplan'), get_string('studyplan_suspend_details', 'local_treestudyplan'), [], ); if (premium::enabled()) { $field = 'template'; $mform->addElement('advcheckbox', $field, get_string('studyplan_template', 'local_treestudyplan'), get_string('studyplan_template_details', 'local_treestudyplan'), [], ); } } $aggregators = []; foreach(aggregator::list_model() as $a) { // Add method only if not deprecated or currently used if ( $customdata->simplemodel['aggregation'] == $a['id'] || !($a['deprecated']) ) { $aggregators[$a['id']] = $a['name']; } } $field = 'aggregation'; $mform->addElement('select', $field, get_string('choose_aggregation_style', 'local_treestudyplan'), $aggregators); /* Start Bistate aggregation specific items */ $field = 'bistate_thresh_excellent'; $mform->addElement('text_integer', $field, get_string('setting_bistate_thresh_excellent', 'local_treestudyplan'), ["unsigned" => false], ); $mform->setType($field, PARAM_INT); $mform->hideif ($field, "aggregation", "neq", "bistate"); $field = 'bistate_thresh_good'; $mform->addElement('text_integer', $field, get_string('setting_bistate_thresh_good', 'local_treestudyplan'), ["unsigned" => true], ); $mform->setType($field, PARAM_INT); $mform->hideif ($field, "aggregation", "neq", "bistate"); $field = 'bistate_thresh_completed'; $mform->addElement('text_integer', $field, get_string('setting_bistate_thresh_completed', 'local_treestudyplan'), ["unsigned" => true], ); $mform->setType($field, PARAM_INT); $mform->hideif ($field, "aggregation", "neq", "bistate"); $field = 'bistate_use_failed'; $mform->addElement('checkbox', $field, get_string('setting_bistate_support_failed', 'local_treestudyplan'), [], ); $mform->hideif ($field, "aggregation", "neq", "bistate"); $field = 'bistate_accept_pending_as_submitted'; $mform->addElement('checkbox', $field, get_string('setting_bistate_accept_pending_submitted', 'local_treestudyplan'), [], ); $mform->hideif ($field, "aggregation", "neq", "bistate"); /* End Bistate aggregation specific items */ $mform->addElement('editor', 'description_editor', get_string('studyplan_description', 'local_treestudyplan'), null, $customdata->editoroptions); $mform->setType('description_editor', PARAM_RAW); } /** * Process the submitted data and perform necessary actions * @param object $entry The processed form data; * @return bool false if submission not successful * @throws \moodle_exception if an error must be given for a specific reason. */ protected function process_submitted_data($entry) { $customdata = (object)$this->_customdata; // Add aggregation configs to entry. $agcfg = json_decode(aggregator::create($entry->aggregation, "")->config_string(), true); // Retrieve default config string from selected aggregation method foreach ($agcfg as $key => $val) { $entrykey = $entry->aggregation."_".$key; $agcfg[$key] = $entry->$entrykey; } $aggregationconfig = json_encode($agcfg); if ($customdata->create) { // Use our own abstraction to create the record, so caches are maintained $plan = studyplan::add(['name' => $entry->name, 'shortname' => $entry->shortname, 'idnumber' => $entry->idnumber, 'context_id' => $entry->context_id, 'aggregation' => $entry->aggregation, 'aggregation_config' => $aggregationconfig, 'startdate' => date("Y-m-d", $entry->startdate), 'enddate' => date("Y-m-d", $entry->enddate), 'periods' => $entry->periods, ]); // Process the provided files in the description editor $entry = file_postupdate_standard_editor($entry, 'description', $customdata->editoroptions, \context_system::instance(), 'local_treestudyplan', 'studyplan', $plan->id()); // Update the description $plan->edit([ 'description' => $entry->description, 'descriptionformat' => $entry->descriptionformat, ]); } else { $plan = $customdata->plan; // Process the provided files in the description editor $entry = file_postupdate_standard_editor($entry, 'description', $customdata->editoroptions, \context_system::instance(), 'local_treestudyplan', 'studyplan', $plan->id()); // Use our own abstraction to update the record, so caches are maintained $plan->edit(['name' => $entry->name, 'shortname' => $entry->shortname, 'idnumber' => $entry->idnumber, 'context_id' => $entry->context_id, 'description' => $entry->description, 'descriptionformat' => $entry->descriptionformat, 'aggregation' => $entry->aggregation, 'aggregation_config' => $aggregationconfig, 'suspended' => $entry->suspended, 'template' => $entry->template, ]); } // Now save the icon file in correct part of the File API. file_save_draft_area_files( // The $entry->icon property contains the itemid of the draft file area. $entry->icon, // The combination of contextid / component / filearea / itemid // form the virtual bucket that file are stored in. \context_system::instance()->id, 'local_treestudyplan', 'icon', $plan->id(), $customdata->fileoptions ); /* Return the simple model of the plan to make sure we can update stuff. Parse it through the clean_returnvalue function of exernal api (of which studyplanservice is a subclass) so we return it in a consistent way */ $response = studyplanservice::clean_returnvalue(studyplan::simple_structure(), $plan->simple_model()); debug::dump($response); return $response; } }