Forráskód Böngészése

This commit was manufactured by cvs2svn to create branch 'DRUPAL-6--1'.

cvs2svn 15 éve
szülő
commit
93764f630e
3 módosított fájl, 617 hozzáadás és 0 törlés
  1. 7 0
      qbf.info
  2. 93 0
      qbf.install
  3. 517 0
      qbf.module

+ 7 - 0
qbf.info

@@ -0,0 +1,7 @@
+; $Id $
+name = QBF
+description = "Add query by form capability to node modules"
+dependencies[] = search
+package = Search
+version = "$Name:  $"
+core = 6.x

+ 93 - 0
qbf.install

@@ -0,0 +1,93 @@
+<?php
+/**
+ * QBF module - installer
+ *
+ * @copyright 2008 Ouest Systèmes Informatiques SARL
+ * @author FG Marand
+ * @license GPL2 or later
+ * @package QBF
+ */
+
+// $Id: qbf.install,v 1.1 2008-09-02 08:39:07 marand Exp $
+
+/**
+ * Implement hook_install().
+ *
+ * - Create the QBF tables
+ *   - qbf_queries
+ * - Assign initial settings
+ *   - none
+ *
+ * @todo support PGSQL
+ * @return void
+ */
+function qbf_install()
+  {
+  switch ($GLOBALS['db_type'])
+    {
+    case 'mysql':
+    case 'mysqli':
+      $sq = 'CREATE TABLE {qbf_queries} '
+          . '  ( '
+          . "  `qid`                   INT(10)          NOT NULL DEFAULT '0' COMMENT 'Query ID', "
+          . "  `uid`                   INT(10)          NOT NULL DEFAULT '0' COMMENT 'User ID', "
+          . "  `name`                  VARCHAR(40)      NOT NULL DEFAULT ''  COMMENT 'Query name', "
+          . "  `query`                 TEXT             NOT NULL             COMMENT 'Query array', "
+          . '  PRIMARY KEY (`qid`) , '
+          . '  INDEX (`uid`) '
+          . '  ) '
+          . 'ENGINE = MyISAM '
+          . 'CHARACTER SET = utf8 '
+          . "COMMENT = 'QBF Query store' ";
+      db_query($sq);
+      break;
+
+    case pgsql:
+    default:
+      drupal_set_message(t('Unsupported database backend for QBF module: @db',
+        array('@db' => $GLOBALS['db_type'])), 'error');
+      break;
+    }
+  }
+
+/**
+ * Implement hook_uninstall().
+ *
+ * - Drop all the tables maintained by the module
+ *   - qbf_queries
+ * - Remove nodes of all the types maintained by the module
+ *   - none
+ * - Remove module settings
+ *   - none
+ * - Do NOT remove module schema version from {system}
+ *
+ * @return void
+ */
+function qbf_uninstall()
+  {
+  switch ($GLOBALS['db_type'])
+    {
+    case 'mysql':
+    case 'mysqli':
+    case 'pgsql':
+      db_query("DROP TABLE {qbf_queries}");
+      break;
+
+    default:
+      drupal_set_message(t('Unsupported database backend for QBF module: @db',
+        array('@db' => $GLOBALS['db_type'])), 'error');
+      break;
+    }
+  }
+
+/**
+ * Implement hook_update_N().
+ *
+ * For now, just define a proper initial schema version.
+ *
+ * @return array
+ */
+function qbf_update_5000()
+  {
+  return array();
+  }

+ 517 - 0
qbf.module

