From a97f8d67209894dd7cef6e7597487a8cbb22fd00 Mon Sep 17 00:00:00 2001 From: raortegar Date: Wed, 5 Oct 2022 10:09:13 +0200 Subject: [PATCH] MDL-75497 mod_data: Review Check existence of field.class.php --- mod/data/classes/search/entry.php | 16 ++++++-- mod/data/edit.php | 29 +++++++++---- mod/data/field.php | 45 +++++++++++++++----- mod/data/lang/en/data.php | 5 +++ mod/data/lib.php | 68 ++++++++++++++++++++++++------- mod/data/locallib.php | 25 +++++++++--- 6 files changed, 146 insertions(+), 42 deletions(-) diff --git a/mod/data/classes/search/entry.php b/mod/data/classes/search/entry.php index b1a6fd9fd61..b476cf8d45c 100644 --- a/mod/data/classes/search/entry.php +++ b/mod/data/classes/search/entry.php @@ -295,6 +295,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.']]'); } @@ -342,16 +346,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/edit.php b/mod/data/edit.php index 85f53d9fd98..cec94eb1060 100644 --- a/mod/data/edit.php +++ b/mod/data/edit.php @@ -277,25 +277,36 @@ if ($data->addtemplate){ $replacements = array(); ///then we generate strings to replace - foreach ($possiblefields as $eachfield){ + foreach ($possiblefields as $eachfield) { $field = data_get_field($eachfield, $data); // To skip unnecessary calls to display_add_field(). - if (strpos($data->addtemplate, "[[".$field->field->name."]]") !== false) { - // Replace the field tag. - $patterns[] = "[[".$field->field->name."]]"; + if (strpos($data->addtemplate, "[[" . $field->field->name . "]]") !== false) { + // Display an error in case the field type is not found. $errors = ''; if (!empty($fieldnotifications[$field->field->name])) { foreach ($fieldnotifications[$field->field->name] as $notification) { $errors .= $OUTPUT->notification($notification); } } - $replacements[] = $errors . $field->display_add_field($rid, $datarecord); - } - // Replace the field id tag. - $patterns[] = "[[".$field->field->name."#id]]"; - $replacements[] = 'field_'.$field->field->id; + // Replace the field tag. + $fielddisplay = ''; + if ($field->type === 'unknown') { + if (has_capability('mod/data:manageentries', $context)) { + // Display notification for users that can manage entries. + $errors .= $OUTPUT->notification(get_string('missingfieldtype', 'data', + (object)['name' => $field->field->name])); + } + } else { + $fielddisplay = $field->display_add_field($rid, $datarecord); + } + + $patterns[] = "[[" . $field->field->name . "]]"; + $replacements[] = $errors . $fielddisplay; + } // Replace the field id tag. + $patterns[] = "[[" . $field->field->name . "#id]]"; + $replacements[] = 'field_' . $field->field->id; } if (core_tag_tag::is_enabled('mod_data', 'data_records')) { diff --git a/mod/data/field.php b/mod/data/field.php index a11c54ea862..4cd9070932e 100644 --- a/mod/data/field.php +++ b/mod/data/field.php @@ -199,9 +199,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; @@ -236,6 +242,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 @@ -277,6 +286,7 @@ if (($mode == 'new') && (!empty($newtype)) && confirm_sesskey()) { /// $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); @@ -295,15 +305,30 @@ if (($mode == 'new') && (!empty($newtype)) && confirm_sesskey()) { /// '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 88daab3cd78..4bae5f84795 100644 --- a/mod/data/lang/en/data.php +++ b/mod/data/lang/en/data.php @@ -226,6 +226,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 7b6014427bd..309ef01834f 100644 --- a/mod/data/lib.php +++ b/mod/data/lib.php @@ -334,6 +334,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"; @@ -351,7 +357,13 @@ 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 '
'; echo ''."\n"; @@ -881,7 +893,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; @@ -893,20 +910,25 @@ 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 object + * @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; } @@ -1914,6 +1936,9 @@ 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 { @@ -2627,7 +2652,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; @@ -2648,7 +2673,7 @@ abstract class data_preset_importer { } else $preservedfields[$cid] = true; } - + $missingfieldtypes = []; foreach ($newfields as $nid => $newfield) { $cid = optional_param("field_$nid", -1, PARAM_INT); @@ -2665,7 +2690,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 = ''; @@ -2676,6 +2706,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 */ @@ -3112,7 +3145,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 2cb5020de80..05efe400dc4 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])); } } @@ -970,7 +974,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; @@ -1001,11 +1009,18 @@ 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.* @@ -1334,7 +1349,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.