I am trying to define a custom inline condition on the commerce_line_item entity that will apply a product discount if an authenticated user has a given role.

The inline form defined in the _configure callback function appears to works fine. However, when I look at the actual rule, I am getting this error on the conditions: Error: Unknown condition order_owner_has_role. (see attached screen cap)

I can't put my finger on what is missing that will get the inline condition to be recognized by the Rule. I based my code on documentation in the API and the examples provided through commerce_order.inline_conditions.inc and commerce_product.inline_conditions.inc

My module is called kcm_commerce_discount and the complete code in kcm_commerce_discount.inline_conditions.inc is presented below.

Please note: the order_owner_has_role_build function is a stub function and I haven't coded the actual logic yet. For now it just returns true

See attached screen caps for discount form and Rules

/**
* Implements hook_inline_conditions_info().
*/
function kcm_commerce_discount_inline_conditions_info() {
  $conditions = array();

  $conditions['order_owner_has_role'] = array(
    'label' => t('Role'),
    'entity type' => 'commerce_line_item',
    'callbacks' => array(
      'configure' => 'order_owner_has_role_configure',
      'build' => 'order_owner_has_role_build',
    ),
  );
  return $conditions;
}

/**
 * Configuration callback for order_owner_has_role.
 *
 * @param array $settings
 *   An array of rules condition settings.
 *
 * @return array;
 *   A form element array.
 */
function order_owner_has_role_configure($settings) {
  $form = array();
  $default_value = '';

  if (!empty($settings)) {
    $default_value = $settings['role'] != '' ? $settings['role'] : '';
  }

  $form['role'] = array(
    '#type' => 'select',
    '#title' => t('Role'),
    '#title_display' => 'invisible',
    '#options' => user_roles(true),
    '#default_value' => $default_value,
    '#required' => TRUE,
    '#suffix' => '<div class="condition-instructions">' . t('Discount is active if the order owner has the selected role.') . '</div>',
  );
  return $form;
}

/**
 * Build callback for order_owner_has_role.
 *
 * @param EntityDrupalWrapper $wrapper
 *   The wrapped entity given by the rule.
 * @param array $account
 *   A fully loaded drupal user.
 *
 * @return bool
 *   Returns true if condition is valid. false otherwise.
 */
function order_owner_has_role_build(EntityDrupalWrapper $wrapper, $role) {
  $has_role = FALSE;
  $tests = TRUE;

  // Do stuff to test condition. 
  if ($tests) {
    $has_role = TRUE;
  }
  return $has_role;
}

Thanks for your help.

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

vuzzbox’s picture

I believe I found a solution to my own problem.

I called hook_rules_condition_info_alter to define the parameters for the inline condition and now the Rule recognizes my custom inline condition and it is tested when the associated event fires.

From the documentation I read, I thought the inline_conditions module itself would have altered the conditions for the Rule itself, but I guess not.

Although I saw the call to this hook in the two sub-modules included with inline_conditions module, which I was referencing as examples, it took me some time to figure out that I needed to call the hook, too, because it didn't actually get called (and have any effect) until I put it into my .module file. Not sure why the hooks are recognized in the .inline_conditions.inc files in the two sub-modules, but not in my .inline_conditions.inc file. Is this by design?

In any event, for anyone else who is trying to define their own inline_conditions, here's the last piece of my puzzle (put it in your .module file):

/**
 * Implements hook_rules_condition_info_alter().
 *
 * Adds new rule conditions to commerce_line_item entity type.
 */
function kcm_commerce_discount_rules_condition_info_alter(&$conditions) {
  $inline_conditions = inline_conditions_get_info();

  $conditions['order_owner_has_role'] = array(
    'label' => t('Order owner has role'),
    'parameter' => array(
      'commerce_line_item' => array(
        'type' => 'commerce_line_item',
        'label' => t('Line Item'),
        'description' => t('A product line item.'),
        'wrapped' => TRUE,
      ),
      'role' => array(
        'type' => 'text',
        'label' => t('Role'),
        'description' => t('Role.'),
      ),
    ),
    'module' => 'kcm_commerce_discount',
    'group' => t('Commerce Order'),
    'callbacks' => array(
      'execute' => $inline_conditions['order_owner_has_role']['callbacks']['build'],
    ),
  );
}
vuzzbox’s picture

And just for reference, here's the complete order_owner_has_role_build function:

/**
 * Build callback for order_owner_has_role.
 *
 * @param EntityDrupalWrapper $line_item_wrapper
 *   The wrapped entity given by the rule.
 * @param integer $role
 *   role id 
 *
 * @return bool
 *   Returns true if condition is valid. false otherwise.
 */
