/[drupal]/contributions/modules/ubercart/ca/ca.module
ViewVC logotype

Contents of /contributions/modules/ubercart/ca/ca.module

Parent Directory Parent Directory | Revision Log Revision Log | View Revision Graph Revision Graph


Revision 1.1 - (show annotations) (download) (as text)
Thu Jul 10 12:40:55 2008 UTC (16 months, 2 weeks ago) by islandusurper
Branch: MAIN
CVS Tags: HEAD
Branch point for: DRUPAL-6--2
File MIME type: text/x-php
Begin the Ubercart 6.x-2.x branch.
1 <?php
2 // $Id$
3
4 /**
5 * @file
6 * This is a demonstration module for the new conditional actions API.
7 */
8
9 require_once('ca.inc');
10
11 /*******************************************************************************
12 * Drupal Hooks
13 ******************************************************************************/
14
15 /**
16 * Implementation of hook_menu().
17 */
18 function ca_menu() {
19 $items['admin/settings/ca'] = array(
20 'title' => 'Conditional actions',
21 'description' => 'Administer the predicates setup to automate your store.',
22 'page callback' => 'ca_admin',
23 'access arguments' => array('administer conditional actions'),
24 'file' => 'ca.admin.inc',
25 );
26 $items['admin/settings/ca/overview'] = array(
27 'title' => 'Overview',
28 'weight' => 0,
29 'type' => MENU_DEFAULT_LOCAL_TASK,
30 );
31 $items['admin/settings/ca/add'] = array(
32 'title' => 'Add a predicate',
33 'description' => 'Allows an administrator to create a new predicate.',
34 'page callback' => 'drupal_get_form',
35 'page arguments' => array('ca_predicate_meta_form', '0'),
36 'access arguments' => array('administer conditional actions'),
37 'weight' => 5,
38 'file' => 'ca.admin.inc',
39 'type' => MENU_LOCAL_TASK,
40 );
41 $items['admin/settings/ca/%/edit'] = array(
42 'title' => 'Edit predicate meta data',
43 'description' => 'Edit the meta data for a predicate like title, trigger, etc.',
44 'page callback' => 'drupal_get_form',
45 'page arguments' => array('ca_predicate_meta_form', 3),
46 'access arguments' => array('administer conditional actions'),
47 'file' => 'ca.admin.inc',
48 'type' => MENU_CALLBACK,
49 );
50 $items['admin/settings/ca/%/reset'] = array(
51 'title' => t('Reset a predicate'),
52 'page callback' => 'drupal_get_form',
53 'page arguments' => array('ca_predicate_delete_form', 3),
54 'access arguments' => array('administer conditional actions'),
55 'file' => 'ca.admin.inc',
56 'type' => MENU_CALLBACK,
57 );
58 $items['admin/settings/ca/%/delete'] = array(
59 'title' => 'Delete a predicate',
60 'page callback' => 'drupal_get_form',
61 'page arguments' => array('ca_predicate_delete_form', 3),
62 'access arguments' => array('administer conditional actions'),
63 'file' => 'ca.admin.inc',
64 'type' => MENU_CALLBACK,
65 );
66 $items['admin/settings/ca/%/conditions'] = array(
67 'title' => 'Edit predicate conditions',
68 'description' => "Edit a predicate's conditions.",
69 'page callback' => 'drupal_get_form',
70 'page arguments' => array('ca_predicate_meta_form', 3),
71 'access arguments' => array('administer conditional actions'),
72 'file' => 'ca.admin.inc',
73 'type' => MENU_CALLBACK,
74 );
75
76 return $items;
77 }
78
79 /**
80 * Implementation of hook_perm().
81 */
82 function ca_perm() {
83 return array('administer conditional actions');
84 }
85
86 /*******************************************************************************
87 * Conditional Actions Hooks
88 ******************************************************************************/
89
90 function ca_ca_entity() {
91 $entities = array();
92
93 $entities['user'] = array(
94 '#title' => t('Drupal user'),
95 '#type' => 'object',
96 '#load' => 'user_load',
97 '#save' => 'user_save',
98 );
99
100 return $entities;
101 }
102
103 /**
104 * Implementation of hook_ca_action().
105 */
106 function ca_ca_action() {
107 $actions['ca_drupal_set_message'] = array(
108 '#title' => t('Display a message to the user'),
109 '#category' => t('Drupal'),
110 '#callback' => 'ca_action_drupal_set_message',
111 '#arguments' => array(),
112 );
113
114 return $actions;
115 }
116
117 /**
118 * Implementation of hook_ca_predicate().
119 */
120 function ca_ca_predicate() {
121 $predicates = array();
122
123 $predicates['complain_payment_not_enough'] = array(
124 '#title' => t('Ask for more money.'),
125 '#description' => t('Displays a message when a payment is recieved, but the total is > $0.00.'),
126 '#class' => 'payment',
127 '#status' => 0,
128 '#trigger' => 'uc_payment_entered',
129 '#conditions' => array(
130 '#operator' => 'AND',
131 '#conditions' => array(
132 array(
133 '#name' => 'uc_order_balance',
134 '#title' => 'If the balance is greater than $0.00.',
135 '#argument_map' => array(
136 'order' => 'order',
137 ),
138 '#settings' => array(
139 'negate' => FALSE,
140 'balance_comparison' => 'greater',
141 ),
142 ),
143 array(
144 '#name' => 'uc_order_status',
145 '#title' => 'If the order status is not already Payment Received.',
146 '#argument_map' => array(
147 'order' => 'order',
148 ),
149 '#settings' => array(
150 'negate' => TRUE,
151 'order_status' => 'payment_received',
152 ),
153 ),
154 array(
155 '#operator' => 'OR',
156 '#conditions' => array(
157 array(
158 '#name' => 'uc_order_payment_method',
159 '#title' => 'If the payment method is not Check.',
160 '#argument_map' => array(
161 'order' => 'order',
162 ),
163 '#settings' => array(
164 'negate' => FALSE,
165 'payment_method' => 'check',
166 ),
167 ),
168 array(
169 '#name' => 'uc_order_payment_method',
170 '#title' => 'If the payment method is not COD.',
171 '#argument_map' => array(
172 'order' => 'order',
173 ),
174 '#settings' => array(
175 'negate' => FALSE,
176 'payment_method' => 'cod',
177 ),
178 ),
179 ),
180 ),
181 ),
182 ),
183 '#actions' => array(
184 array(
185 '#name' => 'ca_drupal_set_message',
186 '#title' => 'Ask the customer to pay more money.',
187 '#settings' => array(
188 'message_type' => 'status',
189 'message_text' => 'Pay more money, yo!',
190 ),
191 ),
192 ),
193 );
194
195 return $predicates;
196 }
197
198
199 /*******************************************************************************
200 * Module and Helper Functions
201 ******************************************************************************/
202
203 /**
204 * Pulls a trigger and evaluates any predicates associated with that trigger.
205 *
206 * @param ...
207 * Accepts a variable number of arguments. The first should always be the
208 * string name of the trigger to pull with any additional arguments being
209 * the arguments expected by the trigger and used for evaluation.
210 * @return
211 * TRUE or FALSE indicating if at least one predicate was evaluated.
212 */
213 function ca_pull_trigger() {
214 $args = func_get_args();
215 $trigger = array_shift($args);
216
217 // Load all the triggers.
218 $triggers = module_invoke_all('ca_trigger');
219
220 // Fail if the specified trigger doesn't exist.
221 if (!is_array($triggers[$trigger])) {
222 return FALSE;
223 }
224
225 // Load any predicates associated with the trigger.
226 $predicates = ca_load_trigger_predicates($trigger);
227
228 // Fail if we didn't find any predicates.
229 if (!$predicates || count($predicates) == 0) {
230 return FALSE;
231 }
232
233 // Prepare the arguments for evaluation.
234 $arguments = ca_parse_trigger_args($triggers[$trigger], $args);
235
236 // Fail if we didn't receive the right type of or enough arguments.
237 if (!$arguments) {
238 return FALSE;
239 }
240
241 // Loop through the predicates and evaluate them one by one.
242 foreach ($predicates as $pid => $predicate) {
243 // If all of a predicate's conditions evaluate to TRUE...
244 if (ca_evaluate_conditions($predicate, $arguments)) {
245 // Then perform its actions.
246 ca_perform_actions($predicate, $arguments);
247 }
248 }
249
250 return TRUE;
251 }
252
253 /**
254 * Parses the argument array into a CA friendly array for the trigger.
255 *
256 * @param $trigger
257 * The name of the trigger for which we are parsing the arguments.
258 * @param $args
259 * An array of arguments to check against the expected arguments.
260 * @return
261 * The array of arguments keyed according to the trigger's argument names.
262 */
263 function ca_parse_trigger_args($trigger, $args) {
264 // Fail if we didn't receive enough arguments for this trigger.
265 if (count($args) < count($trigger['#arguments'])) {
266 return FALSE;
267 }
268
269 // Load all the entity information.
270 $entities = module_invoke_all('ca_entity');
271
272 // Loop through the expected arguments.
273 foreach ($trigger['#arguments'] as $key => $value) {
274 // Grab for comparison the next argument passed to the trigger.
275 $arg = array_shift($args);
276
277 // Check the type and fail if it is incorrect.
278 if (gettype($arg) != $entities[$value['#entity']]['#type']) {
279 return FALSE;
280 }
281
282 // Add the entity to the arguments array along with its meta data.
283 $arguments[$key] = array(
284 '#entity' => $value['#entity'],
285 '#title' => $value['#title'],
286 '#data' => $arg,
287 );
288 }
289
290 return $arguments;
291 }
292
293 /**
294 * Loads predicates based on the specified parameters.
295 *
296 * @param $trigger
297 * The name of the trigger for which to search when loading the predicates.
298 * @param $all
299 * FALSE by default, specifies whether we want to load all possible predicates
300 * or only those that are active (status > 0).
301 * @return
302 * An array of predicates.
303 */
304 function ca_load_trigger_predicates($trigger, $all = FALSE) {
305 // Trigger actions can pull other triggers. Lock predicates when they are
306 // marked for evaluation so they can't be evaluated again.
307 static $locked = array();
308
309 // Load all the module defined predicates.
310 $predicates = module_invoke_all('ca_predicate');
311
312 // Loop through the module defined predicates to prepare the data - unsets
313 // inactive predicates if $all == FALSE and adds a default weight if need be.
314 foreach ($predicates as $key => $value) {
315 // Unset the predicate if it doesn't use the specified trigger.
316 if ($value['#trigger'] != $trigger) {
317 unset($predicates[$key]);
318 continue;
319 }
320
321 if (!$all && $value['#status'] <= 0) {
322 unset($predicates[$key]);
323 }
324 else {
325 // Prevent predicates from being evaluated more than once in a page load.
326 if (in_array($key, $locked)) {
327 unset($predicates[$key]);
328 }
329 else {
330 $locked[$key] = $key;
331 }
332
333 if (!isset($value['#weight'])) {
334 $predicates[$key]['#weight'] = 0;
335 }
336 }
337 }
338
339 // Load and loop through the predicates from the database for this trigger.
340 $result = db_query("SELECT * FROM {ca_predicates} WHERE ca_trigger = '%s'", $trigger);
341 while ($data = db_fetch_array($result)) {
342 // Module defined predicates have string IDs. When a user modifies one of
343 // these, we unset the module defined predicate and reconsider adding it in.
344 if (!is_numeric($data['pid'])) {
345 unset($predicates[$data['pid']]);
346 }
347
348 // Add predicates from the database to our return array if $all == TRUE or
349 // if the predicate is active.
350 if ($all || $data['status'] > 0) {
351 // Prevent predicates from being evaluated more than once in a page load.
352 if (!in_array($data['pid'], $locked)) {
353 $predicates[$data['pid']] = ca_prepare_db_predicate($data);
354 $locked[$key] = $data['pid'];
355 }
356 }
357 }
358
359 uasort($predicates, 'ca_weight_sort');
360
361 return $predicates;
362 }
363
364 /**
365 * Prepares predicate data from the database into a full predicate array.
366 *
367 * @param $data
368 * An array of data representing a row in the predicates table.
369 * @return
370 * A predicate array.
371 */
372 function ca_prepare_db_predicate($data) {
373 $predicate = array();
374
375 foreach ($data as $key => $value) {
376 switch ($key) {
377 // Condition and action data needs to be unserialized.
378 case 'conditions':
379 case 'actions':
380 $predicate['#'. $key] = unserialize($value);
381 break;
382 case 'ca_trigger':
383 $predicate['#trigger'] = $value;
384 break;
385 default:
386 $predicate['#'. $key] = $value;
387 break;
388 }
389 }
390
391 return $predicate;
392 }
393
394 /**
395 * Evaluates a predicate's conditions.
396 *
397 * @param $predicate
398 * A fully loaded predicate array.
399 * @param $arguments
400 * The array of parsed arguments for the trigger.
401 * @return
402 * TRUE or FALSE indicating the success or failure of the evaluation.
403 */
404 function ca_evaluate_conditions($predicate, $arguments) {
405 // Automatically pass if there are no conditions.
406 if (count($predicate['#conditions']) == 0) {
407 return TRUE;
408 }
409
410 // Load the data for the conditions as defined by modules.
411 $condition_data = module_invoke_all('ca_condition');
412
413 // Recurse through the predicate's conditions for evaluation.
414 $result = _ca_evaluate_conditions_tree($predicate['#conditions'], $arguments, $condition_data);
415
416 if (is_null($result)) {
417 $result = FALSE;
418 }
419
420 return $result;
421 }
422
423 function _ca_evaluate_conditions_tree($condition, $arguments, $condition_data) {
424 if (isset($condition['#operator']) && is_array($condition['#conditions'])) {
425 foreach ($condition['#conditions'] as $sub_condition) {
426 $result = _ca_evaluate_conditions_tree($sub_condition, $arguments, $condition_data);
427
428 // Invalid conditions return NULL. Skip it and go to the next one.
429 if (is_null($result)) {
430 continue;
431 }
432 // Save the processors! Apply Boolean shortcutting if we can.
433 if ($condition['#operator'] == 'OR' && $result) {
434 return TRUE;
435 }
436 else if ($condition['#operator'] == 'AND' && !$result) {
437 return FALSE;
438 }
439 }
440 return $result;
441 }
442 else {
443 return _ca_evaluate_condition($condition, $arguments, $condition_data);
444 }
445 }
446
447 function _ca_evaluate_condition($condition, $arguments, $condition_data) {
448 $args = array();
449
450 // Make sure the condition tree is sane.
451 if (!is_array($condition_data[$condition['#name']])) {
452 return NULL;
453 }
454
455 // Get the callback function for the current condition.
456 $callback = $condition_data[$condition['#name']]['#callback'];
457
458 // Skip this condition if the function does not exist.
459 if (!function_exists($callback)) {
460 return NULL;
461 }
462
463 // Loop through the expected arguments for a condition.
464 foreach ($condition_data[$condition['#name']]['#arguments'] as $key => $value) {
465 // Using the argument map for the condition on this predicate, fetch the
466 // argument that was passed to the trigger that matches to the argument
467 // needed to evaluate this condition.
468 if (isset($condition['#argument_map'][$key])) {
469 $args[] = $arguments[$condition['#argument_map'][$key]]['#data'];
470 }
471 else {
472 // Skip this condition of the predicate didn't map the arguments needed.
473 return NULL;
474 }
475 }
476
477 // Add the condition settings to the argument list.
478 $args[] = $condition['#settings'];
479
480 // Call the condition's function with the appropriate arguments.
481 $result = call_user_func_array($callback, $args);
482
483 // If the negate operator is TRUE, then switch the result!
484 if ($condition['#settings']['negate']) {
485
486 $result = !$result;
487 }
488 return $result;
489 }
490
491 /**
492 * Performs a predicate's actions in order, preserving changes to the arguments.
493 *
494 * @param $predicate
495 * A fully loaded predicate array.
496 * @param $arguments
497 * The array of parsed arguments for the trigger.
498 */
499 function ca_perform_actions($predicate, $arguments) {
500 // Exit now if we don't have any actions.
501 if (count($predicate['#actions']) == 0) {
502 return;
503 }
504
505 // Set the actions' weight if necessary and sort actions by their weight.
506 for ($i = 0; $i < count($predicate['#actions']); $i++) {
507 if (!isset($predicate['#actions'][$i]['#weight'])) {
508 $predicate['#actions'][$i]['#weight'] = 0;
509 }
510 }
511 usort($predicate['#actions'], 'ca_weight_sort');
512
513 // Load the data for the actions as defined by modules.
514 $action_data = module_invoke_all('ca_action');
515
516 foreach ($predicate['#actions'] as $action) {
517 $args = array();
518
519 // Get the callback function for the current action.
520 $callback = $action_data[$action['#name']]['#callback'];
521
522 // Do not perform the action if the function does not exist.
523 if (!function_exists($callback)) {
524 continue;
525 }
526
527 // Loop through the expected arguments for a condition.
528 foreach ($action_data[$action['#name']]['#arguments'] as $key => $value) {
529 // Using the argument map for the action on this predicate, fetch the
530 // argument that was passed to the trigger that matches to the argument
531 // needed to perform this action.
532 if (isset($action['#argument_map'][$key])) {
533 // Adding the arguments as references so action functions can update the
534 // arguments here when they make changes to the argument data.
535 $args[] = &$arguments[$action['#argument_map'][$key]]['#data'];
536 }
537 else {
538 // Skip this action of the predicate didn't map the arguments needed.
539 continue 2;
540 }
541 }
542
543 // Add the condition settings to the argument list.
544 $args[] = $action['#settings'];
545
546 // Call the action's function with the appropriate arguments.
547 call_user_func_array($callback, $args);
548 }
549 }
550
551 /**
552 * Loads a predicate by its ID.
553 *
554 * @param $pid
555 * The ID of the predicate to load.
556 * @return
557 * A fully loaded predicate array.
558 */
559 function ca_load_predicate($pid) {
560 $predicate = array();
561
562 // First attempt to load the predicate from the database.
563 $result = db_query("SELECT * FROM {ca_predicates} WHERE pid = '%s'", $pid);
564 if ($predicate = db_fetch_array($result)) {
565 $predicate = ca_prepare_db_predicate($predicate);
566 }
567 else {
568 // Otherwise look for it in the module defined predicates.
569 $predicates = module_invoke_all('ca_predicate');
570
571 if (!empty($predicates[$pid])) {
572 $predicate = $predicates[$pid];
573 }
574 }
575
576 return $predicate;
577 }
578
579 /**
580 * Saves a predicate array to the database.
581 *
582 * @param $predicate
583 * A fully loaded predicate array.
584 */
585 function ca_save_predicate($predicate) {
586 // Check to see if the predicate has been previously saved to the database.
587 $result = db_result(db_query("SELECT COUNT(*) FROM {ca_predicates} WHERE pid = '%s'", $predicate['#pid']));
588
589 if (!$result) {
590 // If not, then insert it.
591 db_query("INSERT INTO {ca_predicates} (pid, title, description, class, status, weight, uid, ca_trigger, conditions, actions, created, modified) VALUES ('%s', '%s', '%s', '%s', %d, %d, %d, '%s', '%s', '%s', %d, %d)",
592 $predicate['#pid'], $predicate['#title'], $predicate['#description'], $predicate['#class'], $predicate['#status'], $predicate['#weight'], $predicate['#uid'], $predicate['#trigger'], serialize($predicate['#conditions']), serialize($predicate['#actions']), time(), time());
593 }
594 else {
595 // Otherwise, update it.
596 db_query("UPDATE {ca_predicates} SET title = '%s', description = '%s', class = '%s', status = %d, weight = %d, uid = %d, ca_trigger = '%s', conditions = '%s', actions = '%s', modified = %d WHERE pid = '%s'",
597 $predicate['#title'], $predicate['#description'], $predicate['#class'], $predicate['#status'], $predicate['#weight'], $predicate['#uid'], $predicate['#trigger'], serialize($predicate['#conditions']), serialize($predicate['#actions']), time(), $predicate['#pid']);
598 }
599 }
600
601 /**
602 * Deletes a predicate from the database.
603 *
604 * @param $pid
605 * The ID of the predicate to delete.
606 */
607 function ca_delete_predicate($pid) {
608 db_query("DELETE FROM {ca_predicates} WHERE pid = '%s'", $pid);
609 }
610
611 // Compares two conditional action arrays to sort them by #weight.
612 function ca_weight_sort($a, $b) {
613 if ($a['#weight'] == $b['#weight']) {
614 return 0;
615 }
616
617 return ($a['#weight'] > $b['#weight']) ? 1 : -1;
618 }
619
620 function _ca_convert_configurations() {
621 global $user;
622
623 if (db_table_exists('workflow_ng_cfgs')) {
624 $result = db_query("SELECT name, data FROM {workflow_ng_cfgs} WHERE altered = 1");
625 $predicates = array();
626 while ($configuration_row = db_fetch_object($result)) {
627 $predicate = array('#pid' => $configuration_row->name);
628 $conditions = array();
629 $actions = array();
630 if ($configuration = unserialize($configuration_row->data)) {
631 $predicate['#class'] = $configuration['#module'];
632 $predicate['#title'] = $configuration['#label'];
633 switch ($configuration['#event']) {
634 case 'order_status_update':
635 $trigger = 'uc_order_status_update';
636 break;
637 case 'payment_entered':
638 $trigger = 'uc_payment_entered';
639 break;
640 case 'checkout_complete':
641 $trigger = 'uc_checkout_complete';
642 break;
643 default:
644 $trigger = $configuration['#event'];
645 break;
646 }
647 $predicate['#trigger'] = $trigger;
648 $predicate['#weight'] = $configuration['#weight'];
649 $predicate['#status'] = $configuration['#active'];
650 for ($i = 0; isset($configuration[$i]); $i++) {
651 if ($configuration[$i]['#type'] == 'action') {
652 $action = $configuration[$i];
653 if (isset($action[$i]['#label'])) {
654 $action['#title'] = $action['#label'];
655 unset($action['#label']);
656 }
657 else {
658 $action['#title'] = $action['#name'];
659 }
660 $actions[] = $action;
661 }
662 else {
663 $condition = _ca_convert_conditions($configuration[$i]);
664 if ($condition) {
665 $conditions[] = $condition;
666 }
667 }
668 }
669 if ($conditions) {
670 $predicate['#conditions'] = array(
671 '#operator' => 'AND',
672 '#conditions' => $conditions,
673 );
674 }
675 $predicate['#uid'] = $user->uid;
676 ca_save_predicate($predicate);
677 }
678 }
679 }
680 }
681
682 function _ca_convert_conditions($condition_tree) {
683 $ca_condition = array();
684
685 if ($condition_tree['#type'] == 'AND' || $condition_tree['#type'] == 'OR') {
686 $ca_condition['#operator'] = $condition_tree['#type'];
687 $ca_condition['#conditions'] = array();
688 for ($i = 0; isset($condition_tree[$i]); $i++) {
689 $condition = _ca_convert_conditions($condition_tree[$i]);
690 if ($condition) {
691 $ca_condition['#conditions'][] = $condition;
692 }
693 }
694 }
695 else if ($condition_tree['#type'] == 'condition') {
696 $ca_condition['#name'] = $condition_tree['#name'];
697 $ca_condition['#title'] = $condition_tree['#label'];
698 $ca_condition['#argument_map'] = $condition_tree['#argument map'];
699 $ca_condition['#settings'] = $condition_tree['#settings'];
700 if (isset($condition_tree['#negate'])) {
701 $ca_condition['#settings']['negate'] = $condition_tree['#negate'];
702 }
703 }
704
705 return $ca_condition;
706 }
707

  ViewVC Help
Powered by ViewVC 1.1.2