517 lines
No EOL
13 KiB
Text
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;
|
|
} |