How to set the value of an entity reference field with the value of another entity reference field in a hook function - D9 - drupal

I am using a hook function to create a commerce product when a node type of PL is created.
I have 2 Entity Reference fields:
Name of Project - Which references content
Type of Use - Which references a taxonomy term
Everything works and the title field is set correctly except for the 2 field values, field_name_of_project and field_type_of_use.
What could I be doing wrong.
Here's my code:
<?php
/**
* #file
* file for the PL Product Creation module, which creates a product and product variations when a Pl node is created.
*
*/
use Drupal\node\Entity\Node;
use Drupal\node\NodeInterface;
use Drupal\commerce_product\Entity\ProductVariation;
use Drupal\commerce_product\Entity\Product;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
/**
* Implementation of hook_entity_insert().
*/
function pl_product_creation_entity_insert(Drupal\Core\Entity\EntityInterface $entity) {
if ($entity->getEntityTypeId() == 'node' && $entity->bundle() == 'pl_node') {
// Get the pl node properties
$name_of_project = $entity->field_name_of_project->entity->getTitle();
$type_of_use = $entity->field_type_of_use->entity->getName();
// Load the product
$pl_product_title = 'Products at ' . $name_of_project;
// Load the product storage.
$entity_type_manager = \Drupal::service('entity_type.manager');
$product_storage = $entity_type_manager->getStorage('commerce_product');
// Load the product by title.
$pl_product = $product_storage->loadByProperties(['title' => $pl_product_title]);
// Check if the Pl product exists
if (!empty($pl_product)) {
// Load the Pl product
$pl_product = reset($pl_product);
}
else {
// Create a new Pl product
$pl_product = Product::create([
'type' => 'pl',
]);
// Set the title field.
$pl_product->setTitle($pl_product_title);
// Set the values of the custom fields.
$pl_product->set('field_name_of_project', $name_of_project);
$pl_product->set('field_type_of_use', $type_of_use);
// Save the product entity.
$pl_product->save();
}
}
}

I was finally able to figure it out. Posting here in case someone needs this in the future.
// Set the values of the custom fields.
$pl_product->set('field_name_of_project', $entity->field_name_of_project->entity);
$pl_product->set('field_type_of_use', $entity->field_type_of_use->entity);

Related

Automatically generate tags based on Post Object (ACF) titles in WordPress