function order_owner_has_role_build(EntityDrupalWrapper $line_item_wrapper, $role) {
  module_load_include('module','commerce_order', 'commerce_order');
  if ($order = commerce_order_load($line_item_wrapper->order_id->value())) {
    if ($user = user_load($order->uid)) {
      if ($user->roles[$role]) {
        return TRUE;
      }
    }
  }
  return FALSE;
}
jkuma’s picture

Status: Active » Needs work

Hello vuzzbox,

Thanks for your various posts here, indeed there is a bug that doesn't allow other modules to add theirs conditions in a .inline_conditions file. I'll soon post patch to fix that.

jkuma’s picture

Status: Needs work » Needs review
FileSize
1.23 KB

Here is the patch attached.

jkuma’s picture

Status: Needs review » Reviewed & tested by the community
jkuma’s picture

Status: Reviewed & tested by the community » Closed (fixed)

Committed in 7.x-1.x branch.

roderik’s picture

I request you revise this.

The reason comes down to: you are not allowed to call any hook, in code that is immediately executed when including a .module file. This is not properly documented anywhere, but it's very true.

For details, look at the current code execution tree:

  • _drupal_bootstrap_full()
  • module_load_all() ... does drupal_load('module', $module) in a loop
  • your code at the beginning of inline_conditions.module
  • inline_conditions_get_info_by_module() -> inline_conditions_get_info()
  • module_implements()...and if the database row 'cache_bootstrap > module_implements' does not have the 'inline_conditions_info' data:
  • module_hook_info() ...and if the database row 'cache_bootstrap > hook_info' is not present:

module_hook_info() does a simple check on function_exists( {$module}_hook_info )

... and will not find and execute any {$module}_hook_info() for .module files that were not loaded yet, by module_load_all(). So it will cache incomplete 'cache_bootstrap > hook_info' data, with unpredictable effects for your site. (Semi-random hook implementations for semi-random modules will never be executed.)

This is probably the cause for #2125459: Node title and other fields disappeared (and the reason you can't reproduce things. You can try "DELETE FROM cache_bootstrap WHERE cid IN ('module_implements', 'hook_info')" but the effect may not be the same on your site.)

roderik’s picture

Issue summary: View changes
Status: Closed (fixed) » Needs work

btw: see #496170: module_implements() cache can be polluted by module_invoke_all() being called (in)directly prior to full bootstrap completion and related for more about this issue. It's kinda been known for ages but never fixed.

jkuma’s picture

Roderic, Thank you so much for your really useful comments on this issue. I'll fix that by tomorrow.

jkuma’s picture

Assigned: Unassigned » jkuma
Status: Needs work » Needs review
FileSize
55.27 KB

Hello roderik,

I've removed the plain php code at the top of inline conditions.module and also I've refactored the custom conditions exposed by IC. Indeed, only inline conditions info and configure callbacks are now placed in the .inline_conditions.inc file. The rules conditions are located in the .rules.inc file which makes more sense from my perspective.

I'll write a complete documentation on how to declare properly new inline conditions on the module page. Please tell me if the patch is working for you.

jkuma’s picture

Forgot to move some validate functions and fix a bug with the inline_condition widget form cache.

jkuma’s picture

jkuma’s picture

jkuma’s picture

Status: Needs review » Needs work
jkuma’s picture

Status: Needs work » Needs review
FileSize
55.99 KB

I hope this one will pass the tests.

jkuma’s picture

Priority: Normal » Critical
jkuma’s picture

Status: Needs review » Closed (fixed)

Hello,

I'm closing this post because I've tagged a new alpha version of IC. This version came with the patch above and fix a lot of errors raised by drupal users.

Feel free to reopen it if you are still encountering errors with inline conditions.

jkuma’s picture

Status: Closed (fixed) » Fixed
roderik’s picture

Sorry, I somehow managed to miss the followup to this issue.

Thanks for rescuing a.o. me and getting a new release out the door so quickly!

I will report if I see anything weird, but trust that I won't.
I can't really comment on your code because I don't know the detailed functionality of this module. I just installed it as a dependency for commerce_coupon 2.x, and then started to see the same strange behavior that my site gave 2 months ago -- so I compiled that 'stack trace' in #7 without further knowledge/suggestions for fixing.

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.

ikigeg’s picture

I realise this has been marked as closed, but I was having the same difficulty as outlined in https://www.drupal.org/node/2066753 (which was redirected to this post) even though I was running with the latest version. I even installed the dev version, but for the life of me, the .inline_conditions.inc would not be used beyond the initial page load following a flushing of the cache. A quick fix, which works wonderfully, is just to add the following line to the main .module file:

module_load_include('inc', 'my_module', 'my_module.inline_conditions');

It may very well turn out to be something odd I had done, but this works well enough.