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

Contents of /contributions/modules/taxonews/taxonews.module

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


Revision 1.13 - (show annotations) (download) (as text)
Wed Jun 11 20:28:41 2008 UTC (17 months, 2 weeks ago) by fgm
Branch: MAIN
CVS Tags: HEAD
Changes since 1.12: +123 -142 lines
File MIME type: text/x-php
Creation of the D7.1 branch - no significant changes over D6.1RC
1 <?php
2 /**
3 * A module to display blocks selected by terms in a taxonomy vocabulary,
4 * as long as they are newer than a set limit
5 *
6 * The module defines one block for each term in the vocabulary.
7 *
8 * Settings allows the administrator to choose
9 * - the vocabulary to be used to select nodes
10 * - the number of days nodes are considered for display
11 *
12 * Blocks without matching nodes do not appear.
13 *
14 * @author Frederic G. MARAND
15 * @version CVS: $Id: taxonews.module,v 1.10.8.8 2008/06/08 13:24:47 fgm Exp $
16 * @copyright 2005-2008 Ouest Systèmes Informatiques (OSI)
17 * @license http://www.cecill.info/licences/Licence_CeCILL_V2-en.html
18 * @link http://drupal.org/project/taxonews
19 * @since Version 1.1
20 */
21
22 $_taxonewsErrorReporting = error_reporting(E_ALL | E_STRICT);
23
24 class Taxonews
25 {
26 const VERSION = '$Id: taxonews.module,v 1.10.8.8 2008/06/08 13:24:47 fgm Exp $';
27 const PATH_SETTINGS = 'admin/build/settings/taxonews';
28
29 /**
30 * Names of persistent variables
31 */
32 const VAR_LIFETIME = 'taxonews_lifetime';
33 const VAR_MAX_ROWS = 'taxonews_max_rows';
34 const VAR_SHOW_NAME = 'taxonews_show_name';
35 const VAR_SHOW_ARCHIVE = 'taxonews_show_archive';
36 const VAR_SHOW_EMPTY = 'taxonews_show_empty';
37 const VAR_EMPTY_MESSAGES = 'taxonews_empty_messages';
38 const VAR_FEED = 'taxonews_feed';
39 const VAR_PONDERATED = 'taxonews_ponderated';
40 const VAR_VOCABULARY = 'taxonews_vid';
41
42 /**
43 * Default values for persistent variables
44 *
45 */
46 const DEF_LIFETIME = 30;
47 const DEF_MAX_ROWS = 5;
48
49 /**
50 * Implement the former hook_settings
51 *
52 * @return array settings form
53 */
54 static function adminSettings()
55 {
56 $sq = 'SELECT v.vid, v.name, v.description '
57 . 'FROM {vocabulary} v '
58 . 'ORDER BY 2, 1 ' ;
59 $q = db_query($sq);
60 $vocabularies = array();
61 while ($o = db_fetch_object($q))
62 {
63 $vocabularies["$o->vid"] = "$o->name - $o->description" ;
64 }
65
66 $form[self::VAR_VOCABULARY] = array
67 (
68 '#type' => 'select',
69 '#title' => t('Vocabularies'),
70 '#default_value' => variable_get(self::VAR_VOCABULARY, 0),
71 '#options' => $vocabularies,
72 '#description' => t('Vocabularies to be used for the generation of blocks. A block will be defined for each term in each vocabulary selected here.'),
73 '#required' => TRUE,
74 '#size' => count($vocabularies),
75 '#multiple' => TRUE,
76 );
77
78 $form[self::VAR_PONDERATED] = array
79 (
80 '#type' => 'select',
81 '#title' => t('Ponderate sorting by'),
82 '#options' => array
83 (
84 0 => t('Creation date'),
85 ),
86 );
87 if (module_exists('statistics'))
88 {
89 $form[self::VAR_PONDERATED]['#options'] += array
90 (
91 1 => t('Total views'),
92 2 => t('Daily views'),
93 );
94 $form[self::VAR_PONDERATED] += array
95 (
96 '#default_value' => variable_get(self::VAR_PONDERATED, 0),
97 '#description' => t('Sort displayed news by descending creation date, possibly ponderated by total number of views and daily views. This ponderation only affects sorting, not news selection, which are always selected by creation date.'),
98 );
99 }
100 else
101 {
102 $form[self::VAR_PONDERATED] += array
103 (
104 '#default_value' => 0,
105 '#description' => t('Sort displayed news by descending creation date. Additional sortings will be available if you !enable. It is currently disabled.',
106 array('!enable' => l(t('enable statistics.module'), 'admin/build/modules'))),
107 );
108 }
109
110 $form[self::VAR_LIFETIME] = array
111 (
112 '#type' => 'textfield',
113 '#title' => t('Lifetime of news before expiration'),
114 '#default_value' => variable_get(self::VAR_LIFETIME, self::DEF_LIFETIME),
115 '#size' => 3,
116 '#max_length' => 3,
117 '#description' => t('The number of days a news item remains displayed in blocks.'),
118 '#required' => TRUE
119 ) ;
120 $form[self::VAR_MAX_ROWS] = array
121 (
122 '#type' => 'textfield',
123 '#title' => t('Maximum number of rows per block'),
124 '#default_value' => variable_get(self::VAR_MAX_ROWS, self::DEF_MAX_ROWS),
125 '#size' => 3,
126 '#max_length' => 3,
127 '#description' => t('The maximum number of rows that the builtin theme will display in a block.'),
128 '#required' => TRUE
129 ) ;
130 $form[self::VAR_FEED] = array
131 (
132 '#type' => 'checkbox',
133 '#title' => t('Display RSS feed icon on taxonews blocks'),
134 '#default_value' => variable_get(self::VAR_FEED, TRUE),
135 '#description' => t('This setting allows taxonews to display a RSS feed icon on each non empty block. Default value is enabled.'),
136 );
137 $form[self::VAR_SHOW_NAME] = array
138 (
139 '#type' => 'checkbox',
140 '#title' => t('Prepend taxonews module name in block list'),
141 '#default_value' => variable_get(self::VAR_SHOW_NAME, TRUE),
142 '#description' => t('This allows the modules to appear grouped on the block list at Administer/Blocks, to avoid cluttering.'),
143 );
144 $form[self::VAR_SHOW_ARCHIVE] = array
145 (
146 '#type' => 'checkbox',
147 '#title' => t('Include "Archive" link to older articles in block'),
148 '#default_value' => variable_get(self::VAR_SHOW_ARCHIVE, TRUE),
149 '#description' => t('If articles matching the term exist, but cannot be placed in the block, for instance if they are too old, add an "Archive" link at the end of the block. The link will not be shown if no matching article exists.'),
150 );
151 $form[self::VAR_SHOW_EMPTY] = array
152 (
153 '#type' => 'checkbox',
154 '#title' => t('Include blocks with currently no matching nodes in the blocks list'),
155 '#default_value' => variable_get(self::VAR_SHOW_EMPTY, TRUE),
156 );
157 $form['advanced'] = array
158 (
159 '#type' => 'fieldset',
160 '#collapsible' => TRUE,
161 '#collapsed' => TRUE,
162 '#title' => t('Advanced settings'),
163 );
164 $form['advanced'][self::VERSION] = array(
165 '#value' => t('<p>This site is running taxonews.module version: @version.</p>',
166 array('@version' => self::VERSION))
167 );
168
169 return system_settings_form($form);
170 }
171
172 /**
173 * Is there any published data matching that term ?
174 *
175 * @param int $tid
176 * @param array $nids Array of displayed nodes, to ignore
177 * @return boolean
178 *
179 * The function expects the nids of the already displayed nodes
180 * to be the keys of the $items array, so it can ignore them
181 */
182 static function archiveExists($tid, $items = array())
183 {
184 $sq = 'SELECT COUNT(tn.nid) '
185 . 'FROM {term_node} tn '
186 . 'INNER JOIN {node} n ON tn.vid = n.vid '
187 . 'WHERE tn.tid = %d AND n.status = 1 ';
188
189 if (count($items))
190 {
191 $sq .= 'AND tn.nid NOT IN (' . db_placeholders(array_keys($items)) . ') ';
192 }
193 $q = db_query_range($sq, $tid, 0, 1);
194 $ret = db_result($q);
195 $ret = $ret > 0;
196 return $ret;
197 }
198
199 /**
200 * Configure the taxonews block identified by $delta
201 *
202 * @param mixed $delta
203 * @return array
204 */
205 static function blockConfigure($delta)
206 {
207 $description = t('By default, blocks without matching content are not displayed. This setting allows you to force a static content.');
208
209 $form [self::VAR_EMPTY_MESSAGES] = array
210 (
211 '#title' => t('Text to be displayed if block has no matching content'),
212 '#type' => 'textfield',
213 '#default_value' => self::getEmptyMessages($delta),
214 '#size' => 60,
215 '#max_length' => 60,
216 '#description' => $description,
217 );
218
219 return $form;
220 }
221
222 /**
223 * Generate block list for hook_block
224 *
225 * @return array
226 * @see taxonews_block
227 */
228 static function blockList()
229 {
230 $arBlocks = array() ;
231 $arTerms = self::getTerms();
232 if (empty($arTerms))
233 {
234 $modulePath = dirname(drupal_get_filename('module', 'taxonews'));
235 drupal_set_message(
236 t('WARNING: You will not be able to configure taxonews blocks until you !configure and !define in the taxonews vocabularies. You might want to refer to !install.',
237 array(
238 '!configure' => l(t('configure taxonews'), self::PATH_SETTINGS),
239 '!define' => l(t('define terms'), 'admin/content/taxonomy'),
240 '!install' => l('INSTALL.txt', "$modulePath/INSTALL.txt")
241 )
242 ),
243 'error');
244 unset($modulePath);
245 return $arBlocks; // No need to run the following code: there is no block
246 }
247
248 $prefix = variable_get(self::VAR_SHOW_NAME, TRUE) ? 'Taxonews/' : '' ;
249 $showEmpty = variable_get(self::VAR_SHOW_EMPTY, TRUE);
250 $sq = 'SELECT tn.tid, COUNT(tn.nid) AS cnt '
251 . 'FROM {term_node} tn '
252 . 'INNER JOIN {node} n ON tn.vid = n.vid '
253 . 'WHERE n.status = 1 AND tn.tid IN (' . db_placeholders($arTerms) . ') '
254 . 'GROUP BY 1 ';
255 $arCounts = array();
256 $q = db_query($sq, array_keys($arTerms));
257 while ($o = db_fetch_object($q))
258 {
259 $arCounts[$o->tid] = $o->cnt;
260 }
261
262 foreach ($arTerms as $tid => $term)
263 {
264 if ((!isset($arCounts[$tid]) || ($arCounts[$tid] == 0)) && !$showEmpty)
265 {
266 continue; // Do not display the block, do not increment the counter
267 }
268 $arBlocks[$tid] = array() ;
269 $arBlocks[$tid]['info'] = "$prefix$term->vocabulary_name/$term->name" ;
270 $arBlocks[$tid]['cache'] = BLOCK_CACHE_GLOBAL;
271 }
272
273 return $arBlocks ;
274 }
275
276 /**
277 * Save the configuration (i.e. the text) of the selected block
278 *
279 * @param mixed $delta Usually an int
280 * @param $edit The edit form fields
281 * @return void
282 * @see hook_block()
283 */
284 static function blockSave($delta, $edit)
285 {
286 $arEmptyMessages = self::getEmptyMessages();
287 $arEmptyMessages[$delta] = $edit[self::VAR_EMPTY_MESSAGES];
288 variable_set(self::VAR_EMPTY_MESSAGES, $arEmptyMessages);
289 }
290
291 /**
292 * Generate block contents for the passed-in delta
293 *
294 * Note: 86400 = 24*60*60 = seconds in one day
295 *
296 * @param mixed $delta Drupal block delta
297 * @return string HTML
298 */
299 static function blockView($delta)
300 {
301 $arTerms = self::getTerms() ;
302
303 /**
304 * ponderation must only be taken into account if statistics module exists
305 * because the module may have been online, allowing ponderation to be set,
306 * then removed, causing stats to no longer be updated
307 */
308 $ponderation = module_exists('statistics')
309 ? variable_get(self::VAR_PONDERATED, 0)
310 : 0;
311
312 /**
313 * There's a small trick for case 0|default: n.created is not a views count, but can
314 * be used exactly like one: more recent nodes will have a higher value in
315 * this field, so we can sort on it for display just like we sort on actual
316 * view counts otherwise. That way we only have one sort rule for all three
317 * different cases.
318 */
319 switch ($ponderation)
320 {
321 case 1: // by total views
322 $sq = 'SELECT n.nid, n.title, td.name, '
323 . ' nc.totalcount AS views '
324 . 'FROM {node} n '
325 . ' INNER JOIN {term_node} tn ON n.vid = tn.vid '
326 . ' INNER JOIN {term_data} td ON tn.tid = td.tid '
327 . ' LEFT JOIN {node_counter} nc on n.nid = nc.nid ' // Some nodes may never have been counted yet
328 . ' /* ignore current time: %d */ ';
329 break;
330
331 case 2: // by daily views since creation.
332 $sq = 'SELECT n.nid, n.title, td.name, '
333 . ' nc.totalcount*86400/(%d-n.created) AS views '
334 . 'FROM {node} n '
335 . ' INNER JOIN {term_node} tn ON n.vid = tn.vid '
336 . ' INNER JOIN {term_data} td ON tn.tid = td.tid '
337 . ' LEFT JOIN {node_counter} nc on n.nid = nc.nid '; // Some nodes may never have been counted yet
338 break;
339
340 case 0: // not ponderated
341 default: // ignore invalid values
342 $sq = 'SELECT n.nid, n.title, td.name, '
343 . ' n.created AS views '
344 . 'FROM {node} n '
345 . ' INNER JOIN {term_node} tn ON n.vid = tn.vid '
346 . ' INNER JOIN {term_data} td ON tn.tid = td.tid '
347 . ' /* ignore current time: %d */ ';
348 break;
349 }
350
351 $sq .= 'WHERE td.tid = %d '
352 . ' AND (n.created > %d) '
353 . ' AND (n.status = 1) '
354 . ' ORDER BY n.created DESC, n.changed DESC ';
355
356 $creation = time() - 86400 * variable_get(self::VAR_LIFETIME, self::DEF_LIFETIME);
357 $ret = array();
358 $items = array();
359 if ($q = db_query_range ($sq,
360 time(), $arTerms[$delta]->tid, $creation, // current time, tid, lifetime
361 0, variable_get(self::VAR_MAX_ROWS, self::DEF_MAX_ROWS) // range
362 ))
363 {
364 while ($o = db_fetch_object ($q))
365 {
366 $items[$o->nid] = l($o->title, 'node/' . $o->nid) ;
367
368 // $o->name is undefined outside the loop, and re-initializing it for every
369 // value in the loop seems to actually be the cheapest way of keeping it, although
370 // its value is the same for all rows in the result set
371 $ret['subject'] = $o->name ;
372 }
373 krsort($items); // sort descending on views
374 }
375 if (count($items) == 0)
376 {
377 $items = NULL;
378 $term = taxonomy_get_term($arTerms[$delta]->tid);
379 $ret['subject'] = $term->name;
380 }
381
382 $ret['content'] = theme('taxonews_block_view', $delta, $items, $arTerms[$delta]->tid);
383 return $ret;
384 }
385
386 /**
387 * Return the list of messages to be issued when an empty taxonews blocks is
388 * to be built
389 *
390 * @param mixed $delta
391 * @return array
392 */
393 static function getEmptyMessages($delta = NULL)
394 {
395 static $arEmptyMessages;
396
397 if (empty($arEmptyMessages))
398 {
399 $arEmptyMessages = variable_get(self::VAR_EMPTY_MESSAGES, array());
400 }
401
402 $ret = isset($delta)
403 ? (array_key_exists($delta, $arEmptyMessages) ? $arEmptyMessages[$delta] : NULL)
404 : $arEmptyMessages;
405
406 return $ret;
407 }
408
409 /**
410 * Query for terms in the vocabularies selected in settings
411 *
412 * @return array [tid, name]
413 */
414 static function getTerms()
415 {
416 static $arTerms = array();
417
418 if (empty($arTerms))
419 {
420 $arVids = variable_get(self::VAR_VOCABULARY, array());
421 $sq = 'SELECT td.tid, td.name, '
422 . ' v.name AS vocabulary_name '
423 . 'FROM {term_data} td '
424 . ' INNER JOIN {vocabulary} v ON td.vid = v.vid '
425 . 'WHERE td.vid IN (' . db_placeholders($arVids) . ') '
426 . 'ORDER BY 2, 1 ';
427 $q = db_query($sq, $arVids);
428 while ($o = db_fetch_object($q))
429 {
430 $arTerms[$o->tid] = $o;
431 }
432 }
433
434 return $arTerms;
435 }
436 } // end of class Taxonews
437
438 /**
439 * Until a more generic way to use class methods as callbacks is available
440 * or included in core
441 *
442 * @param array $formState New Drupal 6 data. Unused
443 * @param string $method
444 * @return mixed
445 */
446 function _taxonews_form_rerouter($formState, $method)
447 {
448 return Taxonews::$method();
449 }
450
451 /**
452 * -----------------------------------------------------------------------------
453 * Drupal hooks below
454 * -----------------------------------------------------------------------------
455 */
456
457 /**
458 * Implement hook_help
459 *
460 * @param string $path
461 * @param string $arg
462 * @return string
463 */
464 function taxonews_help($path, $arg)
465 {
466 $ret = NULL;
467 $help = t('<p>Publish node teasers into blocks based on a taxonomy vocabulary.</p>') ;
468 switch ($path)
469 {
470 case 'admin/help#taxonews':
471 $ret = $help
472 . t('<p>This module creates blocks containing a selection of node titles, linking to the node themselves. Blocks are created for each taxonomy term in the vocabularies enabled for block generation.</p>')
473 . t('<p>Settings allow the administrator to choose the age limit for nodes included in the blocks, as well as various settings to handle special cases like empty blocks.</p>')
474 . t('<p>By default, nodes are displayed in reverse chronological order. If statistics.module is enabled and node counts are active, nodes can also be displayed by descending order of total views or daily views since their creation. The node selection process by creation date is not affected by these sorting rules.</p>');
475 break;
476 case 'admin/modules#description':
477 $ret = $help;
478 break;
479 }
480 return $ret;
481 }
482
483 /**
484 * implement hook_block
485 *
486 * @param string $op list|view|configure|save
487 * @param int $delta
488 * @param array $edit
489 * @return mixed either void or HTML for blocks/block list
490 * @see Taxonews::blockList
491 * @see Taxonews::blockView
492 */
493 function taxonews_block($op = 'list', $delta = 0, $edit = array())
494 {
495 switch ($op)
496 {
497 case 'list': $ret = Taxonews::blockList(); break;
498 case 'view': $ret = Taxonews::blockView($delta); break;
499 case 'configure': $ret = Taxonews::blockConfigure($delta); break;
500 case 'save': $ret = Taxonews::blockSave($delta, $edit); break;
501 default: $ret = NULL;
502 }
503 return $ret;
504 }
505
506 /**
507 * Implement the modified Drupal 6 hook_menu
508 *
509 * @return array
510 */
511 function taxonews_menu()
512 {
513 $items[Taxonews::PATH_SETTINGS] = array
514 (
515 'title' => 'Taxonews',
516 'description' => 'Define the various parameters used by the taxonews module',
517 'page callback' => 'drupal_get_form',
518 'page arguments' => array('_taxonews_form_rerouter', 'adminSettings'),
519 'access arguments' => array('administer site configuration'),
520 'type' => MENU_NORMAL_ITEM,
521 );
522
523 return $items;
524 }
525
526 /**
527 * Implement the new Drupal 6 hook_theme
528 *
529 * @return array
530 */
531 function taxonews_theme($existing = array(), $type = 'module', $theme = 'taxonews')
532 {
533 $ret = array
534 (
535 'taxonews_block_view' => array
536 (
537 'arguments' => array
538 (
539 'delta' => NULL,
540 'arItems' => array(),
541 'tid' => 0,
542 ),
543 ),
544 );
545 return $ret;
546 }
547
548 /**
549 * Themeable function for TN blocks
550 *
551 * The default theme won't link to the "archive" page if there are no
552 * nodes in addition to the ones already listed. If a site-specific theme
553 * want to display the link anyway, pass null instead of $items to
554 * Taxonews::archiveExists to check for the page contents. Note that in most
555 * normal situations, it will exist and contain at least the nodes already listed
556 * in the block.
557 *
558 * @param mixed $delta the block delta, usually integer but now always
559 * @param array $items A possibly empty array of links
560 * @param int $tid The tid for the taxonomy term used to build the block
561 * @see Taxonews::archiveExists
562 *
563 */
564 function theme_taxonews_block_view($delta, $arItems = array(), $tid)
565 {
566 $ret = empty($arItems)
567 ? Taxonews::getEmptyMessages($delta)
568 : theme('item_list', $arItems);
569
570 if (!empty($ret) && Taxonews::archiveExists($tid, $arItems))
571 {
572 if (variable_get(Taxonews::VAR_SHOW_ARCHIVE, TRUE))
573 {
574 $ret .= '<span class="more-link">'
575 . l(t('Archive'), "taxonomy/term/$tid", array('absolute' => TRUE))
576 . '</span> ' ;
577 }
578 }
579
580 if (!empty($ret) && variable_get(Taxonews::VAR_FEED, TRUE))
581 {
582 $ret .= theme('feed_icon', url("taxonomy/term/$tid/0/feed"), t('RSS feed'));
583 }
584
585 $ret = empty($ret)
586 ? NULL
587 : "<div class=\"block-taxonews\">$ret</div>\n";
588
589 return $ret;
590 }
591
592 error_reporting($_taxonewsErrorReporting);
593 unset($_taxonewsErrorReporting);

  ViewVC Help
Powered by ViewVC 1.1.2