save_resource_data_multi()
Description
Batch save resources in a collectionIMPORTANT: 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
Column | Type | Default | Description |
---|---|---|---|
$collection | int | ||
$editsearch | array | array | |
$postvals | array | [] |
Return
true|array | List of errors if unsuccessful, true otherwise |
Location
include/resource_functions.php lines 1472 to 2605
Definition
function save_resource_data_multi($collection,$editsearch = array(), $postvals = [])
{
global $FIXED_LIST_FIELD_TYPES,$DATE_FIELD_TYPES, $edit_contributed_by, $TEXT_FIELD_TYPES, $userref, $lang, $languages, $language, $baseurl;
# Save all submitted data for collection $collection or a search result set, this is for the 'edit multiple resources' feature
if(empty($postvals))
{
$postvals = $_POST;
}
$errors = [];
$save_warnings = [];
if($collection == 0 && isset($editsearch["search"]))
{
// Editing a result set, not a collection
$edititems = do_search($editsearch["search"], $editsearch["restypes"],'resourceid',$editsearch["archive"], -1, 'ASC', false, 0, false, false, '', false, false, true, true, false, $editsearch["search_access"]);
$list = array_column($edititems,"ref");
}
else
{
# Save all submitted data for collection $collection,
$list = get_collection_resources($collection);
}
// Check that user can edit all resources, edit access and not locked by another user
$noeditaccess = array();
$lockedresources = array();
foreach($list as $listresource)
{
$resource_data[$listresource] = get_resource_data($listresource, true);
if(!get_edit_access($listresource,$resource_data[$listresource]["archive"]))
{
$noeditaccess[] = $listresource;
}
if($resource_data[$listresource]["lock_user"] > 0 && $resource_data[$listresource]["lock_user"] != $userref)
{
$lockedresources[] = $listresource;
}
}
if(count($noeditaccess) > 0)
{
$errors[] = $lang["error-edit_noaccess_resources"] . implode(",",$noeditaccess);
}
if (count($lockedresources) > 0)
{
$errors[] = $lang["error-edit_locked_resources"] . implode(",",$lockedresources);
}
if(count($errors) > 0)
{
return $errors;
}
$tmp = hook("altercollist", "", array("save_resource_data_multi", $list));
if(is_array($tmp))
{
if(count($tmp) > 0)
{
$list = $tmp;
}
else
{
return true;
}
}
$ref = $list[0];
$fields = get_resource_field_data($ref,true);
$field_restypes = get_resource_type_field_resource_types();
$expiry_field_edited = false;
// 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 = $postvals['nodes'] ?? [];
// Arrays of nodes to add/ remove from all resources
$all_nodes_to_add = [];
$all_nodes_to_remove = [];
// Nodes to add/remove for specific resources (resource as key)
$resource_nodes_remove = [];
$resource_nodes_add = [];
// Other changes to make
$nodes_check_delete = [];
$resource_log_updates = [];
$log_node_updates = [];
$resource_update_sql_arr = [];
$resource_update_params = [];
$updated_resources = [];
$successfully_edited_resources = [];
$fields = array_values(array_filter($fields,function($field) use ($postvals){
return ($postvals['editthis_field_' . $field['ref']] ?? '') != '' || hook('save_resource_data_multi_field_decision', '', array($field['ref']));
}));
$node_not_active = fn(array $node): bool => !node_is_active($node);
// Get all existing nodes for the edited resources
$existing_nodes = get_resource_nodes_batch($list,array_column($fields,"ref"));
$joins = get_resource_table_joins();
for ($n=0;$n<count($fields);$n++)
{
set_processing_message(str_replace(["[count]","[total]"],[$n+1,count($fields)],$lang["processing_calculating_updates_required"]));
$nodes_to_add = [];
$nodes_to_remove = [];
$oldnodenames = [];
// Append option(s) mode?
$mode = $postvals["modeselect_" . $fields[$n]["ref"]] ?? "";
if(in_array($fields[$n]['type'], $FIXED_LIST_FIELD_TYPES))
{
// Set up arrays of node ids selected and we will later resolve these to add/remove. Don't remove all nodes since user may not have access
$ui_selected_node_values = array();
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
$fieldnodes = get_nodes($fields[$n]["ref"],null,$fields[$n]['type'] == FIELD_TYPE_CATEGORY_TREE);
$inactive_nodes = array_column(array_filter($fieldnodes, $node_not_active), 'ref');
$nodes_by_ref = array_column($fieldnodes, null, 'ref');
$valid_nodes = in_array($fields[$n]['type'], [FIELD_TYPE_DROP_DOWN_LIST, FIELD_TYPE_RADIO_BUTTONS]) && $ui_selected_node_values !== []
// We must include inactive nodes if the type can only hold one value so it can be removed later on...
? array_keys($nodes_by_ref)
// ...but prevent direct removals (ie. no value)
: array_values(array_diff(array_keys($nodes_by_ref), $inactive_nodes));
// $valid_nodes 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_intersect($valid_nodes, $ui_selected_node_values);
$ui_deselected_node_values = array_diff($valid_nodes, $ui_selected_node_values);
if ($mode=="AP")
{
$nodes_to_add = $ui_selected_node_values;
$all_nodes_to_add = array_merge($all_nodes_to_add,$nodes_to_add);
}
elseif ($mode=="RM")
{
// Remove option(s) mode
$nodes_to_remove = $ui_selected_node_values;
$all_nodes_to_remove = array_merge($all_nodes_to_remove,$nodes_to_remove);
}
elseif ($mode=="RT")
{
// Replace option(s) mode
$nodes_to_add = $ui_selected_node_values;
$nodes_to_remove = $ui_deselected_node_values;
$all_nodes_to_add = array_merge($all_nodes_to_add,$nodes_to_add);
$all_nodes_to_remove = array_merge($all_nodes_to_remove,$nodes_to_remove);
}
debug(sprintf('Mode %s - $nodes_to_add = %s', $mode, implode(',', $nodes_to_add)));
debug(sprintf('Mode %s - $nodes_to_remove = %s', $mode, implode(',', $nodes_to_remove)));
if($fields[$n]["required"] == 1 && count($nodes_to_add) == 0 && $mode!=="")
{
// Required field and no value now set, revert to existing and add to array of failed edits
if(!isset($errors[$fields[$n]["ref"]]))
{
$errors[$fields[$n]["ref"]]=$lang["requiredfield"] . ". " . $lang["error_batch_edit_resources"] . ": " ;
}
$errors[$fields[$n]["ref"]] .= implode(",", $list);
$all_nodes_to_remove = array_diff($all_nodes_to_remove, $nodes_to_remove); // Don't remove any nodes in the required field that would be left empty.
$nodes_to_remove = [];
continue;
}
// Loop through all the resources and check current node values so we can check if we need to log this as a change
for ($m=0;$m<count($list);$m++)
{
$ref = $list[$m];
$value_changed = false;
$new_nodes_val = [];
// Nodes to add only to this resource e.g. from hook to revert to previous value
$resource_nodes_to_add[$ref] = [];
$current_field_nodes = $existing_nodes[$ref][$fields[$n]['ref']] ?? [];
debug('Current nodes for resource #' . $ref . ' : ' . implode(',',$current_field_nodes));
/*
Possibility to hook in and alter the value - additional mode support.
Plugins will have to determine if their use cases should handle saving inactive nodes or ignoring them.
For example, rse_version will act upon inactive nodes because we can't assume a node will not be required
later (it could be re-activated) when reverting.
*/
$hookval = hook('save_resource_data_multi_extra_modes', '', array($ref, $fields[$n],$current_field_nodes,$postvals,&$errors));
if($hookval !== false)
{
if(!is_string($hookval))
{
continue;
}
$resource_add_nodes = [];
$valid_hook_nodes = false;
$log_node_names = [];
if(trim((string)$hookval) != "")
{
// Get array of current field options
$nodes_available_keys = [];
foreach($fieldnodes as $node_details)
{
if($fields[$n]['type'] == FIELD_TYPE_CATEGORY_TREE)
{
$nodes_available_keys[mb_strtolower($node_details["path"])] = $node_details["ref"];
$nodes_available_keys[mb_strtolower($node_details["translated_path"])] = $node_details["ref"];
}
$nodes_available_keys[mb_strtolower($node_details["name"])] = $node_details["ref"];
$nodes_available_keys[mb_strtolower($node_details["translated_name"])] = $node_details["ref"];
}
$oldnodenames = explode(NODE_NAME_STRING_SEPARATOR,$hookval);
foreach($oldnodenames as $oldnodename)
{
debug("Looking for previous node: '" . $oldnodename . "'");
$findname = strtolower($oldnodename);
if(isset($nodes_available_keys[$findname]))
{
debug(" - Found valid previous node '" . $nodes_available_keys[$findname] . "'");
$resource_add_nodes[] = $nodes_available_keys[$findname];
$log_node_names[] = $oldnodename;
$valid_hook_nodes = true;
}
else
{
debug(" - Unable to find previous node for '" . $oldnodename . "'");
$save_warnings[] = ["Resource" => $ref,"Field" => $fields[$n]['title'],"Message"=>str_replace("[value]",$oldnodename,$lang["error_invalid_revert_option"])];
}
}
}
else
{
if($fields[$n]["required"])
{
debug(" - No previous node for required field");
$save_warnings[] = ["Resource" => $ref,"Field" => $fields[$n]['title'],"Message"=>$lang["requiredfield"]];
continue;
}
else
{
$resource_add_nodes = [];
$valid_hook_nodes = true;
}
}
if($valid_hook_nodes)
{
sort($resource_add_nodes);
sort($current_field_nodes);
if($resource_add_nodes == $current_field_nodes)
{
debug("hook nodes match existing nodes. Skipping resource " . $ref);
continue;
}
$resource_nodes_add[$ref] =$resource_add_nodes;
$resource_nodes_remove[$ref] = array_diff($current_field_nodes,$resource_add_nodes);
debug(sprintf('$resource_nodes_add[%s] = %s', $ref, implode(',', $resource_nodes_add[$ref])));
debug(sprintf('$resource_nodes_remove[%s] = %s', $ref, implode(',', $resource_nodes_remove[$ref])));
$log_node_updates[$ref][] = [
'from' => $current_field_nodes,
'to' => $resource_add_nodes,
];
if(in_array($fields[$n]['ref'], $joins))
{
// Build new value to add to resource table:
foreach($resource_add_nodes as $new_node)
{
if(FIELD_TYPE_CATEGORY_TREE === $fields[$n]['type'])
{
$new_nodes_val[] = $nodes_by_ref[$new_node]["path"];
}
else
{
$new_nodes_val[] = $nodes_by_ref[$new_node]["name"];
}
}
$resource_update_sql_arr[$ref][] = "field" . (int)$fields[$n]["ref"] . " = ?";
$resource_update_params[$ref][]="s";
$resource_update_params[$ref][] = truncate_join_field_value(implode($GLOBALS['field_column_string_separator'], $new_nodes_val));
}
$updated_resources[$ref][$fields[$n]['ref']] = $new_nodes_val; // To pass to hook
}
}
else
{
$added_nodes = array_diff($nodes_to_add,$current_field_nodes);
debug('Adding nodes to resource #' . $ref . ' : ' . implode(',',$added_nodes));
$removed_nodes = array_intersect($nodes_to_remove,$current_field_nodes);
debug('Removed nodes from resource #' . $ref . ' : ' . implode(',',$removed_nodes));
// Work out what all the new nodes for this resource will be while maintaining their order
$new_nodes = array_filter($nodes_by_ref,function($node) use ($nodes_to_add){return in_array($node["ref"],$nodes_to_add);});
# 'to' should contain the resulting nodes after the amendment. The difference between 'to' and 'from' is what has changed.
$resulting_nodes_for_log = $nodes_to_add;
if ($mode == "AP")
{
$resulting_nodes_for_log = array_unique(array_merge($nodes_to_add, $current_field_nodes));
}
if ($mode == "RM")
{
$resulting_nodes_for_log = array_diff($current_field_nodes, $removed_nodes);
}
$log_node_updates[$ref][] = [
'from' => $current_field_nodes,
'to' => $resulting_nodes_for_log,
];
if((count($added_nodes)>0 || count($removed_nodes)>0) && in_array($fields[$n]['ref'], $joins))
{
// Build new value:
foreach($new_nodes as $noderef=>$new_node)
{
if(FIELD_TYPE_CATEGORY_TREE === $fields[$n]['type'])
{
$new_nodes_val[] = $nodes_by_ref[$noderef]["path"];
}
else
{
$new_nodes_val[] = $nodes_by_ref[$noderef]["name"];
}
}
$resource_update_sql_arr[$ref][] = "field" . (int)$fields[$n]["ref"] . " = ?";
$resource_update_params[$ref][]="s";
$resource_update_params[$ref][] = truncate_join_field_value(implode($GLOBALS['field_column_string_separator'], $new_nodes_val));
$updated_resources[$ref][$fields[$n]['ref']] = $new_nodes_val; // To pass to hook
}
}
// Add any onchange code
if($fields[$n]["onchange_macro"]!="") {
$val = implode(',',array_column(get_nodes_by_refs($resource_add_nodes ?? $resulting_nodes_for_log),"name"));
$macro_resource_id=$ref;
//$val = implode(",",$updated_resources[$ref][$fields[$n]['ref']]);
eval(eval_check_signed($fields[$n]["onchange_macro"]));
}
}
} // End of fixed list field section
elseif($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 = ($postvals["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"] . " : " . $rangeregex;
continue;
}
if(is_numeric($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"], $rangestart, null, null);
$daterangenodes[]=set_node(null, $fields[$n]["ref"], $rangeend, null, null);
}
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 = $postvals["field_" . $fields[$n]["ref"] . $date_part . "-y"] ?? "";
if ((int) $val <= 0)
{
$val="";
}
elseif (($field = ($postvals["field_" . $fields[$n]["ref"] . $date_part . "-m"] ?? "")) != "")
{
$val.="-" . $field;
if (($field=($postvals["field_" . $fields[$n]["ref"] . $date_part . "-d"] ?? "")) != "")
{
$val.="-" . $field;
}
else
{
$val.="-00";
}
}
else
{
$val.="-00-00";
}
if($val!=="")
{
$daterangenodes[]=set_node(null, $fields[$n]["ref"], $val, null, null);
$newval .= ($newval!=""?DATE_RANGE_SEPARATOR:"") . $val;
}
}
}
for ($m=0;$m<count($list);$m++)
{
$ref = $list[$m];
$value_changed = false;
$log_node_names = [];
$current_field_nodes = $existing_nodes[$ref][$fields[$n]['ref']] ?? [];
debug(' - current_field_nodes nodes for resource #' . $ref . ': ' . implode(",",$current_field_nodes));
# Possibility to hook in and alter the value - additional mode support
$hookval = hook('save_resource_data_multi_extra_modes', '', array($ref, $fields[$n],$current_field_nodes,$postvals,&$errors));
if($hookval !== false )
{
if(!is_string($hookval))
{
continue;
}
$resource_add_nodes = [];
$valid_hook_nodes = false;
$oldnodenames = explode(NODE_NAME_STRING_SEPARATOR,$hookval);
foreach($oldnodenames as $oldnodename)
{
if(trim($oldnodename) == "" && $fields[$n]["required"] == false)
{
$valid_hook_nodes = true;
}
elseif(check_date_format($oldnodename) == "")
{
$valid_hook_nodes = true;
debug(" - Found valid previous date '" . $oldnodename . "'");
$resource_add_nodes[] = set_node(null,$fields[$n]['ref'],$oldnodename,null,10);
$log_node_names[] = $oldnodename;
}
else
{
$save_warnings[] = ["Resource" => $ref,"Field" => $fields[$n]['title'],"Message"=>str_replace("[value]",$oldnodename,$lang["error_invalid_revert_date"])];
debug(" - Invalid previous date " . $oldnodename . "'");
}
}
if($valid_hook_nodes)
{
sort($resource_add_nodes);
sort($current_field_nodes);
if($resource_add_nodes == $current_field_nodes)
{
debug("hook nodes match existing nodes. Skipping resource " . $ref);
continue;
}
$resource_nodes_add[$ref] = $resource_add_nodes;
$resource_nodes_remove[$ref] = $current_field_nodes;
$log_node_updates[$ref][] = [
'from' => $current_field_nodes,
'to' => $resource_add_nodes,
];
if(in_array($fields[$n]['ref'], $joins))
{
$resource_update_sql_arr[$ref][] = "field" . (int)$fields[$n]["ref"] . " = ?";
$resource_update_params[$ref][]="s";$resource_update_params[$ref][] = implode(DATE_RANGE_SEPARATOR,$log_node_names);
}
$updated_resources[$ref][$fields[$n]['ref']] = $log_node_names; // To pass to hook
}
}
else
{
$added_nodes = array_diff($daterangenodes, $current_field_nodes);
debug("save_resource_data_multi(): Adding nodes to resource " . $ref . ": " . implode(",",$added_nodes));
$nodes_to_add = array_merge($nodes_to_add, $daterangenodes);
$removed_nodes = array_diff($current_field_nodes,$daterangenodes);
debug("save_resource_data_multi(): Removing nodes from resource " . $ref . ": " . implode(",",$removed_nodes));
$nodes_to_remove = array_merge($nodes_to_remove, $removed_nodes);
if(count($added_nodes)>0 || count($removed_nodes)>0)
{
$log_node_updates[$ref][] = [
'from' => $current_field_nodes,
'to' => $daterangenodes,
];
// If this is a 'joined' field it still needs to add it to the resource column
if(in_array($fields[$n]['ref'], $joins))
{
$resource_update_sql_arr[$ref][] = "field" . (int)$fields[$n]["ref"] . " = ?";
$resource_update_params[$ref][]="s";$resource_update_params[$ref][]=$newval;
}
$updated_resources[$ref][$fields[$n]['ref']][] = $newval; // To pass to hook
}
}
$all_nodes_to_add = array_merge($all_nodes_to_add,$nodes_to_add);
$all_nodes_to_remove = array_merge($all_nodes_to_remove,$nodes_to_remove);
}
}
else
{
if($GLOBALS['use_native_input_for_date_field'] && $fields[$n]['type'] === FIELD_TYPE_DATE)
{
$val = $postvals["field_{$fields[$n]['ref']}"] ?? '';
if($val !== '' && !validateDatetime($val, 'Y-m-d'))
{
$errors[$fields[$n]['ref']] = $val;
continue;
}
}
elseif(in_array($fields[$n]['type'], $DATE_FIELD_TYPES))
{
# date/expiry date type, construct the value from the date dropdowns
$val=sanitize_date_field_input($fields[$n]["ref"], false);
}
else
{
$val = $postvals["field_" . $fields[$n]["ref"]] ?? "";
}
$origval = $val;
# Loop through all the resources and save.
for ($m=0;$m<count($list);$m++)
{
$ref = $list[$m];
$value_changed = false;
$use_node = null;
// Reset nodes to add/remove as may differ for each resource
$nodes_to_add = [];
$nodes_to_remove = [];
if($fields[$n]["global"] == 0 && !in_array($resource_data[$ref]["resource_type"],$field_restypes[$fields[$n]["ref"]]))
{
continue;
}
# Work out existing field value.
$existing = get_data_by_field($ref,$fields[$n]['ref']);
if ($mode=="FR")
{
# Find and replace mode? Perform the find and replace.
$findstring = $postvals["find_" . $fields[$n]["ref"]] ?? "";
$replacestring = $postvals["replace_" . $fields[$n]["ref"]] ?? "";
$val=str_replace($findstring,$replacestring,$existing);
if (html_entity_decode($existing, ENT_QUOTES | ENT_HTML401) != $existing)
{
// Need to replace html characters with html characters
// CkEditor converts some characters to the HTML entity code, in order to use and replace these, we need the
// $rich_field_characters array below so the stored in the database value e.g. ' corresponds to "'"
// that the user typed in the search and replace box
// This array could possibly be expanded to include more such conversions
$rich_field_characters_replace = array("'","’");
$rich_field_characters_sub = array("'","’");
// Set up array of strings to match as we may have a number of variations in the existing value
$html_entity_strings = array();
$html_entity_strings[] = str_replace($rich_field_characters_replace, $rich_field_characters_sub, escape($findstring));
$html_entity_strings[] = str_replace($rich_field_characters_replace, $rich_field_characters_sub, htmlentities($findstring));
$html_entity_strings[] = htmlentities($findstring);
$html_entity_strings[] = escape($findstring);
// Just need one replace string
$replacestring = escape($replacestring);
$val=str_replace($html_entity_strings, $replacestring, $val);
}
}
# Append text/option(s) mode?
elseif ($mode=="AP" && in_array($fields[$n]["type"],$TEXT_FIELD_TYPES))
{
$val = $existing . " " . $origval;
}
# Prepend text/option(s) mode?
elseif ($mode=="PP" && in_array($fields[$n]["type"],$TEXT_FIELD_TYPES))
{
global $filename_field;
if ($fields[$n]["ref"]==$filename_field)
{
$val=rtrim($origval,"_")."_".trim($existing); // use an underscore if editing filename.
}
else {
# Automatically append a space when appending text types.
$val = $origval . " " . $existing;
}
}
elseif ($mode=="RM")
{
# Remove text/option(s) mode
$val = str_replace($origval,"",$existing);
if($fields[$n]["required"] && strip_leading_comma($val)=="")
{
// Required field and no value now set, revert to existing and add to array of failed edits
$val=$existing;
if(!isset($errors[$fields[$n]["ref"]]))
{
$errors[$fields[$n]["ref"]]=$lang["requiredfield"] . ". " . $lang["error_batch_edit_resources"] . ": " ;
}
$errors[$fields[$n]["ref"]] .= $ref;
if($m<count($list)-1)
{
$errors[$fields[$n]["ref"]] .= ",";
}
}
}
elseif ($mode=="CF")
{
# Copy text from another text field
$copyfrom = (int)$postvals["copy_from_field_" . $fields[$n]["ref"]] ?? 0;
if(!in_array($fields[$n]["type"],$TEXT_FIELD_TYPES))
{
// Not a valid option for this field
debug("Copy data from field " . $copyfrom . " to field " . $fields[$n]["ref"] . " requires target field to be of a text type");
continue;
}
$val = get_data_by_field($ref,$copyfrom);
if($fields[$n]["required"] && strip_leading_comma($val)=="")
{
// Required field and no value now set, revert to existing and add to array of failed edits
$val=$existing;
if(!isset($errors[$fields[$n]["ref"]]))
{$errors[$fields[$n]["ref"]]=$lang["requiredfield"] . ". " . $lang["error_batch_edit_resources"] . ": " ;}
$errors[$fields[$n]["ref"]] .= $ref;
if($m<count($list)-1)
{
$errors[$fields[$n]["ref"]] .= ",";
}
continue;
}
}
# Possibility to hook in and alter the value - additional mode support
$hookval = hook('save_resource_data_multi_extra_modes', '', array($ref, $fields[$n],$existing,$postvals,&$errors));
if($hookval !== false )
{
if(!is_string($hookval))
{
continue;
}
$val = $hookval;
}
# Check for regular expression match
if (strlen(trim((string)$fields[$n]["regexp_filter"]))>=1 && strlen($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;
}
}
if ($val !== $existing || $value_changed)
{
if($fields[$n]["required"] && $val=="")
{
// Required field and no value now set, revert to existing and add to array of failed edits
if(!isset($errors[$fields[$n]["ref"]]))
{$errors[$fields[$n]["ref"]]=$lang["requiredfield"] . ". " . $lang["error_batch_edit_resources"] . ": " ;}
$errors[$fields[$n]["ref"]] .= $ref;
if($m<count($list)-1)
{
$errors[$fields[$n]["ref"]] .= ",";
}
continue;
}
// 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;
}
// Find existing node IDs for this non-fixed list field (there should only be one). These can then be resused or deleted, unless used by other resources.
$current_field_nodes = $existing_nodes[$ref][$fields[$n]['ref']] ?? [];
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 > 0 && $inusecount == 1 && is_null($use_node))
{
// Node can be reused or deleted
debug("Found node only in use by resource #" . $ref . ", node # " . $current_field_node);
$use_node = $current_field_node;
}
else
{
// Remove node from resource and create a new node
debug("Removing node from resource #" . $ref . ", node # " . $current_field_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
if(!is_null($use_node))
{
$nodes_to_remove[] = $use_node;
$nodes_check_delete[] = $use_node;
}
}
else
{
$findnode = get_node_id($val,$fields[$n]["ref"]);
if($findnode === false)
{
debug("No existing node found for value : '" . $val . "'");
// No existing node, rename/create node
$newnode = set_node($use_node, $fields[$n]["ref"], $val, null, null);
if($newnode == $use_node)
{
// May have simply renamed the node but add to array as other resources may not have it
$nodes_to_add[] = $newnode;
debug("Renamed node #" . $newnode . " to " . $val);
}
else
{
// New node created, add this to resource and delete old node
debug("Created new node #" . $newnode . " for " . $val);
$nodes_to_add[] = $newnode;
if(!is_null($use_node))
{
$nodes_to_remove[] = $use_node;
$nodes_check_delete[] = $use_node;
}
}
}
else
{
// Another node has the same name, use that and delete existing node
debug("Using existing node #" . $findnode);
$nodes_to_add[] = $findnode;
if(!is_null($use_node))
{
$nodes_to_remove[] = $use_node;
}
}
}
// Need to save data separately as potentially setting different values for each resource
$resource_nodes_add[$ref] = array_merge($resource_nodes_add[$ref] ?? [] ,$nodes_to_add);
$resource_nodes_remove[$ref] = array_diff(array_merge($resource_nodes_remove[$ref] ?? [],$nodes_to_remove),$resource_nodes_add[$ref]);
$resource_log_updates[$ref][] = [
'ref' => $ref,
'type' => LOG_CODE_EDITED,
'field' => $fields[$n]["ref"],
'notes' => '',
'from' => $existing,
'to' => $val,
];
// If this is a 'joined' field it still needs to add it to the resource column
if(in_array($fields[$n]['ref'], $joins))
{
$resource_update_sql_arr[$ref][] = "field" . (int)$fields[$n]["ref"] . " = ?";
$resource_update_params[$ref][]="s";$resource_update_params[$ref][] = truncate_join_field_value($val);
}
$newval=$val;
// Add any onchange code
if($fields[$n]["onchange_macro"]!="")
{
$macro_resource_id=$ref;
eval(eval_check_signed($fields[$n]["onchange_macro"]));
}
$successfully_edited_resources[] = $ref;
$updated_resources[$ref][$fields[$n]['ref']][] = $newval; // To pass to hook
}
} // End of for each resource
} // End of non-fixed list editing section
} // End of foreach field loop
// Perform the actual updates
db_begin_transaction("save_resource_data_multi");
// Add/remove nodes for all resources
if(count($all_nodes_to_add)>0)
{
add_resource_nodes_multi($list, $all_nodes_to_add, false);
}
if(count($all_nodes_to_remove)>0)
{
delete_resource_nodes_multi($list,$all_nodes_to_remove);
}
// Updates for individual resources
foreach($resource_nodes_add as $resource=>$addnodes)
{
add_resource_nodes($resource,$addnodes,false,false);
}
foreach($resource_nodes_remove as $resource=>$delnodes)
{
delete_resource_nodes($resource,$delnodes,false);
}
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);
}
// Update resource table
foreach($resource_update_sql_arr as $resource=>$resource_update_sql)
{
$sql = "UPDATE resource SET " . implode(",",$resource_update_sql) . " WHERE ref=?";
$sqlparams = array_merge($resource_update_params[$resource],["i",$resource]);
ps_query($sql,$sqlparams);
}
// Log the updates
foreach($resource_log_updates as $resource=>$log_add)
{
foreach($log_add as $log_sql)
{
resource_log($resource,$log_sql["type"],$log_sql["field"],$log_sql["notes"],$log_sql["from"],$log_sql["to"]);
}
}
foreach($log_node_updates as $resource=>$log_add)
{
foreach($log_add as $log_node_sql)
{
log_node_changes($resource,$log_node_sql["to"],$log_node_sql["from"]);
}
}
// Autocomplete follows principal resource update
foreach ($list as $resource_id)
{
autocomplete_blank_fields($resource_id, false); // false means only autocomplete blank fields
}
db_end_transaction("save_resource_data_multi");
// Also save related resources field
if(($postvals["editthis_related"] ?? "") != "")
{
$related = explode(',', ($postvals['related'] ?? ''));
// Make sure all submitted values are numeric and each related resource is editable.
$resources_to_relate = array();
$no_access_to_relate = array();
for($n = 0; $n < count($related); $n++)
{
$ref_to_relate = trim($related[$n]);
if(is_numeric($ref_to_relate))
{
if (!get_edit_access($ref_to_relate))
{
debug("Edit multiple - Failed to update related resource - no edit access to resource $ref_to_relate");
$no_access_to_relate[] = $ref_to_relate;
}
else
{
$resources_to_relate[] = $ref_to_relate;
}
}
}
if(count($no_access_to_relate) > 0)
{
$errors[] = $lang["error-edit_noaccess_related_resources"] . implode(",",$no_access_to_relate);
return $errors;
}
// Clear out all relationships between related resources in this collection
ps_query("
DELETE rr
FROM resource_related AS rr
INNER JOIN collection_resource AS cr ON rr.resource = cr.resource
WHERE cr.collection = ?",
["i",$collection]
);
for($m = 0; $m < count($list); $m++)
{
$ref = $list[$m];
// Only add new relationships
$existing_relations = ps_array("SELECT related value FROM resource_related WHERE resource = ?", array("i", $ref));
// Don't relate a resource to itself
$for_relate_sql = array();
$for_relate_parameters = array();
foreach ($resources_to_relate as $resource_to_relate)
{
if ($ref != $resource_to_relate && !in_array($resource_to_relate, $existing_relations))
{
$for_relate_sql = array_merge($for_relate_sql, array('(?, ?)'));
$for_relate_parameters = array_merge($for_relate_parameters, array("i", $ref, "i", $resource_to_relate));
}
}
if(0 < count($for_relate_sql))
{
ps_query("INSERT INTO resource_related (resource, related) VALUES " . implode(",", $for_relate_sql), $for_relate_parameters);
$successfully_edited_resources[] = $ref;
}
}
}
# Also update archive status
if (($postvals["editthis_status"] ?? "") != "")
{
for ($m=0;$m<count($list);$m++)
{
$ref=$list[$m];
if (!hook('forbidsavearchive', '', array($errors)))
{
$oldarchive = ps_value("SELECT archive value FROM resource WHERE ref = ?" ,["i",$ref],"");
$setarchivestate = ((int)$postvals["status"] ?? $oldarchive); // Originally used to get the 'archive' value but this conflicts with the archive used for searching
$successfully_edited_resources[] = $ref;
$set_archive_state_hook = hook("save_resource_data_multi_set_archive_state", "", array($ref, $oldarchive));
if($set_archive_state_hook !== false && is_numeric($set_archive_state_hook))
{
$setarchivestate = $set_archive_state_hook;
}
if($setarchivestate!=$oldarchive && !checkperm("e" . $setarchivestate)) // don't allow change if user has no permission to change archive state
{
$setarchivestate=$oldarchive;
}
if ($setarchivestate!=$oldarchive) // Only if changed
{
update_archive_status($ref,$setarchivestate,array($oldarchive));
}
}
}
}
# Expiry field(s) edited? Reset the notification flag so that warnings are sent again when the date is reached.
if ($expiry_field_edited)
{
if (count($list)>0)
{
ps_query("UPDATE resource SET expiry_notification_sent=0 WHERE ref IN (" . ps_param_insert(count($list)) . ")",ps_param_fill($list,"i"));
}
$successfully_edited_resources = array_merge($successfully_edited_resources,$list);
}
# Also update access level
if (($postvals["editthis_created_by"] ?? "") != "" && $edit_contributed_by)
{
for ($m=0;$m<count($list);$m++)
{
$ref=$list[$m];
$created_by = ps_value("SELECT created_by value FROM resource WHERE ref=?",array("i",$ref),"");
$new_created_by = (int)$postvals["created_by"] ?? 0;
if($new_created_by > 0 && $new_created_by != $created_by)
{
ps_query("UPDATE resource SET created_by=? WHERE ref=?",array("i",$new_created_by,"i",$ref));
$olduser=get_user($created_by);
$newuser=get_user($new_created_by);
resource_log($ref,LOG_CODE_CREATED_BY_CHANGED,0,"",$created_by . " (" . ($olduser["fullname"]=="" ? $olduser["username"] : $olduser["fullname"]) . ")",$new_created_by . " (" . ($newuser["fullname"]=="" ? $newuser["username"] : $newuser["fullname"]) . ")");
$successfully_edited_resources[] = $ref;
}
}
}
# Also update access level
if (($postvals["editthis_access"] ?? "") != "")
{
for ($m=0;$m<count($list);$m++)
{
$ref=$list[$m];
$access = (int)$postvals["access"] ?? 0;
$oldaccess=ps_value("SELECT access value FROM resource WHERE ref=?",array("i",$ref),"");
if ($access!=$oldaccess)
{
ps_query("UPDATE resource SET access=? WHERE ref=?",array("i",$access,"i",$ref));
if ($oldaccess==3)
{
# Moving out of custom access - delete custom usergroup access.
delete_resource_custom_access_usergroups($ref);
}
resource_log($ref,LOG_CODE_ACCESS_CHANGED,0,"",$oldaccess,$access);
$successfully_edited_resources[] = $ref;
}
# For access level 3 (custom) - also save custom permissions
if ($access==3) {save_resource_custom_access($ref);}
}
}
# Update resource type?
if (($postvals["editresourcetype"] ?? "") != "")
{
$newrestype = (int)$postvals["resource_type"] ?? 0;
$alltypes=get_resource_types();
if(in_array($newrestype,array_column($alltypes,"ref")))
{
for ($m=0;$m<count($list);$m++)
{
$ref=$list[$m];
update_resource_type($ref,$newrestype);
$successfully_edited_resources[] = $ref;
}
}
}
# Update location?
if (($postvals["editlocation"] ?? "") != "" || ($postvals["editmaplocation"] ?? "") != "") {
$location=explode(",",$postvals["location"]);
if (count($list)>0) {
$list_data = get_resource_data_batch($list);
$log_location = "";
if (count($location)==2) {
$geo_lat=(float)$location[0];
$geo_long=(float)$location[1];
$log_location = $geo_lat . ", " . $geo_long;
ps_query(
"UPDATE resource SET geo_lat = ?,geo_long = ? WHERE ref IN (" . ps_param_insert(count($list)) . ")",
array_merge(["d",$geo_lat,"d",$geo_long],ps_param_fill($list,"i"))
);
} elseif (($postvals["location"] ?? "") == "") {
ps_query(
"UPDATE resource SET geo_lat=NULL,geo_long=NULL WHERE ref IN (" . ps_param_insert(count($list)) . ")",
ps_param_fill($list,"i")
);
}
foreach ($list as $ref) {
$successfully_edited_resources[] = $ref;
if($list_data[$ref]["geo_lat"] != "" && $list_data[$ref]["geo_long"] != "") {
$old_location = $list_data[$ref]["geo_lat"] . ", " . $list_data[$ref]["geo_long"];
}
resource_log(
$ref,
LOG_CODE_EDITED_RESOURCE,
null,
$log_location!=""?"Edited Location":"Removed Location",
$old_location??"",
$log_location
);
}
}
}
# Update mapzoom?
if (($postvals["editmapzoom"] ?? "") != "")
{
$mapzoom = $postvals["mapzoom"] ?? "";
if (count($list)>0)
{
if ($mapzoom != "")
{
ps_query("UPDATE resource SET mapzoom = ? WHERE ref IN (" . ps_param_insert(count($list)) . ")",array_merge(["i",$mapzoom], ps_param_fill($list,"i")));
}
else
{
ps_query("UPDATE resource SET mapzoom=NULL WHERE ref IN (" . ps_param_insert(count($list)) . ")",ps_param_fill($list,"i"));
}
foreach ($list as $ref)
{
$successfully_edited_resources[] = $ref;
}
}
}
hook("saveextraresourcedata","",array($list));
// Plugins can do extra actions once all fields have been saved and return errors back if needed.
// NOTE: Ensure the list of arguments is matching with aftersaveresourcedata hook in save_resource_data()
$plg_errors = hook('aftersaveresourcedata', '', array($list, $all_nodes_to_add, $all_nodes_to_remove, '', $fields,$updated_resources));
if(is_array($plg_errors) && !empty($plg_errors))
{
$errors = array_merge($errors, $plg_errors);
}
if(!empty($successfully_edited_resources))
{
$successfully_edited_resources = array_unique($successfully_edited_resources);
foreach ($successfully_edited_resources as $editedref)
{
daily_stat("Resource edit", $editedref);
}
}
if(count($save_warnings)>0)
{
$save_message = new ResourceSpaceUserNotification();
$save_message->set_subject("lang_editallresources");
$save_message->set_text($lang["batch_edit_save_warning_message"]); // No line breaks or on screen message will end up with <br> tags
$save_message->append_text("<div>");
foreach($save_warnings as $save_warning)
{
$save_message->append_text("<div><strong>" . $lang["resourceid"] . ": <a href ='" . $baseurl . "/?r=" . $save_warning["Resource"] . "' target='_blank'>" . $save_warning["Resource"] . "</a></strong><br/><strong>" . $lang["field"] . ": </strong>" . $save_warning["Field"] . "<br /><strong>" . $lang["error"] . ": </strong>" . $save_warning["Message"] . "</div><br />");
}
$save_message->append_text("</div>");
send_user_notification([$userref],$save_message);
$errors[] = $lang["batch_edit_save_warning_alert"];
}
if (count($errors)==0)
{
return true;
}
else
{
return $errors;
}
}
This article was last updated 17th November 2024 15:35 Europe/London time based on the source file dated 15th November 2024 18:30 Europe/London time.