<?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.11 2009-03-22 14:30:41 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'); /** * 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'); /** * 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'); /** * 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 */ function _qbf_form_perform_submit($form, &$form_state) { // dsm($form); // dsm($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 */ //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 * @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 */ //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 * @return void */ 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[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'), ); $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), ); 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['#qbf_source_form_id'] = $form_id; // On the form element itself, only keep the QBF properties and the children if (in_array('#qbf', element_properties($form))) { $qbf_form += $form['#qbf']; } 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['#id'] = $qbf_form_id; $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>', ); } } $qbf_form['qbf']['qbf_save_type'] = array ( '#type' => 'hidden', '#value' => $query_info['form'], ); $qbf_form['qbf']['qbf_query'] = array ( '#type' => 'hidden', '#value' => $query_info['callback'], ); $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_perform'] = array ( '#submit' => array('_qbf_form_perform_submit'), '#validate' => array('_qbf_form_perform_validate'), '#type' => 'submit', '#value' => t('Perform query'), ); $qbf_form['qbf']['qbf_save'] = array ( '#submit' => array('_qbf_form_save_submit'), '#validate' => array('_qbf_form_save_validate'), '#type' => 'submit', '#value' => t('Save query'), ); 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 * @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) { // 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'), 'type' => MENU_NORMAL_ITEM, ); $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 job searchs 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 job/internship queries'), // // '#class' => "job-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(JOB_VAR_MAX_QUERIES, JOB_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['job'] = array ( '#type' => 'markup', // in profile, usually user_profile_category '#title' => NULL, ); $ret['job']['queries'] = array ( '#type' => 'markup', // in profile, usually user_profile_item '#value' => $data, ); break; } return $ret; } error_reporting($_qbf_er);