Get the next ID before saving a record - silverstripe

Is it technically possible to get the next ID before saving a dataobject? The ID of the new, until now not created record.
The only solution I came up with, was creating a new table, where I'll save the latest ID, every time a new dataobject of type 'x' was created and than count up.

The ID of the new, until now not created record.
Even if you did get the "ID" as #schellmax suggested you'd never be sure that the same "ID" can be used when saving. This if quite fragile and can be broken if e.g. multiple users try to do the same thing approximately at the same time.
Here's what I did for a project that needed to upload and attach files to a record before submitting the form.
Save the record in the beginning regardless of wether it'll be submitted or not. Now you have the official ID for that record.
Attach any files / other DataObjects as you would when you already have the record.
When submitting the form optionally clean up the table in a way that doesn't destroy other in-use records.
This way you have the correct ID no matter what happens and how many users try this at the same time. Also the performance hit should be minimal unless you have hundreds or thousands of users doing this at concurrently.
Example (not sure it works as-is as I stripped it down):
public function QuoteForm() {
$fields = new FieldList();
// Clears out quotes older than 7 days
Quote::removeEmptyQuotes();
// Get the quote.
$quote = $this->getQuote();
$yourName = new TextField(
'Name',
_t('QuoteForm.YourName', 'Your name')
);
$yourName->setAttribute('required', true);
$fields->push($yourName);
$uploadField = new UploadField(
'Files',
_t('QuoteForm.Files', 'Select file*'),
$quote->Files()
);
$uploadField->setRecord($quote);
$uploadField->setFolderName('quotefiles');
$uploadField->setConfig('canAttachExisting', false);
$uploadField->getValidator()->setAllowedExtensions(array(
'jpg', 'jpeg', 'png', 'gif', 'tiff',
'odt', 'pdf', 'rtf', 'txt',
'doc', 'docx',
'ppt', 'pptx',
'xls', 'xlsx'
));
$uploadField->setAttribute('required', true);
$fields->push($uploadField);
$actions = new FieldList(
new FormAction('saveQuoteRequest', _t('QuoteForm.Send', 'Send'))
);
return new Form($this, 'QuoteForm', $fields, $actions);
}
/*
* Selects quote based on possible QuoteID from request.
* If none are found / if the request param is empty,
* creates a new Quote.
*
* #return Quote
*/
protected function getQuote() {
$quote = null;
$quoteID = (int)Session::get('QuoteID');
if ($quoteID > 0) {
$quote = Quote::get()->byID($quoteID);
}
if ($quote === null) {
$quote = new Quote();
$quote->write();
}
Session::set('QuoteID', $quote->ID);
return $quote;
}

You could query the database first:
$nextId = MyDataobject::get()->sort('ID')->last()->ID + 1;

Related

Not the required data is displayed for the new user, because the data is taken from the cache of the previous user

I made a custom module that displays the weather in a particular city.
But I got these comments after the code review:
1. Interesting question, what happens to your cache data, if the site first comes to a person from the city of London, and then Paris?
As I understand it, it means that a person from Paris, when he enters the site, will see the weather in London, because it will be taken from the cache. But I put the data in the cache so that there are not too many requests, I made a request once, put the data in the cache, and the next time I took the data from the cache.
2. In the small function, you are calling the http://ip-api.com/json/ endpoint twice. What happens when the site is visited by a thousand people per minute?
Here, I do not understand what the problem is. If it meant that the connection limit to the resource would be exhausted, then how to solve this problem? In the getCity() function, put the data in the cache in the same way as I do in the build() function? But then the same problem comes up as in the first remark, if a person from another city visits the site, then the data from the cache (name of the city of London) will be taken and not the name of his real city. How then to be?
Can you please tell me what needs to be changed in my code?
Below I will write a slightly reduced code for my php file. Full version here: https://phpsandbox.io/n/sweet-forest-1lew-1wmof
// ....
use Drupal\Core\Cache\CacheBackendInterface;
use GuzzleHttp\Client;
//....
public function getCity() {
$ip = '193.62.157.66'; // static because for testing
try {
$response_ip = $this->httpClient->get('http://ip-api.com/json/' . $ip);
$response_data_ip = $response_ip->getBody();
$data_ip = json_decode($response_data_ip);
if ($data_ip->status == 'success') {
return $data_ip->city;
}
else {
return $this->configFactory->get('sydneypro_weather.settings')->get('weather_city');
}
}
catch (RequestException $e) {
return FALSE;
}
}
public function build() {
$client = $this->httpClient;
$api_key = $this->configFactory->get('sydneypro_weather.settings')->get('weather_api_key');
$cid = 'sydneypro_weather';
$weather_config = $this->configFactory->get('sydneypro_weather.settings');
if (!$weather_config) {
$this->logger->get('sydneypro_weather')->error('Config "sydneypro_weather.settings" is missing4');
return [];
}
if (empty($api_key) || empty($this->getCity())) {
return [
'#type' => 'markup',
'#markup' => $this->t('Please enter your API key and City in the Admin panel to see the weather'),
];
}
try {
if ($cache = $this->cacheBackend->get($cid)) {
$data = $cache->data;
}
else {
$response = $client->get('http://api.openweathermap.org/data/2.5/weather?q=' . $this->getCity() . ',&appid=' . $api_key . '&units=metric');
$response_data = $response->getBody();
$data = json_decode($response_data);
$this->cacheBackend->set($cid, $data, $this->time->getRequestTime() + 21600);
}
$build = [
'#theme' => 'weather_block',
'#data' => $data,
'#attached' => [
'library' => [
'sydneypro_weather/sydneypro_weather',
],
],
];
return $build;
}
// ....
You need to display some data according to a given city, so you should cache the data on a per-city basis, using a cache id that allows you to retrieve data for a specific city. In other words, $cid must contain a city name or identifier.
$city = $this->getCity();
$cid = 'sydneypro_weather:' . $city;
Using a variable for $city prevents getCity() to be called twice. Also you could set a cache that maps IP adresses to their corresponding city but it might not be a good idea as the number of (possible) distinct adresses populating the cache table could be very high.
Another approach would be to get the region/city of the user from his browser using javascript and/or cookies, and call the api only for those that does not share their location.

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.

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);

