12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316 |
- <?php
- /**
- * @file
- * 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-2009 Ouest Systemes Informatiques (OSInet)
- * @author Frederic G. MARAND
- * @license Licensed under the CeCILL 2.0 and the General Public Licence version 2 or later
- * @package QBF
- */
- // $Id: qbf.module,v 1.9.4.14 2009-03-24 10:55:27 marand Exp $
- /**
- * Saved error reporting level.
- *
- * QBF module is supposed to pass parsing at E_ALL|E_STRICT, but other modules
- * may not be so strict, so we save the level at the start of the module and
- * restore it at the end of the module.
- */
- global $_qbf_er;
- $_qbf_er = error_reporting(E_ALL | E_STRICT);
- /**
- * 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.
- *
- * It MUST be a single component path, without a "/", otherwise qbf_menu() will
- * need to be changed.
- *
- * @ingroup paths
- * @see qbf_menu()
- */
- define('QBF_PATH_MAIN', 'qbf');
- /**
- * The QBF autocomplete path for search fields
- * @ingroup paths
- */
- define('QBF_PATH_AC', 'qbf/ac');
- /**
- * The path to the QBF settings page
- */
- define('QBF_PATH_SETTINGS', 'admin/settings/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');
- /**
- * Maximum number of queries a user may save
- *
- * @ingroup vars
- */
- define('QBF_VAR_MAX_QUERIES', 'qbf_max_queries');
- /**
- * Notify owner about saved query deletions, variable name.
- *
- * @ingroup vars
- */
- define('QBF_VAR_NOTIFY_DELETE', 'qbf_notify_delete');
- /**
- * Name of the profile category under which the list of saved queries will be
- * displayed.
- *
- * @ingroup vars
- *
- * @see qbf_admin_settings(), qbf_profile_alter()
- */
- define('QBF_VAR_PROFILE_CATEGORY', 'qbf_profile_category');
- /**
- * Querying mode: contains, starts, or equals
- *
- * @ingroup vars
- */
- define('QBF_VAR_QUERY_MODE', 'qbf_query_mode');
- /**
- * Default value for the max number of saved queries per user
- *
- * @ingroup vars
- *
- * See QBF_VAR_MAX_QUERIES
- */
- define('QBF_DEF_MAX_QUERIES', 5);
- /**
- * Notify owner about saved query deletions, default value.
- *
- * @ingroup vars
- */
- define('QBF_DEF_NOTIFY_DELETE', FALSE);
- /**
- * Default value for the profile category
- *
- * @ingroup vars
- *
- * See QBF_VAR_PROFILE_CATEGORY
- */
- define('QBF_DEF_PROFILE_CATEGORY', 'Saved queries');
- /**
- * Return #options properties.
- *
- * It has not been implemented as an abstract class with a values array, from
- * which all descendant class could reuse the getter method
- * get_options(), although this would be much cleaner, because the current
- * translation interface (potx.module) relies on t(), and it is impossible for
- * static properties to be initialized to values which are the result of a
- * function, like:
- * <code>protected $arOptions = array('foo' => t('foo'));</code>
- * which would be necessary in this case. The alternative solution of only
- * declaring the array keys and returning their translated version as a value
- * with t($key) in get_options() would also fail because the translation
- * template extractor would not be able to generate the declaration from a
- * t() call with variable content.
- *
- * @link http://drupal.org/project/potx
- */
- Interface Qbf_Optioned_Interface
- {
- static function get_options();
- }
- /**
- * Querying modes for QBF
- */
- class Qbf_Query_Mode implements Qbf_Optioned_Interface
- {
- /**
- * Query mode: contains
- */
- const CONTAINS = 'contains';
- /**
- * Query mode: starts with
- */
- const STARTS = 'starts';
- /**
- * Query mode: equals
- */
- const EQUALS = 'equals';
- /**
- * Returns the list of querying modes
- *
- * @ingroup classmethods
- * @return array
- */
- static function get_options() {
- return array
- (
- self::CONTAINS => t('Contains'),
- self::STARTS => t('Starts with'),
- self::EQUALS => t('Equals'),
- );
- }
- }
- /**
- * A class wrapper for saved QBF queries
- */
- class Qbf_Query
- {
- public $qid;
- public $uid;
- public $name;
- public $type;
- public $query;
- public $created;
- public $updated;
- /**
- * Constructor
- *
- * @param string $name
- * @param array $ar_values
- * @return void
- */
- public function __construct($type, $name = NULL, $ar_values = NULL)
- {
- global $user;
- $this->qid = 0; // will be autoset by the DB serial
- $this->uid = $user->uid;
- $this->type = $type;
- $this->name = $name;
- if (!empty($ar_values))
- {
- $this->query = serialize($ar_values);
- }
- $this->created = $this->updated = time();
- }
- /**
- * Save a named query to the DB, erasing previous homonym queries is any exists.
- *
- * @return int
- */
- public function save()
- {
- // Avoid duplicates
- if (!empty($this->name))
- {
- $sq = "DELETE FROM {%s} WHERE name = '%s' AND uid = '%d' ";
- db_query($sq, QBF_TABLE_NAME, $this->name, $this->uid);
- // $n = db_affected_rows(); // Know how many homonym queries we deleted
- }
- $ret = drupal_write_record(QBF_TABLE_NAME, $this); // no update param: we just deleted the previous version
- if ($ret) // has to be SAVED_NEW, by construction
- {
- $ret = $this->qid; // from serial
- }
- return $ret;
- }
- }
- /**
- * 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.
- *
- * @ingroup forms
- * @param array $form
- * @param array $form_values
- */
- function _qbf_extract_query($element_id, $form, $form_values)
- {
- // Elements which are unnamed (form), removed, or display-only have no place in the query
- if (!empty($element_id) && array_key_exists('#qbf', $form) && array_key_exists('#level', $form['#qbf'])
- && $form['#qbf']['#level'] >= QBF_LEVEL_OPTIONAL)
- {
- $ret = array($element_id => $form_values[$element_id]);
- }
- else
- {
- $ret = array();
- }
- // QBF level is not inherited, so this loop is outside the "if" above
- foreach (element_children($form) as $child_name)
- {
- $ret += _qbf_extract_query($child_name, $form[$child_name], $form_values);
- }
- return $ret;
- }
- /**
- * Submit handler for qbf_form, Perform search button.
- *
- * @param array $form
- * @param array $form_state
- * @return void
- */
- function _qbf_form_perform_submit($form, &$form_state)
- {
- $callback = $form_state['values']['qbf_query'];
- if (function_exists(($callback)))
- {
- $ar_query = _qbf_extract_query(NULL, $form, $form_state['values']);
- $form_state['qbf_results'] = $callback($ar_query);
- }
- $form_state['rebuild'] = TRUE;
- }
- /**
- * Validate handler for qbf_form, Perform search button.
- *
- * @param array $form
- * @param array $form_state
- * Can be passed by reference if changes are needed
- * @return void
- */
- function _qbf_form_perform_validate($form, $form_state)
- {
- // @todo validate searches: checkboxes sets needs at least one value checked, otherwise there won't be any result
- }
- /**
- * Submit handler for qbf_form, Save search button.
- *
- * @param array $form
- * @param array $form_state
- * Can be passed by reference if changes are needed
- * @return integer
- * The id of the saved query.
- */
- function _qbf_form_save_submit($form, $form_state)
- {
- $qid = _qbf_save($form_state['values']['form_id'], $form_state);
- drupal_set_message(t('Your query was saved as "@name".',
- array('@name' => $form_state['values']['qbf_save_name'])));
- global $user;
- $form_state['redirect'] = "user/$user->uid/edit/qbf";
- return $qid;
- }
- /**
- * Validate handler for qbf_form, Save search button.
- *
- * @param array $form
- * @param array $form_state
- * @return void
- */
- //function _qbf_form_save_validate($form, &$form_state)
- // {
- // // @todo validate saves. Check whether any validation is necessary.
- // }
- /**
- * Return the human-readable for a query type.
- *
- * @param string $query_type
- * @return string
- */
- function _qbf_get_name_from_type($query_type)
- {
- static $labels = array();
- if (empty($labels) || empty($labels[$query_type]))
- {
- $ar_forms = qbf_forms();
- foreach ($ar_forms as /* $qbf_form_id => */ $form_info)
- {
- if ($query_type == $form_info['callback arguments'][0]['form'])
- {
- $labels[$query_type] = $form_info['callback arguments'][0]['label'];
- break;
- }
- }
- }
- return $labels[$query_type];
- }
- /**
- * Delete a query by qid
- *
- * In the qbf/<qid>/delete case, $query has been tested for validity and access
- * in qbf_query_load(), so it is safe and accessible.
- *
- * Outside this context, the function can also be invoken with just a qid, and
- * the same check via qbf_query_load() will be performed.
- *
- * @param mixed $query
- * int or object
- */
- function _qbf_query_delete($query)
- {
- global $user;
- if (is_int($query))
- {
- $query = qbf_query_load($query);
- }
- if ($query) // access already checked in explicit or implicit qbf_query_load
- {
- $qid = $query->qid;
- $sq = 'DELETE FROM %s WHERE qid = %d ';
- db_query($sq, QBF_TABLE_NAME, $qid);
- $message = t('Query @id "@name" has been deleted.', array
- (
- '@id' => $qid,
- '@name' => $query->name,
- ));
- drupal_set_message($message, 'status');
- $link = l($qid, QBF_PATH_MAIN .'/'. $qid .'/delete');
- $notify = variable_get(QBF_VAR_NOTIFY_DELETE, QBF_DEF_NOTIFY_DELETE);
- watchdog('qbf', $message, NULL, WATCHDOG_NOTICE, $link);
- // access check: we only send the message to the query owner, so access is
- // granted without an additional check
- if ($notify /* && $query->uid != $user->uid */)
- {
- $owner = user_load(array('uid' => $query->uid));
- $account = user_load(array('uid' => $query->uid));
- $language = user_preferred_language($account);
- $params = array
- (
- 'query' => $query,
- 'owner' => $owner, // unused by default, but can be used in a hook_mail_alter() implementation
- 'deletor' => $user,
- 'language' => $language,
- );
- /* $ret = */ drupal_mail('qbf', __FUNCTION__, $user->mail, $language, $params, $user->mail);
- drupal_set_message(t('User !link has been informed', array
- (
- '!link' => l($account->name, 'user/'. $query->uid),
- )));
- // dsm(array("QQD, ret" => $ret));
- }
- }
- else {
- $message = t('Failed attempt to delete query @qid. Administrator has been alerted.', array
- (
- '@qid' => $qid,
- ));
- drupal_set_message($message, 'error');
- watchdog('qbf', $message, NULL, WATCHDOG_ERROR, $link);
- }
- drupal_goto();
- }
- /**
- * Main query page.
- *
- * This returns the query form if a valid query id or query type is specified,
- * or the list of available query types if several exisit, or jumps to the single
- * available query type if only one exists.
- *
- * @param object $query
- * Valid query, loaded by qbf_query_load().
- * @return string
- */
- function _qbf_query_form($query = NULL )
- {
- if (!empty($query))
- {
- $qbf_form_id = 'qbf_' . $query->type;
- $ret = drupal_get_form($qbf_form_id, $query);
- }
- else
- {
- $ar_forms = qbf_forms();
- $arRet = array();
- foreach ($ar_forms as $qbf_form_id => $form_info)
- {
- $form_id = $form_info['callback arguments'][0]['form'];
- $arRet[QBF_PATH_MAIN . "/$form_id"] = l($form_info['callback arguments'][0]['label'],
- QBF_PATH_MAIN . "/$form_id");
- }
- // If there is only one form type, no need to ask the user.
- if (count($arRet) == 1)
- {
- reset($arRet);
- drupal_goto(key($arRet));
- }
- else
- {
- $ret = theme('item_list', $arRet, t('Choose a query type'));
- }
- }
- return $ret;
- }
- /**
- * Save a query and return its qid.
- *
- * This is not a hook_save() implementation, hence the "_".
- *
- * @ingroup forms
- *
- * @param $form_id string
- * @param $form_state array
- * @return int
- */
- function _qbf_save($form_id, $form_state)
- {
- if (user_is_anonymous())
- {
- $warning = t('Attempt by anonymous user to save a QBF query. Should not happen.');
- drupal_set_message($warning, 'error');
- watchdog('qbf', $warning, NULL, WATCHDOG_WARNING);
- $ret = 0;
- }
- else
- {
- // @FIXME check whether form_state is now needed. It wasn't in QBF for D5
- $form = drupal_retrieve_form($form_id, $form_state);
- // dsm($form, "retrieve");
- drupal_prepare_form($form_id, $form, $form_state);
- // dsm($form, "prepare");
- $name = $form_state['values']['qbf_save_name'];
- $type = $form_state['values']['qbf_save_type'];
- // dsm($form_state);
- $form_values = _qbf_extract_query(NULL, $form, $form_state['values']);
- // dsm($form_values);
- $ar_values = array();
- foreach ($form_values as $key => $value)
- {
- if (empty($value))
- {
- continue;
- }
- $ar_values[$key] = $value;
- }
- $query = new Qbf_Query($type, $name, $ar_values);
- $ret = $query->save();
- }
- return $ret;
- }
- /**
- * Transform a form element for QBF.
- *
- * QBF-specific properties are:
- * - #qbf : array of properties
- * - #level: only within #qbf
- *
- * See QBF_* constants
- *
- * @ingroup forms
- *
- * @param string $key
- * @param array $element
- * @param array $form_state
- * @param object $query
- * @return array
- */
- function _qbf_transform($key, $element, $form_state, $query)
- {
- // dsm(array('key' => $key, 'element' => $element));
- /**
- * List default type transformations applied to widget by FAPI.
- * Types without a default transformation are not transformed
- */
- static $ar_default_type_transformations = array
- (
- 'button' => NULL, // no content
- 'file' => NULL, // non-querable (yet ?)
- 'image_button' => NULL, // new in D6
- 'markup' => NULL, // no content
- 'password' => NULL, // forbidden
- 'radio' => NULL, // single radio is useless, unlike a set of them
- 'submit' => NULL, // no content
- 'textarea' => 'textfield', // reduce text for searches
- // Don't transform these:
- // 'checkbox' => NULL,
- // 'checkboxes' => NULL,
- // 'date' => NULL,
- // 'fieldset' => NULL, // useful visually
- // 'form' => NULL, // removing it would delete the whole shebang
- // 'hidden' => NULL, // non-querable visually, but may be useful
- // 'item' => NULL,
- // 'radios' => NULL,
- // 'select' => NULL,
- // 'textfield' => NULL,
- // 'value' => 'value',
- // 'weight' => NULL,
- );
- /**
- * List default property transformations applied to widget by FAPI property.
- *
- * Properties without a default transformation are not transformed
- */
- static $ar_default_property_transformations = array
- (
- // Standard properties
- '#action' => NULL,
- '#after_build' => NULL,
- // '#base' => NULL, // gone in D6
- '#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 $ar_killer_properties = array
- (
- '#disabled' => TRUE,
- );
- // Transform type
- $source_type = $element['#type'];
- // .. Default transformation
- $dest_type = array_key_exists($source_type, $ar_default_type_transformations)
- ? $ar_default_type_transformations[$source_type]
- : $source_type;
- // .. Apply form-defined type override
- if (isset($element['#qbf']['#type']))
- {
- $dest_type = $element['#qbf']['#type'];
- }
- if (is_null($dest_type))
- {
- $ret = NULL;
- }
- else
- {
- $ret = $element;
- $ret['#type'] = $dest_type;
- if (!array_key_exists('#qbf', $element) || $element['#qbf']['#level'] == QBF_LEVEL_REMOVE)
- {
- $ret = NULL;
- }
- else
- {
- foreach (element_properties($element) as $property_name)
- {
- // Apply killer properties first to avoid useless work
- if (array_key_exists($property_name, $ar_killer_properties)
- && ($element[$property_name] = $ar_killer_properties[$property_name]))
- {
- $ret = NULL;
- break;
- }
- // Now transform or copy remaining properties
- if (array_key_exists($property_name, $ar_default_property_transformations))
- {
- $ret[$property_name] = $ar_default_property_transformations[$property_name];
- }
- else
- {
- $ret[$property_name] = $element[$property_name];
- }
- // And apply form-defined property overrides
- if ($property_name == '#qbf')
- {
- foreach ($element[$property_name] as $override_name => $override_value)
- {
- $ret[$override_name] = $override_value;
- }
- }
- }
- if (isset($form_state['values'][$key]))
- {
- $ret['#default_value'] = $form_state['values'][$key];
- }
- elseif (isset($query->query[$key]))
- {
- $ret['#default_value'] = $query->query[$key];
- }
- // Recursively transform children
- foreach (element_children($element) as $child_name)
- {
- $child = _qbf_transform($child_name, $element[$child_name], $form_state, $query);
- if (is_null($child))
- {
- unset($ret[$child_name]);
- }
- else
- {
- $ret[$child_name] = $child;
- }
- }
- }
- }
- //dsm(array('key' => $key, 'transformed element' => $ret));
- return $ret;
- }
- /**
- * Implement the former hook_settings().
- *
- * @return array
- */
- function qbf_admin_settings()
- {
- $form = array();
- $form['queries'] = array
- (
- '#type' => 'fieldset',
- '#title' => t('Queries'),
- '#collapsible' => TRUE,
- '#collapsed' => TRUE,
- );
- $form['queries'][QBF_VAR_MAX_QUERIES] = array
- (
- '#type' => 'select',
- '#title' => t('Maximum number of saved queries'),
- '#description' => t('The maximum number of queries a user allowed to perform queries may save.'),
- '#default_value' => variable_get(QBF_VAR_MAX_QUERIES, QBF_DEF_MAX_QUERIES),
- '#options' => array_combine($iota = range(1, 99), $iota),
- );
- $ar_options = Qbf_Query_Mode::get_options();
- $form['queries'][QBF_VAR_QUERY_MODE] = array
- (
- '#type' => 'radios',
- '#title' => t('Query mode'),
- '#description' => t('This defines the way QBF will search string fields. "Contains" returns more results and is slowest, "Equals" returns less results and is fastest, while "Starts with" balances results and speed. Even when using "Equals" mode, case is ignored.'),
- '#options' => $ar_options,
- '#default_value' => variable_get(QBF_VAR_QUERY_MODE, Qbf_Query_Mode::CONTAINS),
- );
- $form['queries'][QBF_VAR_PROFILE_CATEGORY] = array
- (
- '#type' => 'textfield',
- '#title' => t('Name of profile category'),
- '#description' => t('Choose a title for the section of the user profiles where the list of search queries will be displayed. It may match an existing profile category.'),
- '#default_value' => variable_get(QBF_VAR_PROFILE_CATEGORY, QBF_DEF_PROFILE_CATEGORY),
- );
- $form['notifications'] = array
- (
- '#type' => 'fieldset',
- '#title' => t('Notifications'),
- '#collapsible' => TRUE,
- '#collapsed' => TRUE,
- );
- $form['notifications'][QBF_VAR_NOTIFY_DELETE] = array
- (
- '#type' => 'checkbox',
- '#default_value' => variable_get(QBF_VAR_NOTIFY_DELETE, QBF_DEF_NOTIFY_DELETE),
- '#title' => t('Notify users when one of their saved searches has been deleted'),
- );
- return system_settings_form($form);
- }
- /**
- * The QBF form builder.
- *
- * @param array $form_state
- * @param array $query_info
- * The query structure array
- * @param string $qbf_form_id
- * The name of the QBF form
- * @param string $query
- * The saved query.
- */
- function qbf_form(&$form_state, $query_info, $qbf_form_id, $query = NULL)
- {
- $form_id = $query_info['form'];
- // Fetch the basic form and rename it, passing it the previous values
- $node = new stdClass();
- $form = $form_id($node, $form_state);
- $qbf_form = array();
- $qbf_form['#id'] = $qbf_form_id;
- $qbf_form['#qbf_source_form_id'] = $form_id;
- // On the form element itself, only keep the QBF properties, the handlers, and the children
- foreach (element_properties($form) as $key)
- {
- if (in_array($key, array('#qbf', '#validate', '#submit')))
- {
- $qbf_form[$key] = $form[$key];
- }
- }
- // Transform the children tree
- foreach (element_children($form) as $key)
- {
- $new_element = _qbf_transform($key, $form[$key], $form_state, $query);
- if (!is_null($new_element))
- {
- $qbf_form[$key] = $new_element;
- }
- }
- $qbf_form['qbf'] = array
- (
- '#type' => 'fieldset',
- '#title' => t('Query'),
- );
- if (isset($form_state['values']) && !empty($form_state['values']))
- {
- if (isset($form_state['qbf_results']))
- {
- $qbf_form['qbf']['qbf_results'] = array
- (
- '#type' => 'markup',
- '#prefix' => '<p>',
- '#value' => $form_state['qbf_results'],
- '#suffix' => '</p>',
- );
- }
- }
- /**
- * Offer query save feature only to logged-in users with query permission.
- * We know they have QBF_PERM_QUERY since it is needed to access this form,
- * from qbf_menu().
- */
- if (user_is_anonymous())
- {
- $qbf_form['qbf']['save'] = array
- (
- '#type' => 'markup',
- '#value' => t('<p>You need to be logged in order to save a query.</p>'),
- );
- }
- else
- {
- global $user;
- $ar_queries = qbf_get_queries_by_user($user->uid);
- $max_queries = variable_get(QBF_VAR_MAX_QUERIES, QBF_DEF_MAX_QUERIES);
- if (count($ar_queries) >= $max_queries)
- {
- $qbf_form['qbf']['save'] = array
- (
- '#type' => 'markup',
- '#value' => t('<p>You already have reached the maximum number of saved queries (@max). To save this query, first <a href="!queries">remove</a> at least one saved query.</p>',
- array
- (
- '@max' => $max_queries,
- '!queries' => url("user/$user->uid/edit/qbf"),
- )
- ),
- );
- }
- else
- {
- $qbf_form['qbf']['qbf_save_type'] = array
- (
- '#type' => 'hidden',
- '#value' => $query_info['form'],
- );
- $qbf_form['qbf']['qbf_save_name'] = array
- (
- '#title' => t('Name of query in your save list'),
- '#type' => 'textfield',
- '#required' => TRUE,
- '#default_value' => empty($query->name)
- ? t('@label - @time', array('@label' => $query_info['label'], '@time' => format_date(time(), 'large')))
- : $query->name,
- );
- $qbf_form['qbf']['qbf_save'] = array
- (
- '#submit' => array('_qbf_form_save_submit'),
- '#validate' => array('_qbf_form_save_validate'),
- '#type' => 'submit',
- '#value' => t('Save query'),
- '#weight' => 2,
- );
- }
- }
- $qbf_form['qbf']['qbf_query'] = array
- (
- '#type' => 'hidden',
- '#value' => $query_info['callback'],
- );
- $qbf_form['qbf']['qbf_perform'] = array
- (
- '#submit' => array('_qbf_form_perform_submit'),
- '#validate' => array('_qbf_form_perform_validate'),
- '#type' => 'submit',
- '#value' => t('Perform query'),
- '#weight' => 1,
- );
- return $qbf_form;
- }
- /**
- * Implement hook_forms().
- *
- * @link http://drupal.org/node/144132#hook-forms @endlink
- *
- * hook_qbf_register() returns an array of QBF-able node types, indexed by the
- * node type, with the following properties:
- * - form: the name of the hook_form() implementation (a $form_id)
- * - label: the human-readable type name under which the queries are saved by QBF
- * - callback: the function QBF must invoke to query the node type. It will
- * receive the query type and a filtered version of $form_state['values']
- * containing only valid node fields, and must return a themed grid of query
- * results, which will be displayed as a #markup FAPI element. In advanced
- * uses, a single callback can be used for several query types by using the
- * query type parameter to know what the values apply to.
- *
- * @ingroup forms
- * @ingroup hooks
- *
- * @param array $args
- * @return array
- */
- function qbf_forms(/* $args = NULL */)
- {
- static $forms = array();
- if (empty($forms))
- {
- $hook_name = 'qbf_register';
- // dsm(array("QBF_forms $qbf_form_id" => $args));
- // More efficient than using module_invoke_all: we avoid array-merging + re-looping
- foreach (module_implements($hook_name) as $module)
- {
- $arImplementations = module_invoke($module, $hook_name);
- // dsm($arImplementations);
- foreach ($arImplementations as /* $node_type => */ $query_info)
- {
- $qbf_form_id = 'qbf_' . $query_info['form'];
- $forms[$qbf_form_id] = array
- (
- 'callback' => 'qbf_form',
- 'callback arguments' => array($query_info, $qbf_form_id),
- );
- } // foreach implementation
- } // foreach module
- } // if empty
- return $forms;
- }
- /**
- * List queries owned by a given user.
- *
- * @param int $uid > 0
- * Defaults to current user
- * @return array
- */
- function qbf_get_queries_by_user($uid = NULL)
- {
- if (is_null($uid))
- {
- global $user;
- $uid = $user->uid;
- }
- $sq = 'SELECT qq.qid, qq.uid, qq.type, qq.name, qq.query, qq.updated '
- . 'FROM {%s} qq '
- . 'WHERE qq.uid = %d '
- . 'ORDER BY qq.type, qq.name ';
- // no db_rewrite_sql: this function is not in a menu callback, so it is up to
- // the caller to check access
- $q = db_query($sq, QBF_TABLE_NAME, $uid);
- $ret = array();
- while (is_object($o = db_fetch_object($q)))
- {
- $ret[$o->qid] = $o; // qid is the PK, so it is present and unique
- }
- return $ret;
- }
- /**
- * Implement hook_mail().
- *
- * @param string $key
- * @param array $message
- * @param array $params
- * @return void
- */
- function qbf_mail($key, &$message, $params)
- {
- if ($key != '_qbf_query_delete')
- {
- return;
- }
- // dsm(array('QBF_mail key' => $key, 'message' => $message, 'params' => $params));
- $deletor_tokens = user_mail_tokens($params['deletor'], $params['language']->language);
- $tokens = array_merge($deletor_tokens, array
- (
- '!qname' => $params['query']->name,
- '!qid' => $params['query']->qid,
- ));
- $message['subject'] = t('Effacement d\'une recherche !site enregistrée', $tokens);
- $message['body'] = t("!date\n\nVotre recherche !qid: !qname\nsur le site !site vient d'être effacée par !username.", $tokens);
- }
- /**
- * Implement hook_menu().
- *
- * @return array
- */
- function qbf_menu()
- {
- $items = array();
- $items[QBF_PATH_SETTINGS] = array
- (
- 'title' => 'Query-By-Form',
- 'access arguments' => array(QBF_PERM_ADMIN),
- 'page callback' => 'drupal_get_form',
- 'page arguments' => array('qbf_admin_settings'),
- );
- $items[QBF_PATH_MAIN] = array
- (
- 'type' => MENU_CALLBACK,
- 'access arguments' => array(QBF_PERM_QUERY),
- 'page callback' => '_qbf_query_form',
- );
- $items[QBF_PATH_MAIN . '/%qbf_query'] = array
- (
- 'type' => MENU_CALLBACK,
- 'access arguments' => array(QBF_PERM_QUERY),
- 'page callback' => '_qbf_query_form',
- 'page arguments' => array(1),
- );
- $items[QBF_PATH_MAIN . '/%qbf_query/delete'] = array
- (
- 'type' => MENU_CALLBACK,
- 'access arguments' => array(QBF_PERM_QUERY),
- 'page callback' => '_qbf_query_delete',
- 'page arguments' => array(1),
- );
- return $items;
- }
- /**
- * Implement hook_perm().
- *
- * @todo D7: Format will change
- * @see http://drupal.org/node/224333#descriptions-permissions
- *
- * @ingroup hooks
- * @return array
- */
- function qbf_perm()
- {
- $ret = array
- (
- QBF_PERM_ADMIN,
- QBF_PERM_QUERY,
- );
- return $ret;
- }
- /**
- * Load a saved QBF query, or an empty query by type
- *
- * @link http://drupal.org/node/109153#load @endlink
- *
- * @param int $us_qid
- * @return array
- * A form_values array
- */
- function qbf_query_load($us_qid)
- {
- static $query = NULL;
- // Only allow query loading by logged-in users
- if (user_is_anonymous())
- {
- return FALSE;
- }
- // Filter out visibly invalid values
- $qid = (is_numeric($us_qid) && ($us_qid > 0))
- ? $us_qid
- : 0;
- // If this is not a saved query, it may be a QBF query type
- if ($qid === 0)
- {
- $ar_forms = qbf_forms();
- foreach ($ar_forms as /* $qbf_form_id => */ $form_info)
- {
- if ($us_qid === $form_info['callback arguments'][0]['form'])
- {
- $query = new Qbf_Query($us_qid);
- break;
- }
- }
- }
- if (is_null($query) && $qid)
- {
- $sq = 'SELECT qq.qid, qq.uid, qq.type, qq.name, qq.query '
- . 'FROM {%s} qq '
- . 'WHERE qq.qid = %d ';
- // db_rewrite_sql does not apply here: access control is further down
- $q = db_query($sq, QBF_TABLE_NAME, $qid);
- $query = db_fetch_object($q); // 0 or 1 row: we are querying on the primary key
- if ($query !== FALSE)
- {
- $query->query = unserialize($query->query);
- // dsm($query);
- }
- }
- global $user;
- $ret = (isset($query) && (($query->uid == $user->uid) || user_access(QBF_PERM_ADMIN)))
- ? $query
- : FALSE;
- return $ret;
- }
- /**
- * Provide an optional automatic mapping mechanism for query building.
- *
- * This function takes a partly built query map $ar_queryMap, and a defaults
- * array to complete it in $ar_defaults, and returns a fully built query array
- * ready to be used for querying.
- *
- * @param array $ar_query_map
- * @param array $ar_defaults
- * @return array
- */
- function qbf_query_mapper($ar_query_map = array(), $ar_defaults = array())
- {
- $ret = array();
- foreach ($ar_query_map as $name => $value)
- {
- // accept NULL, empty strings...
- if (!is_array($value))
- {
- $value = array();
- }
- $item = $value;
- foreach ($ar_defaults as $default_key => $default_value)
- {
- if (!array_key_exists($default_key, $item))
- {
- $item[$default_key] = is_null($default_value)
- ? $name
- : $default_value;
- }
- // else if is already in $item, so we don't touch it
- }
- $ret[$name] = $item;
- }
- return $ret;
- }
- /**
- * Implement hook_user().
- *
- * Display saved QBF searches as an account form category
- *
- * Edit and account could be passed by reference, but are currently not modified.
- *
- * @ingroup hooks
- *
- * @param string $op
- * @param array &$edit
- * @param array $account
- * @param string $category
- * @return array|void
- */
- function qbf_user($op, $edit, $account, $category = NULL)
- {
- $qbf_category = variable_get(QBF_VAR_PROFILE_CATEGORY, QBF_DEF_PROFILE_CATEGORY);
- // dsm("hook user($op, edit, $account->uid = $account->name, $category)");
- switch ($op)
- {
- case 'categories':
- // dsm("hook user($op)");
- $ret = array();
- $ret[] = array
- (
- 'name' => 'qbf',
- 'title' => $qbf_category,
- 'weight' => 2,
- );
- break;
- // case 'view':
- // // Only allow field to QBF admins and own user
- // if ($user->uid != $account->uid && !user_access(QBF_PERM_ADMIN))
- // {
- // return;
- // }
- //
- // $account->content['queries'] = array
- // (
- // '#type' => 'user_profile_category',
- // '#title' => t('Saved queries'),
- // // '#class' => "qbf-user-$category",
- // );
- // $account->content['queries']['list'] = array
- // (
- // '#type' => 'user_profile_item',
- // '#title' => t('List of searches'),
- // '#value' => '<p>Would appear here</p>',
- // );
- // $none_message = ($account->uid == $user->uid)
- // ? t('None yet. !newQuery', array('!newQuery' => $new_query_link))
- // : t('None yet.');
- // $saved = ($count > 0)
- // ? format_plural($count, 'One saved query. ', '@count saved queries. ')
- // . l(t('View/edit'), "user/$account->uid/edit/qbf")
- // : $none_message;
- // dsm($account->content);
- // break;
- case 'form':
- // dsm("hook user($op, $account->uid = $account->name, $category)");
- if ($category != 'qbf')
- {
- $ret = NULL;
- break;
- }
- // No access control: it is already controlled by the
- // "administer users" permission in user.module
- $ar_queries = qbf_get_queries_by_user($account->uid);
- $count = count($ar_queries);
- $ar_header = array
- (
- t('Query type'),
- t('Query title'),
- t('Saved on'),
- // t('# results'),
- t('Actions'),
- );
- $ar_data = array();
- foreach ($ar_queries as $query)
- {
- $ar_data[] = array
- (
- _qbf_get_name_from_type($query->type),
- l($query->name, QBF_PATH_MAIN . "/$query->qid"),
- format_date($query->updated, 'small'),
- // t('n.a.'),
- l(t('Delete'), QBF_PATH_MAIN ."/$query->qid/delete", array('query' => "destination=user/$account->uid/edit/qbf")),
- );
- }
- $data = theme('table', $ar_header, $ar_data);
- $max_count = variable_get(QBF_VAR_MAX_QUERIES, QBF_DEF_MAX_QUERIES);
- if ($count < $max_count)
- {
- $new_query_link = l(t('Create new query'), QBF_PATH_MAIN);
- $data .= format_plural($max_count - $count,
- '<p>You may still save one more query.',
- '<p>You may still save @count more queries.');
- $data .= " $new_query_link.</p>";
- }
- else
- {
- $data .= t("<p>You have reached the maximum number of saved queries (@count).</p>",
- array('@count' => $count));
- }
- /**
- * A first-level form element is needed by contrib module profile_privacy,
- * at least in version 5.x-1.2 and 6.x-1-2. This mimics the
- * user_profile_category/user_profile_item scheme provided by profile.module.
- * We do not use these types, in order to use the full display area width
- * for the queries table.
- */
- $ret['qbf'] = array
- (
- '#type' => 'markup', // in profile, usually user_profile_category
- '#title' => NULL,
- );
- $ret['qbf']['queries'] = array
- (
- '#type' => 'markup', // in profile, usually user_profile_item
- '#value' => $data,
- );
- break;
- }
- return $ret;
- }
- error_reporting($_qbf_er);
|