save_resource_data()

Description

Save resource data

IMPORTANT: inactive nodes should be left alone (don't add/remove) except when processing fixed list field types that
only hold one value (dropdown, radio). Plugins should determine this based on their use cases when hooking.

Parameters

ColumnTypeDefaultDescription
$ref int
$multi bool
$autosave_field string|int ""

Return

true|array List of errors if unsuccessful, true otherwise

Location

include/resource_functions.php lines 626 to 1269

Definition

 
function save_resource_data($ref$multi$autosave_field "")
{
    
debug_function_call("save_resource_data"func_get_args());
    
# Save all submitted data for resource $ref.
    # Also re-index all keywords from indexable fields.
    
global $lang$languages$language$FIXED_LIST_FIELD_TYPES,
           
$DATE_FIELD_TYPES$reset_date_field$reset_date_upload_template,
           
$edit_contributed_by$new_checksums$upload_review_mode$blank_edit_template$is_template$NODE_FIELDS,
           
$userref$userresourcedefaults;

    
hook("befsaveresourcedata""", array($ref));
    
// Ability to avoid editing conflicts by checking checksums.
    // NOTE: this should NOT apply to upload.
    
$check_edit_checksums true;

    if (
$upload_review_mode) {
        
$check_edit_checksums false;
    }

    
// Save resource defaults (functionality available for upload only)
    // Call it here so that if users have access to the field and want
    // to override it, they can do so
    
if ($ref) {
        
set_resource_defaults($ref);

        
$check_edit_checksums false;
    }

    
# Loop through the field data and save (if necessary)
    
$errors = array();
    
$fields get_resource_field_data($ref$multi, !hook("customgetresourceperms"));

    
$expiry_field_edited false;
    
$resource_data get_resource_data($ref);

    if (
$resource_data["lock_user"] > && $resource_data["lock_user"] != $userref) {
        
$errors[] = get_resource_lock_message($resource_data["lock_user"]);
        return 
$errors;
    }

    
# Load the configuration for the selected resource type. Allows for alternative notification addresses, etc.
    
resource_type_config_override($resource_data["resource_type"]);

    
# Set up arrays of node ids to add/remove. We can't remove all nodes as user may not have access
    
$nodes_to_add               = [];
    
$nodes_to_remove            = [];
    
$oldnodenames               = [];
    
$nodes_check_delete         = [];
    
$resource_update_log_sql    = [];
    
$ui_selected_node_values    = [];
    
$all_current_field_nodes    = [];
    
$new_node_values            = [];
    
$updated_resources          = [];

    
$node_not_active = fn(array $node): bool => !node_is_active($node);

    
// All the nodes passed for editing. Some of them were already a value
    // of the fields while others have been added/removed
    
$user_set_values getval('nodes', [], false'is_array');

    
// Initialise array to store new checksums that client needs after autosave, without which subsequent edits will fail
    
$new_checksums = array();
    for (
$n 0$n count($fields); $n++) {
        if (
            !(
            
checkperm('F' $fields[$n]['ref'])
            || (
checkperm("F*") && !checkperm('F-' $fields[$n]['ref']))
            
// If we hide on upload the field, there is no need to check values passed from the UI as there shouldn't be any
            
|| (($ref || $upload_review_mode) && $fields[$n]['hide_when_uploading'])
            )
            && (
'' == $autosave_field || $autosave_field == $fields[$n]['ref']
                || (
is_array($autosave_field) && in_array($fields[$n]['ref'], $autosave_field))
            )
        ) {
            
// Fixed list  fields use node IDs directly
            
if (in_array($fields[$n]['type'], $FIXED_LIST_FIELD_TYPES)) {
                
debug("save_resource_data(): Checking nodes to add/ remove for field {$fields[$n]['ref']} - {$fields[$n]['title']}");

                
// Get currently selected nodes for this field
                
$current_field_nodes get_resource_nodes($ref$fields[$n]['ref']);
                
$all_current_field_nodes array_merge($all_current_field_nodes$current_field_nodes);
                
// Check if resource field data has been changed between form being loaded and submitted
                
$post_cs getval("field_" $fields[$n]['ref'] . "_checksum""");
                
sort($current_field_nodes);
                
$current_cs md5(implode(","$current_field_nodes));
                if (
$check_edit_checksums && $post_cs != "" && $post_cs != $current_cs) {
                    
$errors[$fields[$n]["ref"]] = i18n_get_translated($fields[$n]['title']) . ': ' $lang["save-conflict-error"];
                    continue;
                }

                
debug("save_resource_data(): Current nodes for resource " $ref ": " implode(","$current_field_nodes));

                
// Work out nodes submitted by user
                
if (
                    isset(
$user_set_values[$fields[$n]['ref']])
                    && !
is_array($user_set_values[$fields[$n]['ref']])
                    && 
'' != $user_set_values[$fields[$n]['ref']]
                    && 
is_numeric($user_set_values[$fields[$n]['ref']])
                ) {
                    
$ui_selected_node_values[] = $user_set_values[$fields[$n]['ref']];
                } elseif (
                    isset(
$user_set_values[$fields[$n]['ref']])
                    && 
is_array($user_set_values[$fields[$n]['ref']])
                ) {
                    
$ui_selected_node_values $user_set_values[$fields[$n]['ref']];
                }
                
// Check nodes are valid for this field
                
if (FIELD_TYPE_CATEGORY_TREE === $fields[$n]['type']) {
                    
$all_tree_nodes_ordered get_cattree_nodes_ordered($fields[$n]['ref'], nulltrue);
                    
// remove the fake "root" node which get_cattree_nodes_ordered() is adding since we won't be using get_cattree_node_strings()
                    
array_shift($all_tree_nodes_ordered);
                    
$inactive_nodes array_column(array_filter($all_tree_nodes_ordered$node_not_active), 'ref');
                    
$all_tree_nodes_ordered array_values($all_tree_nodes_ordered);

                    
$node_options array_column($all_tree_nodes_ordered'name''ref');
                    
$validnodes array_keys($node_options);
                } else {
                    
$fieldnodes   get_nodes($fields[$n]['ref'], ''false);
                    
$node_options array_column($fieldnodes'name''ref');
                    
$validnodes   array_column($fieldnodes'ref');
                    
$inactive_nodes array_column(array_filter($fieldnodes$node_not_active), 'ref');
                }

                
// $validnodes are already sorted by the order_by (default for get_nodes). This is needed for the data_joins fields later
                
$ui_selected_node_values array_values(array_intersect($validnodes$ui_selected_node_values));
                
debug("save_resource_data(): UI selected nodes for resource {$ref}: " implode(','$ui_selected_node_values));

                
// Set new value for logging
                
$new_node_values array_merge($new_node_values$ui_selected_node_values);

                
$added_nodes array_diff($ui_selected_node_values$current_field_nodes$inactive_nodes);
                
debug("save_resource_data(): Adding nodes to resource " $ref ": " implode(","$added_nodes));
                
$nodes_to_add array_merge($nodes_to_add$added_nodes);

                if (
                    
// We must release an inactive node if the type can only hold one value...
                    
in_array($fields[$n]['type'], [FIELD_TYPE_DROP_DOWN_LISTFIELD_TYPE_RADIO_BUTTONS])
                    
// ...but prevent direct removals (ie. no value)
                    
&& $ui_selected_node_values !== []
                ) {
                    
$removed_nodes array_diff($current_field_nodes$ui_selected_node_values);
                    
$current_inactive_resource_field_nodes = [];
                } else {
                    
$removed_nodes array_diff($current_field_nodes$ui_selected_node_values$inactive_nodes);
                    
$current_inactive_resource_field_nodes array_intersect($current_field_nodes$inactive_nodes);
                }
                
debug("save_resource_data(): Removed nodes from resource " $ref ": " implode(","$removed_nodes));
                
$nodes_to_remove array_merge($nodes_to_remove$removed_nodes);

                if (
count($added_nodes) > || count($removed_nodes) > 0) {
                    
$new_nodevals = array();
                    
// Build new value
                    
foreach ($ui_selected_node_values as $ui_selected_node_value) {
                        if (
FIELD_TYPE_CATEGORY_TREE === $fields[$n]['type']) {
                            
$new_nodevals[] = implode(
                                
'/',
                                
array_column(
                                    
compute_node_branch_path($all_tree_nodes_ordered$ui_selected_node_value),
                                    
'name'
                                
)
                            );
                            continue;
                        }

                        
$new_nodevals[] = $node_options[$ui_selected_node_value];
                    }
                    
# Is this is a 'joined' field?
                    
$joins get_resource_table_joins();
                    if (
in_array($fields[$n]["ref"], $joins)) {
                        
$new_nodes_val implode($GLOBALS['field_column_string_separator'], $new_nodevals);
                        if ((
== $fields[$n]['required'] && "" != $new_nodes_val) || == $fields[$n]['required']) { # If joined field is required we shouldn't be able to clear it.
                            
update_resource_field_column($ref$fields[$n]["ref"], $new_nodes_val);
                        }
                    }
                    
$val implode(","$new_nodevals);
                    
$ui_selected_node_values array_merge($ui_selected_node_values$current_inactive_resource_field_nodes);
                    
sort($ui_selected_node_values);
                    
$new_checksums[$fields[$n]['ref']] = md5(implode(','$ui_selected_node_values));
                    
$updated_resources[$ref][$fields[$n]['ref']] = $new_nodevals// To pass to hook
                
} else {
                    
$val "";
                }
            } 
// End of if in $FIXED_LIST_FIELD_TYPES
            
else {
                if (
$fields[$n]['type'] == FIELD_TYPE_DATE_RANGE) {
                    
# date range type
                    # each value will be a node so we end up with a pair of nodes to represent the start and end dates

                    
$daterangenodes = array();
                    
$newval "";

                    if ((
$date_edtf getval("field_" $fields[$n]["ref"] . "_edtf""")) !== "") {
                        
// We have been passed the range in EDTF format, check it is in the correct format
                        
$rangeregex "/^(\d{4})(-\d{2})?(-\d{2})?\/(\d{4})(-\d{2})?(-\d{2})?/";
                        if (!
preg_match($rangeregex$date_edtf$matches)) {
                            
$errors[$fields[$n]["ref"]] = $lang["information-regexp_fail"] . " : " $date_edtf;
                            continue;
                        }
                        if (
is_int_loose($fields[$n]["linked_data_field"])) {
                            
// Update the linked field with the raw EDTF string submitted
                            
update_field($ref$fields[$n]["linked_data_field"], $date_edtf);
                        }
                        
$rangedates explode("/"$date_edtf);
                        
$rangestart str_pad($rangedates[0], 10"-00");
                        
$rangeendparts explode("-"$rangedates[1]);
                        
$rangeendyear $rangeendparts[0];
                        
$rangeendmonth = isset($rangeendparts[1]) ? $rangeendparts[1] : 12;
                        
$rangeendday = isset($rangeendparts[2]) ? $rangeendparts[2] : cal_days_in_month(CAL_GREGORIAN$rangeendmonth$rangeendyear);
                        
$rangeend $rangeendyear "-" $rangeendmonth "-" $rangeendday;

                        
$newval $rangestart DATE_RANGE_SEPARATOR $rangeend;
                        
$daterangenodes[] = set_node(null$fields[$n]["ref"], $rangestartnullnull);
                        
$daterangenodes[] = set_node(null$fields[$n]["ref"], $rangeendnullnull);
                    } else {
                        
// Range has been passed via normal inputs, construct the value from the date/time dropdowns
                        
$date_parts = array("_start","_end");

                        foreach (
$date_parts as $date_part) {
                            
$val getval("field_" $fields[$n]["ref"] . $date_part "-y""");
                            if (
intval($val) <= 0) {
                                
$val "";
                            } elseif ((
$field getval("field_" $fields[$n]["ref"] . $date_part "-m""")) != "") {
                                
$val .= "-" $field;
                                if ((
$field getval("field_" $fields[$n]["ref"] . $date_part "-d""")) != "") {
                                    
$val .= "-" $field;
                                } else {
                                    
$val .= "-00";
                                }
                            } else {
                                
$val .= "-00-00";
                            }

                            
$newval .= ($newval != "" DATE_RANGE_SEPARATOR "") . $val;
                            if (
$val !== "") {
                                
$daterangenodes[] = set_node(null$fields[$n]["ref"], $valnullnull);
                            }
                        }
                    }
                    
natsort($daterangenodes);

                    
// Set new value for logging
                    
$new_node_values array_merge($new_node_values$daterangenodes);

                    
// Get currently selected nodes for this field
                    
$current_field_nodes get_resource_nodes($ref$fields[$n]['ref'], falseSORT_ASC);
                    
$all_current_field_nodes array_merge($all_current_field_nodes$current_field_nodes);

                        
// Check if resource field data has been changed between form being loaded and submitted
                        
$post_cs getval("field_" $fields[$n]['ref'] . "_checksum""");
                        
sort($current_field_nodes);
                        
$current_cs md5(implode(","$current_field_nodes));
                    if (
$check_edit_checksums && $post_cs != "" && $post_cs != $current_cs) {
                        
$errors[$fields[$n]["ref"]] = i18n_get_translated($fields[$n]['title']) . ': ' $lang["save-conflict-error"];
                        continue;
                    }

                    if (
$daterangenodes !== $current_field_nodes) {
                        
$added_nodes array_diff($daterangenodes$current_field_nodes);
                        
debug("save_resource_data(): Adding nodes to resource " $ref ": " implode(","$added_nodes));
                        
$nodes_to_add array_merge($nodes_to_add$added_nodes);
                        
$removed_nodes array_diff($current_field_nodes$daterangenodes);
                        
debug("save_resource_data(): Removed nodes from resource " $ref ": " implode(","$removed_nodes));
                        
$nodes_to_remove array_merge($nodes_to_remove$removed_nodes);

                        
$val $newval;
                        
# If this is a 'joined' field it still needs to be added to the resource column
                        
$joins get_resource_table_joins();
                        if (
in_array($fields[$n]["ref"], $joins)) {
                            
update_resource_field_column($ref$fields[$n]["ref"], $newval);
                        }
                        
sort($daterangenodes);
                        
$new_checksums[$fields[$n]['ref']] = md5(implode(","$daterangenodes));
                        
$updated_resources[$ref][$fields[$n]['ref']][] = $newval// To pass to hook
                    
}
                } elseif (
in_array($fields[$n]['type'], $DATE_FIELD_TYPES)) {
                    
# date type, construct the value from the date/time dropdowns to be used in DB
                    
$val sanitize_date_field_input($fields[$n]["ref"], false);

                    
// A proper input:date field
                    
if ($GLOBALS['use_native_input_for_date_field'] && $fields[$n]['type'] === FIELD_TYPE_DATE) {
                        
$val getval("field_{$fields[$n]['ref']}"'');
                        if (
$val !== '' && !validateDatetime($val'Y-m-d')) {
                            
$errors[$fields[$n]['ref']] = $lang['error_invalid_date'] . ' : ' $val;
                            continue;
                        }
                    }

                    
// Upload template: always reset to today's date, if configured and field is hidden
                    
if (
                        
$ref
                        
&& $reset_date_upload_template
                        
&& $reset_date_field == $fields[$n]['ref']
                        && 
$fields[$n]['hide_when_uploading']
                    ) {
                        
$val date('Y-m-d H:i');
                    }

                    
// Check if resource field data has been changed between form being loaded and submitted
                    
$post_cs getval("field_" $fields[$n]['ref'] . "_checksum""");
                    
$current_cs md5((string)$fields[$n]['value']);
                    if (
$check_edit_checksums && $post_cs != "" && $post_cs != $current_cs) {
                        
$errors[$fields[$n]["ref"]] = i18n_get_translated($fields[$n]['title']) . ': ' $lang["save-conflict-error"];
                        continue;
                    }

                    
$new_checksums[$fields[$n]['ref']] = md5($val);
                    
$updated_resources[$ref][$fields[$n]['ref']][] = $val// To pass to hook
                
} else {

                    
# Set the value exactly as sent.
                    
$val getval("field_" $fields[$n]["ref"], "");
                    
$rawval getval("field_" $fields[$n]["ref"], "");
                    
# Check if resource field data has been changed between form being loaded and submitted
                    # post_cs is the checksum of the data when it was loaded from the database
                    # current_cs is the checksum of the data on the database now
                    # if they are the same then there has been no intervening update and so its ok to update with our new value
                    # if our new data yields a different checksum, then we know the new value represents a change
                    # the new checksum for the new value of a field is stored in $new_checksums[$fields[$n]['ref']]
                    
$post_cs getval("field_" $fields[$n]['ref'] . "_checksum""");
                    
$current_cs md5(trim(preg_replace('/\s\s+/'' ', (string) $fields[$n]['value'])));

                    if (
$check_edit_checksums && $post_cs != "" && $post_cs != $current_cs) {
                        
$errors[$fields[$n]["ref"]] = i18n_get_translated($fields[$n]['title']) . ': ' $lang["save-conflict-error"];
                        continue;
                    }

                    if (
$fields[$n]['type'] == FIELD_TYPE_TEXT_BOX_FORMATTED_AND_TINYMCE
                        
&& html_entity_decode($valENT_NOQUOTES ENT_SUBSTITUTE ENT_HTML401'UTF-8') !== strip_tags_and_attributes($val, array("a"), array("href","target","rel","title"))) {
                        
$errors[$fields[$n]["ref"]] = i18n_get_translated($fields[$n]['title']) . ': ' $lang["save-error-invalid"];
                        continue;
                    }

                    
$new_checksums[$fields[$n]['ref']] = md5(trim(preg_replace('/\s\s+/'' '$rawval)));
                    
$updated_resources[$ref][$fields[$n]['ref']][] = $val// To pass to hook
                
}

                
# Check for regular expression match
                
if (strlen(trim((string)$fields[$n]["regexp_filter"])) >= && strlen((string) $val) > 0) {
                    global 
$regexp_slash_replace;
                    if (
preg_match("#^" str_replace($regexp_slash_replace'\\'$fields[$n]["regexp_filter"]) . "$#"$val$matches) <= 0) {
                        global 
$lang;
                        
debug($lang["information-regexp_fail"] . ": -" "reg exp: " str_replace($regexp_slash_replace'\\'$fields[$n]["regexp_filter"]) . ". Value passed: " $val);
                        
$errors[$fields[$n]["ref"]] = $lang["information-regexp_fail"] . " : " $val;
                        continue;
                    }
                }
                
$modified_val hook("modifiedsavedfieldvalue"'', array($fields,$n,$val));
                if (!empty(
$modified_val)) {
                    
$val $modified_val;
                    
$new_checksums[$fields[$n]['ref']] = md5(trim(preg_replace('/\s\s+/'' '$val)));
                }

                
$error hook("additionalvalcheck""all", array($fields$fields[$n]));
                if (
$error) {
                    
$errors[$fields[$n]["ref"]] = $error;
                    continue;
                }
            } 
// End of if not a fixed list field

            // Determine whether a required field has a default for the user
            
$field_has_default_for_user false;
            if (
$userresourcedefaults != '') {
                foreach (
explode(';'$userresourcedefaults) as $rule) {
                    
$rule_detail         explode('='trim($rule));
                    
$field_shortname     $rule_detail[0];
                    
$field_default_value $rule_detail[1];
                    if (
$field_shortname  == $fields[$n]['name'] && $field_default_value != "") {
                        
$field_has_default_for_user true;
                        break;
                    }
                }
            }

            
// Populate empty field with the default if necessary
            
if ($field_has_default_for_user && strlen((string) $val) == 0) {
                
$val $field_default_value;
                
$new_checksums[$fields[$n]['ref']] = md5(trim(preg_replace('/\s\s+/'' '$val)));
            }

            if (
                
$fields[$n]['required'] == 1
                
&& check_display_condition($n$fields[$n], $fieldsfalse$ref)
                && (
                    
// Required node field with no nodes submitted is a candidate for error
                    
(in_array($fields[$n]['type'], $FIXED_LIST_FIELD_TYPES) && count($ui_selected_node_values) == 0)
                    
// Required continuous field with no value is a candidate for error
                    
|| (!in_array($fields[$n]['type'], $FIXED_LIST_FIELD_TYPES) && trim(strip_leading_comma($val)) == '')
                )
                && (
                    
// An existing resource node field with neither any nodes submitted nor a resource default
                    
($ref && in_array($fields[$n]['type'], $FIXED_LIST_FIELD_TYPES) && count($ui_selected_node_values) == && !$field_has_default_for_user)
                    
// An existing resource continuous field with neither an input value nor a resource default
                    
|| ($ref && !in_array($fields[$n]['type'], $FIXED_LIST_FIELD_TYPES) && strlen((string) $val) == && !$field_has_default_for_user)
                    
// A template node field with neither any nodes submitted nor a resource default
                    
|| ($ref && in_array($fields[$n]['type'], $FIXED_LIST_FIELD_TYPES) && count($ui_selected_node_values) == && !$field_has_default_for_user)
                    
// A template continuous field with neither an input value nor a resource default
                    
|| ($ref && !in_array($fields[$n]['type'], $FIXED_LIST_FIELD_TYPES) && strlen((string) $val) == && !$field_has_default_for_user)
                )
                
// Not a metadata template
                
&& !$is_template
            
) {
                
$field_visibility_status getval("field_" $fields[$n]['ref'] . "_displayed""");
                
# Register an error only if the empty required field was actually displayed
                
if (is_field_displayed($fields[$n]) && $field_visibility_status == "block") {
                    
$errors[$fields[$n]['ref']] = i18n_get_translated($fields[$n]['title']) . ": {$lang['requiredfield']}";
                    continue;
                }
            }

            
// If all good so far, then save the data
            
if (
                
in_array($fields[$n]['type'], NON_FIXED_LIST_SINGULAR_RESOURCE_VALUE_FIELD_TYPES)
                && 
str_replace("\r\n""\n"trim((string) $fields[$n]['value'])) !== str_replace("\r\n""\n"trim((string) $val))
            ) {
                
# This value is different from the value we have on record.
                # Expiry field? Set that expiry date(s) have changed so the expiry notification flag will be reset later in this function.
                
if ($fields[$n]["type"] == FIELD_TYPE_EXPIRY_DATE) {
                    
$expiry_field_edited true;
                }

                
$use_node null;
                if (
trim((string) $fields[$n]["nodes"]) != "") {
                    
// Remove any existing node IDs for this non-fixed list field (there should only be one) unless used by other resources.
                    
$current_field_nodes array_filter(explode(","$fields[$n]["nodes"]), "is_int_loose");
                    
$all_current_field_nodes array_merge($all_current_field_nodes$current_field_nodes);
                    foreach (
$current_field_nodes as $current_field_node) {
                        
$inuse get_nodes_use_count([$current_field_node]);
                        
$inusecount $inuse[$current_field_node] ?? 0;
                        if (
$current_field_node && $inusecount == 1) {
                            
// Reuse same node
                            
$use_node $current_field_node;
                        } else {
                            
// Remove node from resource and create new node
                            
$nodes_to_remove[] = $current_field_node;
                            
$nodes_check_delete[] = $current_field_node;
                        }
                    }
                }

                
# Add new node unless empty string
                
if ($val == '') {
                    
// Remove and delete node
                    
$nodes_to_remove[] = $current_field_node;
                    
$nodes_check_delete[] = $current_field_node;
                } else {
                    
// Update the existing node
                    
$newnode set_node($use_node$fields[$n]["ref"], $valnullnull);
                    if ((int)
$newnode != (int)$use_node) {
                        
// Node already exists, remove current node and replace
                        
$nodes_to_add[] = $newnode;
                        
$nodes_to_remove[] = $use_node;
                        
$nodes_check_delete[] = $use_node;
                        
// Set new value for logging
                        
$new_node_values[] = $newnode;
                    } else {
                        
$new_node_values[] = $use_node;
                    }

                    
// Add to array for logging
                    
if (!is_null($use_node)) {
                        
$oldnodenames[$use_node] = $fields[$n]['value'];
                    }
                }

                
# If this is a 'joined' field we need to add it to the resource column
                
$joins get_resource_table_joins();
                if (
in_array($fields[$n]["ref"], $joins)) {
                    
update_resource_field_column($ref$fields[$n]["ref"], $val);
                }
            }

            
# Add any onchange code if new checksum for field shows that it has changed
            
if (
                isset(
$fields[$n]["onchange_macro"]) && $fields[$n]["onchange_macro"] !== ""
                    
&& isset($new_checksums[$fields[$n]["ref"]])
                    && 
$post_cs !== $new_checksums[$fields[$n]["ref"]]
                    && (
$post_cs !== "" || (!$check_edit_checksums && $new_checksums[$fields[$n]["ref"]] != md5(trim(preg_replace('/\s\s+/'' ', (string) $fields[$n]['value'] ?? '')))))
            ) {
                
$macro_resource_id $ref;
                eval(
eval_check_signed($fields[$n]["onchange_macro"]));
            }
        } 
# End of if "allowed to edit field conditions"
    
# End of for $fields

    // When editing a resource, prevent applying the change to the resource if there are any errors
    
if (count($errors) > && $ref 0) {
        return 
$errors;
    }

   
# Save related resource field if value for Related input field is autosaved, or if form has been submitted by user
    
if (($autosave_field == "" || $autosave_field == "Related") && isset($_POST["related"])) {
        
# save related resources field
        
$related explode(","getval("related"""));
        
# Trim whitespace from each entry
        
foreach ($related as &$relatedentry) {
            
$relatedentry trim($relatedentry);
        }
        
# Make sure all submitted values are numeric
        
$to_relate array_filter($related"is_int_loose");

        
$currently_related get_related_resources($ref);
        
$to_add array_diff($to_relate$currently_related);
        
$to_delete array_diff($currently_related$to_relate);

        if (
count($to_add) > 0) {
            
update_related_resource($ref$to_addtrue);
        }
        if (
count($to_delete) > 0) {
            
update_related_resource($ref$to_deletefalse);
        }
    }

    
// Update resource_node table
    
db_begin_transaction("update_resource_node");
    if (
count($nodes_to_remove) > 0) {
        
delete_resource_nodes($ref$nodes_to_removefalse);
    }

    if (
count($nodes_to_add) > 0) {
        
add_resource_nodes($ref$nodes_to_addfalsefalse);
    }
    
log_node_changes($ref$new_node_values$all_current_field_nodes""$oldnodenames);

    if (
count($nodes_check_delete) > 0) {
        
// This has to be after call to log_node_changes() or nodes cannot be resolved
        
check_delete_nodes($nodes_check_delete);
    }

    
db_end_transaction("update_resource_node");

    
// Autocomplete any blank fields without overwriting any existing metadata
    
$autocomplete_fields autocomplete_blank_fields($reffalsetrue);
    foreach (
$autocomplete_fields as $autocomplete_field_ref => $autocomplete_field_value) {
        
$new_checksums[$autocomplete_field_ref] = md5((string)$autocomplete_field_value);
    }

    
// Initialise an array of updates for the resource table
    
$resource_update_sql = array();
    
$resource_update_params = array();
    if (
$edit_contributed_by) {
        
$created_by $resource_data['created_by'];
        
$new_created_by getval("created_by"0true);
        if ((
getval("created_by"0true) > 0) && $new_created_by != $created_by) {
            
# Also update created_by
            
$resource_update_sql[] = "created_by= ?";
            
$resource_update_params[] = "i";
            
$resource_update_params[] = $new_created_by;
            
$olduser get_user($created_by);
            
$newuser get_user($new_created_by);
            
$resource_update_log_sql[] = array(
                    
"ref" => $ref,
                    
"type" => LOG_CODE_CREATED_BY_CHANGED,
                    
"field" => 0,
                    
"notes" => "",
                    
"from" => $created_by " (" . ($olduser["fullname"] == "" $olduser["username"] : $olduser["fullname"])  . ")","to" => $new_created_by " (" . ($newuser["fullname"] == "" $newuser["username"] : $newuser["fullname"])  . ")");
        }
    }

    
# Expiry field(s) edited? Reset the notification flag so that warnings are sent again when the date is reached.
    
if ($expiry_field_edited) {
        
$resource_update_sql[] = "expiry_notification_sent='0'";
    }

    if (!
hook('forbidsavearchive''', array($errors))) {
        
# Also update archive status and access level
        
$oldaccess $resource_data['access'];
        
$access getval("access"$oldaccesstrue);

        
$oldarchive $resource_data['archive'];
        
$setarchivestate getval("status"$oldarchivetrue);
        if (
$setarchivestate != $oldarchive && !checkperm("e" $setarchivestate)) { // don't allow change if user has no permission to change archive state
            
$setarchivestate $oldarchive;
        }

        
// Only if changed
        
if (($autosave_field == "" || $autosave_field == "Status") && $setarchivestate != $oldarchive) {
            
// Check if resource status has already been changed between form being loaded and submitted
            
if (getval("status_checksum""") != "" && getval("status_checksum""") != $oldarchive) {
                
$errors["status"] = $lang["status"] . ': ' $lang["save-conflict-error"];
            } else {
                
// update archive status if different (doesn't matter whether it is a user template or a genuine resource)
                
if ($setarchivestate != $oldarchive) {
                    
update_archive_status($ref$setarchivestate, array($oldarchive));
                }

                
$new_checksums["status"] = $setarchivestate;
            }
        }

        if ((
$autosave_field == "" || $autosave_field == "Access") && $access != $oldaccess) {
            
// Check if resource access has already been changed between form being loaded and submitted
            
if (getval("access_checksum""") != "" && getval("access_checksum""") != $oldaccess) {
                
$errors["access"] = $lang["access"] . ': ' $lang["save-conflict-error"];
            } else {
                
$resource_update_sql[] = "access= ?";
                
$resource_update_params[] = "i";
                
$resource_update_params[] = $access;
                if (
$access != $oldaccess && $ref) {
                    
$resource_update_log_sql[] = array(
                        
'ref'   => $ref,
                        
'type'  => 'a',
                        
'field' => 0,
                        
'notes' => '',
                        
'from'  => $oldaccess,
                        
'to'    => $access);
                }

                if (
$oldaccess == && $access != 3) {
                    
# Moving out of the custom state. Delete any usergroup specific access.
                    # This can delete any 'manual' usergroup grants also as the user will have seen this as part of the custom access.
                    
delete_resource_custom_access_usergroups($ref);
                }
                
$new_checksums["access"] = $access;
            }
        }
    }

    if (
count($resource_update_sql) > 0) {
        
$sql "UPDATE resource SET " implode(","$resource_update_sql) . " WHERE ref=?";
        
$sqlparams array_merge($resource_update_params, ["i",$ref]);
        
ps_query($sql$sqlparams);
    }

    foreach (
$resource_update_log_sql as $log_sql) {
        
resource_log($log_sql["ref"], $log_sql["type"], $log_sql["field"], $log_sql["notes"], $log_sql["from"], $log_sql["to"]);
    }

    
# Save any custom permissions
    
if (getval("access"0) == RESOURCE_ACCESS_CUSTOM_GROUP) {
        
save_resource_custom_access($ref);
    }

    
// Plugins can do extra actions once all fields have been saved and return errors back if needed
    
$plg_errors hook('aftersaveresourcedata''', array($ref$nodes_to_add$nodes_to_remove$autosave_field$fields,$updated_resources));
    if (
is_array($plg_errors) && !empty($plg_errors)) {
        
$errors array_merge($errors$plg_errors);
    }

    if (
count($errors) == 0) {
        
daily_stat("Resource edit"$ref);
        return 
true;
    }
    return 
$errors;
}

This article was last updated 5th June 2025 17:05 Europe/London time based on the source file dated 5th June 2025 10:55 Europe/London time.