How to get drupal custom profile values?

There is a checkbox added in user profile form, subscribe to daily newsletter. I want to get list of users who have subscribed (checkbox ON) and send them daily newsletter. How can I do this in drupal-6. (1) to get list of all subscribed users (2) Send them Email.
If you can explain at code level, I 'll really appreciate that.
If your profile field is profile_newsletter, your :
<?php
function mymodule_get_subscribers() {
$query = 'select * from {profile_fields} where `name`="%s";';
$result = db_query($query,'profile_newsletter');
$field = db_fetch_object($result);
$user_query = 'select * from {profile_values} where `fid`=%d and `value`=1;';
$result = db_query($user_query,$field->fid);
$subscribers = array();
while($subscriber = db_fetch_object($result)) {
$subscribers[] = $subscriber->uid;
}
return $subscribers; // Your array of subscriber user IDs
}
?>
This code was quick, is untested and should probably contain a few sanity-checks. But it should work.
For the sending of newsletters and such I'd recommend using a pre-rolled module. I haven't tried any for Drupal, but Simplenews seems to do the trick. http://drupal.org/project/simplenews
If nothing else it probably contains a good sending-function. Otherwise just use PHP mail()-function.
function your_custom_function ($user_id, "Field you want to get") {
$result = db_query("SELECT t2.value FROM profile_fields t1, profile_values t2 where
t1.title='Field you want to get' and t2.uid=".$user_id." and t1.fid=t2.fid");
$field = db_fetch_object($result);
$profile_type =$field->value;
return $profile_type;
}

Drupal: Modifying a User at Registration

I needed to create a custom select list for the user registration page that pulls a SQL query from nodes I need to link to users. I have successfully accomplished this task.. :)
However, when I submit the value, I can't seem to control where the value is stored. In fact, I can't store the value at all. I have created a custom field for my value, but only the new field name is stored, and it is serialized and stored in the Data column of the user table.
Below is my code, I've commented my issues in it. Any help would be appreciated!
<?php
// $Id$
//create the additional user form field, a select list named "account_name"
//then calls the next function to populate it.
function accountselect_user($op, &$edit, &$account, $category = NULL) {
if ($op == 'register' || 'edit')
$fields['Information']['account_name'] = array(
'#type' => 'select',
'#title' => 'Account',
'#description' => t('Select the account to which the contact belongs'),
'#options' => accountselect_getclubs() ,
);
return $fields;
}
//contains query to pull results to select list...this part is working
function accountselect_getclubs() {
$return = array();
$sql = 'SELECT DISTINCT `title` FROM node WHERE type = \'accounts\' ';
$result = db_query($sql);
while ($row = db_fetch_array($result)) {
$return[] = $row['title'];
}
return $return;
}
//CAN'T GET THIS PART TO WORK - query to update the row - the uid = 29 is for
//testing puposes. Once I get the test value to work
//the test value I will worry about updating the correct user.
function accountselect_submitaccount() {
$sql = "UPDATE users SET account = \'value\' WHERE uid = \'29\'";
db_query($sql);
drupal_set_message(t('The field has been updated.'));
}
//I SUSPECT THE PROBLEM IS HERE...call the submitaccount function.
//I have tried hook_form_alter as well...
function accountselect_submit(&$form, &$form_state) {
if($form_id == 'user-register')
drupal_execute('accountselect_submitaccount');
}
Have you checked Drupal's logs? It should be throwing errors, as this is not a valid query.
$sql = "UPDATE users SET account = \'value\' WHERE uid = \'29\'";
Should be:
$sql = "UPDATE users SET account = 'value' WHERE uid = '29'";
Additionally, in:
function accountselect_submit(&$form, &$form_state) {
if($form_id == 'user-register')
drupal_execute('accountselect_submitaccount');
}
$form_id is never defined.
You say you've created the field in the database, but it must match the name of the Drupal field to be automatically handled. You've got two different names for it - account_name in the Drupal field, but account in the database. Make them consistent and it should be automatically handled, no submit functions required.

Resources