Dynamically add a "Field Collection" in Drupal 7 by script? - drupal-7

I want to add a "field collection" dynamically. But I'm not familiar with Field API or Entity API. New Entity API in Drupal is very poorly documented.
Here is my code, until now:
$node = node_load(1);
$field_collection_item = entity_create('field_collection_item', array('field_name' => 'field_book_text'));
$field_collection_item->setHostEntity('node', $node);
// Adding fields to field_collection
$field_collection_item.save();
"Field Collection" module use function "entity_form_submit_build_entity" which I cannot use because there is no form in my case.
I would appreciate if you can tell me how can I add fields?

Based on some code I used in a live project:
// Create and save research field collection for node.
$field_collection_item = entity_create('field_collection_item', array('field_name' => 'field_article_references'));
$field_collection_item->setHostEntity('node', $node);
$field_collection_item->field_reference_text[$node->language][]['value'] = 'ABCD';
$field_collection_item->field_reference_link[$node->language][]['value'] = 'link-val';
$field_collection_item->field_reference_order[$node->language][]['value'] = 1;
$field_collection_item->save();

Anyone using the above code samples should consider using the entity_metadata_wrapper function from the Entity API to set the values of fields on an entity instead of using an assignment operator. So, the code from the "more complete example" above would be:
if ($node->field_collection[LANGUAGE_NONE][0]) {
// update
$fc_item = reset(entity_load('field_collection_item', array($node->field_collection[LANGUAGE_NONE][0]['value'])));
}
else {
// create
$fc_item = entity_create('field_collection_item', array('field_name' => 'field_collection'));
$fc_item->setHostEntity('node', $node);
}
// Use the Entity API to "wrap" the field collection entity and make CRUD on the
// entity easier
$fc_wrapper = entity_metadata_wrapper('field_collection_item', $fc_item);
// ... set some values ...
$fc_wrapper->field_terms->set('lars-schroeter.com');
// save the wrapper and the node
// Note that the "true" is required due to a bug as of this time
$fc_wrapper->save(true);
node_save($node);

A more complete example:
if ($node->field_collection[LANGUAGE_NONE][0]) {
// update
$fc_item = reset(entity_load('field_collection_item', array($node->field_collection[LANGUAGE_NONE][0]['value'])));
}
else {
// create
$fc_item = entity_create('field_collection_item', array('field_name' => 'field_collection'));
$fc_item->setHostEntity('node', $node);
}
// ... set some values ...
$fc_item->field_terms[LANGUAGE_NONE][0]['value'] = 'lars-schroeter.com';
// save node and field-collection
$node->field_collection[LANGUAGE_NONE][0] = array('entity' => $fc_item);
node_save($node);

You don't need to call node_save($node) when using entity_metadata_wrapper. It will ensure that only the entity's data and the reference to the host are saved without triggering any node_save, which is a good performance boost.
However, you would still need node_save() if you have any node_save-triggered actions that use this field collection (e.g. a rule that sends emails when the node is edited).

use the wrappers, they are your friend:
// Create an Entity
$e = entity_create('node', array('type' => 'CONTENT_TYPE'));
// Specify the author.
$e->uid = 1;
// Create a Entity Wrapper of that new Entity
$entity = entity_metadata_wrapper('node',$e);
// Specify the title
$entity->title = 'Test node';
// Add field data... SO MUCH BETTER!
$entity->field_FIELD_NAME->set(1111);
// Save the node.
$entity->save();

