Create node programmatically with cck location field - drupal

I try to programmatically create a node of custom contenttype "location" in Drupal 6, with the node containing a location field (http://drupal.org/project/location) called "location" (yes I know, the nomenclature could be better, but I am just experimenting on this at the moment).
Creating the node works just fine, but I cannot find a way to set the contents for the location field - i.e. the node is created with all content but the value for then location field.
I try creating the node like this:
$newNode = (object) NULL;
$newNode->type = 'location';
$newNode->title = $locationName;
$newNode->uid = $userId;
$newNode->created = strtotime("now");
$newNode->changed = strtotime("now");
$newNode->status = 1;
$newNode->comment = 0;
$newNode->promote = 0;
$newNode->moderate = 0;
$newNode->sticky = 0;
$newNode->field_location[0]['street'] = 'Teststraße';
$newNode->field_location[0]['postal_code'] = '12345';
$newNode->field_location[0]['city'] = 'Musterstadt';
node_save($newNode);
The node gets created with the correct title, but the location fields remain unset.
How can I programmatically set the location-related fields?
Thanks in advance!

Wanted to add this as a comment, but it seems like putting code into the comment is rather problematic. So here we go: I changed the internas so that I do not use a cck field anymore, but use the default location option as suggested by googletorp.
The actual code to create a new location and assign this to a new node looks like this:
$location['street'] = "myStreet";
$location['postal_code'] = "12345";
...
$newLocationId = location_save($location);
$newNode = ...
$newNode->locations[0]['lid'] = $newLocationId;
node_save($newNode);
Thanks for the guidance :)

