/[drupal]/contributions/modules/tql/tql.module
ViewVC logotype

Contents of /contributions/modules/tql/tql.module

Parent Directory Parent Directory | Revision Log Revision Log | View Revision Graph Revision Graph


Revision 1.8 - (show annotations) (download) (as text)
Thu Jul 16 21:59:32 2009 UTC (4 months, 1 week ago) by roychri
Branch: MAIN
CVS Tags: HEAD
Changes since 1.7: +2 -2 lines
File MIME type: text/x-php
Fixed issue #139623: SQL query error if database installed with prefix
1 <?php
2 // $Id: tql.module,v 1.7 2008/04/09 10:21:40 roychri Exp $
3
4 /**
5 * @file
6 * Taxonomy query language is a views plugin which provides a filter over vocabularies which
7 * can be entered as a query.
8 */
9
10 /**
11 * Implementation of hook_menu().
12 */
13 function tql_menu($may_cache) {
14 $items = array();
15 if ($may_cache) {
16 $items[] = array(
17 'path' => 'tql/autocomplete',
18 'title' => t('Autocomplete tql query'),
19 'callback' => 'tql_autocomplete',
20 'access' => user_access('access content'),
21 'type' => MENU_CALLBACK
22 );
23
24 $items[] = array(
25 'path' => 'admin/settings/tql',
26 'title' => t('Taxonomy Query Language'),
27 'description' => t('Modify the behavior of the TQL module.'),
28 'callback' => 'drupal_get_form',
29 'callback arguments' => array('tql_admin_settings'),
30 'access' => user_access('administer site configuration'),
31 'type' => MENU_NORMAL_ITEM
32 );
33 }
34 return $items;
35 }
36
37
38 /**
39 * Menu callback for administration settings.
40 */
41 function tql_admin_settings() {
42 $form = array();
43
44 $form['tql_dump_query'] = array(
45 '#type' => 'checkbox',
46 '#title' => t('Dump the query as a drupal message'),
47 '#description' => t('Setting this will set a message showing the query in the "message" area of the page.'),
48 '#return_value' => TRUE,
49 '#default_value' => variable_get('tql_dump_query', TRUE)
50 );
51
52 return system_settings_form($form);
53 }
54
55
56 /**
57 * Implementation of hook_search().
58 * This creates a tab on the search page to execute a query.
59 */
60 function tql_search($op = 'search', $keys = NULL) {
61 switch ($op) {
62 case 'name':
63 // Name of tab for search page
64 return t('Taxonomy');
65
66 case 'search':
67 if ($keys && $nid_sql = tql_generate_sql($keys)) {
68 $search_results = array();
69 // Create SQL for nids and number of results
70 $sql = 'SELECT nid FROM {node} WHERE status = 1 AND nid in ('. $nid_sql .')';
71 $count_sql = 'SELECT COUNT(*) FROM {node} WHERE status = 1 AND nid in ('. $nid_sql .')';
72 // Let 'pager' execute the query and make nice 'previous', 'next' links
73 $result = pager_query($sql, 10, 0, $count_sql, $keys);
74 while ($node = db_fetch_object($result)) {
75 // Load node
76 $node = node_load($node->nid);
77 // Generate snippet
78 $node->snippet = check_markup($node->teaser, $node->format, FALSE);
79 $terms = taxonomy_link('taxonomy terms', $node);
80 $node->snippet .= theme('links', $terms);
81 // Let other modules add extra stuff to search result
82 $extra = node_invoke_nodeapi($node, 'search result');
83 // Construct search result entry
84 $search_results[] = array(
85 'link' => url('node/'. $node->nid, NULL, NULL, TRUE),
86 'title' => $node->title,
87 'type' => node_get_types('name', $node),
88 'user' => theme('username', $node),
89 'date' => $node->changed,
90 'extra' => $extra,
91 'snippet' => $node->snippet
92 );
93 }
94 return $search_results;
95 }
96 break;
97 }
98 }
99
100 /**
101 * Implementation of hook_form_alter().
102 */
103 function tql_form_alter($form_id, &$form) {
104 if ($form_id == 'search_form' && arg(1) == 'tql') {
105 $form['basic']['#title'] = t('Enter your query');
106 $form['basic']['inline']['keys']['#autocomplete_path'] = 'tql/autocomplete/0';
107 }
108 }
109
110 /**
111 * Implementation of hook_views_tables().
112 * Provide information for views module.
113 */
114 function tql_views_tables() {
115 if (module_exists('taxonomy')) {
116
117 // Views filter for a query over all vocabularies.
118 $tables["tql_node"] = array(
119 'name' => 'node', // {node} table is used
120 'provider' => 'internal',
121 'filters' => array(
122 'tid' => array(
123 'name' => t('Taxonomy: Query over all Vocabularies'),
124 'value' => array(
125 '#type' => 'textfield',
126 '#autocomplete_path' => 'tql/autocomplete/0',
127 ),
128 'option' => 'string',
129 'operator' => array('Has'),
130 'handler' => 'tql_handler_filter_query',
131 'help' => t('All terms from every vocabulary will be used to check against the specified query. This filter is not aware of taxonomy hierarchies. Please see the taxonomy help for more information.'),
132 ),
133 )
134 );
135
136 $vocabularies = taxonomy_get_vocabularies();
137 foreach ($vocabularies as $voc) {
138 // Views filter for a query over a single vocabulary
139 $tables["tql_node_$voc->vid"] = array(
140 'name' => 'node',
141 'provider' => 'internal',
142 'filters' => array(
143 'tid' => array(
144 'name' => t('Taxonomy: Query for @voc-name', array('@voc-name' => $voc->name)),
145 'value' => array(
146 '#type' => 'textfield',
147 '#autocomplete_path' => 'tql/autocomplete/'. $voc->vid,
148 ),
149 'option' => 'string',
150 'operator' => array('Has'),
151 'handler' => 'tql_handler_filter_query',
152 'vocabulary' => $voc->vid,
153 'help' => t("Only terms associated with %voc-name will be used to check against the specified query. This filter is not aware of taxonomy hierarchies. Please see the taxonomy help for more information.", array('%voc-name' => $voc->name)),
154 ),
155 )
156 );
157
158 }
159 }
160 return $tables;
161 }
162
163 /**
164 * Implementation of hook_views_arguments().
165 * Provide information for views module.
166 */
167 function tql_views_arguments() {
168 $arguments = array(
169 'tql_query' => array(
170 'name' => t('Taxonomy: Query'),
171 'handler' => 'tql_handler_arg_query',
172 'option' => 'string',
173 'help' => t('Todo: query, named query. Options can be set to the name of an exposed <em>Taxonomy: Query</em> filter to insert the argument into this field.'),
174 )
175 );
176 return $arguments;
177 }
178
179 /**
180 * Callback when query filter is used as a views argument.
181 */
182 function tql_handler_arg_query($op, &$query, $argtype, $arg = NULL) {
183 switch ($op) {
184 case 'summary':
185 $query->ensure_table('term_data', true);
186 $query->add_field('name', 'term_data');
187 $query->add_field('weight', 'term_data');
188 $query->add_field('tid', 'term_data');
189 $fieldinfo['field'] = "term_data.name";
190 return $fieldinfo;
191
192 case 'sort':
193 $query->add_orderby('node', 'title', $argtype);
194 break;
195
196 case 'filter':
197 // query or query name is in $arg
198 // execute query or stored query and apply this to $query
199 $filter = array('value' => $arg);
200 $filterinfo = array('table' => 'node');
201 tql_handler_filter_query('', $filter, $filterinfo, $query);
202 // If option field is set, we store the query as a $_GET variable.
203 // That way it can be inserted into an exposed field
204 if (isset($argtype['options'])) {
205 $_GET[$argtype['options']] = $arg;
206 }
207 break;
208
209 case 'link':
210 break;
211
212 case 'title':
213 // query or query name is in $query
214 // return title
215 $title = $query;
216 return $title;
217 }
218 }
219
220 /**
221 * Callback when filter has to construct SQL query for views.
222 */
223 function tql_handler_filter_query($op, $filter, $filterinfo, &$query) {
224
225 if (isset($filterinfo['vocabulary'])) {
226 $sql = tql_generate_sql($filter['value'], array($filterinfo['vocabulary']));
227 }
228 else {
229 $sql = tql_generate_sql($filter['value']);
230 }
231
232 if ($sql) {
233 $table = $filterinfo['table'];
234 $query->ensure_table($table);
235 $query->add_where("node.nid in (". $sql .")");
236 }
237 else {
238 $query->add_where("FALSE");
239 }
240
241 }
242
243 /**
244 * Generate an SQL query of a tag query.
245 *
246 * @param $query
247 * The query which should be parsed
248 * @param $vocabulary_list
249 * An array of vocabulary IDs if the query should be restricted to these vocabularies.
250 * @return
251 * The generated SQL is a select query over a list of node ids and can be used
252 * in a where clause as 'WHERE node.nid in ($sql_query)'.
253 */
254 function tql_generate_sql($query, $vocabulary_list = NULL) {
255 $sql = NULL;
256
257 // Include needed files
258 include_once('ast/TqlAbstractAstVisitor.php');
259 include_once('ast/TqlAstDumper.php');
260 include_once('ast/TqlErrorFormatter.php');
261 include_once('ast/TqlNameToTid.php');
262 include_once('ast/TqlMySqlGenerator.php');
263 include_once('ast/TqlParser.php');
264 include_once('ast/TqlLexer.php');
265
266 // init the lexer with the query
267 $lexer = new TqlLexer($query);
268 $parser = new TqlParser();
269
270 // build the AST
271 while ($lexer->yylex()) {
272 $parser->doParse($lexer->token, $lexer->node);
273 }
274 $parser->doParse(0, 0);
275
276 if ($parser->successful) {
277
278 // map terms (strings) to term ids (integers)
279 $tag_tank = tql_create_tag_tank($parser->ast, $vocabulary_list);
280
281 // generate code for mysql
282 $sql_generator = new TqlMySqlGenerator();
283 $sql = $sql_generator->generate($parser->ast, $tag_tank->terms, '{term_node}');
284
285 if (variable_get('tql_dump_query', TRUE)) {
286 // write the query nicely formatted
287 $dumper = new TqlAstDumper();
288 drupal_set_message($dumper->dump($parser->ast));
289 }
290 }
291 else {
292 tql_create_tag_tank($parser->astParts, $vocabulary_list);
293 $errorPrinter = new TqlErrorFormatter();
294 $error = $errorPrinter->error($parser->astParts, $query);
295 drupal_set_message(t("Error in query: ") . $error, 'error');
296 }
297 return $sql;
298 }
299
300 /**
301 * Creates a TqlNameToTid object and reports missing terms to drupal.
302 *
303 * @param $ast
304 * Object of type TqlNode or an array of such objects.
305 * @param $vocabulary_list
306 * Array of vocabulary IDs to use or 'null' if all should be taken into account.
307 * @return
308 * TqlNameToId Object
309 */
310 function tql_create_tag_tank($ast, $vocabulary_list) {
311 $tag_tank = new TqlNameToTid();
312 if (is_array($ast)) {
313 foreach ($ast as $part_of_ast) {
314 // map terms (strings) to term ids (integers)
315 $tag_tank->computeTermIDs($part_of_ast, $vocabulary_list);
316 }
317 }
318 else {
319 $tag_tank->computeTermIDs($ast, $vocabulary_list);
320 }
321 // report terms which do not occur in the selected vocabularies at all
322 if (count($tag_tank->missingTerms) > 0) {
323 drupal_set_message(t("The following terms were not found: ") . implode(', ', $tag_tank->missingTerms), 'error');
324 }
325 return $tag_tank;
326 }
327
328 /**
329 * Menu callback to autocomplete a query.
330 *
331 * @param $vid
332 * The vocabulary to restrict the autocompletion. If this is zero, no restriction is used.
333 * @param $string
334 * The query for which autocompletion should be done.
335 */
336 function tql_autocomplete($vid = 0, $string = '') {
337
338 // Include needed files
339 include_once('ast/TqlAbstractAstVisitor.php');
340 include_once('ast/TqlParser.php');
341 include_once('ast/TqlLexer.php');
342
343 // Remove unneeded whitespace
344 $string = trim($string);
345
346 // initialize the lexer with the query
347 $lexer = new TqlLexer($string);
348
349 // Get last token
350 $last_token = NULL;
351 while ($lexer->yylex()) {
352 $last_token = $lexer->token;
353 $last_node = $lexer->node;
354 }
355
356 // Only autocomplete identifiers
357 if ($last_token == TqlParser::TK_IDENTIFIER) {
358 // Restrict terms to vocabulary if requested
359 if ($vid) {
360 $result = db_query_range(db_rewrite_sql("SELECT t.tid, t.name FROM {term_data} t WHERE t.vid = %d AND LOWER(t.name) LIKE LOWER('%%%s%%')", 't', 'tid'), $vid, $last_node->value, 0, 10);
361 }
362 else {
363 $result = db_query_range(db_rewrite_sql("SELECT t.tid, t.name FROM {term_data} t WHERE LOWER(t.name) LIKE LOWER('%%%s%%')", 't', 'tid'), $last_node->value, 0, 10);
364 }
365
366 // Base part of autocompleted string
367 $prefix = substr($string, 0, $last_node->column);
368 // Check if a quote is left of the last token
369 $has_left_quote = ($last_node->column - 1 >= 0) ? $string[$last_node->column - 1] == '"' : false;
370
371 // Construct response array
372 $matches = array();
373 while ($tag = db_fetch_object($result)) {
374 $name = $tag->name;
375 if (strpos($name, ' ') !== false) {
376 // the term needs to be in quotes
377 if ($has_left_quote) {
378 $name .= '"';
379 }
380 else {
381 $name = '"'. $name .'"';
382 }
383 }
384 elseif ($has_left_quote) {
385 // a quote is already there, close it
386 $name .= '"';
387 }
388 // Array key is for textfield, array value for display
389 $matches[$prefix . $name] = check_plain($tag->name);
390 }
391 // Return matches as javascript array
392 print(drupal_to_js($matches));
393 exit();
394 }
395 else {
396 exit();
397 }
398 }

  ViewVC Help
Powered by ViewVC 1.1.2