3

I have minimal Drupal and PHP experience ...

Problem:

I have an autocreate function for a Content Type ('hannibal'), which has one defined field type ('faceman'). When Content Type 'hannibal' is created, it is done automatically through rules by passing the value for field 'murdock' from another content type. 'murdock' and 'faceman' are sometimes identical values (they are also identical formats: text, same formatting, direct copies).

I need to check across all other nodes of type 'hannibal', to see if the value for 'faceman' already exists in that field. I have Unique Field module active, but because the nodes are being created through Rules, it is not functioning in this case (perhaps it only works when in a user entered form).

Issue: A method to check field 'faceman' for the value being entered, in all nodes of type 'hannibal'.

I've already tried a LOT of options for this:

Rules - I have tried setting conditions, but can't find a way to load all nodes of type 'hannibal' to check that field value. I've tried to do it in PHP using snippets taken from elsewhere, but no luck. Also tried setting a rules component, but couldn't make sense of it.

Database query - I'm using SQLite. I've identified the table to check, and tried an EntityFieldQuery but the code I've tried as a condition is just crashing the page:

$value = token_replace('[node:field_murdock]', array('node' => $node));

$query = new EntityFieldQuery();
$query->entityCondition('entity_type', 'node')
  ->entityCondition('bundle', 'source')
  ->propertyCondition('status', NODE_PUBLISHED)
  ->fieldCondition('faceman', 'faceman_value', '$value',)

$result = $query->execute();
if (isset($result['node'])) {
  return TRUE;
} 

So long as I can create the nodes automatically, and prevent new nodes being created if they have an identical value to a pre-existing value for field_faceman, I'm not that bothered about how it is done (within reason!).

This seems like such a basic function (checking all the values of a given field in the database), I must be missing an easier way to do this. Right?

Any help?

UPDATE & SOLUTION

Managed to solve it with EntityFieldQuery in the end. I'm convinced I can't be the only person with this issue, so for anyone of around my skill level in the same position in future:

Solution is via Rules using the PHP conditional.

Rules trigger is save content event. Conditional 1 is "content has 'faceman' field". Conditional 2 is Execute Custom PHP as below. No link between the two conditionals - trying an AND function broke them.

Custom PHP code:

// display murdock field taken from saved content as plaintext

$value = token_replace('[node:field_murdock]');

// search database for identical values of field faceman

$query = new EntityFieldQuery();
$query->entityCondition('entity_type', 'node')
->entityCondition('bundle', 'hannibal')
->propertyCondition('status', 1)
->fieldCondition('field_faceman', 'value', $value, '=')
;

// if identical value of field faceman is found, return FALSE to Rules condition

$result = $query->execute();
if (isset($result['node'])) {
return FALSE;
}
else {
return TRUE;
}

Therefore the action part of the Rule will only be executed if the value is not already present in the Faceman field somewhere in another node. So long as your existing node types Hannibal have fields Faceman with equivalent values / formats, then a duplicate node will not be created.

My actions rules then create the Faceman field in the new content by directly copying field Murdock from the saved content which triggered the Rule. This ensures that the above logic works, and the node should only be created once, because Faceman will always be a child & copy of a Murdock field.

3
  • You could create a View that shows all hannibal nodes and then use this View to loop through in your Rule. The latter can be done after installing Views Rules.
    – Jeroen
    Sep 6, 2017 at 23:06
  • Thanks for the quick answer - trying that now. If anyone can explain why the EntityFieldQuery example above is returning an error, I'd also like to understand. As far as I can work out it is formatted correctly. I've tried it with a verified correct value for '$value' but get the same error. What I was trying to do was: -Replace token identified within the Rules function with token value. -Conduct EntityFieldQuery of all published Source type nodes and compare 'faceman' fields with the token value. -Execute the query and return results -Return True if any 'faceman-value' = '$value'
    – Sarastro
    Sep 7, 2017 at 5:59
  • Tried Views Rules, and now have a View list for all 'faceman' fields that is accessible from Rules in the Action section. But it still isn't possible to compare the values - I have tried drupal.org/project/rules_conditional, but found that the "if" component of the module doesn't function correctly (replicated by various people in its bugs section). So the issue remains - is there a way to compare the field values that are now loaded? Rules documentation says that a component is needed, but that has the same problem: field values aren't visible in the component.
    – Sarastro
    Sep 7, 2017 at 9:54

