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

Contents of /contributions/modules/zeitgeist/zeitgeist.module

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


Revision 1.9 - (show annotations) (download) (as text)
Sun Apr 9 19:59:50 2006 UTC (3 years, 7 months ago) by fgm
Branch: MAIN
CVS Tags: HEAD
Branch point for: DRUPAL-5, DRUPAL-4-7, DRUPAL-6--1
Changes since 1.8: +32 -19 lines
File MIME type: text/x-php
- ZT Top Searches and ZG Recent Searches blocks are now themeable
- Fixed an error in the links generated in the same blocks
1 <?php
2 /**
3 * The Zeitgeist module provides history services for search
4 * Look at the ZGSPAN* constants for your own use when creating blocks or pages using
5 * this module. Example code is in _zeitgeist_stats().
6 * WARNING 1: the current method used to trap searches relies
7 * on undocumented search module behaviours, and can break at
8 * any version change until a proper logging API is implemented
9 * in Drupal core.
10 * WARNING 2: always truncate or hand-check the contents of the
11 * zeitgeist table after installing a new module version.
12 *
13 * Copyright 2006 FG Marand. Licensed under the CeCILL 2.0 license.
14 * $Id: zeitgeist.module,v 1.8 2006/04/08 22:00:39 fgm Exp $
15 */
16
17 /**
18 * Time spans for the results of _zeitgeist_stats().
19 * @see function _zeitgeist_stats()
20 */
21 define('ZGSPANDAY', 1); // Return zeitgeist for a day, starting at 00:00
22 define('ZGSPANWEEKM', 2); // Return zeitgeist for a calendar week, starting on Monday
23 define('ZGSPANWEEKS', 3); // Return zeitgeist for a calendar week, starting on Sunday
24 define('ZGSPANMONTH', 4); // Return zeitgeist for a calendar month, starting on the 1st.
25 define('ZGSPANQUARTER', 5); // Return zeitgeist for a calendar quarter, starting on the 1st of Jan, Apr, Jul, or Oct.
26 define('ZGSPANYEAR', 6); // Return zeitgeist for a calendar year, starting on the 1st.
27
28 /**
29 * Miscellaneous data
30 */
31 define('ZGSEARCHFORM', 'search_form'); // The name of the form to trap when recording searches
32 define('ZGTABLE', '{zeitgeist}'); // The name of the table holding our data
33 define('ZGVERSION', '$Id: zeitgeist.module,v 1.8 2006/04/08 22:00:39 fgm Exp $');
34 define('ZGONEDAY', 86400); // 24 hours * 60 minutes * 60 seconds
35
36 /**
37 * Module persistent variables: names and default values
38 */
39 define('ZGVARLATESTCOUNT', 'zeitgeist_latest_count'); // How many recent searches are displayed in their block ?
40 define('ZGVARTOPCOUNT', 'zeitgeist_top_count'); // How many top queries are displayed in their block ?
41 define('ZGVARTOPSPAN', 'zeitgeist_top_span'); // ZG span for the top queries block
42 define('ZGVARHISTORY', 'zeitgeist_history'); // How long does the module keep its historical data.
43 define('ZGVARNOFOLLOW', 'zeitgeist_nofollow'); // Emit a rel=nofollow attribute in feature blocks
44 define('ZGVARTYPE', 'zeitgeist_type'); // Display search type in "recent searches" blocks
45
46 define('ZGDEFLATESTCOUNT', 5); // Default for ZGVARLATESTCOUNT
47 define('ZGDEFTOPCOUNT', 5); // Default for ZGVARTOPCOUNT
48 define('ZGDEFTOPSPAN', ZGSPANMONTH); // Default for ZGVARTOPSPAN
49 define('ZGDEFHISTORY', 0); // Default means forever
50 define('ZGDEFNOFOLLOW', 1); // Default is true
51 define('ZGDEFTYPE', 0); // Default is never
52
53 /**
54 * Enter description here...
55 *
56 * @param string $section
57 * - admin/modules#name The name of a module (unused, but there)
58 * - admin/modules#description The description found on the admin/system/modules page.
59 * - admin/help#modulename The module's help text, displayed on the admin/help page and through the module's individual help link. * - user/help#modulename The help for a distributed authorization module (if applicable).
60 * - node/add#nodetype The description of a node type (if applicable).
61 * @return string HTML, localized
62 */
63 function zeitgeist_help($section)
64 {
65 switch ($section )
66 {
67 case 'admin/modules#description': $ret = t("This module provides a history of search and related features"); break;
68 case 'admin/help#zeitgeist': $ret = t('<p>zeitgeist records recent searches when they are performed. '
69 . '<p>It also supplies a block listing the "n" latest searches, '
70 . 'and a setting to define the value of "n".</p>'); break;
71 }
72 return $ret;
73 }
74
75 /**
76 * @return array An array containing form items to place on the module settings page.
77 *
78 */
79 function zeitgeist_settings()
80 {
81 $form[ZGVARLATESTCOUNT] = array
82 (
83 '#type' => 'textfield',
84 '#title' => t('Recent searches displayed'),
85 '#description' => t('This is the number of recent searches displayed in the "Recent Searches" '
86 . 'block provided by the zeitgeist module. Default: %def.', array('%def' => ZGDEFLATESTCOUNT)),
87 '#default_value' => variable_get(ZGVARLATESTCOUNT, ZGDEFLATESTCOUNT),
88 '#maxlength' => 2,
89 '#size' => 2,
90 );
91 $form[ZGVARTOPCOUNT] = array
92 (
93 '#type' => 'textfield',
94 '#title' => t('Top searches displayed'),
95 '#description' => t('This is the number of top searches displayed in the "Top Searches" '
96 . 'block provided by the zeitgeist module. Default: %def.', array('%def' => ZGDEFTOPCOUNT)),
97 '#default_value' => variable_get(ZGVARTOPCOUNT, ZGDEFTOPCOUNT),
98 '#maxlength' => 2,
99 '#size' => 2,
100 );
101 $form[ZGVARHISTORY] = array
102 (
103 '#type' => 'textfield',
104 '#title' => t('Number of days of history'),
105 '#description' => t('Historical data are kept for that number of days. 0 means forever. '
106 . 'Pruning is performed by cron on a daily basis'),
107 '#default_value' => variable_get(ZGVARHISTORY, ZGDEFHISTORY),
108 '#size' => 4, // this is good until 2037, should be enough for all 4.7 needs.
109 '#max_length' => 4,
110 );
111 $form[ZGVARTYPE] = array
112 (
113 '#type' => 'radios',
114 '#title' => t('When should the search type be displayed in the "recent searches" blocks ?'),
115 '#default_value' => variable_get(ZGVARTYPE, ZGDEFTYPE),
116 '#prefix' => '<div class="container-inline">',
117 '#suffix' => '</div>',
118 "#options" => array (t('Never'), t('Admin only'), t('Authenticated users'), t('Always')),
119 '#description' => t('Recommended value is "Admin only"; '
120 . '"Always" is typically disturbing for visitors not already well-versed in Drupal.'),
121 );
122
123 $form['advanced'] = array
124 (
125 '#type' => 'fieldset',
126 '#title' => t('Advanced settings'),
127 '#collapsible' => true,
128 '#collapsed' => true
129 );
130
131 $form['advanced'][ZGVARNOFOLLOW] = array
132 (
133 "#type" => 'checkbox',
134 '#title' => t('Apply %attr to links on the "recent searches" and "top searches" blocks',
135 array('%attr' => '<code>rel="nofollow"</code>')),
136 '#default_value' => variable_get(ZGVARNOFOLLOW, ZGDEFNOFOLLOW),
137 '#return_value' => 1,
138 '#description' => t('An attribute for the "a" (X)HTML alement, %attr is non-standard, '
139 . 'but is recognized by several search engines, notably Google, MSN and Yahoo!',
140 array('%attr' => '<code>rel="nofollow"</code>')),
141 );
142 $form['advanced'][ZGVERSION] = array
143 (
144 '#value' => '<p>' . t('This site is running Zeitgeist version %version. ',
145 array(
146 '%version' => ZGVERSION,
147 )
148 )
149 . '</p>'
150 );
151 return $form;
152 }
153
154 /**
155 * Trap searches. Warning: the mechanism
156 * currently in use seems to work, but might not cover
157 * all cases. Please report any missing info on the
158 * zeitgeist issues page at
159 * http://drupal.org/node/add/project_issue/zeitgeist/bug
160 * There is a known problem with advanced search artificially
161 * creating an fictitious empty search prior to the actual one,
162 * but this seems to be a search.module bug
163 *
164 * @param string $form_id A registered form_id. We're only interested in the search form
165 * @param array $form_values
166 * @return void
167 */
168 function zeitgeist_form_alter($form_id, &$form)
169 {
170 if ($form_id != 'search_form')
171 return;
172
173 $search = $form['basic']['inline']['keys']['#default_value'];
174 $category = $form['module']['#value'];
175 _zeitgeist_store_search($search, $category);
176 }
177
178 /**
179 * @param string $op
180 * - 'list': A list of all blocks defined by the module.
181 * - 'configure': A configuration form.
182 * - 'save': Save the configuration options.
183 * - 'view': Information about a particular block
184 * @param int $delta
185 * @param array $edit
186 */
187 function zeitgeist_block($op = 'list', $delta = 0, $edit = array())
188 {
189 switch ($op)
190 {
191 case 'list':
192 $blocks[0]['info'] = t(
193 variable_get('zeitgeist_latest_info', t('ZG Latest %count')),
194 array('%count' => variable_get(ZGVARLATESTCOUNT, ZGDEFLATESTCOUNT))
195 );
196 $blocks[1]['info'] = t(
197 variable_get('zeitgeist_top_info', t('ZG Top %count')),
198 array('%count' => variable_get(ZGVARTOPCOUNT, ZGDEFTOPCOUNT))
199 );
200 return $blocks;
201 break;
202 case 'configure':
203 switch ($delta)
204 {
205 case 0:
206 $form = array();
207 _zeitgeist_block_settings_show($form,
208 t('Name of the block in the block list'), 'latest', t('ZG Latest %count'),
209 t('Title of the block when displayed'), 'latest', t('Latest %count searches')
210 );
211 break;
212 case 1:
213 $form = array();
214 _zeitgeist_block_settings_show($form,
215 t('Name of the block in the block list'), 'top', t('ZG Top %count'),
216 t('Title of the block when displayed'), 'top', t('Top %count searches')
217 );
218 break;
219 }
220 return $form;
221 break;
222 case 'save':
223 switch ($delta)
224 {
225 case 0:
226 _zeitgeist_block_settings_save($edit, 'latest');
227 break;
228 case 1:
229 _zeitgeist_block_settings_save($edit, 'top');
230 break;
231 }
232 break;
233 case 'view':
234 switch ($delta)
235 {
236 case 0:
237 $count = variable_get (ZGVARLATESTCOUNT, ZGDEFLATESTCOUNT);
238 $subject = variable_get('zeitgeist_latest_title', t('Latest %count searches'));
239 $subject = t($subject, array('%count' => $count));
240 $block['subject'] = $subject;
241 $block['content'] = theme('zeitgeist_block_latest', $count);
242 break;
243 case 1:
244 $count = variable_get (ZGVARTOPCOUNT, ZGDEFTOPCOUNT);
245 $subject = variable_get('zeitgeist_top_title', t('Top %count searches'));
246 $subject = t($subject, array('%count' => $count));
247 $block['subject'] = $subject;
248 $block['content'] = theme('zeitgeist_block_top', $count);
249 break;
250 }
251 return $block;
252 break;
253 }
254 }
255
256 function _zeitgeist_store_search($search, $category, $ts = NULL)
257 {
258 if (!isset($ts))
259 $ts = time();
260 $sq = 'INSERT INTO ' . ZGTABLE . ' (search, category, ts) '
261 . "VALUES ('%s', '%s', %d) ";
262 db_query($sq, $search, $category, $ts);
263 }
264
265 /**
266 * Provides the block-specific contents common to each ZG block: ability to rename the block and change its title
267 *
268 * @param array $form The current form for which this is built
269 * @param string $infotitle Block information: the title
270 * @param string $infovar Block information: the config variable name
271 * @param string $infodefault Block information: the default name
272 * @param string $titletitle Block title: the title
273 * @param string $titlevar Block title: the config variable name
274 * @param string $titledefault Block title: the default title
275 * @return void
276 */
277 function _zeitgeist_block_settings_show(&$form, $infotitle, $infovar, $infodefault, $titletitle, $titlevar, $titledefault)
278 {
279 $form['info'] = array
280 (
281 '#type' => 'textfield',
282 '#title' => $infotitle,
283 '#default_value' => variable_get('zeitgeist_' . $infovar . '_info', $infodefault),
284 '#weight' => -2,
285 );
286 $form['title'] = array
287 (
288 '#type' => 'textfield',
289 '#title' => $titletitle,
290 '#default_value' => variable_get('zeitgeist_' . $titlevar . '_title', $titledefault),
291 '#weight' => -1,
292 );
293 }
294
295 function _zeitgeist_block_settings_save($edit, $blockname)
296 {
297 variable_set('zeitgeist_' . $blockname . '_info', $edit['info' ]);
298 variable_set('zeitgeist_' . $blockname . '_title', $edit['title']);
299 }
300
301 /**
302 * Builds the contents of the latest searches block
303 *
304 * @param string $count Themed HTML
305 */
306 function _zeitgeist_block_latest($count = NULL)
307 {
308 global $user;
309
310 if (!isset($count))
311 {
312 $count = variable_get(ZGVARLATESTCOUNT, ZGDEFLATESTCOUNT);
313 }
314
315 // We query an unlimited range because of potential duplicates
316 $sq = 'SELECT zg.search, zg.category '
317 . 'FROM ' . ZGTABLE . ' zg '
318 . 'ORDER BY zg.ts DESC' ;
319 $q = db_query($sq);
320 $ar = array();
321
322 // Conditional search type rendering
323 switch (variable_get(ZGVARTYPE, ZGDEFTYPE))
324 {
325 case 1 : // Display only for admin
326 $show_category = ($user->uid == 1);
327 break;
328 case 2 : // Display for all authenticated users
329 $show_category = ($user->uid != 0);
330 break;
331 case 3 : // Always display
332 $show_category = true;
333 break;
334 default: // 0 and any other value
335 $show_category = false;
336 break;
337 }
338
339 // then we weed out the duplicates on the fly, and stop
340 // when we have enough results
341 while (($o = db_fetch_object($q)) && (sizeof($ar) < $count))
342 {
343 $category = $show_category ? " ($o->category)" : null;
344
345 if ((!empty($o->search)) && (!isset($ar["$o->search$category"])))
346 $ar["$o->search$category"] = array($o->search, $o->category) ;
347 }
348 mysql_free_result($q);
349 return $ar;
350 }
351
352 /**
353 * Return zeitgeist as an object holding the boundary dates and a array of query counts
354 *
355 * @param int $span One of the predefined ZGSPAN* constants
356 * @param int $ts A UNIX timestamp within the span, used to select it. Use current time if NULL
357 * @param string $category Restrict on the type of searches taken into account (or not)
358 * @param int $limit Limit results to $count results, or don't limit if 0.
359 * @return object
360 */
361 function _zeitgeist_stats($span = ZGSPANMONTH, $ts = NULL, $category = NULL, $count = 0)
362 {
363 if (!isset($ts))
364 {
365 $ts = time();
366 }
367 $arDate = getdate($ts);
368
369 /**
370 * Some of these ranges go beyond the current timestamp if computed for the current
371 * timestamp, but this is not an issue as there won't be any matching entries anyway
372 */
373 switch ($span)
374 {
375 case ZGSPANDAY:
376 $ts1 = mktime(0, 0, 0, $arDate['mon'], $arDate['mday'], $arDate['year']);
377 $ts2 = $ts1 + ZGONEDAY;
378 break;
379 case ZGSPANWEEKS:
380 $tsBase = $ts - $arDate['wday'] * ZGONEDAY;
381 $arDateBase = getdate($tsBase);
382 $ts1 = mktime(0, 0, 0, $arDateBase['mon'], $arDateBase['mday'], $arDateBase['year']);
383 $ts2 = $ts1 + 7 * ZGONEDAY;
384 unset($arDateBase);
385 break;
386 case ZGSPANWEEKM:
387 $tsBase = $ts - (($arDate['wday'] + 6) % 7) * ZGONEDAY;
388 $arDateBase = getdate($tsBase);
389 $ts1 = mktime(0, 0, 0, $arDateBase['mon'], $arDateBase['mday'], $arDateBase['year']);
390 $ts2 = $ts1 + 7 * ZGONEDAY;
391 unset($arDateBase);
392 break;
393 case ZGSPANMONTH:
394 $ts1 = mktime(0, 0, 0, $arDate['mon'], 1, $arDate['year']);
395 // mktime fixes out-of-range correctly, so we needn't worry about year wrapping
396 $ts2 = mktime(0, 0, 0, $arDate['mon'] + 1, 1, $arDate['year']);
397 break;
398 case ZGSPANQUARTER:
399 $mon = 1 + (int) (($arDate['mon'] - 1) / 3);
400 $ts1 = mktime(0, 0, 0, $mon, 1, $arDate['year']);
401 $ts2 = mktime(0, 0, 0, $mon + 3, 1, $arDate['year']);
402 break;
403 case ZGSPANYEAR:
404 $ts1 = mktime(0, 0, 0, 1, 1, $arDate['year']);
405 $ts2 = mktime(0, 0, 0, 1, 1, $arDate['year']+1);
406 break;
407 }
408 $sq = 'SELECT zg.search, zg.category, count(zg.ts) cnt '
409 . 'FROM ' . ZGTABLE . ' zg '
410 . 'WHERE (zg.ts >= %d) AND( zg.ts < %d) ' ;
411 if (isset($category))
412 {
413 $sq .= "AND (zg.category = '%s') "
414 . 'GROUP BY 1, 2 '
415 . 'ORDER BY 3 DESC, 1 ASC, 2 ASC ' ;
416 $q = (isset($count) && ($count > 0))
417 ? db_query_range($sq, $ts1, $ts2, $category, 0, $count)
418 : db_query($sq, $ts1, $ts2, $category);
419 }
420 else
421 {
422 $sq .= 'GROUP BY 1, 2 '
423 . 'ORDER BY 3 DESC, 1 ASC, 2 ASC ' ;
424 $q = (isset($count) && ($count > 0))
425 ? db_query_range($sq, $ts1, $ts2, 0, $count)
426 : db_query($sq, $ts1, $ts2);
427 }
428
429 $ret = new stdClass();
430 $ret->ts1 = $ts1;
431 $ret->ts2 = $ts2;
432 $ret->scores = array();
433 while ($o = db_fetch_object($q))
434 {
435 $ret->scores[] = array ('search' => $o->search, 'category' => $o->category, 'count' => $o->cnt);
436 }
437 return $ret;
438 }
439
440 /**
441 * Default rendering for the ZG block with the most popular "node" queries
442 * during the preceding month
443 *
444 * @param int $count
445 */
446 function theme_zeitgeist_block_top($count = NULL)
447 {
448 $arDate = getdate();
449 // Uncomment that line for last month's zeitgeist. Leave it commented for the current month
450 // $ts = mktime(0, 0, 0, $arDate['mon'] - 1, 1, $arDate['year']); // mktime wraps months cleanly, no worry
451 unset ($arDate);
452
453 $arStats = _zeitgeist_stats(ZGSPANMONTH, $ts, 'node', $count);
454 $arScores = array();
455 $attr = variable_get(ZGVARNOFOLLOW, ZGDEFNOFOLLOW) ? array ('rel' => 'nofollow') : NULL ;
456 foreach ($arStats->scores as $score)
457 {
458 $arScores [] = l($score['search'] . " (${score['count']})", "search/node/$score[search]", $attr, NULL, NULL, TRUE);
459 }
460 unset($arStats);
461
462 $ret = theme('item_list', $arScores);
463 unset ($arScores);
464 return $ret;
465 }
466
467 /**
468 * Default rendering for the ZG latest searches block
469 *
470 * @param int $count
471 */
472 function theme_zeitgeist_block_latest($count = NULL)
473 {
474 $ar = _zeitgeist_block_latest($count);
475 $arLinks = array();
476 $attr = variable_get(ZGVARNOFOLLOW, ZGDEFNOFOLLOW) ? array ('rel' => 'nofollow') : NULL ;
477 foreach ($ar as $key => $value)
478 {
479 $arLinks[] = l($key, "search/$value[1]/$value[0]", $attr, NULL, NULL, TRUE);
480 }
481 $ret = theme('item_list', $arLinks);
482 return $ret;
483 }

  ViewVC Help
Powered by ViewVC 1.1.2