drupal_module__qbf/qbf.module
Frederic G. Marand 888c553a01 - Can now save a QBF query, returning its qid
- Still limited to a single client module per site
2008-09-02 08:25:24 +00:00

517 lines
No EOL
13 KiB
Text

<?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;
}