qbf.module 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449
  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.7 2008-08-28 17:06:04 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. $newForm['#submit'] = array('qbf_submit' => array());
  86. // dsm($newForm);
  87. return $newForm;
  88. }
  89. /**
  90. * Transform a form element for QBF.
  91. *
  92. * QBF-specific properties are:
  93. * - #qbf : array of properties
  94. * - #level: only within #qbf
  95. *
  96. * @see QBF_* constants
  97. *
  98. * @param string $key
  99. * @param array $element
  100. * @return void
  101. */
  102. function _qbf_transform_element($key, $element)
  103. {
  104. // dsm(array('key' => $key, 'element' => $element));
  105. /**
  106. * List default type transformations applied to widget by FAPI.
  107. *
  108. * Types without a default transformation are not transformed
  109. */
  110. static $arDefaultTypeTransformations = array
  111. (
  112. 'button' => NULL,
  113. 'file' => NULL,
  114. // 'hidden' => NULL,
  115. 'markup' => NULL,
  116. 'password' => NULL,
  117. 'radio' => NULL,
  118. 'submit' => NULL,
  119. 'textarea' => 'textfield',
  120. // 'value' => 'value',
  121. );
  122. /**
  123. * List default property transformations applied to widget by FAPI property.
  124. *
  125. * Properties without a default transformation are not transformed
  126. */
  127. static $arDefaultPropertyTransformations = array
  128. (
  129. // Standard properties
  130. '#action' => NULL,
  131. '#after_build' => NULL,
  132. '#base' => NULL,
  133. '#button_type' => NULL,
  134. '#built' => NULL,
  135. '#description' => NULL,
  136. '#method' => NULL,
  137. '#parents' => NULL,
  138. '#redirect' => NULL,
  139. '#ref' => NULL,
  140. '#required' => NULL,
  141. '#rows' => NULL,
  142. '#submit' => NULL,
  143. '#tree' => NULL,
  144. '#validate' => NULL,
  145. );
  146. /**
  147. * List properties causing causing element removal.
  148. *
  149. * The key is the property name, the value is the one causing removal.
  150. */
  151. static $arKillerProperties = array
  152. (
  153. '#disabled' => TRUE,
  154. );
  155. // Transform type
  156. $sourceType = $element['#type'];
  157. // .. Default transformation
  158. $destType = array_key_exists($sourceType, $arDefaultTypeTransformations)
  159. ? $arDefaultTypeTransformations[$sourceType]
  160. : $sourceType;
  161. // .. Apply form-defined type override
  162. if (isset($element['#qbf']['#type']))
  163. {
  164. $destType = $element['#qbf']['#type'];
  165. }
  166. if (is_null($destType))
  167. {
  168. $ret = NULL;
  169. }
  170. else
  171. {
  172. $ret = $element;
  173. $ret['#type'] = $destType;
  174. if (!array_key_exists('#qbf', $element) || $element['#qbf']['#level'] == QBF_LEVEL_REMOVE)
  175. {
  176. $ret = NULL;
  177. }
  178. else
  179. {
  180. foreach (element_properties($element) as $propertyName)
  181. {
  182. // Apply killer properties first to avoid useless work
  183. if (array_key_exists($propertyName, $arKillerProperties)
  184. && ($element[$propertyName] = $arKillerProperties[$propertyName]))
  185. {
  186. $ret = NULL;
  187. break;
  188. }
  189. // Now transform or copy remaining properties
  190. if (array_key_exists($propertyName, $arDefaultPropertyTransformations))
  191. {
  192. $ret[$propertyName] = $arDefaultPropertyTransformations[$propertyName];
  193. }
  194. else
  195. {
  196. $ret[$propertyName] = $element[$propertyName];
  197. }
  198. // And apply form-defined property overrides
  199. if ($propertyName == '#qbf')
  200. {
  201. foreach ($element[$propertyName] as $overrideName => $overrideValue)
  202. {
  203. $ret[$overrideName] = $overrideValue;
  204. }
  205. }
  206. }
  207. // Recursively transform children
  208. foreach (element_children($element) as $childName)
  209. {
  210. $child = _qbf_transform_element($childName, $element[$childName]);
  211. if (is_null($child))
  212. {
  213. unset($ret[$childName]);
  214. }
  215. else
  216. {
  217. $ret[$childName] = $child;
  218. }
  219. }
  220. }
  221. }
  222. //dsm(array('key' => $key, 'transformed element' => $ret));
  223. return $ret;
  224. }
  225. /**
  226. * Implement hook_perm().
  227. *
  228. * @return array
  229. */
  230. function qbf_perm()
  231. {
  232. $ret = array
  233. (
  234. QBF_PERM_QUERY,
  235. );
  236. return $ret;
  237. }
  238. /**
  239. * Implement hook_forms().
  240. *
  241. * @return array
  242. */
  243. function qbf_forms()
  244. {
  245. $hookName = 'qbf_register';
  246. foreach (module_implements($hookName) as $module)
  247. {
  248. foreach (module_invoke($module, $hookName) as $formName)
  249. {
  250. $forms["qbf_$formName"] = array
  251. (
  252. 'callback' => 'qbf_transform_form',
  253. 'callback arguments' => array($formName),
  254. );
  255. }
  256. }
  257. return $forms;
  258. }
  259. /**
  260. * Insert the query results at the bottom of the query form.
  261. *
  262. * @param array $form
  263. * @param array $form_values
  264. * @return array
  265. */
  266. function qbf_after_build($form, $form_values)
  267. {
  268. if (empty($form['#post']))
  269. {
  270. return $form;
  271. }
  272. // If #post is not emtpy, we are indeed querying
  273. $arQuery = _qbf_extract_query($form, $form_values);
  274. /* This function is called at the end of the form building process, which
  275. * means that child properties of #qbf have already been upgraded to element
  276. * properties. So we look for $form['#callback'] and not
  277. * $form['#qbf']['#callback']
  278. */
  279. if (isset($form['#callback']) && function_exists($function = $form['#callback']))
  280. {
  281. $results = $function($arQuery);
  282. }
  283. else
  284. {
  285. drupal_set_message(t('QBF: incorrect callback function for search'), 'error');
  286. }
  287. $form['qbf_query_results'] = array
  288. (
  289. '#type' => 'markup',
  290. '#value' => $results,
  291. '#weight' => 10,
  292. );
  293. return $form;
  294. }
  295. /**
  296. * Recursively build a query array from the form and its values
  297. *
  298. * In the current version, element names are supposed to be unique, even at
  299. * different levels in the tree.
  300. *
  301. * @param array $form
  302. * @param array $form_values
  303. */
  304. function _qbf_extract_query($form, $form_values)
  305. {
  306. $name = $form['#parents'][0];
  307. // Elements which are removed or display-only have no place in the query
  308. if (array_key_exists('#qbf', $form) && array_key_exists('#level', $form['#qbf'])
  309. && $form['#qbf']['#level'] >= QBF_LEVEL_OPTIONAL)
  310. {
  311. $ret = array($name => $form_values[$name]);
  312. }
  313. else
  314. {
  315. $ret = array();
  316. }
  317. // QBF level is not inherited, so this loop is outside the "if" above
  318. foreach (element_children($form) as $childName)
  319. {
  320. $ret += _qbf_extract_query($form[$childName], $form_values);
  321. }
  322. return $ret;
  323. }
  324. /**
  325. * Provide an optional automatic mapping mechanism for query building.
  326. *
  327. * This function takes a partly built query map $arQueryMap, and a defaults
  328. * array to complete it in $arDefaults, and returns a fully built query array
  329. * ready to be used for querying.
  330. *
  331. * @param array $arQuery
  332. * @param array $arDefaults
  333. * @return array
  334. */
  335. function qbf_query_mapper($arQueryMap = array(), $arDefaults = array())
  336. {
  337. $ret = array();
  338. foreach ($arQueryMap as $name => $value)
  339. {
  340. if (!is_array($value)) // accept NULL, empty strings...
  341. {
  342. $value = array();
  343. }
  344. $item = $value;
  345. foreach ($arDefaults as $defaultKey => $defaultValue)
  346. {
  347. if (!array_key_exists($defaultKey, $item))
  348. {
  349. $item[$defaultKey] = is_null($defaultValue)
  350. ? $name
  351. : $defaultValue;
  352. }
  353. // else if is already in $item, so we don't touch it
  354. }
  355. $ret[$name] = $item;
  356. }
  357. return $ret;
  358. }
  359. /**
  360. * Load a form_values array into a form used by QBF.
  361. *
  362. * This is typically useful when loading saved queries using qbf_load().
  363. * For other cases, the mechanisms built within FAPI should be used instead.
  364. *
  365. * @see qbf_load()
  366. *
  367. * @param array $form
  368. * @param array $form_values
  369. * @return array The modified form
  370. */
  371. function qbf_import_values($element, $form_values)
  372. {
  373. foreach (element_children($element) as $childName)
  374. {
  375. if (!empty($form_values[$childName]))
  376. {
  377. $element[$childName]['#qbf']['#default_value'] = $form_values[$childName];
  378. }
  379. $element[$childName] = qbf_import_values($element[$childName], $form_values);
  380. }
  381. return $element;
  382. }
  383. /**
  384. * Load a saved QBF query.
  385. *
  386. * @see qbf_import_values()
  387. *
  388. * @param int $qid
  389. * @return array A form_values array usable by qbf_import_values
  390. */
  391. function qbf_load($qid)
  392. {
  393. $sq = 'SELECT qq.qid, qq.uid, qq.query '
  394. . 'FROM {qbf_queries} qq '
  395. . 'WHERE qq.qid = %d ';
  396. // db_rewrite_sql does not apply here until we add more advanced support for access control
  397. $q = db_query($sq, $qid);
  398. $ret = db_fetch_object($q); // 0 or 1 row: we are querying on the primary key
  399. if ($ret === FALSE)
  400. {
  401. $ret = NULL;
  402. }
  403. else
  404. {
  405. $ret->query = unserialize($ret->query);
  406. }
  407. return $ret;
  408. }
  409. function qbf_submit($form_id, $form_values)
  410. {
  411. dsm(func_num_args());
  412. dsm(array('QS' => $form_values));
  413. return url('goo');
  414. }