Coding standards
Security in ResourceSpace
Developer reference
Database
Action functions
Admin functions
Ajax functions
Annotation functions
API functions
Collections functions
Comment functions
Config functions
CSV export functions
Dash functions
Debug functions
Encryption functions
Facial recognition functions
File functions
General functions
Language functions
Log functions
Login functions
Message functions
Migration functions
Node functions
PDF functions
Plugin functions
Render functions
Reporting functions
Request functions
Research functions
Slideshow functions
Theme permission functions
User functions
Video functions
Database functions
Metadata functions
Resource functions
Search functions
Map functions
Job functions
Tab functions
Test functions

save_resource_data_multi()

Description

Batch save resources in a collection

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
$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 2609

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 == && isset($editsearch["search"]))
        {
        
// Editing a result set, not a collection
        
$edititems  do_search($editsearch["search"], $editsearch["restypes"],'resourceid',$editsearch["archive"], -1'ASC'false0falsefalse''falsefalsetruetruefalse$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($listresourcetrue);
        if(!
get_edit_access($listresource,$resource_data[$listresource]["archive"]))
            {
            
$noeditaccess[] = $listresource;
            }
        if(
$resource_data[$listresource]["lock_user"] > && $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++)
        {
        if (
PHP_SAPI !== "cli") {
            
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($fieldnodesnull'ref');
            
$valid_nodes in_array($fields[$n]['type'], [FIELD_TYPE_DROP_DOWN_LISTFIELD_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'$modeimplode(','$nodes_to_add)));
            
debug(sprintf('Mode %s - $nodes_to_remove = %s'$modeimplode(','$nodes_to_remove)));

            if(
$fields[$n]["required"] == && count($nodes_to_add) == && $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'$refimplode(','$resource_nodes_add[$ref])));
                        
debug(sprintf('$resource_nodes_remove[%s] = %s'$refimplode(','$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)>|| 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;
                    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"], $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 $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"], $valnullnull);
                        
$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"])
                            {
                            
$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)>|| 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"] == && !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($existingENT_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. &#39; 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("&#39;","&rsquo;");

                        
// 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_subescape($findstring));
                        
$html_entity_strings[] = str_replace($rich_field_characters_replace$rich_field_characters_subhtmlentities($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"]))>=&& 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 && $inusecount == && 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"], $valnullnull);
                            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_addfalse);
        }
    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_idfalse);  // 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(
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 && $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 13th December 2024 20:35 Europe/London time based on the source file dated 13th December 2024 14:45 Europe/London time.