diff --git a/mod/data/classes/local/importer/preset_importer.php b/mod/data/classes/local/importer/preset_importer.php index 5b898290845..c5e48682847 100644 --- a/mod/data/classes/local/importer/preset_importer.php +++ b/mod/data/classes/local/importer/preset_importer.php @@ -19,6 +19,7 @@ namespace mod_data\local\importer; use mod_data\manager; use mod_data\preset; use stdClass; +use html_writer; /** * Abstract class used for data preset importers @@ -212,7 +213,7 @@ abstract class preset_importer { * @return bool Wether the importing has been successful. */ public function import(bool $overwritesettings): bool { - global $DB; + global $DB, $OUTPUT; $params = $this->get_preset_settings(); $settings = $params->settings; @@ -252,7 +253,12 @@ abstract class preset_importer { unset($fieldobject); } else { /* Make a new field */ - include_once("field/$newfield->type/field.class.php"); + $filepath = "field/$newfield->type/field.class.php"; + if (!file_exists($filepath)) { + $missingfieldtypes[] = $newfield->name; + continue; + } + include_once($filepath); if (!isset($newfield->description)) { $newfield->description = ''; @@ -263,6 +269,9 @@ abstract class preset_importer { unset($fieldclass); } } + if (!empty($missingfieldtypes)) { + echo $OUTPUT->notification(get_string('missingfieldtypeimport', 'data') . html_writer::alist($missingfieldtypes)); + } } // Get rid of all old unused data. diff --git a/mod/data/classes/output/template_editor_tools.php b/mod/data/classes/output/template_editor_tools.php index 54915c95c87..5302bfa0fed 100644 --- a/mod/data/classes/output/template_editor_tools.php +++ b/mod/data/classes/output/template_editor_tools.php @@ -84,6 +84,9 @@ class template_editor_tools implements templatable, renderable { $taglist = []; $fields = $this->manager->get_fields(); foreach ($fields as $field) { + if ($field->type === 'unknown') { + continue; + } $fieldname = $field->get_name(); $taglist["[[$fieldname]]"] = $fieldname; } @@ -105,6 +108,9 @@ class template_editor_tools implements templatable, renderable { // Field IDs. $fields = $this->manager->get_fields(); foreach ($fields as $field) { + if ($field->type === 'unknown') { + continue; + } $fieldname = $field->get_name(); $taglist["[[$fieldname#id]]"] = "$fieldname id"; } diff --git a/mod/data/classes/search/entry.php b/mod/data/classes/search/entry.php index dc65a5751e7..95ee8a0a95c 100644 --- a/mod/data/classes/search/entry.php +++ b/mod/data/classes/search/entry.php @@ -299,6 +299,10 @@ class entry extends \core_search\base_mod { foreach ($filteredcontents as $content) { $classname = $this->get_field_class_name($content->fieldtype); + if (!$classname) { + $content->addtemplateposition = -1; + continue; + } $content->priority = $classname::get_priority(); $content->addtemplateposition = strpos($template, '[['.$content->fldname.']]'); } @@ -346,16 +350,22 @@ class entry extends \core_search\base_mod { } /** - * Returns the class name for that field type and includes it. + * Returns the class name for the given field type and includes it. * * @param string $fieldtype - * @return string + * @return string|null It will return the class name or null if the field type is not available. */ protected function get_field_class_name($fieldtype) { global $CFG; $fieldtype = trim($fieldtype); - require_once($CFG->dirroot . '/mod/data/field/' . $fieldtype . '/field.class.php'); + + $fieldpath = $CFG->dirroot . '/mod/data/field/' . $fieldtype . '/field.class.php'; + if (!file_exists($fieldpath)) { + return null; + } + + require_once($fieldpath); return 'data_field_' . $fieldtype; } diff --git a/mod/data/classes/template.php b/mod/data/classes/template.php index fec7402f261..96c2097e389 100644 --- a/mod/data/classes/template.php +++ b/mod/data/classes/template.php @@ -850,7 +850,17 @@ class template { $errors .= $renderer->notification($notification); } } - $replacements[] = $errors . $field->display_add_field($entryid, $entrydata); + $fielddisplay = ''; + if ($field->type === 'unknown') { + if ($this->canmanageentries) { // Display notification for users that can manage entries. + $errors .= $renderer->notification(get_string('missingfieldtype', 'data', + (object)['name' => $field->field->name])); + } + } else { + $fielddisplay = $field->display_add_field($entryid, $entrydata); + } + + $replacements[] = $errors . $fielddisplay; } // Replace the field id tag. diff --git a/mod/data/field.php b/mod/data/field.php index 156c753d7a2..b5f02983d2f 100644 --- a/mod/data/field.php +++ b/mod/data/field.php @@ -260,9 +260,15 @@ switch ($mode) { // Print confirmation message. $field = data_get_field_from_id($fid, $data); - echo $OUTPUT->confirm(''.$field->name().': '.$field->field->name.'

'. get_string('confirmdeletefield','data'), - 'field.php?d='.$data->id.'&mode=delete&fid='.$fid.'&confirm=1', - 'field.php?d='.$data->id); + if ($field->type === 'unknown') { + $fieldtypename = get_string('unknown', 'data'); + } else { + $fieldtypename = $field->name(); + } + echo $OUTPUT->confirm(''.$fieldtypename.': '.$field->field->name.'

'. + get_string('confirmdeletefield', 'data'), + 'field.php?d='.$data->id.'&mode=delete&fid='.$fid.'&confirm=1', + 'field.php?d='.$data->id); echo $OUTPUT->footer(); exit; @@ -317,6 +323,9 @@ $plugins = core_component::get_plugin_list('datafield'); $menufield = array(); foreach ($plugins as $plugin=>$fulldir){ + if (!is_dir($fulldir)) { + continue; + } $menufield[$plugin] = get_string('pluginname', 'datafield_'.$plugin); //get from language files } asort($menufield); //sort in alphabetical order @@ -360,6 +369,7 @@ if (($mode == 'new') && (!empty($newtype))) { // Adding a new field. $table->wrap = array(false,false,false,false); if ($fff = $DB->get_records('data_fields', array('dataid'=>$data->id),'id')){ + $missingfieldtypes = []; foreach ($fff as $ff) { $field = data_get_field($ff, $data); @@ -378,15 +388,30 @@ if (($mode == 'new') && (!empty($newtype))) { // Adding a new field. 'mode' => 'delete', )); - $table->data[] = array( - html_writer::link($displayurl, $field->field->name), - $field->image() . ' ' . $field->name(), + // It display a notification when the field type does not exist. + $deletelink = html_writer::link($deleteurl, $OUTPUT->pix_icon('t/delete', get_string('delete'))); + $editlink = html_writer::link($displayurl, $OUTPUT->pix_icon('t/edit', get_string('edit'))); + if ($field->type === 'unknown') { + $missingfieldtypes[] = $field->field->name; + $fieldnamedata = $field->field->name; + $fieltypedata = $field->field->type; + $fieldlinkdata = $deletelink; + } else { + $fieldnamedata = html_writer::link($displayurl, $field->field->name); + $fieltypedata = $field->image() . ' ' . $field->name(); + $fieldlinkdata = $editlink . ' ' . $deletelink; + } + + $table->data[] = [ + $fieldnamedata, + $fieltypedata, $field->field->required ? get_string('yes') : get_string('no'), shorten_text($field->field->description, 30), - html_writer::link($displayurl, $OUTPUT->pix_icon('t/edit', get_string('edit'))) . - ' ' . - html_writer::link($deleteurl, $OUTPUT->pix_icon('t/delete', get_string('delete'))), - ); + $fieldlinkdata + ]; + } + if (!empty($missingfieldtypes)) { + echo $OUTPUT->notification(get_string('missingfieldtypes', 'data') . html_writer::alist($missingfieldtypes)); } } echo html_writer::table($table); diff --git a/mod/data/lang/en/data.php b/mod/data/lang/en/data.php index 9ceef0f8d69..2f95d831a5d 100644 --- a/mod/data/lang/en/data.php +++ b/mod/data/lang/en/data.php @@ -238,6 +238,11 @@ $string['invalidfieldid'] = 'Field ID is incorrect'; $string['invalidfieldname'] = 'Please choose another name for this field'; $string['invalidfieldtype'] = 'Field type is incorrect'; $string['invalidid'] = 'Incorrect data ID'; +$string['missingfieldtype'] = 'Field type for {$a->name} not found'; +$string['missingfieldtypes'] = 'The following fields do not have their corresponding field types installed and will not be included in the forms when adding or editing entries. + Their labels may still show on the form, so please update the "Add entry template" accordingly:'; +$string['missingfieldtypeimport'] = 'The following fields were not imported because their corresponding field types are not installed:'; +$string['unknown'] = 'Unknown field'; $string['invalidpreset'] = '{$a} is not a preset.'; $string['invalidrecord'] = 'Incorrect record'; $string['invalidurl'] = 'The URL you just entered is not valid'; diff --git a/mod/data/lib.php b/mod/data/lib.php index 9643740463f..8760c2e23eb 100644 --- a/mod/data/lib.php +++ b/mod/data/lib.php @@ -400,6 +400,12 @@ class data_field_base { // Base class for Database Field Types (see field/*/ if (empty($this->field)) { // No field has been defined yet, try and make one $this->define_default_field(); } + + // Throw an exception if field type doen't exist. Anyway user should never access to edit a field with an unknown fieldtype. + if ($this->type === 'unknown') { + throw new \moodle_exception(get_string('missingfieldtype', 'data', (object)['name' => $this->field->name])); + } + echo $OUTPUT->box_start('generalbox boxaligncenter boxwidthwide'); echo '
'."\n"; @@ -417,7 +423,14 @@ class data_field_base { // Base class for Database Field Types (see field/*/ echo $OUTPUT->heading($this->name(), 3); - require_once($CFG->dirroot.'/mod/data/field/'.$this->type.'/mod.html'); + + $filepath = $CFG->dirroot.'/mod/data/field/'.$this->type.'/mod.html'; + + if (!file_exists($filepath)) { + throw new \moodle_exception(get_string('missingfieldtype', 'data', (object)['name' => $this->field->name])); + } else { + require_once($filepath); + } echo html_writer::start_div('mt-3'); echo html_writer::tag('input', null, array('type' => 'submit', 'value' => $savebutton, @@ -916,7 +929,12 @@ function data_get_field_from_id($fieldid, $data){ function data_get_field_new($type, $data) { global $CFG; - require_once($CFG->dirroot.'/mod/data/field/'.$type.'/field.class.php'); + $filepath = $CFG->dirroot.'/mod/data/field/'.$type.'/field.class.php'; + // It should never access this method if the subfield class doesn't exist. + if (!file_exists($filepath)) { + throw new \moodle_exception('invalidfieldtype', 'data'); + } + require_once($filepath); $newfield = 'data_field_'.$type; $newfield = new $newfield(0, $data); return $newfield; @@ -928,20 +946,24 @@ function data_get_field_new($type, $data) { * input: $param $field - record from db * * @global object - * @param object $field - * @param object $data - * @param object $cm - * @return data_field_base + * @param stdClass $field the field record + * @param stdClass $data the data instance + * @param stdClass|null $cm optional course module data + * @return data_field_base the field object instance or data_field_base if unkown type */ function data_get_field($field, $data, $cm=null) { global $CFG; - - if ($field) { - require_once('field/'.$field->type.'/field.class.php'); - $newfield = 'data_field_'.$field->type; - $newfield = new $newfield($field, $data, $cm); - return $newfield; + if (!isset($field->type)) { + return new data_field_base($field); } + $filepath = $CFG->dirroot.'/mod/data/field/'.$field->type.'/field.class.php'; + if (!file_exists($filepath)) { + return new data_field_base($field); + } + require_once($filepath); + $newfield = 'data_field_'.$field->type; + $newfield = new $newfield($field, $data, $cm); + return $newfield; } @@ -1764,6 +1786,10 @@ function data_print_preference_form($data, $perpage, $search, $sort='', $order=' $fieldname = preg_quote($fieldname, '/'); $patterns[] = "/\[\[$fieldname\]\]/i"; $searchfield = data_get_field_from_id($field->field->id, $data); + + if ($searchfield->type === 'unknown') { + continue; + } if (!empty($search_array[$field->field->id]->data)) { $replacement[] = $searchfield->display_search_field($search_array[$field->field->id]->data); } else { @@ -2450,7 +2476,7 @@ abstract class data_preset_importer { * @return bool */ function import($overwritesettings) { - global $DB, $CFG; + global $DB, $CFG, $OUTPUT; $params = $this->get_preset_settings(); $settings = $params->settings; @@ -2471,7 +2497,7 @@ abstract class data_preset_importer { } else $preservedfields[$cid] = true; } - + $missingfieldtypes = []; foreach ($newfields as $nid => $newfield) { $cid = optional_param("field_$nid", -1, PARAM_INT); @@ -2488,7 +2514,12 @@ abstract class data_preset_importer { unset($fieldobject); } else { /* Make a new field */ - include_once("field/$newfield->type/field.class.php"); + $filepath = "field/$newfield->type/field.class.php"; + if (!file_exists($filepath)) { + $missingfieldtypes[] = $newfield->name; + continue; + } + include_once($filepath); if (!isset($newfield->description)) { $newfield->description = ''; @@ -2499,6 +2530,9 @@ abstract class data_preset_importer { unset($fieldclass); } } + if (!empty($missingfieldtypes)) { + echo $OUTPUT->notification(get_string('missingfieldtypeimport', 'data') . html_writer::alist($missingfieldtypes)); + } } /* Get rid of all old unused data */ @@ -2954,7 +2988,12 @@ function data_import_csv($cm, $data, &$csvdata, $encoding, $fielddelimiter) { unset($fieldnames[$id]); // To ensure the user provided content fields remain in the array once flipped. } else { $field = $rawfields[$name]; - require_once("$CFG->dirroot/mod/data/field/$field->type/field.class.php"); + $filepath = "$CFG->dirroot/mod/data/field/$field->type/field.class.php"; + if (!file_exists($filepath)) { + $errorfield .= "'$name' "; + continue; + } + require_once($filepath); $classname = 'data_field_' . $field->type; $fields[$name] = new $classname($field, $data, $cm); } diff --git a/mod/data/locallib.php b/mod/data/locallib.php index 37cf3e805ec..dad60d6d662 100644 --- a/mod/data/locallib.php +++ b/mod/data/locallib.php @@ -270,7 +270,11 @@ class data_portfolio_caller extends portfolio_module_caller_base { return true; // too early yet } foreach ($this->fieldtypes as $key => $field) { - require_once($CFG->dirroot . '/mod/data/field/' . $field .'/field.class.php'); + $filepath = $CFG->dirroot . '/mod/data/field/' . $field .'/field.class.php'; + if (!file_exists($filepath)) { + continue; + } + require_once($filepath); $this->fields[$key] = unserialize(serialize($this->fields[$key])); } } @@ -974,7 +978,11 @@ function data_get_tag_title_field($dataid) { if ($field->addtemplateposition === false) { continue; } - require_once($CFG->dirroot . '/mod/data/field/' . $field->type . '/field.class.php'); + $filepath = $CFG->dirroot . '/mod/data/field/' . $field->type . '/field.class.php'; + if (!file_exists($filepath)) { + continue; + } + require_once($filepath); $classname = 'data_field_' . $field->type; $field->priority = $classname::get_priority(); $filteredfields[] = $field; @@ -1005,11 +1013,19 @@ function data_get_tag_title_field($dataid) { * * @param stdClass $field The field from the 'data_fields' table * @param stdClass $entry The entry from the 'data_records' table - * @return string The title of the entry + * @return string|null It will return the title of the entry or null if the field type is not available. */ function data_get_tag_title_for_entry($field, $entry) { global $CFG, $DB; - require_once($CFG->dirroot . '/mod/data/field/' . $field->type . '/field.class.php'); + + if (!isset($field->type)) { + return null; + } + $filepath = $CFG->dirroot . '/mod/data/field/' . $field->type . '/field.class.php'; + if (!file_exists($filepath)) { + return null; + } + require_once($filepath); $classname = 'data_field_' . $field->type; $sql = "SELECT dc.* @@ -1338,7 +1354,7 @@ function data_build_search_array($data, $paging, $searcharray, $defaults = null, $searchfield = data_get_field_from_id($field->id, $data); // Get field data to build search sql with. If paging is false, get from user. // If paging is true, get data from $searcharray which is obtained from the $SESSION (see line 116). - if (!$paging) { + if (!$paging && $searchfield->type != 'unknown') { $val = $searchfield->parse_search_field($defaults); } else { // Set value from session if there is a value @ the required index.