Instead of node_save, many people recommend using drupal_execute to programmatically submit the node edit form. This gives you the benefit of form validation.
See http://thedrupalblog.com/programmatically-create-any-node-type-using-drupal-execute for an excellent example of using drupal_execute. Don't forget to look at the comment http://thedrupalblog.com/programmatically-create-any-node-type-using-drupal-execute#comment-70 to see some additional info on CCK fields.
The advantage of drupal_execute is that you get form validation also. So after the drupal_executestatement you can see if there were any errors using form_get_errors ( http://api.drupal.org/api/function/form_get_errors/6 ). See snippet (pasted below) from http://civicactions.com/blog/cck_import_and_update for an example of using form_get_errors
$node->type = 'yourtype';
$values = array();
$values[...] = ...;
drupal_execute('yourtype_node_form', $values, $node);
$errors = form_get_errors();
if (count($errors)) {
// do something ...
}
Another very nice resource on programmatic submission of nodes using drupal_execute can be found at http://drupal.org/node/293663

I have done this, only not with a CCK field but the default location option you can add to nodes.
What I did to make it work, was to first save the location (there's an API function for it) and then add the location id from the saved location.
Sample code:
Note, $center is from an external source, so it's not Drupal related. I know all my locations are from Denmark in my example, so that part is just hardcoded.
When you don't use a CCK field, you don't need to save the location data on the node, instead you can just save the location and pair the location yourself. It's a quick solution, instead of running through the node form like suggested. For complex nodes, that might be the better choice, but when it's simple, this is done faster.
// Update the location data.
$location = is_array($node->location) ? $node->location : array();
$location += array(
'street' => $center->address->address2,
'city' => $center->address->zipName,
'postal_code' => $center->address->zip,
'country' => 'dk',
'country_name' => 'Denmark',
);
location_save($location);
// Insert location instance, if it's not set yet.
$criteria = array(
':nid' => $node->nid,
':vid' => $node->vid,
':lid' => $location['lid'],
);
if (!db_result(db_query("SELECT COUNT(*) FROM {location_instance} WHERE nid = %d AND vid = %d AND lid = %d;", $criteria))) {
db_query("INSERT INTO {location_instance} (nid, vid, lid) VALUES (%d, %d, %d)", $criteria);
}

For Drupal 7, saving as default location tab.
$location = array(
'latitude' => $row->gmapycord,
'longitude' => $row->gmapxcord,
);
$lid = location_save($location);
if ($lid) {
$entity->locations['0']['lid'] = $lid;
}
Inspired from: here

Related

Drupal - Importing a taxonomy with migrate module from a table and creating/updating existing terms

I need to import a list of terms into my taxonomy from a source I loaded in the database.
The problem is I allready have this taxonomy on my site (loaded wihtout migrate) with terms that are used by reference in other content, so I got to keep existing term and update them or create the new ones.
To link my taxonomy source and the existing taxonomy I have an unique code for each term, so I added a code field to my vocabulary and filled it for each existing term.
I am currently able to create and update terms with my current Migration class, but if the name of my term on the site and the name of the term in my source is different, the import will create a new term instead of updating its name even if the code is the same.
Here my Migration Class :
class TotoMigration extends Migration {
private $list_term = array();
public function __construct($arguments) {
parent::__construct();
$this->softDependencies = array('TotoParent');
// get data from the custom table containing the new terms to create or update
$query = db_select('toto', 'f')
->fields('f', array(
'CODE', // code
'LIBLONG', // name
'PARENT', // parent
)
);
$this->source = new MigrateSourceSQL($query);
$this->destination = new MigrateDestinationTerm('toto_tax');
$this->map = new MigrateSQLMap($this->machineName,
array(
'CODE' => array('type' => 'varchar',
'length' => 5,
'not null' => TRUE,
'description' => 'Code',
)
),
MigrateDestinationTerm::getKeySchema()
);
$this->addFieldMapping('name', 'LIBLONG');
$this->addFieldMapping('field_code', 'CODE');
$this->addFieldMapping('parent', 'PARENT')
->arguments(array('source_type' => 'tid'))
->sourceMigration('TotoParent');
// create a list of existing toto terms with code => tid
$list_term = db_query("select fc.field_code_value, ttd.tid
from taxonomy_term_data ttd
left join taxonomy_term_hierarchy tth on tth.tid=ttd.tid
left join field_data_field_code fc on fc.entity_id = ttd.tid
where ttd.vid=10
and tth.parent!=0;")->fetchAllKeyed();
}
public function prepareRow($row) {
// Always include this fragment at the beginning of every prepareRow()
// implementation, so parent classes can ignore rows.
if (parent::prepareRow($row) === FALSE) {
return FALSE;
}
// if the destination is not mapped in migrate we tell him where to go
if (!isset($row->migrate_map_destid1) && isset($list_term[$row->CODE])) {
$row->migrate_map_destid1 = $list_term[$row->CODE];
}
}
}
I then load the import with drush (and --update option).
I must be missing something, if anyone got a clue it will be welcome.
After many tries, the problem reside in the fact the module Migrate does not support Creating content and Updating content in the same migration class (I even read it will sometime claim to update content and just do nothing).
So the solution is pretty simple, create 2 classes :
One for Creating content
One for Updating content
Your Creating class will be the same.
Your Updating class will need to have a systemeOfRecord set to DESTINATION :
$this->systemOfRecord = Migration::DESTINATION;
So it knows to only update and not recreate the content, it will keep current fields not mapped and update fields mapped that are not part of the MigrateSQLMap :
$this->map = new MigrateSQLMap($this->machineName,array(...));
The tricky part will be to find corresponding nid/tid of your content so you can map it to your imported data and then to separate data used to update or create content.

How to add / update a field collection without updating parent node/entity drupal 7

I needed to to add / update field collections to node entities without updating the node entities. I tried two ways listed in https://www.drupal.org/node/1842304 and http://alexrayu.com/blog/saveupdate-field-collection-without-nodesave but none of them seems to be working exactly the way I want.
I tried as follows:
$node = node_load($nid);
$field_collection_item = entity_create('field_collection_item', array('field_name' => 'field_college_rating_data'));
$field_collection_item->setHostEntity('node', $node);
$field_collection_item->field_text1['und'][0]['tid'] = $form_state['input']['field_text1']['und'];
$field_collection_item->field_text2['und'][0]['value'] = $form_state['input']['field_text2']['und'];
$field_collection_item->save();
It added a field collection but it updates the node.
I also tried to alter the field collection form submit and used custom submit handler as follows:
function own_custom_field_collection_submit($form,$form_state) {
$field_collection_item = field_collection_item_form_submit_build_field_collection($form, $form_state);
$field_collection_item->save(TRUE);
drupal_set_message(t('The changes have been saved.'));
$form_state['redirect'] = $field_collection_item->path();
}
I have copied this code from core field collection module to change the default argument to "TRUE" in the save function. It added the field collection but didn't associated with the parent node.
I need to save the field collection separately since my node entity form is very large with 50 to 60 fields and field collections and I don't want to update it as many times as I add / update any field collections to the node.
Any help will be much appreciated.
Thanks
You have to pragmatically relate your field collection to the host entity by the following code.
$field_collection_item = field_collection_item_form_submit_build_field_collection($form, $form_state);
$field_collection_item->save(TRUE);
$host_entity = $field_collection_item->hostEntity();
$lang = 'und';
if (isset($host_entity->nid) && isset($host_entity->vid) && isset($lang) && isset($field_collection_item->item_id) && isset($field_collection_item->revision_id)) {
$query = db_select('field_data_field_text1', 'fd');
$query->addExpression('max(fd.delta)', 'total_row');
$query->condition('fd.entity_id', $host_entity->nid);
$max_delta = $query->execute()->fetchField();
if (isset($max_delta)) {
$max_delta = $max_delta + 1;
} else {
$max_delta = 0;
}
db_insert('field_data_{field_collection}')->fields(array(
'entity_type' => 'node',
'bundle' => '{node_type}',
'entity_id' => $host_entity->nid,
'revision_id' => $host_entity->vid,
'language' => $lang,
'delta' => $max_delta,
'field_text' => $field_collection_item->item_id,
'field_text_revision_id' => $field_collection_item->revision_id
))
->execute();
}
Replace your content type and field type in { } and then you just have your field collection relate to your host entity.
Thanks

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.

Programmatically insert a file in Drupal's file system?

I am grabbing a file using CURL, and want to save it locally, but so it plugs into Drupal's file system. Would I have to do a manual insert or is there a better way? The closest I could find was:
function image_field_insert($entity_type, $entity, $field, $instance, $langcode, &$items);
But I don't know how to use this. Any better suggestions?
There are a couple of ways but the easiest would be to use file_save_upload():
$source = '/path/to/file.ext';
$dest = 'public://uploads/'; // Or whatever
$file = file_save_upload($source, array(), $dest, FILE_EXISTS_RENAME);
if ($file) {
// file_save_upload marks the file as temporary, we need to mark as permanent or it will be cleared from the database in a few hours
$file->status = FILE_STATUS_PERMANENT;
file_save($file);
}
This just about drove me out of my mind and in trying to figure out a simple way to circumvent the form API expectations I came across this question. Clive's answer was the beginning of how I figured it out because it looks like you can provide a source as the first arg for file_save_upload() but as it turns out, you cannot. At least not in the current drupal 7.30.
I checked the link he provided and deconstructed the function. Basically, even if you pass a full source path it still looks for the first arg in the $_FILES array way down the path. Who knows why, but what a dumb idea. So I did this:
//after verifying all post fields are set...
$node = new stdClass();
$node->type = 'user_files';
node_object_prepare($node);
$node->title = $_POST['title'];
$node->author = 2; //hardcoded because only admin uses this form
$node->uid = 2;
$node->language = LANGUAGE_NONE;
$node->body[$node->language][0]['value'] = $_POST['body'];
$node->body[$node->language][0]['summary'] = text_summary($_POST['body']);
$node->body[$node->language][0]['format'] = 'filtered_html';
$node->field_first_name[$node->language][0]['value'] = $_POST['first_name'];
$node->field_last_name[$node->language][0]['value'] = $_POST['last_name'];
node_save($node);
if(isset($_FILES['file']['tmp_name']) && $_FILES['file']['name'] != '')
{
//upload file
$file = new stdClass();
$file->uid = 2;
$file->status = 0;
$file->filename = trim(drupal_basename($_FILES['file']['name']), '.');
$file->uri = $_FILES['file']['name'];
$file->filemime = file_get_mimetype($file->filename);
$file->filesize = $_FILES['file']['size'];
$file->filename = file_munge_filename($file->filename, 'jpg jpeg gif png doc docx pdf');
$file->destination = file_destination('private://' . $file->filename, FILE_EXISTS_RENAME);
$file->uri = $file->destination;
if (!drupal_move_uploaded_file($_FILES['file']['tmp_name'], $file->uri)) {
return false; //set errors or do whatever you want on error
}
else
{
drupal_chmod($file->uri);
$existing_files = file_load_multiple(array(), array('uri' => $file->uri));
if (count($existing_files)) {
$existing = reset($existing_files);
$file->fid = $existing->fid;
}
if ($file = file_save($file)) {
// Add file to the cache.
$node->field_file[$node->language][0]['uri'] = 'private://'.$file->filename;
$node->field_file[$node->language][0]['fid'] = $file->fid;
$node->field_file[$node->language][0]['display'] = 1;
$node->field_file[$node->language][0]['description'] = 'User uploaded file';
node_save($node);
//do we notify someone?
}
}
}
What this does is creates a node of a specified type. In this case, user_files, then if a file is uploaded, add the file to the media table, or file table or whatever it's called. Then adds the association to the newly created node.
Is it ugly? Yeah. Why didn't I use the internal drupal form API? Shut up, that's why. We don't always have a choice, so when a client asks for a basic form that just emails them, it is quick and easy to make a simple field that sends through a mail chimp API or something. Then later they add that they want files. Then later they want it to add to the drupal back end and suddenly you have a snowball of horror that would have been a lot easier if it was done with the form API to begin with. I don't know who started it, but I had to end it and I did it with this ghastly hack. Hope it helps you or whoever else is currently spinning in the drupal vortex of horror.
I was able to do the following to add a file from the public:// Drupal folder (typically sites/default/files or sites/DOMAIN/files) using file_save.
$uri = 'public://filename.pdf';
$file = file_save((object) array(
'filename' => basename($uri),
'uri' => $uri,
'status' => FILE_STATUS_PERMANENT,
'filemime' => file_get_mimetype($uri),
));
This adds the appropriate entry into the Drupal core file_managed table. If you have the File Entity module installed, an entry is created there as well.
The returned file object will include the database ID as $file->fid.

Setting location data programmatically

I'm stuck on a problem that I've researched for several days with no luck and the answers here are usually spot on.
I have custom module code that adds a node from form supplied data:
$edit = array();
$edit['uid'] = $user->id;
$edit['name'] = $user->name;
$edit['status'] = 1;
$edit['taxonomy'] = array($term_id);
$edit['title'] = $Title;
$edit['body'] = $body;
etc...
and then saved with:
node_invoke_nodeapi($edit, $nType);
node_validate($edit);
if ($errors = form_get_errors()) {
print_r($errors);
}
$node = node_submit($edit);
node_save($node);
This all works perfectly. But I'm trying to add location data to each node based on a supplied (sanitized) zip field.
I have gmap and location modules installed and working. When I add the zip directly using the drupal content editor it all works. Even the views gmap. So i know versions and mods are all correct.
I've used this:
$location = array(
'country' => 'US',
'postal_code' => $zip,
);
$locationID = location_save($location);
and this:
$location['country'] = "US";
$location['postal_code'] = $zip;
$locationID = location_save($location);
with and without the country element.
And then in the node data init section (above) this:
$edit->locations[0]['lid'] = $locationID;
or
if($locationID) $edit['field_location'][0]['lid'] = $locationID;
or
if($locationID) $edit['location'][0]['lid'] = $locationID;
But none of this works. The submit will go through ok actually but no location data is saved. And no errors thrown.
Any help with this would be greatly appreciated.
I did get this to work, (in case anyone is having the same issue and stumbles upon this), by creating the node first and then adding the location data to the node with:
$locations = array();
$locations[0]['postal_code'] = $zip;
$criteria = array();
$criteria['nid'] = $node->nid;
$criteria['vid'] = $node->vid;
$criteria['genid'] = 'NAME OF NODE TYPE HERE';
location_save_locations( $locations, $criteria );
I guess location_save_locations is the correct way of doing it, not location_save.
Following your approach, as exposed by the wider location_save_locations() at line 4, you can update a location with location_save($locations[$key], TRUE, $criteria).
A few notes:
location_save return the lid of the saved location, or FALSE if the location is considered "empty."
Clean your cache or query the database ( mysql> SELECT * FROM location ORDER BY lid DESC limit 6 ) for a fresh view of the new entity location (ie: node_load data may be cached).
Alternatively, if you are handling an entity with a location field, you may try something cleaner like:
// Updated the location.
$node->field_location['und'][0] = $location;
// Save the node.
node_save ($node);

Resources