I'm creating a custom post type for our projects page. I've also made a custom posts type for our employees.
With ACF I've made a Relationship field which where you can add the team members to a project and it's displayed on the website.
Based in the selected team members posts in the relationship field I would like the generate a tag for each title (Employee name) that is loaded in the relationship field.
This is were I'm stuck now.
The name in the Post Object is called teamleden. I've tried adding code to my customs posts type file but it doesn't work.
<?php
// save post action
add_action('acf/save_post', 'set_employee_tags_on_save_update', 20);
/**
* #param $post_id int|string
*/
function set_employee_tags_on_save_update($post_id) {
// get our current post object
$post = get_post($post_id);
// if post is object
if(is_object($post)) {
// check we are on the projects custom type and post statuses are either publish or draft
// change 'projects' post type to your post type name which has the relationship field
if($post->post_type === 'projects' && ($post->post_status === 'publish' || $post->post_status === 'draft')) {
// get relationship field employees
// this example uses Post Object as the Return Format and is a multiple value
// change get field param 'employees' to your relationship field name
$employees = get_field('employees');
// team member tags to set empty array
$team_member_tags_to_set = [];
// if employees has value or values
if($employees) {
// get all of our current team member tags
// change 'team_members' taxonomy value to your members taxonomy name
$team_member_tags = get_terms([
'taxonomy' => 'team_members',
'orderby' => 'name',
'order' => 'ASC'
]);
// empty array for existing team member tags
$existing_team_member_tags = [];
// if we have existing team member tags
if(!empty($team_member_tags)) {
// foreach team member tags as team member tag
foreach($team_member_tags as $team_member_tag) {
// add existing team member to our existing team member tags array by tag ID => tag name
// this is so we can use this later to check if a team member tag already exists so we dont create duplicates
$existing_team_member_tags[$team_member_tag->ID] = $team_member_tag->name;
}
}
// foreach employees as employee
foreach($employees as $employee) {
// get the title for current employee
$title = get_the_title($employee->ID);
// search existing team members array and return the tag id via key
$existing_team_member_tag_id = array_search($title, $existing_team_member_tags);
// if we have an existing team member tag id
if($existing_team_member_tag_id) {
// add the existing team member tag id as integer to array
$team_member_tags_to_set[] = (int)$existing_team_member_tag_id;
} else {
// else create a new tag for team member by adding title (name) as string to array
$team_member_tags_to_set[] = (string)$title;
}
}
}
// remove the action
remove_action('acf/save_post', 'acf_save_post');
// set post tags for this post id, removing any unused team member tags if relationship field team members are changed
wp_set_object_terms($post_id, $team_member_tags_to_set, $taxonomy = 'team_members', false);
// re add the action
add_action('acf/save_post', 'acf_save_post');
}
}
// finally return
return;
}
This is not tested but should get you on the right track.
You will need to add this in your function.php.
Reference to things you will need to change in my example code...
projects post_type is the post type name which this code fires when post is saved or updated.
employees is the name of the acf relationship field in the projects post type.
team_members is the custom tag taxonomy name which you should have working on your projects post type.
Basically what this code does, is when a projects post is saved, published or updated using the acf/save_post action. It gets the acf relationship field member data for this post. If there are members in the field, it then gets all existing team member tags and creates a simple array of existing member tags by ID => Title(name).
It then loops through all members in the relationship field, and checks if tag already exists for member, if it does it adds the existing member tag (int) ID to the $team_member_tags_to_set array. If no existing member tag is found, we just add the member title (name) as a (string) to the $team_member_tags_to_set array.
After all this is done, we simply use wp_set_post_tags() passing our $team_member_tags_to_set array to update team_members taxonomy terms for the current projects post.
I've also set append to false in wp_set_post_tags() which removes all previous tags and creates a new set of tags. This will help if members get updated in the acf relationship field.
https://developer.wordpress.org/reference/functions/wp_set_post_tags/
See code below, and read my comments so you know whats happening.
<?php
// save post action
add_action('acf/save_post', 'set_employee_tags_on_save_update', 20);
/**
* #param $post_id int|string
*/
function set_employee_tags_on_save_update($post_id) {
// get our current post object
$post = get_post($post_id);
// if post is object
if(is_object($post)) {
// check we are on the projects custom type and post statuses are either publish or draft
// change 'projects' post type to your post type name which has the relationship field
if($post->post_type === 'projects' && ($post->post_status === 'publish' || $post->post_status === 'draft')) {
// get relationship field employees
// this example uses Post Object as the Return Format and is a multiple value
// change get field param 'employees' to your relationship field name
$employees = get_field('employees');
// team member tags to set empty array
$team_member_tags_to_set = [];
// if employees has value or values
if($employees) {
// get all of our current team member tags
// change 'team_members' taxonomy value to your members taxonomy name
$team_member_tags = get_terms([
'taxonomy' => 'team_members',
'orderby' => 'name',
'order' => 'ASC'
]);
// empty array for existing team member tags
$existing_team_member_tags = [];
// if we have existing team member tags
if(!empty($team_member_tags)) {
// foreach team member tags as team member tag
foreach($team_member_tags as $team_member_tag) {
// add existing team member to our existing team member tags array by tag ID => tag name
// this is so we can use this later to check if a team member tag already exists so we dont create duplicates
$existing_team_member_tags[$team_member_tag->ID] = $team_member_tag->name;
}
}
// foreach employees as employee
foreach($employees as $employee) {
// get the title for current employee
$title = get_the_title($employee->ID);
// search existing team members array and return the tag id via key
$existing_team_member_tag_id = array_search($title, $existing_team_member_tags);
// if we have an existing team member tag id
if($existing_team_member_tag_id) {
// add the existing team member tag id as integer to array
$team_member_tags_to_set[] = (int)$existing_team_member_tag_id;
} else {
// else create a new tag for team member by adding title (name) as string to array
$team_member_tags_to_set[] = (string)$title;
}
}
}
// remove the action
remove_action('acf/save_post', 'acf_save_post');
// set post tags for this post id, removing any unused team member tags if relationship field team members are changed
wp_set_post_tags($post_id, $team_member_tags_to_set, false);
// re add the action
add_action('acf/save_post', 'acf_save_post');
}
}
// finally return
return;
}

Trying to get shipping fields with a new custome field created