You can find Entity API documented in Entity API Tutorial at Drupal.org.
There you can find some useful examples, especially check for Entity metadata wrappers page.
Here is example based on your variables:
$node = node_load(1);
$field_collection_item = entity_create('field_collection_item', array('field_name' => 'field_book_text')); // field_book_text is field collection
$field_collection_item->setHostEntity('node', $node);
$cwrapper = entity_metadata_wrapper('field_collection_item', $field_collection_item);
// Adding fields to field_collection
$cwrapper->field_foo_text->set("value");
$cwrapper->field_foo_multitext->set(array("value1", "value2"));
$cwrapper.save();
Here is another example using field collections from above docs page:
<?php
// Populate the fields.
$ewrapper = entity_metadata_wrapper('node', $node);
$ewrapper->field_lead_contact_name->set($contact_name);
$ewrapper->field_lead_contact_phone->set($contact_phone);
$ewrapper->field_lead_contact_email->set($contact_email);
// Create the collection entity and set it's "host".
$collection = entity_create('field_collection_item', array('field_name' => 'field_facilities_requested'));
$collection->setHostEntity('node', $node);
// Now define the collection parameters.
$cwrapper = entity_metadata_wrapper('field_collection_item', $collection);
$cwrapper->field_facility->set(intval($offset));
$cwrapper->save();
// Save.
$ewrapper->save();
?>
Here is more advanced example of mine which for given entity it loads taxonomy term references from field_rs_property_features, then for each secondary term which has a parent term, adds its term name and its parent term name into field_feed_characteristics_value by grouping them together into title (parent) and value (child). It's probably more difficult to explain without seeing the code. So here it is:
/**
* Function to set taxonomy term names based on term references for given entity.
*/
function MYMODULE_refresh_property_characteristics(&$entity, $save = FALSE) {
try {
$w_node = entity_metadata_wrapper('node', $entity);
$collections = array();
foreach ($w_node->field_rs_property_features->getIterator() as $delta => $term_wrapper) {
if ($term_wrapper->parent->count() > 0) {
$name = $term_wrapper->name->value();
$pname = $term_wrapper->parent->get(0)->name->value();
if (array_key_exists($pname, $collections)) {
$collections[$pname]->field_feed_characteristics_value[] = $name;
} else {
// Create the collection entity, set field values and set it's "host".
$field_collection_item = entity_create('field_collection_item', array('field_name' => 'field_feed_characteristics'));
$field_collection_item->setHostEntity('node', $w_node->value());
$collections[$pname] = entity_metadata_wrapper('field_collection_item', $field_collection_item);
$collections[$pname]->field_feed_characteristics_title = $pname;
$collections[$pname]->field_feed_characteristics_value = array($name);
}
}
}
if ($save) {
$w_node->save();
}
} catch (Exception $e) {
drupal_set_message(t('Error setting values for field collection: #title, message: #error.',
array('#title' => $w_node->title->value(), '#error' => $e->getMessage())), 'error');
watchdog_exception('MYMODULE', $e);
return FALSE;
}
return TRUE;
}

Related

Adding addCondition() in a Form with fields from other tables

