Pipes: an outline

  1. pipes.info
  2. ==========
  3. ; $Id$
  4. name = Pipes
  5. description = Allows for creation of pipes through the ui.
  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.       'parent' => array(
  81.         'type' => 'int',
  82.         'unsigned' => TRUE,
  83.         'default' => 0,
  84.         'description' => t("The {pipes_step_data}.sid of the step's parent (where it's output goes). NULL indicates that the step's output goes to root. Steps without output are not listed."),
  85.       ),
  86.     ),
  87.     'indexes' => array(
  88.       'parent' => array('parent'),
  89.     ),
  90.     'unique keys' => array('sid_parent' => array('sid', 'parent')),
  91.   );
  92.  
  93.   $schema['pipes'] = array(
  94.     'description' => t('Stores pipe information.'),
  95.     'fields' => array(
  96.       'pid' => array(
  97.         'type' => 'serial',
  98.         'unsigned' => TRUE,
  99.         'not null' => TRUE,
  100.         'description' => t('Primary Key: Unique pipe ID.'),
  101.       ),
  102.       'name' => array(
  103.         'type' => 'varchar',
  104.         'length' => 255,
  105.         'not null' => TRUE,
  106.         'default' => '',
  107.         'description' => t('Name of the pipe.'),
  108.       ),
  109.       'module' => array(
  110.         'type' => 'varchar',
  111.         'length' => 255,
  112.         'not null' => TRUE,
  113.         'default' => '',
  114.         'description' => t('The module which created and manages the pipe.'),
  115.       ),
  116.     ),
  117.     'primary key' => array('pid'),
  118.   );
  119.  
  120.   return $schema;
  121. }
  122. pipes.module
  123. ============
  124. <?php
  125. // $Id$
  126.  
  127. // An array of the modules pipes core has .inc files for.
  128. define('PIPES_CORE_SUPPORTED_MODULES', array());
  129.  
  130. /**
  131.  * Lists all steps through invoking hook_pipes_steps.
  132.  * @param $class
  133.  *   If set, the class of a specific step to return information about.
  134.  * @param $refresh
  135.  *   If TRUE, will re-invoke the hook even if already stored in the static variable.
  136.  * @return
  137.  *   Either an array of steps or a specific step in the array, depending on whether $class was set or not.
  138.  */
  139. function pipes_steps_list($class = NULL, $refresh = FALSE) {
  140.   static $list;
  141.   if ($refresh || !isset($list)) {
  142.     $list = module_invoke_all('pipes_steps');
  143.   }
  144.   if (!is_null($class)) {
  145.     if (isset($list[$class])) }
  146.       return $list[$class];
  147.     }
  148.     return FALSE;
  149.   }
  150.   return $list;
  151. }
  152.  
  153. /**
  154.  * Load a specific step through invoking hook_pipes_steps.
  155.  * @param $class
  156.  *   The class of the step to load.
  157.  * @return
  158.  *   The specified step, or FALSE on failure.
  159.  */
  160. function pipes_steps_load($class) {
  161.   return pipes_steps_list($class);
  162. }
  163.  
  164. /**
  165.  * List all pipes.
  166.  * @param $pid
  167.  *   The pid of a a specific pipe to load. If not set, all will be loaded.
  168.  * @param $refresh
  169.  *   If TRUE, will re-load the data even if already stored in the static variable.
  170.  * @return
  171.  *   Either an array of pipes or a specific pipe in the array, depending on whether $pid was set or not.
  172.  */
  173. function pipes_list($pid = NULL, $refresh = FALSE) {
  174.   static $list;
  175.   if (!is_null($pid)) {
  176.     if (isset($list[$pid])) {
  177.       return $list[$pid];
  178.     }
  179.   }
  180.   $regenerate = ($refresh || (is_null($pid) ? !isset($list) : (!isset($list[$pid]))));
  181.   if ($regenerate) {
  182.     $list = array();
  183.     $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';
  184.     $where = array();
  185.     $vars = array();
  186.     if (!is_null($pid)) {
  187.       $where[] = 'p.pid = %d';
  188.       $vars[] = $pid;
  189.     }
  190.     if (!empty($where)) {
  191.       $query .= ' WHERE '. implode(' AND ', $where) .' ORDER BY psd.weight';
  192.       $result = call_user_func_array('db_query', array_merge(array($query), $vars));
  193.     }
  194.     else {
  195.       $result = db_query($query .' ORDER BY psd.weight');
  196.     }
  197.     while ($pipe = db_fetch_object($result)) {
  198.       foreach (array('name', 'module') as $element) {
  199.         if (!isset($list[$pipe->p.pid][$element])) {
  200.           $list[$pipe->p.pid][$element] = $pipe->('p.'. $element);
  201.         }
  202.         // Multiple steps per pipe.
  203.         $data = array();
  204.         $data['class'] = $pipe->psd.class;
  205.         $data['step_data'] = pipes_step_load($pipe->psd.class);
  206.         $data['parent'] = $pipe->psh.parent;
  207.         $data['settings'] = $pipe->psd.settings;
  208.         $list[$pipe->p.pid][$pipe->psd.sid] = $data;
  209.       }
  210.     }
  211.   }
  212.   if (!is_null($pid)) {
  213.     if (isset($list[$pid])) {
  214.       return $list[$pid];
  215.     }
  216.     return FALSE;
  217.   }
  218.   return $list;
  219. }
  220.  
  221. /**
  222.  * Fetch a pipe based on a given pipe id.
  223.  * @param $pid
  224.  *   The pid of the pipe to load.
  225.  * @return
  226.  *   The specified step, or FALSE on failure.
  227.  */
  228. function pipes_load($pid) {
  229.   return pipes_list($pid);
  230. }
  231.  
  232. /**
  233.  * Main pipe callback. Executes an array of steps.
  234.  * @param $steps
  235.  *   Either an array of steps, or the id of a pipe to load.
  236.  * @param $reorder
  237.  *   Defaults to FALSE. If set to TRUE, will cause the steps to be re-ordered.
  238.  * @return
  239.  *   Return value of the pipe. Unknown.
  240.  */
  241. function pipes_execute($steps, $reorder = FALSE) {
  242.   if (is_numeric($steps)) {
  243.     // It's a pid, so load the pipe.
  244.     $steps = pipes_load($steps);
  245.   }
  246.   // Convert to an array of necessary.
  247.   $steps = (array)$steps;
  248.   // Reorder the pipes if called for.
  249.   if ($reorder) {
  250.     $steps = pipes_order_steps($steps);
  251.   }
  252.   // Preserve a copy of the steps array before trimming.
  253.   $pipe = $steps;
  254.   // Trim irrelevant data from the steps array in order to protect it during foreach() loop.
  255.   pipes_trim_steps($steps);
  256.  
  257.   $values = array();
  258.  
  259.   // Cycle through each of the steps in the pipe.
  260.   foreach ($steps as $sid => $info) {
  261.     $value = call_user_func_array($info['class'], array_merge($values[$sid], $info['settings']));
  262.     if (isset($info['step_data']['escapes'])) {
  263.       foreach ($info['step_data']['escapes'] as $check => $action) {
  264.         // Check for strict equality.
  265.         if ($value === $check) {
  266.           // A match has been found. Execute functions / return stuff.
  267.           foreach ($action as $type => $function_or_return) {
  268.             if ($type == 'function') {
  269.               // We do not use the return value. If changes to $value are to be made, $value should be asked for by reference.
  270.               $function_or_return($value);
  271.             }
  272.             if ($type == 'return') {
  273.               return $function_or_return;
  274.             }
  275.           }
  276.         }
  277.       }
  278.     }
  279.     if (!is_null($value) && !is_null($info['parent'])) {
  280.       $values[$info['parent']][] = $value;
  281.     }
  282.   }
  283.  
  284.   // Return final value, if we reach here.
  285.   return $value;
  286. }
  287.  
  288. /**
  289.  * Removes non-step data from a pipe array.
  290.  * @param &$steps
  291.  *   An array of steps that need non-step data trimmed. Passed in by reference.
  292.  */
  293. function pipes_trim_steps(&$steps) {
  294.   unset($steps['name']);
  295.   unset($steps['module']);
  296. }
  297.  
  298. /**
  299.  * Orders steps based on which step needs which information.
  300.  * @param $steps
  301.  *   An array of steps to order.
  302.  */
  303. function pipes_order_steps($steps) {
  304.   $dependencies = array();
  305.   $new_steps = array();
  306.   foreach ($steps as $sid => $step) {
  307.     // The step gives data to its parent, thus the parent is dependent on it.
  308.     $dependencies[$step->parent][] = $sid;
  309.   }
  310.   $being_productive = TRUE;
  311.   while ($being_productive && !empty($steps)) {
  312.     $being_productive = FALSE;
  313.     foreach ($steps as $sid => $step) {
  314.       if (empty($dependencies[$sid])) {
  315.         // It has no unhandled dependencies, so might as well put it in.
  316.         $new_steps[$sid] = $step;
  317.         // We no longer have to worry about it.
  318.         unset($steps[$sid]);
  319.         // We just made a change, so we are being productive.
  320.         $being_productive = TRUE;
  321.         // Cycle through dependencies and remove this one.
  322.         foreach ($dependencies as $parent => $children) {
  323.           if (($key = array_search($sid, $children, TRUE)) !== FALSE) {
  324.             unset($dependencies[$parent][$key]);
  325.           }
  326.         }
  327.       }
  328.     }
  329.   }
  330.   if (!$being_productive) {
  331.     // Circular dependency.
  332.     return FALSE;
  333.   }
  334.   return $new_steps;
  335. }