Collections functions
General functions
Render functions
Theme permission functions
Resource functions

save_resource_data_multi()

Parameters

ColumnTypeDefaultDescription
$collection
$editsearch array
$postvals []

Location

include/resource_functions.php lines 1328 to 2433

Definition

 
function save_resource_data_multi($collection,$editsearch = array(), $postvals = [])
    {
    global 
$FIXED_LIST_FIELD_TYPES,$DATE_FIELD_TYPES$range_separator$date_validator$edit_contributed_by$TEXT_FIELD_TYPES$userref$lang$multilingual_text_fields$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',false,0,false,false,'',false,falsetruetrue);
        
$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"], $resource_data[$listresource]))
            {
            
$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);
    
$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']));
        }));

    
// 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++)
        {
        
$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']];
                }
            else if(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);
            
$nodes_by_ref array_combine(array_column($fieldnodes'ref'),$fieldnodes);
            
$valid_nodes array_column($fieldnodes'ref');
            
$node_options array_column($fieldnodes'name''ref');

            
// $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);
                
debug("Removing nodes: " .  implode(",",$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);
                }

            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);
                
$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
                
$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);                        
                        
$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);});
                   
                    
$log_node_updates[$ref][] = [
                        
'from'  => $current_field_nodes,
                        
'to'    => $nodes_to_add,
                        ];

                    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
                        
}
                    }
                }
            } 
// 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 $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 "year"] ?? "";
                    if ((int) 
$val <= 0)
                        {
                        
$val="";
                        }
                    elseif ((
$field = ($postvals["field_" $fields[$n]["ref"] . $date_part "month"] ?? "")) != "")
                        {
                        
$val.="-" $field;
                        if ((
$field=($postvals["field_" $fields[$n]["ref"] . $date_part "day"] ?? "")) != "")
                            {
                            
$val.="-" $field;
                            }
                        else
                            {
                            
$val.="-00";
                            }
                        }
                    else
                        {
                        
$val.="-00-00";
                        }
                    if(
$val!=="")
                        {
                        
$daterangenodes[]=set_node(null$fields[$n]["ref"], $valnullnull);
                        
$newval .= ($newval!=""?$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($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)
                        {
                        
$new_nodes array_diff(array_merge($current_field_nodes$added_nodes), $removed_nodes);
                        
                        
$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(
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);

                if (
$date_validator && $val != "")
                    {
                    
# date type, construct the value from the date/time dropdowns to be used in date validator
                    
$check_date_val=sanitize_date_field_input($fields[$n]["ref"], true);

                    
$valid_date str_replace("%field%"$fields[$n]['name'], check_date_format($check_date_val));
                    
$valid_date str_replace("%row% """$valid_date);
                    if (
$valid_date && !$valid_date == "")
                        {
                        
$errors[$fields[$n]["ref"]] = $valid_date;
                        continue;
                        }
                    }
                }
            elseif (
                    
$multilingual_text_fields
                
&& (
                    
$fields[$n]["type"]==FIELD_TYPE_TEXT_BOX_SINGLE_LINE
                    
|| $fields[$n]["type"]==FIELD_TYPE_TEXT_BOX_MULTI_LINE
                    
|| $fields[$n]["type"]==FIELD_TYPE_TEXT_BOX_LARGE_MULTI_LINE
                    
)
                )
                {
                
# Construct a multilingual string from the submitted translations
                
$val $postvals["field_" $fields[$n]["ref"]] ?? "";
                
$val="~" $language ":" $val;
                
reset ($languages);
                foreach (
$languages as $langkey => $langname)
                    {
                    if (
$language!=$langkey)
                        {
                        
$val.="~" $langkey ":" . (string)($postvals["multilingual_" $n "_" $langkey] ?? "");
                        }
                    }
                }
            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(
                    (
                        
// Not applicable for global fields or archive only fields
                        
!in_array($fields[$n]["resource_type"], array(0999))
                        && 
$resource_data[$ref]["resource_type"] != $fields[$n]["resource_type"]
                    )
                    || (
$fields[$n]["resource_type"] == 999 && $resource_data[$ref]["archive"] != 2)
                )
                    {
                    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_subhtmlspecialchars($findstring));
                        
$html_entity_strings[] = str_replace($rich_field_characters_replace$rich_field_characters_subhtmlentities($findstring));
                        
$html_entity_strings[] = htmlentities($findstring);
                        
$html_entity_strings[] = htmlspecialchars($findstring);

                        
// Just need one replace string
                        
$replacestring htmlspecialchars($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 reesources
    
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
        
$resources_to_relate = array();
        for(
$n 0$n count($related); $n++)
            {
            if(
is_numeric(trim($related[$n])))
                {
                
$resources_to_relate[] = trim($related[$n]);
                }
            }

        
// 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"] ?? "") != "")
        {
        
$notifyrefs=array();
        
$usernotifyrefs=array();
        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,true);
                
$newuser=get_user($new_created_by,true);
                
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"] ?? "") != "")
        {
        
$location=explode(",",$postvals["editlocation"]);
        if (
count($list)>0)
            {
            if (
count($location)==2)
                {
                
$geo_lat=(float)$location[0];
                
$geo_long=(float)$location[1];
                
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 $key => $ref)
                {
                
$successfully_edited_resources[] = $ref;
                }
            }
        }

    
# 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 $key => $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)
            {
            
$field get_resource_type_field($save_warning["Field"]);
            
$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 5th February 2023 07:35 Europe/London time based on the source file dated 3rd February 2023 13:55 Europe/London time.