I don't know the syntax to access "CurrentTable.ForeignKey" nor "OtherTable.PrimaryKey" in a $model->addCondition() statement.
This is a fragment of my code which works:
$mm = new SYSPC_MODEL($this->app->db,['title_field'=>'MODEL_NAME']);
$mm->addCondition('MODEL_NAME', 'LIKE', 'DESK%');
In place of simply searching for MODEL_NAME like 'DESK%', I would like to display only the FK_MODEL_id values which exist in the SYSPC_MODEL table for the same FK_OS_ID than the current record FK_OS_ID value. So in SQL, we should have something like:
SELECT SYSPC_MODEL.MODEL_NAME WHERE ( DHCP_PC.FK_OS_ID = SYSPC_MODEL.id )
To understand easier the context, I reduced my code as much as possible:
<?php
include_once ('../include/config.php');
require '../vendor/autoload.php';
class SYSPC_OS extends \atk4\data\Model {
public $table = 'SYSPC_OS';
function init() {
parent::init();
$this->addFields([ ['OS_NAME', 'required'=>true, 'caption'=>'Identifiant d\'OS'],
['OS_DESCRIPTION', 'required'=>true, 'caption'=>'Description d\'OS']
]);
}
} // End of class SYSPC_OS
class SYSPC_MODEL extends \atk4\data\Model {
public $table = 'SYSPC_MODEL';
function init() {
parent::init();
$this->addFields([ ['MODEL_NAME', 'caption'=>'Nom du modele'],
['MODEL_BASE_RPM', 'caption'=>'Rpm de base']
]);
$this->hasOne('FK_OS_id',[new SYSPC_OS(),'ui'=>['visible'=>false]])->addField('OS_NAME','OS_NAME');
}
} // End of class SYSPC_MODEL
class DHCP_PC extends \atk4\data\Model {
public $table = 'DHCP_PC';
function init() {
parent::init();
$this->addFields([ ['PCNAME', 'required'=>true, 'caption'=>'Nom du pc']
]);
$this->hasOne('FK_OS_ID',['required'=>true,new SYSPC_OS(),'ui'=>['visible'=>false]])->addField('OS_NAME','OS_NAME');
$this->setOrder('PCNAME','asc');
$this->hasOne('FK_MODEL_id',['required'=>true,new SYSPC_MODEL(),'ui'=>['visible'=>false]])->addField('MODEL_NAME','MODEL_NAME');
}
} // End of class DHCP_PC
class PcForm extends \atk4\ui\Form {
function setModel($m, $fields = null) {
$PcWidth = 'three';
parent::setModel($m, false);
$gr = $this->addGroup('PC name');
$gr->addField('PCNAME',['required'=>true,'caption'=>'Nom du pc']);
$gr = $this->addGroup('OS');
$mm2 = new SYSPC_OS($this->app->db,['title_field'=>'OS_NAME']);
$gr->addField('FK_OS_ID',['width'=>$PcWidth],['DropDown'])->setModel($mm2);
$gr = $this->addGroup('Modèle');
$mm = new SYSPC_MODEL($this->app->db,['title_field'=>'MODEL_NAME']);
$mm->addCondition('MODEL_NAME', 'LIKE', 'DESK%'); // Works fine but I would like to display only the FK_MODEL_id values
// which exist in the SYSPC_MODEL table for the same FK_OS_ID
// than the current record FK_OS_ID value :
// SELECT SYSPC_MODEL.MODEL_NAME WHERE ( DHCP_PC.FK_OS_ID = SYSPC_MODEL.id )
$gr->addField('FK_MODEL_id', ['width'=>$PcWidth], ['DropDown'])->setModel($mm);
return $this->model;
}
} // End of class PcForm
$app = new \atk4\ui\App();
$app->title = 'Gestion des PC';
$app->initLayout($app->stickyGET('layout') ?: 'Admin');
$app->db = new \atk4\data\Persistence_SQL(
"pgsql:host=".$GLOBALS['dbhost'].";dbname=".$GLOBALS['dbname'],
$GLOBALS['dbuser'],
$GLOBALS['dbpass']
);
$g = $app->add(['CRUD', 'formDefault'=>new PcForm()]);
$g->setIpp([10, 25, 50, 100]);
$g->setModel(new DHCP_PC($app->db),['PCNAME', 'OS_NAME', 'MODEL_NAME']);
?>
Please look at https://github.com/atk4/ui/pull/551 - it might be what you're looking for.
Example here: https://ui.agiletoolkit.org/demos/autocomplete.php
Docs: https://agile-ui.readthedocs.io/en/latest/autocomplete.html?highlight=lookup#lookup-field
$form = $app->add(new \atk4\ui\Form(['segment']));
$form->add(['Label', 'Add city', 'top attached'], 'AboveFields');
$l = $form->addField('city',['Lookup']);
// will restraint possible city value in droddown base on country and/or language.
$l->addFilter('country', 'Country');
$l->addFilter('language', 'Lang');
//make sure country and language belong to your model.
$l->setModel(new City($db));
Alternatively you can use something other than drop-down, here is UI example:
https://ui.agiletoolkit.org/demos/multitable.php
Selecting value in the first column narrows down options in the next. You can have a hidden field inside your form where you can put the final value.
Thanks for your support but I still have some questions.
Question 1: I found "addRelatedEntity" and "relEntity" but I didn't found a description of those commands. Does it exist ? Is this a possible solution for my issue ?
Question 2: Is it possible to 'Lookup' in another table and if yes, how ?
Question 3: If 'Lookup' is not the solution, how to make a join (with filtering in the where clause) inside a model ?
Question 4: If the join is not the solution, is it possible to use DSQL inside a model ?
Question 5: Or do you have a DSQL example (with a self made join between several tables) associated with a CRUD ?

Drupal - Get custom taxonomy fields

