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

Contents of /contributions/modules/category_aggregator/category_aggregator.module

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


Revision 1.4 - (show annotations) (download) (as text)
Sat Sep 22 12:53:41 2007 UTC (2 years, 2 months ago) by kaustubhsrikanth
Branch: MAIN
CVS Tags: DRUPAL-5--0-1, HEAD
Changes since 1.3: +10 -9 lines
File MIME type: text/x-php
Bug Fixes
1 <?php
2
3 // $Id: category_aggregator.module,v 1.4.2.18 2007/04/28 21:32:13 mistknight Exp $
4
5 /**
6 * A number of defs to ease naming
7 */
8 define("FEEDS_ARENT_ASSIGNED_TERM_UNDER_ACTIVE_VOCABULARY", 1);
9 define("FTP_REQUEST_FAILED", 2);
10 define("HTTP_REQUEST_FAILED", 3);
11 define("MALFORMED_XML", 4);
12 define("category_aggregator_FAILED", 5);
13 define("EMPTY_IMAGE", 6);
14 define("COULD_NOT_WRITE_TO_FILE", 7);
15
16 // These two variables are needed to get the etag and/or last modified
17
18 static $etag;
19 static $last_modified;
20
21 /**
22 * Implementation of hook_help
23 */
24 function category_aggregator_help($section)
25 {
26 switch ($section)
27 {
28 case 'admin/help#category_aggregator':
29 $output = '<p>'. t('The category_aggregator module is a module that is responsible for aggregating any XML feed, custom feeds need a feed_handler. Check the accompanying readme file.') .'</p>';
30 $output .= '<p>' .t('Visit the <a href="!admin-settings">settings</a> page to tweak its behavior', array('!admin-settings'=> url('admin/settings/category_aggregator')));
31 return $output;
32 }
33 }
34
35
36 /**
37 * Implementation of hook_perm().
38 */
39 function category_aggregator_perm()
40 {
41 return array('manage category aggregator feeds', 'manage category aggregator items', 'view category aggregator items',
42 'manage own feed items', 'manage feed items', 'specify feed categories');
43 }
44
45 /**
46 * Implementation of hook_node_info().
47 */
48 function category_aggregator_node_info()
49 {
50 return array
51 (
52 'category_aggregator_feed' => array('name' => t('Feed'), 'module' => 'category_aggregator_feed', 'description' => t('Represents the feed the site aggregates from. Items are generated from feeds during cron runs.'), 'has_title' => TRUE, 'title_label' => t('Feed name'), 'has_body' => TRUE, 'body_label' => t('Description')),
53 'category_aggregator_item' => array('name' => t('Feed Item'), 'module' => 'category_aggregator_item', 'description' => t('Items are usually auto-generated on cron runs, but this type exists in case you would like to manually add an item.'))
54 );
55 }
56
57 /**
58 * Implementation of hook_menu().
59 *
60 */
61 function category_aggregator_menu($may_cache)
62 {
63 $items = array();
64
65 if (!$may_cache)
66 {
67 $items[] = array(
68 'path' => 'admin/settings/category_aggregator',
69 'title' => t('Category Aggregator'),
70 'description' => t('These options control the general behavior of the category aggregator module.'),
71 'callback' => 'drupal_get_form',
72 'callback arguments' => array('category_aggregator_admin_settings'),
73 'access' => user_access('administer site configuration'),
74 'type' => MENU_NORMAL_ITEM);
75 }
76
77 return $items;
78 }
79
80 /**
81 * This is the function that will be called instead of the old hook_settings
82 */
83
84 function category_aggregator_admin_settings()
85 {
86 $form = array();
87
88 $vocabs = taxonomy_get_vocabularies('category_aggregator_feed');
89
90 foreach ($vocabs AS $vid => $vocab_object)
91 $vocabs[$vid] = $vocab_object->name;
92
93 $form['category_aggregator_current_vid'] = array(
94 '#type' => 'select',
95 '#title' => t('category aggregator feed types'),
96 '#default_value' => variable_get('category_aggregator_current_vid', -1),
97 '#options' => $vocabs,
98 '#description' => t('This vocabulary contains the XML types -as terms- that the module can aggregate from.'),
99 '#required' => TRUE
100 );
101
102 $form['category_aggregator_item_time_to_live'] = array(
103 '#type' => 'textfield',
104 '#default_value' => variable_get('category_aggregator_item_time_to_live', 30),
105 '#required' => TRUE,
106 '#title' => t('Please specify the number of DAYS an item will live'),
107 '#description' => t('If feed is set to delete old items, this variable will determine the age after which an item will be deleted.')
108 );
109
110 $form['category_aggregator_enable_logging'] = array(
111 '#type' => 'checkbox',
112 '#default_value' => variable_get('category_aggregator_enable_logging', 1),
113 '#title' => t('Log Operations'),
114 '#description' => t('After moving to production, you may wish to disable this if you feel everything is stable enough. But it\'s recommended you keep it on. Fatal errors will ALWAYS be logged.')
115 );
116
117 $form['category_aggregator_image_to_display'] = array(
118 '#type' => 'textfield',
119 '#default_value' => variable_get('category_aggregator_image_to_display', 'preview'),
120 '#required' => TRUE,
121 '#title' => t('Image to display'),
122 '#description' => t('This is the image size that will be displayed on aggregated items\' node page for feeds that aggregate images.')
123 );
124
125 $form['category_aggregator_enable_cron'] = array(
126 '#type' => 'checkbox',
127 '#default_value' => variable_get('category_aggregator_enable_cron', 1),
128 '#title' => t('Enable Cron'),
129 '#description' => t('Remove this check if you want to stop category aggregator cron temporarily.')
130 );
131
132 $form['category_aggregator_feeds_to_refresh_per_cron'] = array(
133 '#type' => 'textfield',
134 '#default_value' => variable_get('category_aggregator_feeds_to_refresh_per_cron', 7),
135 '#required' => TRUE,
136 '#title' => t('The number of feeds to refresh every cron'),
137 '#description' => t('This is important if you have many feeds that may slow the site.').'<br />'.
138 t('All feeds will eventually be processed, so this option will simply slow the process down to minimize effects on the site. But it will delay the time needed for all feeds to refresh.')
139 );
140
141 $form['category_aggregator_feed_refresh_cooldown'] = array(
142 '#type' => 'textfield',
143 '#default_value' => variable_get('category_aggregator_feed_refresh_cooldown', 0),
144 '#required' => TRUE,
145 '#title' => t('Time Between Feeds'),
146 '#description' => t('Does your server die when a refresh occurs? Try setting this IN SECONDS.')
147 );
148
149 $form['category_aggregator_feed_global_categories'] = array(
150 '#type' => 'textfield',
151 '#default_value' => variable_get('category_aggregator_feed_global_categories', ''),
152 '#title' => t('Feed Global Categotries/Tags'),
153 '#description' => t('Specify the Global categories/Tags. '.
154 'You can enter multiple categories seprated by comma Ex.Social Tool, Startup')
155 );
156
157 return system_settings_form($form);
158 }
159
160 /**
161 * This function validates the settings form
162 */
163 function category_aggregator_validate_settings($form)
164 {
165 if (!is_numeric($form['category_aggregator_item_time_to_live']))
166 form_set_error('category_aggregator_item_time_to_live', t('This field must be numeric.'));
167
168 if ($form['category_aggregator_item_time_to_live'] <= 0)
169 form_set_error('category_aggregator_item_time_to_live', t('This field must be a positive number.'));
170
171 if (!is_numeric($form['category_aggregator_feeds_to_refresh_per_cron']))
172 form_set_error('category_aggregator_feeds_to_refresh_per_cron', t('This field must be numeric.'));
173
174 if ($form['category_aggregator_feeds_to_refresh_per_cron'] <= 0)
175 form_set_error('category_aggregator_feeds_to_refresh_per_cron', t('This field must be a positive number.'));
176
177 if (!is_numeric($form['category_aggregator_feed_refresh_cooldown']))
178 form_set_error('category_aggregator_feed_refresh_cooldown', t('This field must be numeric.'));
179
180 if ($form['category_aggregator_feed_refresh_cooldown'] < 0)
181 form_set_error('category_aggregator_feed_refresh_cooldown', t('This field must be positive or zero.'));
182 }
183
184 /**
185 * Implementation of hook_cron().
186 */
187 function category_aggregator_cron()
188 {
189 if (!variable_get('category_aggregator_enable_cron', TRUE))
190 {
191 watchdog('category_aggregator', 'You seem to have disabled category_aggregator from category_aggregator settings.');
192 return;
193 }
194 $vid = variable_get('category_aggregator_current_vid', -1);
195 if ($vid == -1)
196 {
197 watchdog('category_aggregator', 'For some reason, category_aggregator\'s vocabulary is not set.'.
198 'Try visiting category_aggregator\'s settings to set it!');
199 return;
200 }
201
202 $max_exec_time = ini_get('max_execution_time');
203 $max_exec_time = (int)$max_exec_time;
204
205 if (!ini_get('safe_mode'))
206 set_time_limit(20 * 60);
207
208 $feeds = db_query_range("SELECT n.nid FROM {category_aggregator_feed} af, {node} n ".
209 "WHERE af.nid = n.nid AND af.enabled = 'yes' AND (UNIX_TIMESTAMP() - af.last_refreshed) >= af.refresh_interval * 60 ORDER BY af.last_refreshed", 0, variable_get('category_aggregator_feeds_to_refresh_per_cron', 7));
210
211 while ($feed_nid = db_fetch_object($feeds)->nid)
212 {
213 db_query("UPDATE {category_aggregator_feed} SET last_refreshed = %d WHERE nid = %d", time(), $feed_nid);
214
215 $feed = node_load($feed_nid);
216 category_aggregator_feed_prepare($feed);
217
218 try {
219 $success = _category_aggregator_parse($feed, $vid);
220
221 if (is_null($success)) continue;
222
223 if (!$success)
224 throw new Exception('Something wrong occured during the category_aggregator process!', category_aggregator_FAILED);
225
226 if (variable_get('category_aggregator_enable_logging', TRUE))
227 watchdog('category_aggregator', 'category_aggregator has finished aggregating items from '.$feed->title);
228
229 // using this instead of node_save for performance reasons
230 db_query("UPDATE {category_aggregator_feed} SET etag = '%s', ".
231 "last_modified = %d, last_refreshed = %d WHERE nid = %d", $feed->etag,
232 $feed->last_modified, time(), $feed->nid);
233
234 if ($feed->delete_old_items)
235 {
236 $items_to_be_deleted = db_query("SELECT n.nid FROM {node} n, ".
237 "{category_aggregator_item} ai WHERE n.nid = ai.nid AND ai.fid = %d AND ".
238 "(UNIX_TIMESTAMP() - n.created) >= %d", $feed->nid,
239 variable_get('category_aggregator_item_time_to_live', 30) * 24 * 60 * 60);
240
241 while ($item = db_fetch_object($items_to_be_deleted))
242 node_delete($item->nid);
243 }
244 }
245 catch (Exception $e) {
246 watchdog('category_aggregator', $e->getMessage(), WATCHDOG_ERROR, l(t('view'), "node/$feed->nid"));
247 }
248 }
249
250 if (!ini_get('safe_mode') && is_numeric($max_exec_time) && $max_exec_time > 0)
251 set_time_limit($max_exec_time);
252 }
253
254 /* Following are the category_aggregator_feed hooks */
255
256 /**
257 * Implementation of hook_access().
258 */
259
260 function category_aggregator_feed_access($op, $node)
261 {
262
263 return user_access('manage category aggregator feeds');
264
265 }
266
267 /**
268 * Implementation of hook_form().
269 */
270
271 function category_aggregator_feed_form(&$node, &$param)
272 {
273 global $category_perm;
274
275 $form = array();
276
277 $form['title'] = array (
278 '#type' => 'textfield',
279 '#default_value' => $node->title,
280 '#required' => TRUE,
281 '#title' => t(node_get_types('type', 'category_aggregator_feed')->title_label),
282 '#description' => t('Enter something descriptive to identify your feed.')
283 );
284
285 $form['body_filter']['body'] = array (
286 '#type' => 'textarea',
287 '#default_value' => $node->body,
288 '#required' => FALSE,
289 '#title' => t(node_get_types('type', 'category_aggregator_feed')->body_label),
290 '#description' => t('Any description for your feed.')
291 );
292
293 $form['body_filter']['format'] = filter_form($node->format);
294
295 $form['original_author'] = array(
296 '#type' => 'textfield',
297 '#default_value' => $node->original_author,
298 '#required' => TRUE,
299 '#title' => t('Original Author'),
300 '#description' => t('Please specify the original author for the items of this feed. '.
301 'This is used when none is present in the feed itself.')
302 );
303
304
305 if (user_access('specify feed categories'))
306 {
307 $form['categories'] = array (
308 '#type' => 'textfield',
309 '#default_value' => $node->categories,
310 '#title' => t('User Specified Categories/Tags.'),
311 '#description' => t('Specify the Tag/Categories for which you want your feed items to be aggregated. '.
312 'You can enter multiple categories seprated by comma. Ex: Social Tools,Startup.')
313 );
314
315 $form ['global_categories'] = array (
316 '#type' => 'textfield',
317 '#disabled' => 'yes',
318 '#title'=> 'Global Tags/Categories',
319 '#default_value' => variable_get('category_aggregator_feed_global_categories',''),
320 '#description' => t('Feeds will be aggreagted based on these global categories in addition to User Specified Categories/Tags. '.
321 'If user does not specify a category, feeds will be aggregated on the basis of Global Tags/Categories. '.
322 'A blank field indicates no global categories has been defined. '.
323 'If User Specified Categories/Tags field and Global Tags/Categories both are empty then the feeds will be '.
324 'aggregated irrespective of the Categories/Tags.')
325 );
326
327 }
328
329 else {
330
331 $form ['global_categories'] = array (
332 '#type' => 'textfield',
333 '#disabled' => 'yes',
334 '#title'=> 'Global Tags/Categories',
335 '#default_value' => variable_get('category_aggregator_feed_global_categories',''),
336 '#description' => t('Feeds will be aggreagted based on these global categories. '.
337 'A blank field indicates no global categories has been defined and the feeds will be '.
338 'aggregated irrespective of the Categories/Tags.')
339 );
340
341 }
342
343 $form['url'] = array (
344 '#type' => 'textfield',
345 '#default_value' => $node->url,
346 '#required' => TRUE,
347 '#title' => t('Feed URL'),
348 '#description' => t('Please provide the feed URL.')
349 );
350
351 $form['authentication'] = array (
352 '#type' => 'fieldset',
353 '#title' => t('Authentication Settings'),
354 '#collapsible' => TRUE,
355 '#collapsed' => FALSE,
356 '#tree' => FALSE
357 );
358
359 $form['authentication']['username'] = array(
360 '#type' => 'textfield',
361 '#default_value' => $node->username,
362 '#required' => FALSE,
363 '#description' => t('If your site uses authentication, please specify the username here.'),
364 '#title' => t('Enter Username')
365 );
366
367 $description = t('If your site uses authentication, please specify the password here.');
368
369 // This indicates we're in edit mode, in which case I'd like to add an additional directive
370 if ($node->password)
371 $description .= '<br />'.
372 t('(leave empty to preserve value)');
373
374 $form['authentication']['password'] = array(
375 '#type' => 'password',
376 '#default_value' => $node->password,
377 '#required' => FALSE,
378 '#description' => $description,
379 '#title' => 'Enter Password'
380 );
381
382 $description = t('If your site uses authentication, please retype your password here.');
383
384 // This indicates we're in edit mode, in which case I'd like to add an additional directive
385 if ($node->password)
386 $description .= '<br />'.
387 t('(leave empty to preserve value)');
388
389 $form['authentication']['repeat_password'] = array(
390 '#type' => 'password',
391 '#default_value' => $node->password,
392 '#required' => FALSE,
393 '#description' => $description,
394 '#title' => t('Retype Your Password')
395 );
396
397 $form['refresh_interval'] = array(
398 '#type' => 'textfield',
399 '#default_value' => $node->refresh_interval,
400 '#required' => TRUE,
401 '#title' => t('Refresh Interval'),
402 '#description' => t('Please specify the refresh interval IN MINUTES.')
403 );
404
405 $form['title_as_guid_interval'] = array(
406 '#type' => 'textfield',
407 '#default_value' => $node->title_as_guid_interval ? $node->title_as_guid_interval : 0,
408 '#required' => TRUE,
409 '#title' => t('Title as GUID interval'),
410 '#description' => t('When a feed contains no GUID or image URL, then the title is it\'s guid. This field '.
411 'specifies the time period the title is considered as a GUID. If you provide a GUID or image URL then '.
412 'this field value is irrelevant. If not, then you can set this to 0 which would prevent any new article '.
413 'with the same title from ever entering (unless the old one is deleted) - this could also be achieved by sending '.
414 'the title as the GUID - , or you can set it to the time IN HOURS the title will act as a GUID, after which '.
415 'any other article still in the feed with the same title will enter the system.')
416 );
417
418 $form['enabled'] = array(
419 '#type' => 'checkbox',
420 '#default_value' => $node->enabled,
421 '#title' => t('Enabled'),
422 '#description' => t('Use this checkbox to enable/disable refreshing this feed.')
423 );
424
425 $form['publish_new_items'] = array(
426 '#type' => 'checkbox',
427 '#default_value' => $node->publish_new_items,
428 '#description' => t('Use this checkbox to publish all aggregated feed items.'),
429 '#title' => t('Publish items')
430 );
431
432 $form['link_items_to_original_urls'] = array(
433 '#type' => 'checkbox',
434 '#default_value' => $node->link_items_to_original_urls,
435 '#description' => t('Link items generated from this feed to original URLs.'),
436 '#title' => t('Link items to URLs')
437 );
438
439 $form['aggregate_to_moderation_queue'] = array(
440 '#type' => 'checkbox',
441 '#default_value' => $node->aggregate_to_moderation_queue,
442 '#description' => t('Use this checkbox to send aggregated items to the moderation queue. Weather they appear on the site or not depends on the \'publish items\', not weather they are on the moderation queue.'),
443 '#title' => t('Add aggregated items to moderation queue')
444 );
445
446 $form['sticky_items'] = array(
447 '#type' => 'checkbox',
448 '#default_value' => $node->sticky_items,
449 '#description' => t('All items aggregated from this feed will be sticky.'),
450 '#title' => t('Sticky items')
451 );
452
453 $form['enable_comments_on_articles'] = array(
454 '#type' => 'checkbox',
455 '#default_value' => $node->enable_comments_on_articles,
456 '#description' => t('All aggregated articles will have commenting set to read/write.'),
457 '#title' => t('Aggregated article comments')
458 );
459
460 $form['enable_comments_on_images'] = array(
461 '#type' => 'checkbox',
462 '#default_value' => $node->enable_comments_on_images,
463 '#description' => t('All aggregated images will have commenting set to read/write.'),
464 '#title' => t('Aggregated images\' comments')
465 );
466
467 $form['delete_old_items'] = array(
468 '#type' => 'checkbox',
469 '#default_value' => $node->delete_old_items,
470 '#description' => t('Use this checkbox to delete items older than (n) days, change this value from the settings. Uncheck and items under this feed will never be deleted until this option is turned on.'),
471 '#title' => t('Delete Old Items')
472 );
473
474 $form['promote_to_frontpage'] = array(
475 '#type' => 'textfield',
476 '#default_value' => $node->promote_to_frontpage ? $node->promote_to_frontpage : 0,
477 '#required' => TRUE,
478 '#title' => t('Promote to frontpage'),
479 '#description' => t('Please specify the number of articles to promote to the frontpage.')
480 );
481
482 $form['item_taxonomies'] = array (
483 '#type' => 'fieldset',
484 '#title' => t('Item Taxonomies'),
485 '#collapsible' => TRUE,
486 '#collapsed' => FALSE,
487 '#tree' => FALSE,
488 '#description' => t('These taxonomies will be assigned to all items under this feed.')
489 );
490
491 $fakeform = array();
492 $fakeform['#node'] = new stdClass();
493
494 if ($node->item_categories)
495 $fakeform['#node']->taxonomy = $node->item_categories;
496
497 // This part will trick taxonomy into believing it's dealing with an category_aggregator_item
498 $fakeform['type']['#value'] = 'category_aggregator_item';
499 $fakeform['#node']->type = 'category_aggregator_item';
500
501 taxonomy_form_alter('category_aggregator_item_node_form', $fakeform);
502
503 $form['item_taxonomies']['item_categories'] = $fakeform['taxonomy'];
504
505 return $form;
506 }
507
508 /**
509 * Implementation of hook_validate().
510 */
511
512 function category_aggregator_feed_validate(&$node)
513 {
514 if (!is_numeric($node->refresh_interval) || $node->refresh_interval < 0)
515 form_set_error('refresh_interval', 'The refresh interval is a numeric value greater than or equal to 0.');
516 else if (!is_numeric($node->title_as_guid_interval) || $node->title_as_guid_interval < 0)
517 form_set_error('title_as_guid_interval', 'The title as GUID interval is a positive numeric or 0.');
518 else if (!is_numeric($node->promote_to_frontpage) || $node->promote_to_frontpage < 0)
519 form_set_error('promote_to_frontpage', 'The number of items to promote to the frontpage is a positive numeric or 0.');
520 else if ($node->password != $node->repeat_password)
521 form_set_error('password', 'Your passwords did not match.');
522 }
523
524 /**
525 * Implementation of hook_submit().
526 */
527
528 function category_aggregator_feed_submit(&$node)
529 {
530 $node->title = check_plain($node->title);
531 $node->original_author = check_plain($node->original_author);
532 $node->categories = check_plain($node->categories);
533
534 $node->enabled = $node->enabled ? 'yes' : 'no';
535 $node->publish_new_items = $node->publish_new_items ? 'yes' : 'no';
536 $node->delete_old_items = $node->delete_old_items ? 'yes' : 'no';
537 $node->link_items_to_original_urls = $node->link_items_to_original_urls ? 'yes' : 'no';
538 $node->aggregate_to_moderation_queue = $node->aggregate_to_moderation_queue ? 'yes' : 'no';
539 $node->sticky_items = $node->sticky_items ? 'yes' : 'no';
540 $node->enable_comments_on_articles = $node->enable_comments_on_articles ? 'yes' : 'no';
541 $node->enable_comments_on_images = $node->enable_comments_on_images ? 'yes' : 'no';
542 $node->etag = '';
543 $node->last_modified = 0;
544
545 $node->title = trim($node->title);
546 $node->original_author = trim($node->original_author);
547 $node->categories = trim($node->categories);
548 $node->url = trim($node->url);
549 $node->username = trim($node->username);
550 $node->password = trim($node->password);
551
552 $node->item_categories = serialize($node->item_categories);
553 }
554
555 /**
556 * Implementation of hook_prepare().
557 */
558
559 function category_aggregator_feed_prepare(&$node)
560 {
561 $node->refresh_interval = $node->refresh_interval ? $node->refresh_interval : 15;
562
563 $node->enabled = $node->enabled ? ($node->enabled == 'yes' ? TRUE : FALSE) : TRUE;
564
565 $node->publish_new_items = $node->publish_new_items ?
566 ($node->publish_new_items == 'yes' ? TRUE : FALSE) : TRUE;
567
568 $node->delete_old_items = $node->delete_old_items ?
569 ($node->delete_old_items == 'yes' ? TRUE : FALSE) : FALSE;
570
571 $node->link_items_to_original_urls = $node->link_items_to_original_urls ?
572 ($node->link_items_to_original_urls == 'yes' ? TRUE : FALSE) : TRUE;
573
574 $node->aggregate_to_moderation_queue = $node->aggregate_to_moderation_queue ?
575 ($node->aggregate_to_moderation_queue == 'yes' ? TRUE : FALSE) : FALSE;
576
577 $node->sticky_items = $node->sticky_items ? ($node->sticky_items == 'yes' ? TRUE : FALSE) : FALSE;
578
579 $node->enable_comments_on_articles = $node->enable_comments_on_articles ?
580 ($node->enable_comments_on_articles == 'yes' ? TRUE : FALSE) : FALSE;
581
582 $node->enable_comments_on_images = $node->enable_comments_on_images ?
583 ($node->enable_comments_on_images == 'yes' ? TRUE : FALSE) : FALSE;
584
585 if ($node->item_categories && $node->item_categories != '')
586 $node->item_categories = unserialize($node->item_categories);
587
588 // There's a specific format needed to reconstruct a form, we're creating it now
589 if ($node->item_categories)
590 {
591 $formatted_item_categories = array();
592 if (is_array($node->item_categories))
593 {
594 foreach ($node->item_categories AS $vid => $tids)
595 {
596 if (is_array($tids) && count($tids) > 0)
597 {
598 foreach ($tids AS $tid => $tid)
599 if ($tid > 0)
600 $formatted_item_categories[$tid] = taxonomy_get_term($tid);
601 }
602 else if (is_string($tids))
603 {
604 if ($tids > 0)
605 $formatted_item_categories[$tids] = taxonomy_get_term($tids);
606 }
607 }
608 }
609
610 $node->item_categories = $formatted_item_categories;
611 }
612 }
613
614 /**
615 * Implementation of hook_load().
616 */
617
618 function category_aggregator_feed_load($node)
619 {
620 return db_fetch_object(db_query("SELECT * FROM {category_aggregator_feed} WHERE nid = %d", $node->nid));
621 }
622
623 /**
624 * Implementation of hook_insert().
625 */
626
627 function category_aggregator_feed_insert($node)
628 {
629 db_query("INSERT INTO {category_aggregator_feed} VALUES ".
630 "(%d, '%s', '%s', '%s', '%s', '%s', %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', %d ,'%s', '%s', '%s', '%s', %d, %d)",
631 $node->nid, $node->original_author, $node->categories, $node->url, $node->username, $node->password,
632 $node->refresh_interval, $node->title_as_guid_interval, $node->enabled, $node->publish_new_items,
633 $node->aggregate_to_moderation_queue, $node->sticky_items, $node->enable_comments_on_articles,
634 $node->enable_comments_on_images, $node->promote_to_frontpage, $node->delete_old_items,
635 $node->link_items_to_original_urls, $node->item_categories,
636 '', 0, 0);
637 }
638
639 /**
640 * Implementation of hook_update().
641 */
642
643 function category_aggregator_feed_update($node)
644 {
645 if (trim($node->password) != '')
646 {
647 db_query("UPDATE {category_aggregator_feed} SET original_author = '%s', categories = '%s', url = '%s', ".
648 "username = '%s', password = '%s', refresh_interval = %d, title_as_guid_interval = %d, enabled = '%s', ".
649 "publish_new_items = '%s', aggregate_to_moderation_queue = '%s', sticky_items = '%s', enable_comments_on_articles = '%s', enable_comments_on_images = '%s', promote_to_frontpage = %d, delete_old_items = '%s', item_categories = '%s', ".
650 "last_modified = %d, last_refreshed = %d, link_items_to_original_urls = '%s' WHERE nid = %d",
651 $node->original_author, $node->categories, $node->url, $node->username, $node->password,
652 $node->refresh_interval, $node->title_as_guid_interval, $node->enabled, $node->publish_new_items,
653 $node->aggregate_to_moderation_queue, $node->sticky_items, $node->enable_comments_on_articles,
654 $node->enable_comments_on_images, $node->promote_to_frontpage,
655 $node->delete_old_items, $node->item_categories,
656 ($node->etag && $node->etag != '') ? $node->etag : '',
657 ($node->last_modified && $node->last_modified != 0) ? $node->last_modified : 0,
658 0, $node->link_items_to_original_urls, $node->nid);
659 }
660 else
661 {
662 db_query("UPDATE {category_aggregator_feed} SET original_author = '%s', categories = '%s', url = '%s', ".
663 "username = '%s', refresh_interval = %d, title_as_guid_interval = %d, enabled = '%s', ".
664 "publish_new_items = '%s', aggregate_to_moderation_queue = '%s', sticky_items = '%s', enable_comments_on_articles = '%s', enable_comments_on_images = '%s', promote_to_frontpage = %d, delete_old_items = '%s', item_categories = '%s', ".
665 "etag = '%s', last_modified = %d, last_refreshed = %d, link_items_to_original_urls = '%s' WHERE nid = %d",
666 $node->original_author, $node->categories, $node->url, $node->username,
667 $node->refresh_interval, $node->title_as_guid_interval, $node->enabled, $node->publish_new_items,
668 $node->aggregate_to_moderation_queue, $node->sticky_items, $node->enable_comments_on_articles,
669 $node->enable_comments_on_images, $node->promote_to_frontpage,
670 $node->delete_old_items, $node->item_categories,
671 ($node->etag && $node->etag != '') ? $node->etag : '',
672 ($node->last_modified && $node->last_modified != 0) ? $node->last_modified : 0,
673 0, $node->link_items_to_original_urls, $node->nid);
674 }
675
676 }
677
678 /**
679 * Implementation of hook_delete().
680 */
681
682 function category_aggregator_feed_delete(&$node)
683 {
684 $items = _category_aggregator_get_feed_items($node->nid);
685
686 while ($item = db_fetch_object($items))
687 node_delete($item->nid);
688
689 db_query("DELETE FROM {category_aggregator_feed} WHERE nid = %d", $node->nid);
690 }
691
692 /* Following are the category_aggregator_item hooks */
693
694 /**
695 * Implementation of hook_access().
696 */
697
698 function category_aggregator_item_access($op, $node)
699 {
700 global $user;
701
702 if ($op == 'create') return user_access('manage category aggregator items');
703 if ($op == 'update' || $op == 'delete')
704 {
705 if (user_access('manage feed items')) return TRUE;
706 return (user_access('manage own feed items') && ($user->uid == $node->uid));
707 }
708 if ($op == 'view') return user_access('view category aggregator items');
709 }
710
711 /**
712 * Implementation of hook_form().
713 */
714
715 function category_aggregator_item_form(&$node, &$param)
716 {
717 $form = array();
718
719 $form['title'] = array (
720 '#type' => 'textfield',
721 '#default_value' => $node->title,
722 '#required' => TRUE,
723 '#title' => t(node_get_types('type', 'category_aggregator_item')->title_label),
724 '#description' => t('The title of your feed item.')
725 );
726
727 $form['teaser'] = array (
728 '#type' => 'textarea',
729 '#default_value' => $node->teaser,
730 '#required' => FALSE,
731 '#title' => t('Teaser'),
732 '#description' => t('This is the article\'s teaser.')
733 );
734
735 $form['body_filter']['body'] = array (
736 '#type' => 'textarea',
737 '#default_value' => $node->body,
738 '#required' => FALSE,
739 '#title' => t(node_get_types('type', 'category_aggregator_item')->body_label),
740 '#description' => t('This is the main body of your article.')
741 );
742
743 $form['body_filter']['format'] = filter_form($node->format);
744
745 $form['original_author'] = array(
746 '#type' => 'textfield',
747 '#default_value' => $node->original_author,
748 '#required' => TRUE,
749 '#title' => t('Original Author'),
750 '#description' => t('Specify the author of this feed item.'),
751 );
752
753 $form['url'] = array (
754 '#type' => 'textfield',
755 '#default_value' => $node->url,
756 '#required' => FALSE,
757 '#title' => t('Original URL'),
758 '#description' => t('Provide the URL of the original article if needed.')
759 );
760
761 $form['link_to_original_url'] = array(
762 '#type' => 'checkbox',
763 '#default_value' => $node->link_to_original_url,
764 '#description' => t('Link this item to its original URL (if available).'),
765 '#title' => t('Link to URL')
766 );
767
768 $form['image_id'] = array (
769 '#type' => 'textfield',
770 '#default_value' => $node->image_id,
771 '#required' => FALSE,
772 '#title' => t('Image ID'),
773 '#description' => t('Provide an image id to attach an image.')
774 );
775
776 $description = t('The GUID is a unique string that distingishes your article from '.
777 'all others and prevents a second category_aggregator. Please provide it if available.');
778
779 // This indicates we're in edit mode, in which case I'd like to add an additional directive
780 if ($node->story_guid)
781 $description .= '<br />'.
782 t('(leave empty to preserve value)');
783
784 $form['story_guid'] = array (
785 '#type' => 'textfield',
786 '#default_value' => '',
787 '#required' => FALSE,
788 '#title' => t('GUID'),
789 '#description' => $description
790 );
791
792 $feeds = db_query('SELECT n.nid, n.title FROM {node} n, {category_aggregator_feed} af '.
793 'WHERE n.nid = af.nid');
794
795 if (db_num_rows($feeds) != 0)
796 {
797 $feed_array = array();
798
799 $feed_array[0] = '<none>';
800
801 while ($feed = db_fetch_object($feeds))
802 $feed_array[$feed->nid] = $feed->title;
803
804 $form['fid'] = array (
805 '#type' => 'select',
806 '#default_value' => $node->fid,
807 '#required' => TRUE,
808 '#title' => t('Source Feed'),
809 '#description' => t('Provide the source feed if valid for this item.'),
810 '#options' => $feed_array
811 );
812 }
813
814 return $form;
815 }
816
817 /**
818 * Implementation of hook_validate().
819 */
820
821 function category_aggregator_item_validate(&$node)
822 {
823 if (trim($node->image_id) != '')
824 {
825 if (!is_numeric($node->image_id))
826 form_set_error('image_id', 'The image id must be numeric.');
827 // A node load is simply too costly, so I'm going to directly query instead.
828 else if ($node->image_id != 0 && db_fetch_object(db_query("SELECT COUNT(n.nid) AS image_count FROM {node} n ".
829 "WHERE n.nid = %d AND n.type = 'image'", $node->image_id))->image_count == 0)
830 form_set_error('image_id', 'This image ID seems to be incorrect!');
831 }
832 }
833
834 /**
835 * Implementation of hook_submit().
836 */
837
838 function category_aggregator_item_submit(&$node)
839 {
840 $node->title = check_plain($node->title);
841 $node->teaser = check_plain($node->teaser);
842 $node->original_author = check_plain($node->original_author);
843
844 $node->link_to_original_url = $node->link_to_original_url ? 'yes' : 'no';
845
846 if (!$node->fid)
847 $node->fid = 0;
848
849 if ($node->image_id == '')
850 $node->image_id = 0;
851
852 if (trim($node->story_guid) != '')
853 $node->story_guid = sprintf('%u', crc32($node->story_guid));
854 else
855 $node->story_guid = '';
856 }
857
858 /**
859 * Implementation of hook_prepare().
860 */
861
862 function category_aggregator_item_prepare(&$node)
863 {
864 $node->link_to_original_url = $node->link_to_original_url ?
865 ($node->link_to_original_url == 'yes' ? TRUE : FALSE) : TRUE;
866 }
867
868 /**
869 * Implementation of hook_load().
870 */
871
872 function category_aggregator_item_load($node)
873 {
874 return db_fetch_object(db_query('SELECT * FROM {category_aggregator_item} WHERE nid = %d', $node->nid));
875 }
876
877 /**
878 * Implementation of hook_insert().
879 */
880
881 function category_aggregator_item_insert($node)
882 {
883 db_query("INSERT INTO {category_aggregator_item} (nid, url, link_to_original_url, original_author, ".
884 "story_guid, fid, image_id, image_guid) VALUES (%d, '%s', '%s', '%s', %s, %d, ".
885 "%d, %s)", $node->nid, $node->url, $node->link_to_original_url, $node->original_author, $node->story_guid, $node->fid, $node->image_id, $node->image_guid ? $node->image_guid : 0);
886 }
887
888 /**
889 * Implementation of hook_update().
890 */
891
892 function category_aggregator_item_update($node)
893 {
894 if ($node->story_guid === '')
895 db_query("UPDATE {category_aggregator_item} SET url = '%s', original_author = '%s', ".
896 "fid = %d, image_id = %d, image_guid = %s, link_to_original_url = '%s' ".
897 "WHERE nid = %d", $node->url, $node->original_author, $node->fid, $node->image_id,
898 $node->image_guid ? $node->image_guid : 0, $node->link_to_original_url, $node->nid);
899 else
900 db_query("UPDATE {category_aggregator_item} SET url = '%s', original_author = '%s', ".
901 "story_guid = %s, fid = %d, image_id = %d, image_guid = %s, link_to_original_url = '%s' ".
902 "WHERE nid = %d", $node->url, $node->original_author, $node->story_guid, $node->fid,
903 $node->image_id, $node->image_guid ? $node->image_guid : 0, $node->link_to_original_url, $node->nid);
904 }
905
906 /**
907 * Implementation of hook_delete().
908 */
909
910 function category_aggregator_item_delete(&$node)
911 {
912 if (db_fetch_object(db_query(
913 "SELECT count(nid) AS image_count FROM {category_aggregator_item} WHERE image_id = %d", $node->image_id))
914 ->image_count == 1) node_delete($node->image_id);
915
916 db_query("UPDATE {category_aggregator_feed} SET etag = '', last_modified = 0 WHERE nid = %d", $node->fid);
917
918 db_query('DELETE FROM {category_aggregator_item} WHERE nid = %d', $node->nid);
919 }
920
921 /**
922 * This function is the implementation of hook_view
923 */
924
925 function category_aggregator_item_view(&$node, $teaser = FALSE, $page = FALSE)
926 {
927 if (!$teaser)
928 {
929 if ($node->image_id > 0)
930 {
931 $image = node_load($node->image_id);
932 $node->image = $image;
933
934 // theme image
935 $image_render = theme('category_aggregator_image_render', $image);
936 }
937 else
938 {
939 $node->image_nid = NULL;
940 $node->image = NULL;
941
942 $image_render = '';
943 }
944
945 // theme body
946 $body_render = theme('category_aggregator_body_render', $node->body);
947
948 // theme the final item
949 $node->content['body']['#value'] = theme('category_aggregator_item_render', $image_render, $body_render);
950
951 return $node;
952 }
953
954 return node_prepare($node, $teaser);
955 }
956
957 /**
958 * Implementation of hook_link
959 *
960 */
961 function category_aggregator_link($type, $node = NULL, $teaser = FALSE)
962 {
963 $links = array();
964 if (!$teaser && $type == 'node' && $node->type == 'category_aggregator_item')
965 if ($node->link_to_original_url == 'yes' && valid_url($node->url, TRUE))
966 $links[] = array(
967 'href' => $node->url,
968 'title' => t('Original article'),
969 'attributes' => array('class' => 'category_aggregator-link')
970 );
971
972 return $links;
973 }
974
975 function theme_category_aggregator_item_render($image_render, $body_render)
976 {
977 return "<div class=\"category_aggregator_item\">{$image_render}{$body_render}</div>";
978 }
979
980 function theme_category_aggregator_body_render($body)
981 {
982 return '<div class="category_aggregator_item_body">'.$body.'</div>';
983 }
984
985 function theme_category_aggregator_image_render($image)
986 {
987 return '<div class="category_aggregator_item_image">'.'<img src="'.base_path().file_directory_path().'/'.$image->images[variable_get('category_aggregator_image_to_display', 'preview')].'" />'.
988 '</div>';
989 }
990
991 /* Following are the module's private methods */
992
993 /**
994 * This function returns the feed items for a particular feed
995 */
996
997 function _category_aggregator_get_feed_items($fid)
998 {
999 return db_query("SELECT n.nid FROM {node} n, {category_aggregator_item} ai WHERE n.nid = ai.nid AND ai.fid = %d", $fid);
1000 }
1001
1002 /**
1003 * This method retrieved the etag and last_modified tags
1004 */
1005
1006 function read_etag_and_modified($ch, $header)
1007 {
1008 global $etag;
1009 global $last_modified;
1010
1011 $length = strlen($header);
1012 if(strstr($header, "Last-Modified:"))
1013 {
1014 $last_modified = strtotime(substr($header, 15));
1015 }
1016 if (strstr($header, "ETag:"))
1017 {
1018 $etag = substr($header, 6);
1019 }
1020 return $length;
1021 }
1022
1023 /**
1024 * This function returns the data in a URL (XML, image, etc...)
1025 */
1026 function category_aggregator_get_URL($url, $username = NULL, $password = NULL, $feed = NULL,
1027 $feed_etag = NULL, $feed_last_modified = NULL)
1028 {
1029 global $etag;
1030 global $last_modified;
1031
1032 if (trim($username) == '') $username = NULL;
1033 if (trim($password) == '') $password = NULL;
1034
1035 $headers = array();
1036
1037 if (!is_null($feed_etag) && $feed_etag != '')
1038 $headers['If-None-Match'] = $feed_etag;
1039
1040 if (!is_null($feed_last_modified) && $feed_last_modified != 0)
1041 $headers['If-Modified-Since'] = gmdate('D, d M Y H:i:s', $feed_last_modified) .' GMT';
1042
1043 if(count($headers) > 0)
1044 {
1045 $temp = array();
1046
1047 foreach ($headers as $header => $value)
1048 $temp[] = $header .': '. $value;
1049
1050 $headers = $temp;
1051 }
1052
1053 $ch = curl_init();
1054
1055 $is_ftp = FALSE;
1056
1057 if (stripos($url, 'ftp://') !== FALSE)
1058 $is_ftp = TRUE;
1059
1060 if (!$is_ftp)
1061 curl_setopt($ch, CURLOPT_USERAGENT, "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322)");
1062
1063 if (is_array($headers) && count($headers) > 0 && !$is_ftp)
1064 curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
1065
1066 if (!$is_ftp)
1067 curl_setopt($ch, CURLOPT_HEADERFUNCTION, 'read_etag_and_modified');
1068
1069 if (!$is_ftp)
1070 {
1071 curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
1072 curl_setopt($ch, CURLOPT_MAXREDIRS, 5);
1073 }
1074
1075 if (!is_null($username) && !is_null($password))
1076 {
1077 curl_setopt($ch, CURLOPT_USERPWD, "$username:$password");
1078 if (!$is_ftp)
1079 curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_ANY);
1080 }
1081 curl_setopt($ch, CURLOPT_URL, $url);
1082 curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
1083 if ($is_ftp)
1084 curl_setopt($ch, CURLOPT_FTP_USE_EPSV, FALSE);
1085 curl_setopt($ch, CURLOPT_TIMEOUT, 15);
1086 $data = curl_exec($ch);
1087
1088 $return_code = curl_getinfo($ch);
1089 $return_code = $return_code['http_code'];
1090
1091 $error = curl_error($ch);
1092
1093 curl_close($ch);
1094 unset($ch);
1095
1096 if ($return_code != 304)
1097 {
1098 // It was an FTP request but failed
1099 if ($is_ftp && $return_code != 226)
1100 {
1101 $fragment = '';
1102
1103 if (!is_null($feed))
1104 $fragment = " while processing feed \"$feed->title\"";
1105
1106 throw new Exception("FTP request returned error code \"$return_code\"".
1107 "$fragment on \"$url\"", FTP_REQUEST_FAILED);
1108 }
1109
1110 // It was an HTTP request but failed
1111 if (!$is_ftp && $return_code != 200)
1112 {
1113 $fragment = '';
1114
1115 if (!is_null($feed))
1116 $fragment = " while processing feed \"$feed->title\"";
1117
1118 throw new Exception("HTTP request returned error code \"$return_code\"".
1119 "$fragment on \"$url\"", HTTP_REQUEST_FAILED);
1120 }
1121 }
1122 else
1123 return FALSE;
1124
1125 if (!is_null($feed_etag) && !is_null($feed_last_modified))
1126 return array($data, $etag, $last_modified);
1127
1128 return $data;
1129 }
1130
1131 /**
1132 * This function returns the simpleXML object of a given string, or throws an exception
1133 * if the string is in an invalid XML format
1134 */
1135 function category_aggregator_get_XML($string, $feed = NULL)
1136 {
1137 // PHP versions earlier than 5.1 have different argument counts for simplexml_load_string,
1138 // this condition was added to check against this case
1139
1140 if (!defined('LIBXML_VERSION') || (version_compare(phpversion(), '5.1.0', '<')))
1141 @ $xml = simplexml_load_string($string, NULL);
1142 else
1143 @ $xml = simplexml_load_string($string, NULL, LIBXML_NOERROR | LIBXML_NOWARNING);
1144
1145 // We got a malformed XML
1146 if ($xml === FALSE)
1147 {
1148 $fragment = '';
1149
1150 if (!is_null($feed))
1151 $fragment = " while processing feed \"$feed->title\"";
1152