1 Answer 1

3

You can make this work by using Rules in combination with the Views Bulk Operations module, as further detailed below.

In what follows, I'm assuming that the machine name of your field is field_optionlist (change to whatever fits).

Step 1 - Create a VBO view

Use the Views Bulk Operations (VBO) module to create a view of all relevant nodes, in which you should:

  1. Add a VBO field to it.
  2. Add a contextual filter for the field_optionlist field for which you want to check that its value is unique. Make sure to correctly configure this contextual filter, similar to what is mentioned in this answer to "How to have a field that counts the nodes submitted by the current user?" (credits to Niall Murphy, merci!).

Include a field like nid for each node (no need for any other fields). Just make sure you get exactly 1 row for each node you want to take into consideration.

Here is an export of such VBO view:

$view = new view();
$view->name = 'relevant_nodes';
$view->description = 'Produce a list of node ids that satisfy a specific filter';
$view->tag = 'default';
$view->base_table = 'node';
$view->human_name = 'Relevant nodes';
$view->core = 7;
$view->api_version = '3.0';
$view->disabled = FALSE; /* Edit this to true to make a default view disabled initially */

/* Display: Master */
$handler = $view->new_display('default', 'Master', 'default');
$handler->display->display_options['title'] = 'Relevant nodes';
$handler->display->display_options['use_more_always'] = FALSE;
$handler->display->display_options['access']['type'] = 'perm';
$handler->display->display_options['cache']['type'] = 'none';
$handler->display->display_options['query']['type'] = 'views_query';
$handler->display->display_options['exposed_form']['type'] = 'basic';
$handler->display->display_options['pager']['type'] = 'full';
$handler->display->display_options['pager']['options']['items_per_page'] = '10';
$handler->display->display_options['style_plugin'] = 'table';
$handler->display->display_options['style_options']['columns'] = array(
  'title' => 'title',
);
$handler->display->display_options['style_options']['default'] = '-1';
$handler->display->display_options['style_options']['info'] = array(
  'title' => array(
    'sortable' => 0,
    'default_sort_order' => 'asc',
    'align' => '',
    'separator' => '',
    'empty_column' => 0,
  ),
);
/* Field: Content: Nid */
$handler->display->display_options['fields']['nid']['id'] = 'nid';
$handler->display->display_options['fields']['nid']['table'] = 'node';
$handler->display->display_options['fields']['nid']['field'] = 'nid';
/* Field: Content: Title */
$handler->display->display_options['fields']['title']['id'] = 'title';
$handler->display->display_options['fields']['title']['table'] = 'node';
$handler->display->display_options['fields']['title']['field'] = 'title';
$handler->display->display_options['fields']['title']['label'] = '';
$handler->display->display_options['fields']['title']['alter']['word_boundary'] = FALSE;
$handler->display->display_options['fields']['title']['alter']['ellipsis'] = FALSE;
/* Field: Content: OptionList */
$handler->display->display_options['fields']['field_optionlist']['id'] = 'field_optionlist';
$handler->display->display_options['fields']['field_optionlist']['table'] = 'field_data_field_optionlist';
$handler->display->display_options['fields']['field_optionlist']['field'] = 'field_optionlist';
/* Field: Bulk operations: Content */
$handler->display->display_options['fields']['views_bulk_operations']['id'] = 'views_bulk_operations';
$handler->display->display_options['fields']['views_bulk_operations']['table'] = 'node';
$handler->display->display_options['fields']['views_bulk_operations']['field'] = 'views_bulk_operations';
$handler->display->display_options['fields']['views_bulk_operations']['vbo_settings']['display_type'] = '0';
$handler->display->display_options['fields']['views_bulk_operations']['vbo_settings']['enable_select_all_pages'] = 1;
$handler->display->display_options['fields']['views_bulk_operations']['vbo_settings']['row_clickable'] = 1;
$handler->display->display_options['fields']['views_bulk_operations']['vbo_settings']['force_single'] = 0;
$handler->display->display_options['fields']['views_bulk_operations']['vbo_settings']['entity_load_capacity'] = '10';
/* Contextual filter: Content: OptionList (field_optionlist) */
$handler->display->display_options['arguments']['field_optionlist_value']['id'] = 'field_optionlist_value';
$handler->display->display_options['arguments']['field_optionlist_value']['table'] = 'field_data_field_optionlist';
$handler->display->display_options['arguments']['field_optionlist_value']['field'] = 'field_optionlist_value';
$handler->display->display_options['arguments']['field_optionlist_value']['default_action'] = 'default';
$handler->display->display_options['arguments']['field_optionlist_value']['default_argument_type'] = 'raw';
$handler->display->display_options['arguments']['field_optionlist_value']['default_argument_options']['index'] = '0';
$handler->display->display_options['arguments']['field_optionlist_value']['summary']['number_of_records'] = '0';
$handler->display->display_options['arguments']['field_optionlist_value']['summary']['format'] = 'default_summary';
$handler->display->display_options['arguments']['field_optionlist_value']['summary_options']['items_per_page'] = '25';
$handler->display->display_options['arguments']['field_optionlist_value']['limit'] = '0';
/* Filter criterion: Content: Published */
$handler->display->display_options['filters']['status']['id'] = 'status';
$handler->display->display_options['filters']['status']['table'] = 'node';
$handler->display->display_options['filters']['status']['field'] = 'status';
$handler->display->display_options['filters']['status']['value'] = 1;
$handler->display->display_options['filters']['status']['group'] = 1;
$handler->display->display_options['filters']['status']['expose']['operator'] = FALSE;
/* Filter criterion: Content: Type */
$handler->display->display_options['filters']['type']['id'] = 'type';
$handler->display->display_options['filters']['type']['table'] = 'node';
$handler->display->display_options['filters']['type']['field'] = 'type';
$handler->display->display_options['filters']['type']['value'] = array(
  'article' => 'article',
);

