qbf.module 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374
  1. <?php
  2. /**
  3. * Query By Form
  4. *
  5. * This module allows node modules to add a query by form tab for their node
  6. * types to the default search form
  7. *
  8. * @copyright 2008 Ouest Systemes Informatiques (OSInet)
  9. * @author Frederic G. MARAND
  10. * @version $Id: qbf.module,v 1.6 2008-08-28 09:53:54 marand Exp $
  11. * @license CeCILL 2.0
  12. * @package QBF
  13. */
  14. /**
  15. * Remove this element from the generated form
  16. */
  17. define('QBF_LEVEL_REMOVE', 0);
  18. /**
  19. * This element is only for display in the generated form: do not include it
  20. * in the query vector.
  21. */
  22. define('QBF_LEVEL_DISPLAY', 1);
  23. /**
  24. * Include this element in the generated form and in the query vector, but do
  25. * not mark it as required.
  26. */
  27. define('QBF_LEVEL_OPTIONAL', 2);
  28. /**
  29. * Include this element in the generated form and in the query vector, and
  30. * mark it as required.
  31. */
  32. define('QBF_LEVEL_REQUIRED', 3);
  33. /**
  34. * The main QBF path
  35. */
  36. define('QBF_PATH_QUERY', 'qbf/query');
  37. /**
  38. * Authorize use of QBF searches
  39. */
  40. define('QBF_PERM_QUERY', 'use QBF search functions');
  41. /**
  42. * Authorize QBF administration
  43. */
  44. define('QBF_PERM_ADMIN', 'administer QBF');
  45. /**
  46. * Transform a form array for QBF.
  47. *
  48. * This function obtains the form array using Forms API, and transforms it by
  49. * modifying widgets to other types where needed.
  50. *
  51. * Any additional parameter passed to the function is transmitted to the form
  52. * generating function.
  53. *
  54. * @param string $form_id
  55. * @return array
  56. */
  57. function qbf_transform_form($form_id)
  58. {
  59. $arArgs = func_get_args();
  60. //dsm(array('qtf' => $arArgs));
  61. // Fetch the basic form and rename it, passing it the caller's arguments
  62. $form = call_user_func_array('drupal_retrieve_form', $arArgs);
  63. $newFormId = "qbf_$form_id";
  64. // Only keep the children of the form and QBF properties on the form itself
  65. $elements = array();
  66. $newForm = array();
  67. $newForm['#qbf_source_form_id'] = $form_id;
  68. if (in_array('#qbf', element_properties($form)))
  69. {
  70. $newForm += $form['#qbf'];
  71. }
  72. foreach (element_children($form) as $key)
  73. {
  74. // dsm("Transforming $key, type " . $form[$key]['#type']);
  75. $newElement = _qbf_transform_element($key, $form[$key]);
  76. if (!is_null($newElement))
  77. {
  78. $newForm[$key] = $newElement;
  79. }
  80. }
  81. $newForm['#id'] = $newFormId;
  82. $newForm['#multistep'] = TRUE;
  83. $newForm['#redirect'] = FALSE;
  84. $newForm['#after_build'][] = 'qbf_after_build';
  85. // dsm($newForm);
  86. return $newForm;
  87. }
  88. /**
  89. * Transform a form element for QBF.
  90. *
  91. * QBF-specific properties are:
  92. * - #qbf : array of properties
  93. * - #level: only within #qbf @see QBF_* constants
  94. *
  95. * @param array &$element
  96. * @return void
  97. */
  98. function _qbf_transform_element($key, array $element)
  99. {
  100. // dsm(array('key' => $key, 'element' => $element));
  101. // Types without a default transformation are not transformed
  102. static $arDefaultTypeTransformations = array
  103. (
  104. 'button' => NULL,
  105. 'file' => NULL,
  106. // 'hidden' => NULL,
  107. 'markup' => NULL,
  108. 'password' => NULL,
  109. 'radio' => NULL,
  110. 'submit' => NULL,
  111. 'textarea' => 'textfield',
  112. // 'value' => 'value',
  113. );
  114. // Properties without a default transformation are not transformed
  115. static $arDefaultPropertyTransformations = array
  116. (
  117. // Standard properties
  118. '#action' => NULL,
  119. '#after_build' => NULL,
  120. '#base' => NULL,
  121. '#button_type' => NULL,
  122. '#built' => NULL,
  123. '#description' => NULL,
  124. '#method' => NULL,
  125. '#parents' => NULL,
  126. '#redirect' => NULL,
  127. '#ref' => NULL,
  128. '#required' => NULL,
  129. '#rows' => NULL,
  130. '#submit' => NULL,
  131. '#tree' => NULL,
  132. '#validate' => NULL,
  133. );
  134. // Property values causing element removal
  135. static $arKillerProperties = array
  136. (
  137. '#disabled' => TRUE,
  138. );
  139. // Transform type
  140. $sourceType = $element['#type'];
  141. // .. Default transformation
  142. $destType = array_key_exists($sourceType, $arDefaultTypeTransformations)
  143. ? $arDefaultTypeTransformations[$sourceType]
  144. : $sourceType;
  145. // .. Apply form-defined type override
  146. if (isset($element['#qbf']['#type']))
  147. {
  148. $destType = $element['#qbf']['#type'];
  149. }
  150. if (is_null($destType))
  151. {
  152. $ret = NULL;
  153. }
  154. else
  155. {
  156. $ret = $element;
  157. $ret['#type'] = $destType;
  158. if (!array_key_exists('#qbf', $element) || $element['#qbf']['#level'] == QBF_LEVEL_REMOVE)
  159. {
  160. $ret = NULL;
  161. }
  162. else
  163. {
  164. foreach (element_properties($element) as $propertyName)
  165. {
  166. // Apply killer properties first to avoid useless work
  167. if (array_key_exists($propertyName, $arKillerProperties)
  168. && ($element[$propertyName] = $arKillerProperties[$propertyName]))
  169. {
  170. $ret = NULL;
  171. break;
  172. }
  173. // Now transform or copy remaining properties
  174. if (array_key_exists($propertyName, $arDefaultPropertyTransformations))
  175. {
  176. $ret[$propertyName] = $arDefaultPropertyTransformations[$propertyName];
  177. }
  178. else
  179. {
  180. $ret[$propertyName] = $element[$propertyName];
  181. }
  182. // And apply form-defined property overrides
  183. if ($propertyName == '#qbf')
  184. {
  185. foreach ($element[$propertyName] as $overrideName => $overrideValue)
  186. {
  187. $ret[$overrideName] = $overrideValue;
  188. }
  189. }
  190. }
  191. // Recursively transform children
  192. foreach (element_children($element) as $childName)
  193. {
  194. $child = _qbf_transform_element($childName, $element[$childName]);
  195. if (is_null($child))
  196. {
  197. unset($ret[$childName]);
  198. }
  199. else
  200. {
  201. $ret[$childName] = $child;
  202. }
  203. }
  204. }
  205. }
  206. //dsm(array('key' => $key, 'transformed element' => $ret));
  207. return $ret;
  208. }
  209. /**
  210. * Implement hook_perm().
  211. *
  212. * @return array
  213. */
  214. function qbf_perm()
  215. {
  216. $ret = array
  217. (
  218. QBF_PERM_QUERY,
  219. );
  220. return $ret;
  221. }
  222. /**
  223. * Implement hook_forms().
  224. *
  225. * @return array
  226. */
  227. function qbf_forms()
  228. {
  229. $hookName = 'qbf_register';
  230. foreach (module_implements($hookName) as $module)
  231. {
  232. foreach (module_invoke($module, $hookName) as $formName)
  233. {
  234. $forms["qbf_$formName"] = array
  235. (
  236. 'callback' => 'qbf_transform_form',
  237. 'callback arguments' => array($formName),
  238. );
  239. }
  240. }
  241. return $forms;
  242. }
  243. /**
  244. * Insert the query results at the bottom of the query form.
  245. *
  246. * @param array $form
  247. * @param array $form_values
  248. * @return array
  249. */
  250. function qbf_after_build($form, $form_values)
  251. {
  252. if (empty($form['#post']))
  253. {
  254. return $form;
  255. }
  256. // If #post is not emtpy, we are indeed querying
  257. $arQuery = _qbf_extract_query($form, $form_values);
  258. /* This function is called at the end of the form building process, which
  259. * means that child properties of #qbf have already been upgraded to element
  260. * properties. So we look for $form['#callback'] and not
  261. * $form['#qbf']['#callback']
  262. */
  263. if (isset($form['#callback']) && function_exists($function = $form['#callback']))
  264. {
  265. $results = $function($arQuery);
  266. }
  267. else
  268. {
  269. drupal_set_message(t('QBF: incorrect callback function for search'), 'error');
  270. }
  271. $form['qbf_query_results'] = array
  272. (
  273. '#type' => 'markup',
  274. '#value' => $results,
  275. '#weight' => 10,
  276. );
  277. return $form;
  278. }
  279. /**
  280. * Recursively build a query array from the form and its values
  281. *
  282. * In the current version, element names are supposed to be unique, even at
  283. * different levels in the tree.
  284. *
  285. * @param array $form
  286. * @param array $form_values
  287. */
  288. function _qbf_extract_query($form, $form_values)
  289. {
  290. $name = $form['#parents'][0];
  291. // Elements which are removed or display-only have no place in the query
  292. if (array_key_exists('#qbf', $form) && array_key_exists('#level', $form['#qbf'])
  293. && $form['#qbf']['#level'] >= QBF_LEVEL_OPTIONAL)
  294. {
  295. $ret = array($name => $form_values[$name]);
  296. }
  297. else
  298. {
  299. $ret = array();
  300. }
  301. // QBF level is not inherited, so this loop is outside the "if" above
  302. foreach (element_children($form) as $childName)
  303. {
  304. $ret += _qbf_extract_query($form[$childName], $form_values);
  305. }
  306. return $ret;
  307. }
  308. /**
  309. * Provide an optional automatic mapping mechanism for query building.
  310. *
  311. * This function takes a partly built query map $arQueryMap, and a defaults
  312. * array to complete it in $arDefaults, and returns a fully built query array
  313. * ready to be used for querying.
  314. *
  315. * @param array $arQuery
  316. * @param array $arDefaults
  317. * @return array
  318. */
  319. function qbf_query_mapper($arQueryMap = array(), $arDefaults = array())
  320. {
  321. $ret = array();
  322. foreach ($arQueryMap as $name => $value)
  323. {
  324. if (!is_array($value)) // accept NULL, empty strings...
  325. {
  326. $value = array();
  327. }
  328. $item = $value;
  329. foreach ($arDefaults as $defaultKey => $defaultValue)
  330. {
  331. if (!array_key_exists($defaultKey, $item))
  332. {
  333. $item[$defaultKey] = is_null($defaultValue)
  334. ? $name
  335. : $defaultValue;
  336. }
  337. // else if is already in $item, so we don't touch it
  338. }
  339. $ret[$name] = $item;
  340. }
  341. return $ret;
  342. }