Pipes

  1. pipes.info
  2. ==========
  3. ; $Id$
  4. name = Pipes
  5. description = Provides a framework for creation and execution of Drupal pipes.
  6. core = "6.x"
  7.  
  8. pipes.install
  9. =============
  10. <?php
  11. // $Id$
  12.  
  13. /**
  14.  * Implementation of hook_install().
  15.  */
  16. function pipes_install() {
  17. }
  18.  
  19. /**
  20.  * Implementation of hook_uninstall().
  21.  */
  22. function pipes_uninstall() {
  23. }
  24.  
  25. /**
  26.  * Implementation of hook_schema().
  27.  */
  28. function pipes_schema() {
  29.   $schema['pipes_step_data'] = array(
  30.     'description' => t('Stores step information.'),
  31.     'fields' => array(
  32.       'sid' => array(
  33.         'type' => 'serial',
  34.         'unsigned' => TRUE,
  35.         'not null' => TRUE,
  36.         'description' => t('Primary Key: Unique term ID.'),
  37.       ),
  38.       'pid' => array(
  39.         'type' => 'int',
  40.         'unsigned' => TRUE,
  41.         'not null' => TRUE,
  42.         'default' => 0,
  43.         'description' => t('The {pipes}.pid of the pipe to which the step is assigned.'),
  44.       ),
  45.       'class' => array(
  46.         'type' => 'varchar',
  47.         'length' => 255,
  48.         'not null' => TRUE,
  49.         'default' => '',
  50.         'description' => t('The step class (the function name).'),
  51.       ),
  52.       'settings' => array(
  53.         'type' => 'text',
  54.         'size' => 'big',
  55.         'not null' => TRUE,
  56.         'description' => t('Serialized array of any settings that have been set for this step (not including hierarchical settings).'),
  57.       ),
  58.       'weight' => array(
  59.         'type' => 'int',
  60.         'not null' => TRUE,
  61.         'default' => 0,
  62.         'size' => 'tiny',
  63.         'description' => t('The saved weight of this step in relation to other steps. Lighter steps are fired first.'),
  64.       ),
  65.     ),
  66.     'primary key' => array('sid'),
  67.     'indexes' => array('pid' => array('pid')),
  68.   );
  69.  
  70.   $schema['pipes_step_hierarchy'] = array(
  71.     'description' => t('Stores the hierarchical relationship between steps.'),
  72.     'fields' => array(
  73.       'sid' => array(
  74.         'type' => 'int',
  75.         'unsigned' => TRUE,
  76.         'not null' => TRUE,
  77.         'default' => 0,
  78.         'description' => t('The {pipes_step_data}.sid of the step.'),
  79.       ),
  80.       'destination' => array(
  81.         'type' => 'int',
  82.         'unsigned' => TRUE,
  83.         'default' => 0,
  84.         'description' => t("The {pipes_step_data}.sid of the step's destination (where it's output goes). NULL indicates that the step's output goes to root. Steps without output are not listed."),
  85.       ),
  86.       'param_id' => array(
  87.         'type' => 'int',
  88.         'not null' => TRUE,
  89.         'default' => 0,
  90.         'size' => 'tiny',
  91.         'description' => t('The number of the parameter that this step\'s output needs to go to.'),
  92.       ),
  93.     ),
  94.     'indexes' => array(
  95.       'destination' => array('destination'),
  96.     ),
  97.     'unique keys' => array('sid_destination_param_id' => array('sid', 'destination', 'param_id')),
  98.   );
  99.  
  100.   $schema['pipes'] = array(
  101.     'description' => t('Stores pipe information.'),
  102.     'fields' => array(
  103.       'pid' => array(
  104.         'type' => 'serial',
  105.         'unsigned' => TRUE,
  106.         'not null' => TRUE,
  107.         'description' => t('Primary Key: Unique pipe ID.'),
  108.       ),
  109.       'name' => array(
  110.         'type' => 'varchar',
  111.         'length' => 255,
  112.         'not null' => TRUE,
  113.         'default' => '',
  114.         'description' => t('Name of the pipe.'),
  115.       ),
  116.       'module' => array(
  117.         'type' => 'varchar',
  118.         'length' => 255,
  119.         'not null' => TRUE,
  120.         'default' => '',
  121.         'description' => t('The module which created and manages the pipe.'),
  122.       ),
  123.     ),
  124.     'primary key' => array('pid'),
  125.   );
  126.  
  127.   return $schema;
  128. }
  129.  
  130. pipes.module
  131. =============
  132. <?php
  133. // $Id$
  134.  
  135. /**
  136.  * Lists all steps through invoking hook_pipes_steps.
  137.  * @param $refresh
  138.  *   If TRUE, will re-invoke the hook even if already stored in the static variable.
  139.  * @return
  140.  *   Either an array of steps or a specific step in the array, depending on whether $class was set or not.
  141.  */
  142. function pipes_list_steps($refresh = FALSE) {
  143.   static $list;
  144.   if ($refresh || !isset($list)) {
  145.     $list = module_invoke_all('pipes_steps');
  146.     foreach ($list as $class => $info) {
  147.       // Allow modules to alter pipe steps.
  148.       drupal_alter('pipes_step', $info, $class);
  149.     }
  150.   }
  151.   return $list;
  152. }
  153.  
  154. /**
  155.  * Load a specific step through invoking hook_pipes_steps.
  156.  * @param $class
  157.  *   The class of the step to load.
  158.  * @return
  159.  *   The specified step, or FALSE on failure.
  160.  */
  161. function pipes_step_load($class) {
  162.   $steps = pipes_list_steps();
  163.   if (!isset($steps[$class])) {
  164.     return FALSE;
  165.   }
  166.   return $steps[$class];
  167. }
  168.  
  169. /**
  170.  * List all pipes.
  171.  * @param $pid
  172.  *   The pid of a a specific pipe to load. If not set, all will be loaded.
  173.  * @param $module
  174.  *   The module whose pipe(s) should be loaded. If not set, all will be loaded.
  175.  * @param $refresh
  176.  *   If TRUE, will re-load the data even if already stored in the static variable.
  177.  * @return
  178.  *   Either an array of pipes or a specific pipe in the array, depending on whether $pid was set or not.
  179.  */
  180. function pipes_list($pid = NULL, $module = NULL, $refresh = FALSE) {
  181.   static $list;
  182.   if (!is_null($pid)) {
  183.     if (isset($list[$pid])) {
  184.       return $list[$pid];
  185.     }
  186.   }
  187.   $regenerate = ($refresh || (is_null($pid) ? !isset($list) : (!isset($list[$pid]))));
  188.   if ($regenerate) {
  189.     $list = array();
  190.     $query = 'SELECT p.* psd.* psh.* FROM {pipes} p INNER JOIN {pipes_step_data} psd ON psd.pid = p.pid LEFT JOIN {pipes_step_hierarchy} psh ON psh.sid = p.sid';
  191.     $where = array();
  192.     $vars = array();
  193.     if (!is_null($pid)) {
  194.       $where[] = 'p.pid = %d';
  195.       $vars[] = $pid;
  196.     }
  197.     if (!is_null($module)) {
  198.       $where[] = 'p.module = %s';
  199.       $vars[] = $module;
  200.     }
  201.     if (!empty($where)) {
  202.       $query .= ' WHERE '. implode(' AND ', $where) .' ORDER BY psd.weight';
  203.       $result = call_user_func_array('db_query', array_merge(array($query), $vars));
  204.     }
  205.     else {
  206.       $result = db_query($query .' ORDER BY psd.weight');
  207.     }
  208.     while ($pipe = db_fetch_array($result)) {
  209.       foreach (array('name', 'module') as $element) {
  210.         if (!isset($list[$pipe['p.pid']][$element])) {
  211.           $list[$pipe['p.pid']][$element] = $pipe['p.'. $element];
  212.         }
  213.         // Multiple steps per pipe.
  214.         $data = array();
  215.         $data['class'] = $pipe['psd.class'];
  216.         $data['step_data'] = pipes_step_load($pipe['psd.class']);
  217.         $data['destination'][] = array('step' => $pipe['psh.destination'], 'param' => $pipe['psh.param_id']);
  218.         $data['settings'] = unserialize($pipe['psd.settings']);
  219.         $list[$pipe['p.pid']][$pipe['psd.sid']] = $data;
  220.       }
  221.     }
  222.   }
  223.   if (!is_null($pid)) {
  224.     if (isset($list[$pid])) {
  225.       return $list[$pid];
  226.     }
  227.     return FALSE;
  228.   }
  229.   return $list;
  230. }
  231.  
  232. /**
  233.  * Fetch a pipe based on a given pipe id.
  234.  * @param $pid
  235.  *   The pid of the pipe to load.
  236.  * @return
  237.  *   The specified step, or FALSE on failure.
  238.  */
  239. function pipes_load($pid) {
  240.   return pipes_list($pid);
  241. }
  242.  
  243. /**
  244.  * Main pipe callback. Executes an array of steps.
  245.  * @param $steps
  246.  *   Either an array of steps, or the id of a pipe to load.
  247.  * @param $reorder
  248.  *   Defaults to FALSE. If set to TRUE, will cause the steps to be re-ordered.
  249.  * @return
  250.  *   Return value of the pipe. Unknown.
  251.  */
  252. function pipes_execute($steps, $reorder = FALSE) {
  253.   if (is_numeric($steps)) {
  254.     // It's a pid, so load the pipe.
  255.     $steps = pipes_load($steps);
  256.   }
  257.   // Convert to an array of necessary.
  258.   $steps = array($steps);
  259.   // Reorder the pipes if called for.
  260.   if ($reorder) {
  261.     $steps = pipes_order_steps($steps);
  262.   }
  263.   // Preserve a copy of the steps array before trimming.
  264.   $pipe = $steps;
  265.   // Trim irrelevant data from the steps array in order to protect it during foreach() loop.
  266.   pipes_trim_steps($steps);
  267.  
  268.   $values = array();
  269.  
  270.   // Cycle through each of the steps in the pipe.
  271.   foreach ($steps as $sid => $info) {
  272.     foreach ($info['step_data']['defaults'] as $key => $var) {
  273.       if (is_int($var)) {
  274.         $info['step_data']['defaults'][$key] = arg($var);
  275.       }
  276.     }
  277.     $value = call_user_func_array($info['class'], ($values[$sid] + $info['settings'] + $info['step_data']['defaults']));
  278.     if (isset($info['step_data']['decision'])) {
  279.       $decision = pipes_evaluate_condition($info['step_data']['decision'], $value, $steps, $values);
  280.       if ($decision !== NULL) {
  281.         $value = $decision;
  282.       }
  283.     }
  284.     if (isset($info['step_data']['escape'])) {
  285.       $escape = pipes_evaluate_condition($info['step_data']['escape'], $value, $steps, $values);
  286.       if ($escape !== NULL) {
  287.         return $escape;
  288.       }
  289.     }
  290.     if (!is_null($info['destination'])) {
  291.       foreach ($info['destination'] as $destination_data) {
  292.         $values[$destination_data['step']][$destination_data['param']] = $value;
  293.       }
  294.     }
  295.   }
  296.  
  297.   // Return final value, if we reach here.
  298.   return $value;
  299. }
  300.  
  301. /**
  302.  * Helper function for pipes_execute().
  303.  * @param $condition
  304.  *   The condition to be evaluated.
  305.  * @param $value
  306.  *   The value, passed in by reference.
  307.  * @param $steps
  308.  *   The steps, passed in by reference.
  309.  * @param $values
  310.  *   The values, passed in by reference.
  311.  * @return
  312.  *   The produced data.
  313.  */
  314. function pipes_evaluate_condition($condition, &$value, &$steps, &$destination_data) {
  315.   foreach ($condition as $function => $transition) {
  316.     $result = FALSE;
  317.     switch ($function) {
  318.       case 'empty':
  319.         $result = empty($value);
  320.         break;
  321.       case 'isset':
  322.         $result = isset($value);
  323.         break;
  324.       default:
  325.         $result = $function($value);
  326.     }
  327.     if ($result) {
  328.       preg_match('/(\w+)(\(\))$/', $transition, $matches);
  329.       if ($matches[2] == '()') {
  330.         // It's a function.
  331.         $function = $matches[1];
  332.         return $function($value, $steps, $destination_data);
  333.       }
  334.       else {
  335.         return $transition;
  336.       }
  337.     }
  338.   }
  339. }
  340.  
  341. /**
  342.  * Removes non-step data from a pipe array.
  343.  * @param &$steps
  344.  *   An array of steps that need non-step data trimmed. Passed in by reference.
  345.  */
  346. function pipes_trim_steps(&$steps) {
  347.   unset($steps['name']);
  348.   unset($steps['module']);
  349. }
  350.  
  351. /**
  352.  * Orders steps based on which step needs which information.
  353.  * @param $steps
  354.  *   An array of steps to order.
  355.  */
  356. function pipes_order_steps($steps) {
  357.   $dependencies = array();
  358.   $new_steps = array();
  359.   foreach ($steps as $sid => $step) {
  360.     // The step gives data to its destination, thus the destination is dependent on it.
  361.     $dependencies[$step->destination][] = $sid;
  362.   }
  363.   $being_productive = TRUE;
  364.   while ($being_productive && !empty($steps)) {
  365.     $being_productive = FALSE;
  366.     foreach ($steps as $sid => $step) {
  367.       if (empty($dependencies[$sid])) {
  368.         // It has no unhandled dependencies, so might as well put it in.
  369.         $new_steps[$sid] = $step;
  370.         // We no longer have to worry about it.
  371.         unset($steps[