I have Pathauto configured to generate an alias based on the title of a node, for a specific content type. The problem is that I want to make small changes in this title before Pathauto uses it to generate the alias.
The first comment in this post suggests the use of hook_token_values, but I couldn't really understand how to use it, even after reading the docs. In my tests, when I implement this hook, the alias generated is always "array", which means I'm missing something.
Any help? Thanks.
It might be that you missed to implement hook_token_list as well. Providing a new token is a two step process:
Implement hook_token_list to declare the tokens you are going to provide. This will just be the name of the tokens, along with a short explanation, and the information to what type of objects the tokens will apply (e.g. node, user, taxonomy, ...)
Implement hook_token_value to actually generate the content of the tokens. This will be called when the tokens are to be replaced with the content they should stand for.
As you just want to provide an alternative version of the title token already provided by the token module, it is probably best to just copy the relevant portions from token_node.inc, stripped down to the relevant cases and adjusted to be used in another module:
/**
* Implementation of hook_token_list().
*/
function yourModule_token_list($type = 'all') {
if ($type == 'node' || $type == 'all') {
$tokens['node']['yourModule-title'] = t('Node title (customized version by yourModule)');
return $tokens;
}
}
This simply says that yourModule provides a token for node objects, named yourModule-title, along with a short description. The main work gets done in the other hook:
/**
* Implementation of hook_token_values().
*/
function yourModule_token_values($type, $object = NULL, $options = array()) {
$values = array();
switch ($type) {
case 'node':
$node = $object;
// TODO: Replace the check_plain() call with your own token value creation logic!
$values['yourModule-title'] = check_plain($node->title);
break;
}
return $values;
}
This will be called whenever the tokens for node objects are needed, with the node in question being passed as the $object parameter (for a user token, the $type would be 'user', and $object would be the user object, and so on for other types). What it does is creating an array of values, keyed by the token name, with the replacement for that token as the value. The original code from token_node.inc just runs the title through check_plain(), so this would be the place to insert your own logic.
In Drupal 7, the token functionality has been moved to core. Tokens are implemented by the hook_tokens and hook_token_info methods. For usage examples, follow the links provided, and look for links to functions that implement hook_tokens and hook_token_info… I found the statistics_tokens and statistics_token_info functions helpful in understanding how this hook works.
It's probably also worth noting that this hook needs to be implemented by a module… my first attempt I dropped my test functions into the theme's template.php, only to have nothing happen at all :-p
Related
I'm building an application where users are tied to accounts (as in, multiple users all share an account), then other entities - lets call them products are tied to the accounts. The products are associated with the accounts and only users that are tied to that account can edit the products.
The difference being in my case, there are many different entities being shared in the same model.
If it was just the one (product) entity, it wouldn't be a problem to have a method in my ProductRepository like:
public function checkOwnership($id, $account)
{
$count = $this->createQueryBuilder('s')
->select('count(s.id)')
->where('s.account = :acc')
->andWhere('s.id = :id')
->setParameters(array('id' => $id, 'acc' => $account))
->getQuery()
->getSingleScalarResult();
if($count == 0)
throw $this->createNotFoundException('ye..');
return;
}
To make sure the id of the product is tied to the user's account.
The account argument is passed to this function by using:
$this->getUser();
In the controller.
What is the best way for me to make this function available to the other entities to prevent code duplication? There are (currently) only 3 other entities, so it wouldn't be the end of the world to just have it in each repository, but I'm just wondering if there were a way to create a 'common' or global repository that follows good standards? I'd sure like to know about it.
Edit:
Or have I just completely over-thought this? Can I just create a 'Common' directory in my 'MainBundle', define a namespace and add a use statement at the start of my controllers that need access to the function?
I hope I fully understand your question.
Solution one, duplication, easy: let #ParamConverter do the job (automatic response to 404 if doesn't exist)
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
/**
* #Route("/pets/{id}")
*/
public function showAction(Pet $pet)
{
// Current logged user
$currentUser = $this->get('security.context')->getToken()->getUser();
// Owner
$ownerUser = $pet->getUser();
if(!$currentUser->equals($ownerUser)) {
// 401, 403 o 404 depends on you
}
}
Solution two, DRY, a bit harder: use JMSAOPBundle and define an interceptor that intercepts all request to you controller and actions (show, delete, etc.). The pointcut (connected to the interceptor) will get the current user from the context and the owner from the entity. Then you do the check...
I have a Drupal content type which contains a number of computed fields. Some (but not all) items are being added to this content type via a cron-triggered RSS feed importer. I'm trying to trigger computed field generation for new items in hook_cron. The following code grabs all items that haven't been tagged as 'submitted', loads and re-saves the node, and then marks the node as 'submitted'.
$query = db_select('node', 'n');
$query->fields('n', array('nid'));
$table_alias = $query->join('field_data_field_submitted', 'r', 'n.nid = r.entity_id AND r.field_submitted_value = 0');
$result = $query->execute();
foreach ($result as $record){
$q = $record->nid;
$n = node_load($q);
node_save($n);
$query = db_update('field_data_field_submitted')
->fields(array('field_submitted_value' => 1))
->condition('entity_id', $q)
->execute();
}
This code works the way I expect it to if I call it from a module-generated page (created using hook_menu with a page callback function). Nodes are resubmitted, and the computed field data is generated. When I put this code in my hook_cron function, the query works, it loops through the records and updates the 'submitted' value, but the computed fields are not computed. I'm confused as to why this would not get triggered in cron. Any help?
Doh! Finally realized that this was completely my own doing. Due to the nature of this content type, where we allow anonymous users to create new content, but explicitly do not trigger the computed fields when they create the content (long story, but short form is that authenticated users then verify & enhance this content, which is where the computed fields come in). So, as I was setting up the initial content, I disabled the computed fields for anonymous users (if $user->uid > 0), and completely forgot about that. Once I tweaked that logic to allow computed fields to be processed on import (triggering it with a field that has a value for the imported content, but not for other content), the problem was solved.
The cron run has access to the full bootstrap so there's no logical reason why your code would produce different results in that context.
That said, you're only updating the field_data_field_submitted table when you also need to update the field_revision_field_submitted table, so that might somehow account for the discrepancy.
Drupal provides an API for the field system so that these sorts of problems can be avoided completely. The same code you've used, rewritten the 'Drupal' way, would be:
$query = new EntityFieldQuery;
$query->entityCondition('entity_type', 'node')
->fieldCondition('field_submitted', 'value', 0);
$results = $query->execute();
if (!empty($results['node'])) {
$nodes = node_load_multiple(array_keys($results['node']));
foreach ($nodes as $node) {
$node->field_submitted[$node->language][0]['value'] = 1;
node_save($node);
}
}
I can't think of a good reason why the above code would fail on cron either so it might be worth giving it a whirl.
I have an issue with triming a field before it is saved. I wanted to use substr(), or regex() with preg_match(). I have built a Drupal 7 module, but it can't work at all. I have tried using the trim plugin in feeds tamper module, but it doesn't seem to work. The data I am using is from a feed from Google Alerts. I have posted this issue here.
This is what I have done so far, and I know my regular expression is wrong; I was trying to get it do anything, just to see if I could get it to work, but I am pretty lost on how to add this type of function to a Drupal module.
function sub_node_save() {
$url = $node->field_web_screenhot['und'][0]['url'];
$url = preg_match('~^(http|ftp)(s)?\:\/\/((([a-z0-9\-]*)(\.))+[a-z0-9]*)($|/.*$)~i',$url );
$node->field_web_screenhot['und'][0]['url'] =$url;
return ;
}
I used the Devel module to get the field.
If there's an easy way to use substr(), I would consider that or something else.
Basically, I just want to take the Google redirect off the URL, so it is just the basic URL to the web site.
Depending on your question and later comments, I'd suggesting using node_presave hook (http://api.drupal.org/api/drupal/modules!node!node.api.php/function/hook_node_presave/7) for this.
It's called before both insert (new) and update ops so you will need extra validations to prevent it from executing on node updates if you want.
<?php
function MYMODULE_node_presave($node) {
// check if nodetype is "mytype"
if ($node->type == 'mytype'){
// PHP's parse_url to get params set to an array.
$parts = parse_url($node->field_web_screenhot['und'][0]['url']);
// Now we explode the params by "&" to get the URL.
$queryParts = explode('&', $parts['query']);
$params = array();
foreach ($queryParts as $param) {
$item = explode('=', $param);
$params[$item[0]] = $item[1];
}
//valid_url validates the URL (duh!), urldecode() makes the URL an actual one with fixing "//" in http, q is from the URL you provided.
if (valid_url(urldecode($parms['q']))){
$node->field_web_screenhot['und'][0]['url'] = urldecode($parms['q']);
}
}
}
I've created a simple module for displaying a flash game in a custom block by overwriting game_block_view() and game_block_info() in the sites/default/modules/game.module and it works ok.
I need however to pass user avatar and also gender and city (I've added the 2 mandatory fields to the registration form) through the FlashVars-parameter to the flash game in my block.
So I'm trying to overload the hook_user_load, because I suppose that this is the method where you add properties to the $user object after it has been initiated from the database (this probably happens when the user logins or alters his/her profile data?):
function game_user_load($users) {
global $user;
$uid = $user->uid;
$result = db_query('select filename from {file_managed} where uid=:uid', array(':uid' => array($uid)));
$avatar = $result->fetchField();
$users[$uid]->avatar = $avatar;
drupal_set_message("<pre>$uid: $avatar</pre>\n");
print_r($users);
}
Unfortunately I see no output produced by the last 2 lines above in the web page
What am I doing wrong?
Thank you!
Alex
The global user object does not go through hook_user_load(), see http://api.drupal.org/api/drupal/includes--session.inc/function/_drupal_session_read/7. Don't ask me why, that's just the way it is :)
When using user_load(), any added fields will automatically be loaded, you don't need custom code for that. You just need to know how to access them, which is a bit complicated.
Something like this should work:
global $user;
// $account is now a fully loaded user object.
$account = user_load($user->uid);
// Your field name is probably 'field_avatar'.
if ($avatar = field_get_items('user', $account, 'field_avatar')) {
dpm($avatar); // only works with devel.module, strongly suggested!
}
Using the autocomplete field for a cck nodereference always displays the node id as a cryptic bracketed extension:
Page Title [nid:23]
I understand that this ensures that selections are unique in case nodes have the same title, but obviously this is a nasty thing to expose to the user.
Has anyone had any success in removing these brackets, or adding a different unique identifier?
Ultimately, you need to change the output of nodereference_autocomplete() in nodereference.module.
To do this properly, you want a custom module to cleanly override the function.
This function is defined as a menu callback, thus,
/**
* Implementation of hook_menu_alter().
*/
function custom_module_menu_alter(&$items) {
$items['nodereference/autocomplete']['page callback'] = 'custom_module_new_nodereference_autocomplete';
}
Then, copy the nodereference_autocomplete function into your custom module, changing it's name to match your callback. Then change this one line:
$matches[$row['title'] ." [nid:$id]"] = '<div class="reference-autocomplete">'. $row['rendered'] . '</div>';
Dropping the nid reference.
$matches[$row['title']] = '<div class="reference-autocomplete">'. $row['rendered'] . '</div>';
I believe the identifier is purely cosmetic at this point, which means you could also change the text however you like. If it is not purely cosmetic, well, I haven't tested to see what will happen in the wrong conditions.
I always meant to identify how to do this. Thank you for motivating me with your question.
What Grayside has posted will work... as long as you don't have two nodes with the same title. In other words, if you want to do as Grayside has proposed, you need to be aware that the nid is not entirely unimportant. The nodereference_autocomplete_validate() function does two things. It checks to see if there is a node that matches, and if so, it passes the nid on, setting it to the $form_state array. If it can't find a node, it will set an error. If the nid is present, it will be used to get the node, which also is faster, the code is here:
preg_match('/^(?:\s*|(.*) )?\[\s*nid\s*:\s*(\d+)\s*\]$/', $value, $matches);
if (!empty($matches)) {
// Explicit [nid:n].
list(, $title, $nid) = $matches;
if (!empty($title) && ($n = node_load($nid)) && $title != $n->title) {
form_error($element[$field_key], t('%name: title mismatch. Please check your selection.', array('%name' => t($field['widget']['label']))));
}
}
This just checks to see if there is a nid and checks if that node matches with the title, if so the nid is passed on.
The 2nd option is a bit slower, but it is here errors can happen. If you follow the execution, you will see, that if will try to find a node based on title alone, and will take the first node that matches. The result of this, is that if you have two nodes with the same title, one of them will always be used. This might not be a problem for you, but the thing is, that you will never find out if this happens. Everything will work just fine and the user will think that he selected the node he wanted to. This might be the case, but he might as well have chosen the wrong node.
So in short, you can get rid of the nid in the autocomplete callback, but it has 2 drawbacks:
performance (little)
uncertainty in selecting the correct node.
So you have to think about it, before going this route. Especially, since you most likely wont be able to find the problem of the selection of the wrong nodes, should it happen. Another thing to be aware of, is that the nid showing up, also brings some valuable info to the users, a quick way to lookup the node, should they be in doubt if it is the one they want, if several nodes have similar titles.
I got Grayside's answer to work, but I had to use MENU alter, instead of the FORM alter he posted. No biggy!
function custommodule_menu_alter(&$items) {
$items['nodereference/autocomplete']['page callback'] = 'fp_tweaks_nodereference_autocomplete';
}
I've found an alternative solution is to change your widget type to select list and then use the chosen module to convert your list to an autocomplete field.
This handles nodes with the same title, and actually I think the UI is better than the one provided by the autocomplete widget.
To anyone coming across this (rather old) topic by way of a google search - for Drupal 7 please consider using entityreference module and "Entity Reference" field type if possible.
You can acheive a lot more in configuration with an "Entity Reference" field. It doesn't have this problem with the nid in square brackets.
Here is the full Drupal 7 version (References 7.x-2.1) of Grayside's answer. This goes in your custom module:
/**
* Implementation of hook_menu_alter().
*/
function custom_menu_alter(&$items) {
$items['node_reference/autocomplete/%/%/%']['page callback'] = 'custom_new_node_reference_autocomplete';
}
/**
* Implementation of Menu callback for the autocomplete results.
*/
function custom_new_node_reference_autocomplete($entity_type, $bundle, $field_name, $string = '') {
$field = field_info_field($field_name);
$instance = field_info_instance($entity_type, $field_name, $bundle);
$options = array(
'string' => $string,
'match' => $instance['widget']['settings']['autocomplete_match'],
'limit' => 10,
);
$references = node_reference_potential_references($field, $options);
$matches = array();
foreach ($references as $id => $row) {
// Markup is fine in autocompletion results (might happen when rendered
// through Views) but we want to remove hyperlinks.
$suggestion = preg_replace('/<a href="([^<]*)">([^<]*)<\/a>/', '$2', $row['rendered']);
// Add a class wrapper for a few required CSS overrides.
$matches[$row['title']] = '<div class="reference-autocomplete">' . $suggestion . '</div>'; // this is the line that was modified to remove the "[nid:XX]" disambiguator
}
drupal_json_output($matches);
}