Programmatically insert a file in Drupal's file system? - drupal

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.

Related

How to use nodeapi in drupal?

guys i have this node type -> movie. this movie has casts in it. so now i was instructed to display the number of cast in a movie. how do i achieve this by using nodeapi? please someone guide mo through this. im very new to drupal coding here. here's what ive made so far:
function count_cast_nodeapi(&$node, $op) {
switch ($op) {
case 'update index':
if ($node->type == 'movie') {
$text = '';
$q = db_query('SELECT count(field_movie_cast_nid) FROM content_field_movie_cast ' .
'WHERE nid = %d', $node->nid);
if ($r = db_fetch_object($q)) {
$text = $r->count;
}
drupal_set_message($text);
}
}
}
but i have no idea where to put this code. where to save it. do i make a module of this? and is this code even correct?
also i have only copied this code. so i dont know what that 'update index' means. and who or what function will set that parameter $op
I can suggest you follow solution:
Create new integer CCK field for content type "movie" which will store the number of casts. Name it movie_cast_number.
This field will be updating in hook_nodeapi (similar to your example). For that you need to create custom module "count_cast". Place it to sites/all/modules directory.
In hook_nodeapi need to use $op "insert" and "update". $op generating by module node and means which operation is performed with node. Insert means new node has been created and update - existing node has been updated.
If you need custom output for this content type then you need to create node-movie.tpl.php in your theme directory.
Code which update the number of casts can looks follow way:
function count_cast_nodeapi(&$node, $op) {
switch ($op) {
case 'update':
case 'insert':
if ($node->type == 'movie') {
$result = db_result(db_query('
SELECT count(field_movie_cast_nid) cnt
FROM {content_field_movie_cast}
WHERE nid = %d
', $node->nid));
$node->field_movie_cast_number[0]['value'] = $result->cnt;
}
}
}

Drupal Bulk Update Url Alias

I have a set of nodes (about 200) that need to have their url alias updated since we changed the setup; from "site.com/things-to-do/title" to "site.com/guides/title"
I tried using VBO, but when I select the nodes, click Update Url Alias, and then execute, nothing happens.
Although I'd rather not do a straight DB update, I also tried:
"UPDATE url_alias set dst = replace(dst, 'things-to-do', 'guides') WHERE url_alias LIKE 'things-to-do/%';
Thanks
Path auto should fix your problem:
http://drupal.org/project/pathauto
It will give you the option to delete existing aliases and regenerate them.
Apart from using the pathauto module stated above, you should also use path redirect module - http://drupal.org/project/path_redirect - so that you can redirect your old links to the newly created ones, else your old links will die out.
You can also use Global redirect - http://drupal.org/project/globalredirect
Here's a quick script I wrote that solves the problem. It does a simple find and replace on the alias and then creates a redirect.
You need the url_alias and path_redirect modules installed, this is for Drupal 6. This isn't the cleanest syntax (it was written quickly) but it works.
$string_to_replace = "foo";
$replacement_string = "bar";
//Get an array containing all aliases that need to be updated
$query = "SELECT * from url_alias where dst like '%$string_to_replace%'";
$result = db_query($query);
$paths = array();
while ($row = db_fetch_array($result)){
$paths[] = $row;
}
foreach($paths as $path){
//Determine the new path
$path['new_dst'] = str_replace($string_to_replace,$replacement_string,$path['dst']);
//Update the existing url_alias
$query = "UPDATE url_alias SET dst = '".$path['new_dst']."' WHERE pid = " . $path['pid'];
$result = db_query($query);
//Create and save a redirect
$redirect = array(
'source' => $path['dst'],
'redirect' => $path['src'],
);
path_redirect_save($redirect);
}
$nids = Drupal::entityQuery('node')
->condition('type', 'CONTENT_TYPE')
->execute();
$nodes = Node::loadMultiple($nids);
foreach($nodes as $node) {
$node->path->pathauto = 1;
$node->save();
}
I hope above code will solve the issue.

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

Create node programmatically with cck location field

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

Loading the previous revision of a node

When you get a node, how do you load the previous version (revision)?
I know how loading a revision but not how getting the previous revision number ($node->vid is the current revision).
thanks
Supposing that you have a node object $node, you can use the following code to get the previous revision.
$previous_vid = db_result(
db_query('SELECT MAX(vid) AS vid FROM {node_revisions} WHERE vid < %d AND nid = %d', $node->vid, $node->nid)
);
Once you have the previous revision, you can load the new node object with node_load(array('nid' => $node-nid, 'vid' => $previous_vid)).
The code should check if db_result() returns FALSE, in the case there isn't a previous revision.
To note that the field vid is global for each node; it doesn't contain the same value for different nodes.
Thanks all.
I found also an other solution:
$revisions = node_revision_list($node);
next($revisions);
if ($preview_key = key($revisions)) {
$preview_revision = $revisions[$preview_key];
$old_node = node_load($node->nid, $preview_revision->vid);
}
But if you have a lot of revision you get a big array.
If I understand what you're trying to do; you want to get the preview of the node after someone submits changes?
The preview button has its own submit handler, node_form_build_preview(). There, it creates a new node object using the data in $form_state and runs node_preview(), which returns the markup for the preview.
If you want to capture that preview when the user clicks the preview button, you'll need to use hook_form_alter to add another submit handler to the preview button:
$['form']['buttons']['preview']['#submit'][] = 'mymodule_custom_preview';
where mymodule_custom_preview is the name of your custom submit function. Take a look at node_form_build_preview() for guidance, but your submit function is going to look something like this:
function mymodule_custom_preview($form, &$form_state) {
$node = node_form_submit_build_node($form, $form_state);
$preview = node_preview($node);
}
Also take a look at node_form(), which gives you an idea of how the node form is structured. When you're all done, you're going to have code in your module that looks something like this:
function mymodule_form_alter(&$form, $form_state, $form_id) {
if (strstr($form_id, '_node_form') !== FALSE) {
$['form']['buttons']['preview']['#submit'][] = 'mymodule_custom_preview';
}
}
function mymodule_custom_preview($form, &$form_state) {
$node = node_form_submit_build_node($form, $form_state);
$preview = node_preview($node);
// Do what you will with $preview.
}

Resources