@@ -0,0 +1,517 @@
+<?php
+/**
+ * Query By Form
+ *
+ * This module allows node modules to add a query by form tab for their node
+ * types to the default search form
+ *
+ * @copyright 2008 Ouest Systemes Informatiques (OSInet)
+ * @author Frederic G. MARAND
+ * @version $Id: qbf.module,v 1.9 2008-09-02 08:25:24 marand Exp $
+ * @license CeCILL 2.0
+ * @package QBF
+ */
+
+/**
+ * Remove this element from the generated form
+ */
+define('QBF_LEVEL_REMOVE',           0);
+/**
+ * This element is only for display in the generated form: do not include it
+ * in the query vector.
+ */
+define('QBF_LEVEL_DISPLAY',          1);
+/**
+ * Include this element in the generated form and in the query vector, but do
+ * not mark it as required.
+ */
+define('QBF_LEVEL_OPTIONAL',         2);
+/**
+ * Include this element in the generated form and in the query vector, and
+ * mark it as required.
+ */
+define('QBF_LEVEL_REQUIRED',         3);
+
+/**
+ * The main QBF path
+ */
+define('QBF_PATH_MAIN',             'qbf');
+
+/**
+ * Authorize use of QBF searches
+ */
+define('QBF_PERM_QUERY',             'use QBF search functions');
+/**
+ * Authorize QBF administration
+ */
+define('QBF_PERM_ADMIN',             'administer QBF');
+
+/**
+ * The name of the table used to store queries
+ */
+define('QBF_TABLE_NAME',             'qbf_queries');
+
+/**
+ * Transform a form array for QBF.
+ *
+ * This function obtains the form array using Forms API, and transforms it by
+ * modifying widgets to other types where needed.
+ *
+ * Any additional parameter passed to the function is transmitted to the form
+ * generating function.
+ *
+ * @param string $form_id
+ * @return array
+ */
+function qbf_transform_form($form_id)
+  {
+  $arArgs = func_get_args();
+//dsm(array('qtf' => $arArgs));
+  // Fetch the basic form and rename it, passing it the caller's arguments
+  $form = call_user_func_array('drupal_retrieve_form', $arArgs);
+  $newFormId = "qbf_$form_id";
+
+  // Only keep the children of the form and QBF properties on the form itself
+  $elements = array();
+  $newForm = array();
+  $newForm['#qbf_source_form_id'] = $form_id;
+  if (in_array('#qbf', element_properties($form)))
+    {
+    $newForm += $form['#qbf'];
+    }
+
+  foreach (element_children($form) as $key)
+    {
+    // dsm("Transforming $key, type " . $form[$key]['#type']);
+    $newElement = _qbf_transform_element($key, $form[$key]);
+    if (!is_null($newElement))
+      {
+      $newForm[$key] = $newElement;
+      }
+    }
+
+  $newForm['#id'] = $newFormId;
+  $newForm['#multistep'] = TRUE;
+  // Do not set #redirect, even to FALSE (submit handlers)
+  // $newForm['#redirect']  = FALSE;
+  $newForm['#after_build'][] = 'qbf_after_build';
+  $newForm['#submit'] = array('qbf_submit' => array());
+// dsm($newForm);
+  return $newForm;
+  }
+
+/**
+ * Transform a form element for QBF.
+ *
+ * QBF-specific properties are:
+ * - #qbf : array of properties
+ * - #level: only within #qbf
+ *
+ * @see QBF_* constants
+ *
+ * @param string $key
+ * @param array $element
+ * @return void
+ */
+function _qbf_transform_element($key, $element)
+  {
+  // dsm(array('key' => $key, 'element' => $element));
+
+  /**
+   * List default type transformations applied to widget by FAPI.
+   *
+   * Types without a default transformation are not transformed
+   */
+  static $arDefaultTypeTransformations = array
+    (
+    'button'         => NULL,
+    'file'           => NULL,
+    // 'hidden'         => NULL,
+    'markup'         => NULL,
+    'password'       => NULL,
+    'radio'          => NULL,
+    'submit'         => NULL,
+    'textarea'       => 'textfield',
+    // 'value'          => 'value',
+    );
+
+  /**
+   * List default property transformations applied to widget by FAPI property.
+   *
+   * Properties without a default transformation are not transformed
+   */
+  static $arDefaultPropertyTransformations = array
+    (
+    // Standard properties
+    '#action'        => NULL,
+    '#after_build'   => NULL,
+    '#base'          => NULL,
+    '#button_type'   => NULL,
+    '#built'         => NULL,
+    '#description'   => NULL,
+    '#method'        => NULL,
+    '#parents'       => NULL,
+    '#redirect'      => NULL,
+    '#ref'           => NULL,
+    '#required'      => NULL,
+    '#rows'          => NULL,
+    '#submit'        => NULL,
+    '#tree'          => NULL,
+    '#validate'      => NULL,
+    );
+
+  /**
+   * List properties causing causing element removal.
+   *
+   * The key is the property name, the value is the one causing removal.
+   */
+  static $arKillerProperties = array
+    (
+    '#disabled'      => TRUE,
+    );
+
+  // Transform type
+  $sourceType = $element['#type'];
+  // .. Default transformation
+  $destType = array_key_exists($sourceType, $arDefaultTypeTransformations)
+    ? $arDefaultTypeTransformations[$sourceType]
+    : $sourceType;
+  // .. Apply form-defined type override
+  if (isset($element['#qbf']['#type']))
+    {
+    $destType = $element['#qbf']['#type'];
+    }
+
+  if (is_null($destType))
+    {
+    $ret = NULL;
+    }
+  else
+    {
+    $ret = $element;
+    $ret['#type'] = $destType;
+    if (!array_key_exists('#qbf', $element) || $element['#qbf']['#level'] == QBF_LEVEL_REMOVE)
+      {
+      $ret = NULL;
+      }
+    else
+      {
+      foreach (element_properties($element) as $propertyName)
+        {
+        // Apply killer properties first to avoid useless work
+        if (array_key_exists($propertyName, $arKillerProperties)
+          && ($element[$propertyName] = $arKillerProperties[$propertyName]))
+          {
+          $ret = NULL;
+          break;
+          }
+
+        // Now transform or copy remaining properties
+        if (array_key_exists($propertyName, $arDefaultPropertyTransformations))
+          {
+          $ret[$propertyName] = $arDefaultPropertyTransformations[$propertyName];
+          }
+        else
+          {
+          $ret[$propertyName] = $element[$propertyName];
+          }
+
+        // And apply form-defined property overrides
+        if ($propertyName == '#qbf')
+          {
+          foreach ($element[$propertyName] as $overrideName => $overrideValue)
+            {
+          	$ret[$overrideName] = $overrideValue;
+            }
+          }
+        }
+
+      // Recursively transform children
+      foreach (element_children($element) as $childName)
+        {
+        $child = _qbf_transform_element($childName, $element[$childName]);
+        if (is_null($child))
+          {
+          unset($ret[$childName]);
+          }
+        else
+          {
+          $ret[$childName] = $child;
+          }
+        }
+      }
+    }
+
+  //dsm(array('key' => $key, 'transformed element' => $ret));
+  return $ret;
+  }
+
+/**
+ * Implement hook_perm().
+ *
+ * @return array
+ */
+function qbf_perm()
+  {
+  $ret = array
+    (
+    QBF_PERM_QUERY,
+    );
+
+  return $ret;
+  }
+
+/**
+ * Implement hook_forms().
+ *
+ * @return array
+ */
+function qbf_forms()
+  {
+  $hookName = 'qbf_register';
+
+  foreach (module_implements($hookName) as $module)
+    {
+    foreach (module_invoke($module, $hookName) as $formName)
+      {
+    	$forms["qbf_$formName"] = array
+    	  (
+    	  'callback'       => 'qbf_transform_form',
+    	  'callback arguments' => array($formName),
+    	  );
+      }
+    }
+
+  return $forms;
+  }
+
+/**
+ * Insert the query results at the bottom of the query form.
+ *
+ * @param array $form
+ * @param array $form_values
+ * @return array
+ */
+function qbf_after_build($form, $form_values)
+  {
+  if (empty($form['#post']))
+    {
+    return $form;
+    }
+
+  // If #post is not emtpy, we are indeed querying
+  $arQuery = _qbf_extract_query($form, $form_values);
+
+  /* This function is called at the end of the form building process, which
+   * means that child properties of #qbf have already been upgraded to element
+   * properties. So we look for $form['#callback'] and not
+   * $form['#qbf']['#callback']
+   */
+  if (isset($form['#callback']) && function_exists($function = $form['#callback']))
+    {
+    $results = $function($arQuery);
+    }
+  else
+    {
+    drupal_set_message(t('QBF: incorrect callback function for search'), 'error');
+    }
+  $form['qbf_query_results'] = array
+    (
+    '#type'    => 'markup',
+    '#value'   => $results,
+    '#weight'  => 10,
+    );
+  return $form;
+  }
+
+/**
+ * Recursively build a query array from the form and its values
+ *
+ * In the current version, element names are supposed to be unique, even at
+ * different levels in the tree.
+ *
+ * @param array $form
+ * @param array $form_values
+ */
+function _qbf_extract_query($form, $form_values)
+  {
+  $name = $form['#parents'][0];
+  // Elements which are removed or display-only have no place in the query
+  if (array_key_exists('#qbf', $form) && array_key_exists('#level', $form['#qbf'])
+    && $form['#qbf']['#level'] >= QBF_LEVEL_OPTIONAL)
+    {
+    $ret = array($name => $form_values[$name]);
+    }
+  else
+    {
+    $ret = array();
+    }
+
+  // QBF level is not inherited, so this loop is outside the "if" above
+  foreach (element_children($form) as $childName)
+    {
+  	$ret += _qbf_extract_query($form[$childName], $form_values);
+    }
+
+  return $ret;
+  }
+
+/**
+ * Provide an optional automatic mapping mechanism for query building.
+ *
+ * This function takes a partly built query map $arQueryMap, and a defaults
+ * array to complete it in $arDefaults, and returns a fully built query array
+ * ready to be used for querying.
+ *
+ * @param array $arQuery
+ * @param array $arDefaults
+ * @return array
+ */
+function qbf_query_mapper($arQueryMap = array(), $arDefaults = array())
+  {
+  $ret = array();
+
+  foreach ($arQueryMap as $name => $value)
+    {
+    if (!is_array($value)) // accept NULL, empty strings...
+      {
+      $value = array();
+      }
+  	$item = $value;
+
+  	foreach ($arDefaults as $defaultKey => $defaultValue)
+  	  {
+  	  if (!array_key_exists($defaultKey, $item))
+  	    {
+  	    $item[$defaultKey] = is_null($defaultValue)
+  	      ? $name
+  	      : $defaultValue;
+  	    }
+  	  // else if is already in $item, so we don't touch it
+  	  }
+  	$ret[$name] = $item;
+    }
+  return $ret;
+  }
+
+/**
+ * Load a form_values array into a form used by QBF.
+ *
+ * This is typically useful when loading saved queries using qbf_load().
+ * For other cases, the mechanisms built within FAPI should be used instead.
+ *
+ * @see qbf_load()
+ *
+ * @param array $form
+ * @param array $form_values
+ * @return array The modified form
+ */
+function qbf_import_values($element, $form_values)
+  {
+  foreach (element_children($element) as $childName)
+    {
+  	if (!empty($form_values[$childName]))
+  	  {
+  	  $element[$childName]['#qbf']['#default_value'] = $form_values[$childName];
+  	  }
+  	$element[$childName] = qbf_import_values($element[$childName], $form_values);
+    }
+  return $element;
+  }
+
+/**
+ * Load a saved QBF query.
+ *
+ * @see qbf_import_values()
+ *
+ * @param int $qid
+ * @return array A form_values array usable by qbf_import_values
+ */
+function qbf_load($qid)
+  {
+  $sq = 'SELECT qq.qid, qq.uid, qq.query '
+      . 'FROM {%s} qq '
+      . 'WHERE qq.qid = %d ';
+  // db_rewrite_sql does not apply here until we add more advanced support for access control
+  $q = db_query($sq, QBF_TABLE_NAME, $qid);
+  $ret = db_fetch_object($q); // 0 or 1 row: we are querying on the primary key
+  if ($ret === FALSE)
+    {
+    $ret = NULL;
+    }
+  else
+    {
+    $ret->query = unserialize($ret->query);
+    }
+  return $ret;
+  }
+
+function qbf_submit($form_id, $form_values)
+  {
+  switch ($form_values['op'])
+    {
+    case t('Search'):
+      $ret = FALSE;
+      break;
+    case t('Save query'):
+      _qbf_save($form_id, $form_values);
+      drupal_set_message(t('Your query was saved as "@name".',
+        array('@name' => $form_values['save-name'])));
+      global $user;
+      $ret = "user/$user->uid/edit/job";;
+      break;
+    }
+  //dsm(array('QS' => $form_values));
+  return $ret;
+  }
+
+/**
+ * List queries owned by a given user.
+ *
+ * @param int $uid > 0
+ * @return array
+ */
+function qbf_get_queries_by_user($uid)
+  {
+  $sq = 'SELECT qq.qid, qq.uid, qq.name, qq.query '
+      . 'FROM {%s} qq '
+      . 'WHERE qq.uid = %d '
+      . 'ORDER BY qq.name ';
+  $q = db_query($sq, QBF_TABLE_NAME, $uid);
+  $ret = array();
+  while ($o = db_fetch_object($q))
+    {
+    $ret[$o->qid] = $o; // qid is the PK, so it is present and unique
+    }
+  return $ret;
+  }
+
+/**
+ * Save a query and return its qid.
+ *
+ * @global $user
+ * @param string $form_id
+ * @param array $form_values
+ * @return int
+ */
+function _qbf_save($form_id, $form_values)
+  {
+  global $user;
+
+  if ($user->uid == 0)
+    {
+    $warning = t('Attempt by anonymous user to save a QBF query. Should not happen.');
+    drupal_set_message($warning, 'error');
+    watchdog('qbf', $warning, WATCHDOG_WARNING);
+    $ret = 0;
+    }
+  else
+    {
+    $sq = 'INSERT INTO {%s} (qid, uid, name, query) '
+        . "VALUES           (%d,  %d,  '%s', '%s' ) ";
+    $ret = db_next_id('qbf_qid');
+    $q = db_query($sq, QBF_TABLE_NAME, $ret, $user->uid, $form_values['save-name'], serialize($form_values));
+    }
+
+  return $ret;
+  }