/* Display: Page */
$handler = $view->new_display('page', 'Page', 'page');
$handler->display->display_options['path'] = 'relevant-nodes';

Step 2 - Create a Rule

Create a rule like so:

  • Rules Event: something like "Before saving content of type Article" seems most appropriate, though you can use any variation that fits.

  • Rules Condition: "Check number of results returned by a VBO view". And select the VBO view created in the previous step. In doing so, you will be asked to "specify arguments to pass to the view, one per line. You may use token replacement patterns.". The token to be entered there is node:field-optionlist. If the number of results is 1 or more, then you know the value already exists. So within Minimum number of results you should enter the value 1.

  • Rules Action: this is pretty much up to your own imagination. For example if the Rules Condition is TRUE, you could use "Set a data value", to restore the value of that field to what it was before, i.e. node-unchanged:field-optionlist. Maybe also display an appropriate message, such as (when trying to change the value from Advanced to Basic):

    The value "Basic" has already been used in at least 1 other node ... (field value remains "Advanced").

Here is an export of a Rule that does exactly what is described above (if you have the Rules UI enabled, and a field with the machine name field_optionlist, you should be able to just import this rule):

{ "rules_prevent_multiple_nodes_with_same_field_value" : {
    "LABEL" : "Prevent multiple nodes with same field value",
    "PLUGIN" : "reaction rule",
    "OWNER" : "rules",
    "REQUIRES" : [ "views_bulk_operations", "rules" ],
    "ON" : { "node_presave--article" : { "bundle" : "article" } },
    "IF" : [
      { "views_bulk_operations_condition_result_count" : {
          "view" : "relevant_nodes|page",
          "args" : [ "node:field-optionlist" ],
          "minimum" : "1"
        }
      }
    ],
    "DO" : [
      { "drupal_message" : {
          "message" : "The value \u0022[node:field-optionlist]\u0022 has already been used in at least 1 other node ... (field value remains \u0022[node-unchanged:field-optionlist]\u0022).",
          "type" : "error"
        }
      },
      { "data_set" : {
          "data" : [ "node:field-optionlist" ],
          "value" : [ "node-unchanged:field-optionlist" ]
        }
      }
    ]
  }
}

Note that my Rules Event is limited to "Article" (change to whatever fits), and replace (prior to trying to import this rule) the machine name of field_optionlist to whatever other machine name your field.

Done!

1
  • Pierre - huge thanks for the effort to put all that down. I've actually just managed to solve it another route (below)...BUT. I have another feature using Views which, if I read it right, the above will be ideal for. Thanks again!
    – Sarastro
    Sep 7, 2017 at 16:59

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.