I am trying to get the array for all the shipping forms with a new custom element that I already added. This code is not working:
$newObj = new WC_Checkout();
$shipping_fields = $newObj->get_checkout_fields($fieldset = 'shipping');
Woocommerce WC_Checkout methods:
Visit https://docs.woocommerce.com/wc-apidocs/source-class-WC_Checkout.html#197-281 for more info!
https://superuser.com?
I get NULL so the object is not existing
In detail:
I'm developing a website, where the total price is calculated according to a delivery area in some city. I decided to create a custom field within the shipping-form:
// Adding districts for the city of Lima on shipping form
add_filter('woocommerce_checkout_fields', 'custom_district_checkout_field');
function custom_district_checkout_field($fields) {
//the list for this example was shortened
$option_cities = array(
'' =>__('Select your district'),
'chorrillos' =>'Chorrillos',
'miraflores' =>'Miraflores'
};
$fields['shipping']['shipping_district']['type'] = 'select';
$fields['shipping']['shipping_district']['options'] = $option_cities;
$fields['shipping']['shipping_district']['class'] = array('update_totals_on_change');
$fields['shipping']['shipping_district']['input_class'] = array('wc-enhanced-select');
$fields['billing']['billing_district']['type'] = 'select';
$fields['billing']['billing_district']['options'] = $option_cities;
$fields['billing']['billing_district']['input_class'] = array('wc-enhanced-select');
wc_enqueue_js("jQuery( ':input.wc-enhanced-select' ).filter( ':not(.enhanced)' ).each( function() {var select2_args = { minimumResultsForSearch:5};jQuery( this ).select2( select2_args ).addClass( 'enhanced' );})");
return $fields;
I can confirm that the custome field is working. For other hand I'm trying to change the way the WooCommerce Advance Shipping plugin works as Jeroen Sormani (who is the developer) explain in his blogs:
How the plugin works!
and WAS Shipping fields conditions
The idea is to add to the condition list the shipping fields, the plugin shows by default this fields: WC Advanced Shipping Fields by Default
The goal is to been able to select the newly created field in the conditions (for example: "districts") so the price would appear in the cart when the user select the correct option, the plugin already has a list of the different districts with their respective prices. However, there is an error in the plugin because this line is not working (check the Github for the WAS Shipping fields conditions inside the first function:
$shipping_fields = WC()->checkout()->checkout_fields['shipping'];
I have been trying to solve this for weeks, hence the original ask in this post.
/**
* WAS all checkout fields
*/
add_filter('was_conditions', 'was_conditions_add_shipping_fields', 10, 1);
function was_conditions_add_shipping_fields($conditions) {
$newObj = new WC_Checkout();
$shipping_fields = $newObj->get_checkout_fields($fieldset = 'shipping');
debugToConsole($shipping_fields);
foreach ($shipping_fields as $key => $values) :
if (isset($values['label'])) :
$conditions['Shipping Fields'][$key] = $values['label'];
endif;
endforeach;
return $conditions;
}
The above results in a NULL with the debugToConsole function.

Retrieve field group (tab) label name in ACF

I'm running Advanced Custom Fields in Wordpress. In ACF, you can group items in Tabs, making it easier to navigate to create a post/page content.
I'm wanting to retrieve the name of the tab, and it's content, programmatically.
Is it possible? I can't find any documentation regarding this.
Not sure if you got an answer for your question,
There is no way through php code to get field/subfield data as there is no relationship between tabs and fields under them.
ACF Tabs are only for admin layout to organise fields(hide/show etc) and is done completely through JS/CSS. If you have ACF PRO check the functions in this folder - plugins/advanced-custom-fields-pro/assets/js/acf-input.js which explains how tabs work in ACF
If you want to get fields which are not in repeater/flexible layouts in an array then, You can get an entire set of fields data, which is explained in this ACF GET FIELDS IN A GROUP
I was faced with this problem today, figured I'd post my solution. Based on my research, there's no way to access the tab field directly except to find it in the field group and iterate backwards until a field of type tab is found.
Class ACFHelper {
/**
* get_field_group_by_name: gets the first matching field group for a given title
*
* #param $title the title of the acf field group
* #return Array the fields of the field group or false if not found
*
*/
public static function get_field_group_by_name($title) {
$field_group_post = get_posts([
'numberposts' => 1,
'post_type' => 'acf-field-group',
's' => $title
]);
return ( !empty($field_group_post[0]) ? acf_get_fields( $field_group_post[0]->ID ):false );
}
/**
* get_tab_by_field_name: gets the tab of a specified field by iterating backwards through the field group
*
* #param $field_group_title the title of the acf field group
* #param $field_name the name of the field for which we want the tab it's under
* #return Array the field group of the tab or false if not found
*
*/
public static function get_tab_by_field_name($field_group_title, $field_name) {
$field_group = Self::get_field_group_by_name($field_group_title);
if($field_group) {
$field_index = array_search($field_name, array_column($field_group, 'name'));
if($field_index) {
for($field_index; $field_index > -1; $field_index--) {
if( $field_group[$field_index]['type'] == 'tab' )
return $field_group[$field_index];
}
}
}
return false;
}
}
So now if I have a field strawberry under the fruits tab, I can find my fruits tab in my foods field group like:
$tab = ACFHelper::get_tab_by_field('foods', 'strawberry');

Update a field after Linking / Unlinking Many-Many records in SilverStripe

I have created a Customer DataObject by extending Member. Customer has a many_many data relation with a Package DataObject.
I would like increment/decrement a Credits field in the Customer DataObject when a Package is linked / unlinked through the CMS based on the Limit field in the Package table.
Customer
class Customer extends Member {
private static $db = array(
'Gender' => 'Varchar(2)',
'DateOfBirth' => 'Date',
'Featured' => 'Boolean',
'Credits' => 'Int'
);
private static $many_many = array(
'Packages' => 'Package'
);
public function getCMSFields() {
$fields = new FieldList();
$config = GridFieldConfig_RelationEditor::create();
$config->removeComponentsByType('GridFieldAddNewButton');
$packageField = new GridField(
'Packages',
'Package',
$this->Packages(),
$config
);
$fields->addFieldToTab('Root.Package', $packageField);
Session::set('SingleID', $this->ID);
$this->extend('updateCMSFields', $fields);
return $fields;
}
}
Package
class Package extends DataObject {
private static $db = array(
'Title' => 'Varchar(255)',
'Limit' => 'Int'
);
private static $belongs_many_many = array(
'Customers' => 'Customer'
);
}
When you create or delete many to many relationship just one record is modified in your database - the one in table which joins elements of both sides of the relationship. Therefore neither object the relationship is based on is updated. This is why methods like: onBeforeWrite, onAfterWrite, onBeforeDelete and onAfterDelete will not be called at all and you cannot use them to detect such change.
However, Silverstripe provides class ManyManyList which is responsible for all operations connected to many to many relationships. There are two methods which are of your interest: add and remove. You can override them and put inside action to do what you need. These methods are obviously called on each link or unlink operation no matter object types are, so you should make some filtering on classes you are particularly interested in.
The proper way to override the ManyManyList class is to use Injector mechanism, so as not to modify anything inside the framework or cms folder. The example below uses relationship between Members and Groups in Silverstripe but you can easily adopt it to your need (Customer -> Member; Package -> Group).
app.yml
Injector:
ManyManyList:
class: ManyManyListExtended
ManyManyListExtended.php
/**
* When adding or removing elements on a many to many relationship
* neither side of the relationship is updated (written or deleted).
* SilverStripe does not provide any built-in actions to get information
* that such event occurs. This is why this class is created.
*
* When it is uses together with SilverStripe Injector mechanism it can provide
* additional actions to run on many-to-many relations (see: class ManyManyList).
*/
class ManyManyListExtended extends ManyManyList {
/**
* Overwritten method for adding new element to many-to-many relationship.
*
* This is called for all many-to-many relationships combinations.
* 'joinTable' field is used to make actions on specific relation only.
*
* #param mixed $item
* #param null $extraFields
* #throws Exception
*/
public function add($item, $extraFields = null) {
parent::add($item, $extraFields);
if ($this->isGroupMembershipChange()) {
$memberID = $this->getMembershipID($item, 'MemberID');
$groupID = $this->getMembershipID($item, 'GroupID');
SS_Log::log("Member ($memberID) added to Group ($groupID)", SS_Log::INFO);
// ... put some additional actions here
}
}
/**
* Overwritten method for removing item from many-to-many relationship.
*
* This is called for all many-to-many relationships combinations.
* 'joinTable' field is used to make actions on specific relation only.
*
* #param DataObject $item
*/
public function remove($item) {
parent::remove($item);
if ($this->isGroupMembershipChange()) {
$memberID = $this->getMembershipID($item, 'MemberID');
$groupID = $this->getMembershipID($item, 'GroupID');
SS_Log::log("Member ($memberID) removed from Group ($groupID)", SS_Log::INFO);
// ... put some additional actions here
}
}
/**
* Check if relationship is of Group-Member type.
*
* #return bool
*/
private function isGroupMembershipChange() {
return $this->getJoinTable() === 'Group_Members';
}
/**
* Get the actual ID for many-to-many relationship part - local or foreign key value.
*
* This works both ways: make action on a Member being element of a Group OR
* make action on a Group being part of a Member.
*
* #param DataObject|int $item
* #param string $keyName
* #return bool|null
*/
private function getMembershipID($item, $keyName) {
if ($this->getLocalKey() === $keyName)
return is_object($item) ? $item->ID : $item;
if ($this->getForeignKey() === $keyName)
return $this->getForeignID();
return false;
}
}
The solution provided by 3dgoo should also work fine but IMO that code does much more "hacking" and that's why it is much less maintainable. It demands more modifications (in both classes) and needs to be multiplied if you would like to do any additional link/unlink managing, like adding custom admin module or some forms.
The problem is when adding or removing items on a many to many relationship neither side of the relationship is written. Therefore onAfterWrite and onBeforeWrite is not called on either object.
I've come across this problem before. The solution I used isn't great but it was the only thing that worked for me.
What we can do is set an ID list of Packages to a session variable when getCMSFields is called. Then when an item is added or removed on the grid field we refresh the CMS panel to call getCMSFields again. We then retrieve the previous list and compare it to the current list. If the lists are different we can do something.
Customer
class Customer extends Member {
// ...
public function getCMSFields() {
// Some JavaScript to reload the panel each time a package is added or removed
Requirements::javascript('/mysite/javascript/cms-customer.js');
// This is the code block that saves the package id list and checks if any changes have been made
if ($this->ID) {
if (Session::get($this->ID . 'CustomerPackages')) {
$initialCustomerPackages = json_decode(Session::get($this->ID . 'CustomerPackages'), true);
$currentCustomerPackages = $this->Packages()->getIDList();
// Check if the package list has changed
if($initialCustomerPackages != $currentCustomerPackages) {
// In here is where you put your code to do what you need
}
}
Session::set($this->ID . 'CustomerPackages', json_encode($this->Packages()->getIDList()));
}
$fields = parent::getCMSFields();
$config = GridFieldConfig_RelationEditor::create();
$config->removeComponentsByType('GridFieldAddNewButton');
$packageField = GridField::create(
'Packages',
'Package',
$this->Packages(),
$config
);
// This class needs to be added so our javascript gets called
$packageField->addExtraClass('refresh-on-reload');
$fields->addFieldToTab('Root.Package', $packageField);
Session::set('SingleID', $this->ID);
$this->extend('updateCMSFields', $fields);
return $fields;
}
}
The if ($this->ID) { ... } code block is where all our session code happens. Also note we add a class to our grid field so our JavaScript refresh works $packageField->addExtraClass('refresh-on-reload');
As mentioned before, we need to add some JavaScript to reload the panel each time a package is added or removed from the list.
cms-customer.js
(function($) {
$.entwine('ss', function($){
$('.ss-gridfield.refresh-on-reload').entwine({
reload: function(e) {
this._super(e);
$('.cms-content').addClass('loading');
$('.cms-container').loadPanel(location.href, null, null, true);
}
});
});
})(jQuery);
Inside the if($initialCustomerPackages != $currentCustomerPackages) { ... } code block there are a number of things you can do.
You could use $this->Packages() to fetch all the current packages associated to this customer.
You could call array_diff and array_merge to get just the packages that have been added and removed:
$changedPackageIDs = array_merge(array_diff($initialCustomerPackages, $currentCustomerPackages), array_diff($currentCustomerPackages, $initialCustomerPackages));
$changedPackages = Package::get()->byIDs($changedPackageIDs);
The above code will add this functionality to the Customer side of the relationship. If you also want to manage the many to many relationship on the Package side of the relationship you will need to add similar code to the Package getCMSFields function.
Hopefully someone can come up with a nicer solution. If not, I hope this works for you.
note: Didn't actually check does the model work but by visually checking this should help you:
On the link you provided you are using
$customer = Customer::get()->Filter...
That returns a DataList of objects, not a singular object unless you specify what is the object you want from the DataList.
If you are filtering the Customers then you want to get a SPECIFIC customer from the DataList, e.g. the first one in this case.
$customer = Customer::get()->filter(array('ID' => $this->CustomerID))->first();
But You should be able to get the singular DataObject with:
$customer = $this->Customer();
As you are defining the Customer as "has_one". If the relation was a Has many, using () would get you a DataList of objects.
Protip:
You don't need to write our own debug files in SilverStripe. It has own functions for it. For example Debug::log("yay"); what writes the output to a file and Debug::dump("yay") that dumps it directly out.
Tip is that you can check what is the object that you accessing right. Debug::dump(get_class($customer)); would output only the class of the object.

Modifying a field collection programmatically missing hostEntity fields

I am trying to modify a field collection in a node that already exists so I can change an image on the first element in an array of 3. The problem is, the hostEntity info is not set when I do a entity_load or entity_load_single so when I do a:
$field_collection_item->save(true); // with or without the true
// OR
$fc_wrapper->save(true); // with or without the true
I get the following error:
Exception: Unable to save a field collection item without a valid reference to a host entity. in FieldCollectionItemEntity->save()
When i print_r the field collection entity the hostEntity:protected fields are indeed empty. My field collection is setup as follows:
field_home_experts
Expert Image <--- Want to change this data only and keep the rest below
field_expert_image
Image
Expert Name
field_expert_name
Text
Expert Title
field_expert_title
Text
Here is the code I am trying to use to modify the existing nodes field collection:
$node = getNode(1352); // Get the node I want to modify
// There can be up to 3 experts, and I want to modify the image of the first expert
$updateItem = $node->field_home_experts[LANGUAGE_NONE][0];
if ($updateItem) { // Updating
// Grab the field collection that currently exists in the 0 spot
$fc_item = reset(entity_load('field_collection_item', array($updateItem)));
// Wrap the field collection entity in the field API wrapper
$fc_wrapper = entity_metadata_wrapper('field_collection_item', $fc_item);
// Set the new image in place of the current
$fc_wrapper->field_expert_image->set((array)file_load(4316));
// Save the field collection
$fc_wrapper->save(true);
// Save the node with the new field collection (not sure this is needed)
node_save($node);
}
Any help would be greatly appreciated, I am still quite new to Drupal as a whole (end-user or developer)
Alright so I think I have figured this out, I wrote up a function that will set a field collection values:
// $node: (obj) node object returned from node_load()
// $collection: (string) can be found in drupal admin interface:
// structure > field collections > field name
// $fields: (array) see usage below
// $index: (int) the index to the element you wish to edit
function updateFieldCollection($node, $collection, $fields = Array(), $index = 0) {
if ($node && $collection && !empty($fields)) {
// Get the field collection ID
$eid = $node->{$collection}[LANGUAGE_NONE][$index]['value'];
// Load the field collection with the ID from above
$entity = entity_load_single('field_collection_item', array($eid));
// Wrap the loaded field collection which makes setting/getting much easier
$node_wrapper = entity_metadata_wrapper('field_collection_item', $entity);
// Loop through our fields and set the values
foreach ($fields as $field => $data) {
$node_wrapper->{$field}->set($data);
}
// Once we have added all the values we wish to change then we need to
// save. This will modify the node and does not require node_save() so
// at this point be sure it is all correct as this will save directly
// to a published node
$node_wrapper->save(true);
}
}
USAGE:
// id of the node you wish to modify
$node = node_load(123);
// Call our function with the node to modify, the field collection machine name
// and an array setup as collection_field_name => value_you_want_to_set
// collection_field_name can be found in the admin interface:
// structure > field collections > manage fields
updateFieldCollection(
$node,
'field_home_experts',
array (
'field_expert_image' => (array)file_load(582), // Loads up an existing image
'field_expert_name' => 'Some Guy',
'field_expert_title' => 'Some Title',
)
);
Hope this helps someone else as I spent a whole day trying to get this to work (hopefully I won't be a noob forever in Drupal7). There may be an issue getting formatted text to set() properly but I am not sure what that is at this time, so just keep that in mind (if you have a field that has a format of filtered_html for example, not sure that will set correctly without doing something else).
Good luck!
Jake
I was still getting the error, mentioned in the question, after using the above function.
This is what worked for me:
function updateFieldCollection($node, $collection, $fields = Array(), $index = 0) {
$eid = $node->{$collection}[LANGUAGE_NONE][$index]['value'];
$fc_item = entity_load('field_collection_item', array($eid));
foreach ($fields as $field => $data) {
$fc_item[$eid]->{$field}[LANGUAGE_NONE][0]['value'] = $data;
}
$fc_item[$eid]->save(TRUE);
}
I hope this helps someone as it took me quite some time to get this working.

Resources