I am trying to get a custom field assigned to taxonomy. I have tried this:
$vid = 'zeme';
$terms =\Drupal::entityTypeManager()->getStorage('taxonomy_term')->loadTree($vid);
$terms is now storing all the terms from the vocabulary called 'zeme'. The problem is when I print this variable, it doesnt show the custom field that I need to get.
Any idea how can I get this custom field?
My code looks like this:
$vid = 'zeme';
$terms =\Drupal::entityTypeManager()->getStorage('taxonomy_term')->loadTree($vid);
foreach ($terms as $term) {
$term_data[] = array(
'id' => $term->tid,
'name' => $term->name
);
}
Here is the loadTree function official documentation :
TermStorage::loadTree
When you use the loadTree function, it will only get you the minimal datas to save execution time. You can see there is a $load_entities parameter set to false by default.
bool $load_entities: If TRUE, a full entity load will occur on the
term objects. Otherwise they are partial objects queried directly from
the {taxonomy_term_data} table to save execution time and memory
consumption when listing large numbers of terms. Defaults to FALSE.
So if you want to get all the datas of each of your taxonomy terms, you have to set $load_entities to true.
$vid = 'zeme';
$terms =\Drupal::entityTypeManager()
->getStorage('taxonomy_term')
->loadTree($vid, 0, null, true);
Found this way from this post Get custom fields assigned to taxonomy:
$contact_countries = \Drupal::service('entity_type.manager')->getStorage("taxonomy_term")->loadTree('contact_country');
$terms = array();
foreach($contact_countries as $contact_countrie) {
$terms[] = array(
'contact_country' => $contact_countrie->name,
'contact_phone' => \Drupal::entityTypeManager()->getStorage('taxonomy_term')->load($contact_countrie->tid)->get('field_phone')->getValue()[0]['value'],
'contact_flag' => \Drupal::entityTypeManager()->getStorage('taxonomy_term')->load($contact_countrie->tid)->get('field_category_flag')->entity->uri->value,
);
}
Very usefull!
public function getTaxonomyBuild(){
$terms = \Drupal::service('entity_type.manager')->getStorage("taxonomy_term")->loadTree('faq_sec');
foreach($terms as $term) {
$term_data[] = array(
'name' => $term->name,
'img' => file_create_url(\Drupal::entityTypeManager()->getStorage('taxonomy_term')->load($term->tid)->get('field_sec_img')->entity->uri->value),
);
}
return $term_data;
}
good solution

symfony2 custom entity to array function

I have to help me convert an entity to array but I have issues resolving associated records, which I need.
However, this gives me an error
The class 'Doctrine\ORM\PersistentCollection' was not found in the
chain configured namespaces ...
The code follows:
public function serialize($entityObject)
{
$data = array();
$className = get_class($entityObject);
$metaData = $this->entityManager->getClassMetadata($className);
foreach ($metaData->fieldMappings as $field => $mapping)
{
$method = "get" . ucfirst($field);
$data[$field] = call_user_func(array($entityObject, $method));
}
foreach ($metaData->associationMappings as $field => $mapping)
{
// Sort of entity object
$object = $metaData->reflFields[$field]->getValue($entityObject);
if ($object instanceof ArrayCollection) {
$object = $object->toArray();
}
else {
$data[$field] = $this->serialize($object);
}
}
return $data;
}
How can I resolve the associated fields into their respective arrays.
I have tried using the built-in, and JMS serialiser, but this gives me issues of nestedness limits, so this is not an option for me.
UPDATE:
I have updated the code to handle instance of ArrayCollection as per #ScayTrase's suggestion. However, the error above is still reported with a one-to-many field map. In debug, the variable $object is of type "Doctrine\ORM\PersistentCollection"
For *toMany association properties implemented with ArrayCollection you should call ArrayCollection::toArray() first. Just check it with instanceof before, like this
if ($object instanceof ArrayCollection) {
$object = $object->toArray();
}

Add wordpress custom post type data to an external db

