$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; $newForm['#redirect'] = FALSE; $newForm['#after_build'][] = 'qbf_after_build'; // 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 array &$element * @return void */ function _qbf_transform_element($key, array $element) { // dsm(array('key' => $key, 'element' => $element)); // 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', ); // 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, ); // Property values causing element 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; }