This function adds custom post 'event' data into a Salesforce db. I've tested the function outside of Wordpress and it works flawlessly. When I test it inside Wordpress by adding a new event, no error is generated and a the data is not inserted into the SF db. I've also tested this by printing out the $_POST and saw that the data is being collected. How can I get this display some errors so that I can trouble shoot this?
function add_campaign_to_SF( $post_id) {
global $SF_USERNAME;
global $SF_PASSWORD;
if ('event' == $_POST['post-type']) {
try {
$mySforceConnection = new SforceEnterpriseClient();
$mySoapClient = $mySforceConnection->createConnection(CD_PLUGIN_PATH . 'Toolkit/soapclient/enterprise.wsdl.xml');
$mySFlogin = $mySforceConnection->login($SF_USERNAME, $SF_PASSWORD);
$sObject = new stdclass();
$sObject->Name = get_the_title( $post_id );
$sObject->StartDate = date("Y-m-d", strtotime($_POST["events_startdate"]));
$sObject->EndDate = date("Y-m-d", strtotime($_POST["events_enddate"]));
$sObject->IsActive = '1';
$createResponse = $mySforceConnection->create(array($sObject), 'Campaign');
$ids = array();
foreach ($createResponse as $createResult) {
error_log($createResult);
array_push($ids, $createResult->id);
}
} catch (Exception $e) {
error_log($mySforceConnection->getLastRequest());
error_log($e->faultstring);
die;
}
}
}
add_action( 'save_post', 'add_campaign_to_SF');
I would use get_post_type() to check for "event" posts. Use error_log() to write to the PHP error log for additional debugging - check the status of your Salesforce login, etc.
Keep in mind that save_post will run every time a post is saved - created or updated - so you might want to do some additional checking (like setting a meta value) before creating a new Campaign in Salesforce, otherwise you will end up with duplicates.
function add_campaign_to_SF( $post_id ) {
$debug = true;
if ($debug) error_log("Running save_post function add_campaign_to_SF( $post_id )");
if ( 'event' == get_post_type( $post_id ) ){
if ($debug) error_log("The post type is 'event'");
if ( false === get_post_meta( $post_id, 'sfdc_id', true ) ){
if ($debug) error_log("There is no meta value for 'sfdc_id'");
// add to Salesforce, get back the ID of the new Campaign object
if ($debug) error_log("The new object ID is $sfdc_id");
update_post_meta( $post_id, 'sfdc_id', $sfdc_id );
}
}
}
add_action( 'save_post', 'add_campaign_to_SF' );

Wordpress: Create pages through database?

Currently I am working on a new traveling website, but am having problems with 1 thing:
I have a list with all the country's, regions and city's i want to publish. How do I quickly create a page for all of them like this:
Every page should be a subpage like: country/region/city
Every page should have a certain page template
Please let me know, thanks in advance for your time and information!
You can do something like this.
<?php
// $country_list = get_country_list(); // returns list, of the format eg. array('India' => 'Content for the country India', 'Australia' => 'Content for the country Australia')
// $region_list = get_region_list($country); // Get the list of regions for given country, Assuming similar format as country.
// $city_list = get_city_list($region); // Get the list of cities for given region, Assuming similar format as country
/* Code starts here...*/
$country_list = get_country_list();
foreach($country_list as $country_title => $country_content) {
$country_template = 'template_country.php';
$country_page_id = add_new_page($country_title, $country_content, $country_template);
// validate if id is not 0 and break loop or take needed action.
$region_list = get_region_list($country_title);
foreach($region_list as $region_title => $region_content) {
$region_template = 'template_region.php';
$region_page_id = add_new_page($region_title, $region_content, $region_template, $country_page_id);
// validate if id is not 0 and break loop or take needed action.
$city_list = get_city_list($region_title);
foreach($city_list as $city_title => $city_content) {
$city_template = 'template_city.php';
add_new_page($city_title, $city_content, $city_template, $region_page_id);
}
}
}
function add_new_page($title, $content, $template_file, $post_parent = 0) {
$post = array();
$post['post_title'] = $title;
$post['post_content'] = $content;
$post['post_parent'] = $post_parent;
$post['post_status'] = 'publish'; // Can be 'draft' / 'private' / 'pending' / 'future'
$post['post_author'] = 1; // This should be the id of the author.
$post['post_type'] = 'page';
$post_id = wp_insert_post($post);
// check if wp_insert_post is successful
if(0 != $post_id) {
// Set the page template
update_post_meta($post_id, '_wp_page_template', $template_file); // Change the default template to custom template
}
return $post_id;
}
Warning: Make sure that the is executed only once or add any validation to avoid duplicate pages.

Resources