1 | <?php |
---|
2 | |
---|
3 | /** |
---|
4 | * @file |
---|
5 | * Feeds - basic API functions and hook implementations. |
---|
6 | */ |
---|
7 | |
---|
8 | // Common request time, use as point of reference and to avoid calls to time(). |
---|
9 | define('FEEDS_REQUEST_TIME', time()); |
---|
10 | // Do not schedule a feed for refresh. |
---|
11 | define('FEEDS_SCHEDULE_NEVER', -1); |
---|
12 | // Never expire feed items. |
---|
13 | define('FEEDS_EXPIRE_NEVER', -1); |
---|
14 | // An object that is not persistent. Compare EXPORT_IN_DATABASE, EXPORT_IN_CODE. |
---|
15 | define('FEEDS_EXPORT_NONE', 0x0); |
---|
16 | // Status of batched operations. |
---|
17 | define('FEEDS_BATCH_COMPLETE', 1); |
---|
18 | define('FEEDS_BATCH_ACTIVE', 0); |
---|
19 | |
---|
20 | /** |
---|
21 | * @defgroup hooks Hook and callback implementations |
---|
22 | * @{ |
---|
23 | */ |
---|
24 | |
---|
25 | /** |
---|
26 | * Implements hook_cron(). |
---|
27 | */ |
---|
28 | function feeds_cron() { |
---|
29 | if ($importers = feeds_reschedule()) { |
---|
30 | foreach ($importers as $id) { |
---|
31 | feeds_importer($id)->schedule(); |
---|
32 | $result = db_query("SELECT feed_nid FROM {feeds_source} WHERE id = '%s'", $id); |
---|
33 | while ($row = db_fetch_object($result)) { |
---|
34 | feeds_source($id, $row->feed_nid)->schedule(); |
---|
35 | } |
---|
36 | } |
---|
37 | feeds_reschedule(FALSE); |
---|
38 | return; |
---|
39 | } |
---|
40 | } |
---|
41 | |
---|
42 | /** |
---|
43 | * Implementation of hook_cron_queue_info(). |
---|
44 | * |
---|
45 | * Invoked by drupal_queue module if present. |
---|
46 | */ |
---|
47 | function feeds_cron_queue_info() { |
---|
48 | $queues = array(); |
---|
49 | $queues['feeds_source_import'] = array( |
---|
50 | 'worker callback' => 'feeds_source_import', |
---|
51 | 'time' => variable_get('feeds_worker_time', 15), |
---|
52 | ); |
---|
53 | $queues['feeds_importer_expire'] = array( |
---|
54 | 'worker callback' => 'feeds_importer_expire', |
---|
55 | 'time' => variable_get('feeds_worker_time', 15), |
---|
56 | ); |
---|
57 | return $queues; |
---|
58 | } |
---|
59 | |
---|
60 | /** |
---|
61 | * Scheduler callback for importing from a source. |
---|
62 | */ |
---|
63 | function feeds_source_import($job) { |
---|
64 | $source = feeds_source($job['type'], $job['id']); |
---|
65 | try { |
---|
66 | $source->existing()->import(); |
---|
67 | } |
---|
68 | catch (FeedsNotExistingException $e) { |
---|
69 | // Do nothing. |
---|
70 | } |
---|
71 | catch (Exception $e) { |
---|
72 | watchdog('feeds_source_import()', $e->getMessage(), array(), WATCHDOG_ERROR); |
---|
73 | } |
---|
74 | $source->schedule(); |
---|
75 | } |
---|
76 | |
---|
77 | /** |
---|
78 | * Scheduler callback for expiring content. |
---|
79 | */ |
---|
80 | function feeds_importer_expire($job) { |
---|
81 | $importer = feeds_importer($job['type']); |
---|
82 | try { |
---|
83 | $importer->existing()->expire(); |
---|
84 | } |
---|
85 | catch (FeedsNotExistingException $e) { |
---|
86 | // Do nothing. |
---|
87 | } |
---|
88 | catch (Exception $e) { |
---|
89 | watchdog('feeds_importer_expire()', $e->getMessage(), array(), WATCHDOG_ERROR); |
---|
90 | } |
---|
91 | $importer->schedule(); |
---|
92 | } |
---|
93 | |
---|
94 | /** |
---|
95 | * Reschedule one or all importers. |
---|
96 | * |
---|
97 | * Note: variable_set('feeds_reschedule', TRUE) is used in update hook |
---|
98 | * feeds_update_6013() and as such must be maintained as part of the upgrade |
---|
99 | * path from pre 6.x 1.0 beta 6 versions of Feeds. |
---|
100 | * |
---|
101 | * @param $importer_id |
---|
102 | * If TRUE, all importers will be rescheduled, if FALSE, no importers will |
---|
103 | * be rescheduled, if an importer id, only importer of that id will be |
---|
104 | * rescheduled. |
---|
105 | * |
---|
106 | * @return |
---|
107 | * TRUE if all importers need rescheduling. FALSE if no rescheduling is |
---|
108 | * required. An array of importers that need rescheduling. |
---|
109 | */ |
---|
110 | function feeds_reschedule($importer_id = NULL) { |
---|
111 | $reschedule = variable_get('feeds_reschedule', FALSE); |
---|
112 | if ($importer_id === TRUE || $importer_id === FALSE) { |
---|
113 | $reschedule = $importer_id; |
---|
114 | } |
---|
115 | elseif (is_string($importer_id) && $reschedule !== TRUE) { |
---|
116 | $reschedule = is_array($reschedule) ? $reschedule : array(); |
---|
117 | $reschedule[$importer_id] = $importer_id; |
---|
118 | } |
---|
119 | variable_set('feeds_reschedule', $reschedule); |
---|
120 | if ($reschedule === TRUE) { |
---|
121 | return feeds_enabled_importers(); |
---|
122 | } |
---|
123 | return $reschedule; |
---|
124 | } |
---|
125 | |
---|
126 | /** |
---|
127 | * Implementation of hook_perm(). |
---|
128 | */ |
---|
129 | function feeds_perm() { |
---|
130 | $perms = array('administer feeds'); |
---|
131 | foreach (feeds_importer_load_all() as $importer) { |
---|
132 | $perms[] = 'import '. $importer->id .' feeds'; |
---|
133 | $perms[] = 'clear '. $importer->id .' feeds'; |
---|
134 | } |
---|
135 | return $perms; |
---|
136 | } |
---|
137 | |
---|
138 | /** |
---|
139 | * Implementation of hook_forms(). |
---|
140 | * |
---|
141 | * Declare form callbacks for all known classes derived from FeedsConfigurable. |
---|
142 | */ |
---|
143 | function feeds_forms() { |
---|
144 | $forms = array(); |
---|
145 | $forms['FeedsImporter_feeds_form']['callback'] = 'feeds_form'; |
---|
146 | $plugins = feeds_get_plugins(); |
---|
147 | foreach ($plugins as $plugin) { |
---|
148 | $forms[$plugin['handler']['class'] .'_feeds_form']['callback'] = 'feeds_form'; |
---|
149 | } |
---|
150 | return $forms; |
---|
151 | } |
---|
152 | |
---|
153 | /** |
---|
154 | * Implementation of hook_menu(). |
---|
155 | */ |
---|
156 | function feeds_menu() { |
---|
157 | // Register a callback for all feed configurations that are not attached to a content type. |
---|
158 | $items = array(); |
---|
159 | foreach (feeds_importer_load_all() as $importer) { |
---|
160 | if (empty($importer->config['content_type'])) { |
---|
161 | $items['import/'. $importer->id] = array( |
---|
162 | 'title' => $importer->config['name'], |
---|
163 | 'page callback' => 'drupal_get_form', |
---|
164 | 'page arguments' => array('feeds_import_form', 1), |
---|
165 | 'access callback' => 'feeds_access', |
---|
166 | 'access arguments' => array('import', $importer->id), |
---|
167 | 'file' => 'feeds.pages.inc', |
---|
168 | ); |
---|
169 | $items['import/'. $importer->id .'/import'] = array( |
---|
170 | 'title' => 'Import', |
---|
171 | 'type' => MENU_DEFAULT_LOCAL_TASK, |
---|
172 | 'weight' => -10, |
---|
173 | ); |
---|
174 | $items['import/'. $importer->id .'/delete-items'] = array( |
---|
175 | 'title' => 'Delete items', |
---|
176 | 'page callback' => 'drupal_get_form', |
---|
177 | 'page arguments' => array('feeds_delete_tab_form', 1), |
---|
178 | 'access callback' => 'feeds_access', |
---|
179 | 'access arguments' => array('clear', $importer->id), |
---|
180 | 'file' => 'feeds.pages.inc', |
---|
181 | 'type' => MENU_LOCAL_TASK, |
---|
182 | ); |
---|
183 | } |
---|
184 | else { |
---|
185 | $items['node/%node/import'] = array( |
---|
186 | 'title' => 'Import', |
---|
187 | 'page callback' => 'drupal_get_form', |
---|
188 | 'page arguments' => array('feeds_import_tab_form', 1), |
---|
189 | 'access callback' => 'feeds_access', |
---|
190 | 'access arguments' => array('import', 1), |
---|
191 | 'file' => 'feeds.pages.inc', |
---|
192 | 'type' => MENU_LOCAL_TASK, |
---|
193 | 'weight' => 10, |
---|
194 | ); |
---|
195 | $items['node/%node/delete-items'] = array( |
---|
196 | 'title' => 'Delete items', |
---|
197 | 'page callback' => 'drupal_get_form', |
---|
198 | 'page arguments' => array('feeds_delete_tab_form', NULL, 1), |
---|
199 | 'access callback' => 'feeds_access', |
---|
200 | 'access arguments' => array('clear', 1), |
---|
201 | 'file' => 'feeds.pages.inc', |
---|
202 | 'type' => MENU_LOCAL_TASK, |
---|
203 | 'weight' => 11, |
---|
204 | ); |
---|
205 | } |
---|
206 | $items += $importer->fetcher->menuItem(); |
---|
207 | } |
---|
208 | if (count($items)) { |
---|
209 | $items['import'] = array( |
---|
210 | 'title' => 'Import', |
---|
211 | 'page callback' => 'feeds_page', |
---|
212 | 'access callback' => 'feeds_page_access', |
---|
213 | 'file' => 'feeds.pages.inc', |
---|
214 | ); |
---|
215 | } |
---|
216 | return $items; |
---|
217 | } |
---|
218 | |
---|
219 | /** |
---|
220 | * Menu loader callback. |
---|
221 | */ |
---|
222 | function feeds_importer_load($id) { |
---|
223 | return feeds_importer($id); |
---|
224 | } |
---|
225 | |
---|
226 | /** |
---|
227 | * Implementation of hook_theme(). |
---|
228 | */ |
---|
229 | function feeds_theme() { |
---|
230 | return array( |
---|
231 | 'feeds_upload' => array( |
---|
232 | 'file' => 'feeds.pages.inc', |
---|
233 | ), |
---|
234 | ); |
---|
235 | } |
---|
236 | |
---|
237 | /** |
---|
238 | * Menu access callback. |
---|
239 | * |
---|
240 | * @param $action |
---|
241 | * The action to be performed. Possible values are: |
---|
242 | * - import |
---|
243 | * - clear |
---|
244 | * @param $param |
---|
245 | * Node object or FeedsImporter id. |
---|
246 | */ |
---|
247 | function feeds_access($action, $param) { |
---|
248 | if (!in_array($action, array('import', 'clear'))) { |
---|
249 | // If $action is not one of the supported actions, we return access denied. |
---|
250 | return FALSE; |
---|
251 | } |
---|
252 | |
---|
253 | if (is_string($param)) { |
---|
254 | $importer_id = $param; |
---|
255 | } |
---|
256 | elseif ($param->type) { |
---|
257 | $importer_id = feeds_get_importer_id($param->type); |
---|
258 | } |
---|
259 | |
---|
260 | // Check for permissions if feed id is present, otherwise return FALSE. |
---|
261 | if ($importer_id) { |
---|
262 | if (user_access('administer feeds') || user_access($action .' '. $importer_id .' feeds')) { |
---|
263 | return TRUE; |
---|
264 | } |
---|
265 | } |
---|
266 | return FALSE; |
---|
267 | } |
---|
268 | |
---|
269 | /** |
---|
270 | * Menu access callback. |
---|
271 | */ |
---|
272 | function feeds_page_access() { |
---|
273 | if (user_access('administer feeds')) { |
---|
274 | return TRUE; |
---|
275 | } |
---|
276 | foreach (feeds_enabled_importers() as $id) { |
---|
277 | if (user_access("import $id feeds")) { |
---|
278 | return TRUE; |
---|
279 | } |
---|
280 | } |
---|
281 | return FALSE; |
---|
282 | } |
---|
283 | |
---|
284 | /** |
---|
285 | * Implementation of hook_views_api(). |
---|
286 | */ |
---|
287 | function feeds_views_api() { |
---|
288 | return array( |
---|
289 | 'api' => '2.0', |
---|
290 | 'path' => drupal_get_path('module', 'feeds') .'/views', |
---|
291 | ); |
---|
292 | } |
---|
293 | |
---|
294 | /** |
---|
295 | * Implementation of hook_ctools_plugin_api(). |
---|
296 | */ |
---|
297 | function feeds_ctools_plugin_api($owner, $api) { |
---|
298 | if ($owner == 'feeds' && $api == 'plugins') { |
---|
299 | return array('version' => 1); |
---|
300 | } |
---|
301 | } |
---|
302 | |
---|
303 | /** |
---|
304 | * Implementation of hook_ctools_plugin_plugins(). |
---|
305 | * |
---|
306 | * Psuedo hook defintion plugin system options and defaults. |
---|
307 | */ |
---|
308 | function feeds_ctools_plugin_plugins() { |
---|
309 | return array( |
---|
310 | 'cache' => TRUE, |
---|
311 | 'use hooks' => TRUE, |
---|
312 | ); |
---|
313 | } |
---|
314 | |
---|
315 | /** |
---|
316 | * Implementation of hook_feeds_plugins(). |
---|
317 | */ |
---|
318 | function feeds_feeds_plugins() { |
---|
319 | module_load_include('inc', 'feeds', 'feeds.plugins'); |
---|
320 | return _feeds_feeds_plugins(); |
---|
321 | } |
---|
322 | |
---|
323 | /** |
---|
324 | * Implementation of hook_nodeapi(). |
---|
325 | * |
---|
326 | * @todo For Drupal 7, revisit static cache based shuttling of values between |
---|
327 | * 'validate' and 'update'/'insert'. |
---|
328 | */ |
---|
329 | function feeds_nodeapi(&$node, $op, $form) { |
---|
330 | |
---|
331 | // $node looses any changes after 'validate' stage (see node_form_validate()). |
---|
332 | // Keep a copy of title and feeds array between 'validate' and subsequent |
---|
333 | // stages. This allows for automatically populating the title of the node form |
---|
334 | // and modifying the $form['feeds'] array on node validation just like on the |
---|
335 | // standalone form. |
---|
336 | static $last_title; |
---|
337 | static $node_feeds; |
---|
338 | |
---|
339 | // Break out node processor related nodeapi functionality. |
---|
340 | _feeds_nodeapi_node_processor($node, $op); |
---|
341 | |
---|
342 | if ($importer_id = feeds_get_importer_id($node->type)) { |
---|
343 | switch ($op) { |
---|
344 | case 'validate': |
---|
345 | // On validation stage we are working with a FeedsSource object that is |
---|
346 | // not tied to a nid - when creating a new node there is no |
---|
347 | // $node->nid at this stage. |
---|
348 | $source = feeds_source($importer_id); |
---|
349 | |
---|
350 | // Node module magically moved $form['feeds'] to $node->feeds :P |
---|
351 | $node_feeds = $node->feeds; |
---|
352 | $source->configFormValidate($node_feeds); |
---|
353 | |
---|
354 | // If node title is empty, try to retrieve title from feed. |
---|
355 | if (trim($node->title) == '') { |
---|
356 | try { |
---|
357 | $source->addConfig($node_feeds); |
---|
358 | if (!$last_title = $source->preview()->getTitle()) { |
---|
359 | throw new Exception(); |
---|
360 | } |
---|
361 | } |
---|
362 | catch (Exception $e) { |
---|
363 | drupal_set_message($e->getMessage(), 'error'); |
---|
364 | form_set_error('title', t('Could not retrieve title from feed.')); |
---|
365 | } |
---|
366 | } |
---|
367 | break; |
---|
368 | case 'presave': |
---|
369 | if (!empty($last_title)) { |
---|
370 | $node->title = $last_title; |
---|
371 | } |
---|
372 | $last_title = NULL; |
---|
373 | break; |
---|
374 | case 'insert': |
---|
375 | case 'update': |
---|
376 | // A node may not have been validated, make sure $node_feeds is present. |
---|
377 | if (empty($node_feeds)) { |
---|
378 | // If $node->feeds is empty here, nodes are being programatically |
---|
379 | // created in some fashion without Feeds stuff being added. |
---|
380 | if (empty($node->feeds)) { |
---|
381 | return; |
---|
382 | } |
---|
383 | $node_feeds = $node->feeds; |
---|
384 | } |
---|
385 | // Add configuration to feed source and save. |
---|
386 | $source = feeds_source($importer_id, $node->nid); |
---|
387 | $source->addConfig($node_feeds); |
---|
388 | $source->save(); |
---|
389 | |
---|
390 | // Refresh feed if import on create is selected and suppress_import is |
---|
391 | // not set. |
---|
392 | if ($op == 'insert' && feeds_importer($importer_id)->config['import_on_create'] && !isset($node_feeds['suppress_import'])) { |
---|
393 | feeds_batch_set(t('Importing'), 'import', $importer_id, $node->nid); |
---|
394 | } |
---|
395 | // Add source to schedule, make sure importer is scheduled, too. |
---|
396 | if ($op == 'insert') { |
---|
397 | $source->schedule(); |
---|
398 | $source->importer->schedule(); |
---|
399 | } |
---|
400 | $node_feeds = NULL; |
---|
401 | break; |
---|
402 | case 'delete': |
---|
403 | $source = feeds_source($importer_id, $node->nid); |
---|
404 | if (!empty($source->importer->processor->config['delete_with_source'])) { |
---|
405 | feeds_batch_set(t('Deleting'), 'clear', $importer_id, $node->nid); |
---|
406 | } |
---|
407 | // Remove attached source. |
---|
408 | $source->delete(); |
---|
409 | break; |
---|
410 | } |
---|
411 | } |
---|
412 | } |
---|
413 | |
---|
414 | /** |
---|
415 | * Handles FeedsNodeProcessor specific nodeapi operations. |
---|
416 | */ |
---|
417 | function _feeds_nodeapi_node_processor($node, $op) { |
---|
418 | switch ($op) { |
---|
419 | case 'load': |
---|
420 | if ($result = db_fetch_object(db_query("SELECT imported, guid, url, feed_nid FROM {feeds_node_item} WHERE nid = %d", $node->nid))) { |
---|
421 | $node->feeds_node_item = $result; |
---|
422 | } |
---|
423 | break; |
---|
424 | case 'insert': |
---|
425 | if (isset($node->feeds_node_item)) { |
---|
426 | $node->feeds_node_item->nid = $node->nid; |
---|
427 | drupal_write_record('feeds_node_item', $node->feeds_node_item); |
---|
428 | } |
---|
429 | break; |
---|
430 | case 'update': |
---|
431 | if (isset($node->feeds_node_item)) { |
---|
432 | $node->feeds_node_item->nid = $node->nid; |
---|
433 | drupal_write_record('feeds_node_item', $node->feeds_node_item, 'nid'); |
---|
434 | } |
---|
435 | break; |
---|
436 | case 'delete': |
---|
437 | db_query("DELETE FROM {feeds_node_item} WHERE nid = %d", $node->nid); |
---|
438 | break; |
---|
439 | } |
---|
440 | } |
---|
441 | |
---|
442 | /** |
---|
443 | * Implementation of hook_taxonomy(). |
---|
444 | */ |
---|
445 | function feeds_taxonomy($op = NULL, $type = NULL, $term = NULL) { |
---|
446 | if ($type == 'term' && !empty($term['tid'])) { |
---|
447 | switch ($op) { |
---|
448 | case 'delete': |
---|
449 | db_query("DELETE FROM {feeds_term_item} WHERE tid = %d", $term['tid']); |
---|
450 | break; |
---|
451 | case 'update': |
---|
452 | if (isset($term['feeds_term_item'])) { |
---|
453 | db_query("DELETE FROM {feeds_term_item} WHERE tid = %d", $term['tid']); |
---|
454 | } |
---|
455 | case 'insert': |
---|
456 | if (isset($term['feeds_term_item'])) { |
---|
457 | $term['feeds_term_item']['tid'] = $term['tid']; |
---|
458 | drupal_write_record('feeds_term_item', $term['feeds_term_item']); |
---|
459 | } |
---|
460 | break; |
---|
461 | } |
---|
462 | } |
---|
463 | } |
---|
464 | |
---|
465 | /** |
---|
466 | * Implements hook_form_alter(). |
---|
467 | */ |
---|
468 | function feeds_form_alter(&$form, $form_state, $form_id) { |
---|
469 | if (isset($form['#node']->type) && $form['#node']->type . '_node_form' == $form_id) { |
---|
470 | if ($importer_id = feeds_get_importer_id($form['#node']->type)) { |
---|
471 | // Set title to not required, try to retrieve it from feed. |
---|
472 | if (isset($form['title'])) { |
---|
473 | $form['title']['#required'] = FALSE; |
---|
474 | } |
---|
475 | |
---|
476 | // Enable uploads. |
---|
477 | $form['#attributes']['enctype'] = 'multipart/form-data'; |
---|
478 | |
---|
479 | // Build form. |
---|
480 | $source = feeds_source($importer_id, empty($form['#node']->nid) ? 0 : $form['#node']->nid); |
---|
481 | $form['feeds'] = array( |
---|
482 | '#type' => 'fieldset', |
---|
483 | '#title' => t('Feed'), |
---|
484 | '#tree' => TRUE, |
---|
485 | '#weight' => 0, |
---|
486 | ); |
---|
487 | $form['feeds'] += $source->configForm($form_state); |
---|
488 | $form['#feed_id'] = $importer_id; |
---|
489 | } |
---|
490 | } |
---|
491 | } |
---|
492 | |
---|
493 | /** |
---|
494 | * Implements hook_content_extra_fields(). |
---|
495 | */ |
---|
496 | function feeds_content_extra_fields($type) { |
---|
497 | $extras = array(); |
---|
498 | if (feeds_get_importer_id($type)) { |
---|
499 | $extras['feeds'] = array( |
---|
500 | 'label' => t('Feed'), |
---|
501 | 'description' => t('Feeds module form elements'), |
---|
502 | 'weight' => 0, |
---|
503 | ); |
---|
504 | } |
---|
505 | return $extras; |
---|
506 | } |
---|
507 | |
---|
508 | /** |
---|
509 | * @} |
---|
510 | */ |
---|
511 | |
---|
512 | /** |
---|
513 | * @defgroup batch Batch functions |
---|
514 | */ |
---|
515 | |
---|
516 | /** |
---|
517 | * Batch helper. |
---|
518 | * |
---|
519 | * @param $title |
---|
520 | * Title to show to user when executing batch. |
---|
521 | * @param $method |
---|
522 | * Method to execute on importer; one of 'import', 'clear' or 'expire'. |
---|
523 | * @param $importer_id |
---|
524 | * Identifier of a FeedsImporter object. |
---|
525 | * @param $feed_nid |
---|
526 | * If importer is attached to content type, feed node id identifying the |
---|
527 | * source to be imported. |
---|
528 | */ |
---|
529 | function feeds_batch_set($title, $method, $importer_id, $feed_nid = 0) { |
---|
530 | $batch = array( |
---|
531 | 'title' => $title, |
---|
532 | 'operations' => array( |
---|
533 | array('feeds_batch', array($method, $importer_id, $feed_nid)), |
---|
534 | ), |
---|
535 | 'progress_message' => '', |
---|
536 | ); |
---|
537 | batch_set($batch); |
---|
538 | } |
---|
539 | |
---|
540 | /** |
---|
541 | * Batch callback. |
---|
542 | * |
---|
543 | * @param $method |
---|
544 | * Method to execute on importer; one of 'import' or 'clear'. |
---|
545 | * @param $importer_id |
---|
546 | * Identifier of a FeedsImporter object. |
---|
547 | * @param $feed_nid |
---|
548 | * If importer is attached to content type, feed node id identifying the |
---|
549 | * source to be imported. |
---|
550 | * @param $context |
---|
551 | * Batch context. |
---|
552 | */ |
---|
553 | function feeds_batch($method, $importer_id, $feed_nid = 0, &$context) { |
---|
554 | $context['finished'] = 1; |
---|
555 | try { |
---|
556 | $context['finished'] = feeds_source($importer_id, $feed_nid)->$method(); |
---|
557 | } |
---|
558 | catch (Exception $e) { |
---|
559 | drupal_set_message($e->getMessage(), 'error'); |
---|
560 | } |
---|
561 | } |
---|
562 | |
---|
563 | /** |
---|
564 | * @} |
---|
565 | */ |
---|
566 | |
---|
567 | /** |
---|
568 | * @defgroup utility Utility functions |
---|
569 | * @{ |
---|
570 | */ |
---|
571 | |
---|
572 | /** |
---|
573 | * Loads all importers. |
---|
574 | * |
---|
575 | * @param $load_disabled |
---|
576 | * Pass TRUE to load all importers, enabled or disabled, pass FALSE to only |
---|
577 | * retrieve enabled importers. |
---|
578 | * |
---|
579 | * @return |
---|
580 | * An array of all feed configurations available. |
---|
581 | */ |
---|
582 | function feeds_importer_load_all($load_disabled = FALSE) { |
---|
583 | $feeds = array(); |
---|
584 | // This function can get called very early in install process through |
---|
585 | // menu_router_rebuild(). Do not try to include CTools if not available. |
---|
586 | if (function_exists('ctools_include')) { |
---|
587 | ctools_include('export'); |
---|
588 | $configs = ctools_export_load_object('feeds_importer', 'all'); |
---|
589 | foreach ($configs as $config) { |
---|
590 | if (!empty($config->id) && ($load_disabled || empty($config->disabled))) { |
---|
591 | $feeds[$config->id] = feeds_importer($config->id); |
---|
592 | } |
---|
593 | } |
---|
594 | } |
---|
595 | return $feeds; |
---|
596 | } |
---|
597 | |
---|
598 | /** |
---|
599 | * Gets an array of enabled importer ids. |
---|
600 | * |
---|
601 | * @return |
---|
602 | * An array where the values contain ids of enabled importers. |
---|
603 | */ |
---|
604 | function feeds_enabled_importers() { |
---|
605 | return array_keys(_feeds_importer_digest()); |
---|
606 | } |
---|
607 | |
---|
608 | /** |
---|
609 | * Gets an enabled importer configuration by content type. |
---|
610 | * |
---|
611 | * @param $content_type |
---|
612 | * A node type string. |
---|
613 | * |
---|
614 | * @return |
---|
615 | * A FeedsImporter id if there is an importer for the given content type, |
---|
616 | * FALSE otherwise. |
---|
617 | */ |
---|
618 | function feeds_get_importer_id($content_type) { |
---|
619 | $importers = array_flip(_feeds_importer_digest()); |
---|
620 | return isset($importers[$content_type]) ? $importers[$content_type] : FALSE; |
---|
621 | } |
---|
622 | |
---|
623 | /** |
---|
624 | * Helper function for feeds_get_importer_id() and feeds_enabled_importers(). |
---|
625 | */ |
---|
626 | function _feeds_importer_digest() { |
---|
627 | $importers = &ctools_static(__FUNCTION__); |
---|
628 | if ($importers === NULL) { |
---|
629 | if ($cache = cache_get(__FUNCTION__)) { |
---|
630 | $importers = $cache->data; |
---|
631 | } |
---|
632 | else { |
---|
633 | $importers = array(); |
---|
634 | foreach (feeds_importer_load_all() as $importer) { |
---|
635 | $importers[$importer->id] = isset($importer->config['content_type']) ? $importer->config['content_type'] : ''; |
---|
636 | } |
---|
637 | cache_set(__FUNCTION__, $importers); |
---|
638 | } |
---|
639 | } |
---|
640 | return $importers; |
---|
641 | } |
---|
642 | |
---|
643 | /** |
---|
644 | * Resets importer caches. Call when enabling/disabling importers. |
---|
645 | */ |
---|
646 | function feeds_cache_clear($rebuild_menu = TRUE) { |
---|
647 | cache_clear_all('_feeds_importer_digest', 'cache'); |
---|
648 | ctools_static_reset('_feeds_importer_digest'); |
---|
649 | ctools_include('export'); |
---|
650 | ctools_export_load_object_reset('feeds_importer'); |
---|
651 | node_get_types('types', NULL, TRUE); |
---|
652 | if ($rebuild_menu) { |
---|
653 | menu_rebuild(); |
---|
654 | } |
---|
655 | } |
---|
656 | |
---|
657 | /** |
---|
658 | * Exports a FeedsImporter configuration to code. |
---|
659 | */ |
---|
660 | function feeds_export($importer_id, $indent = '') { |
---|
661 | ctools_include('export'); |
---|
662 | $result = ctools_export_load_object('feeds_importer', 'names', array('id' => $importer_id)); |
---|
663 | if (isset($result[$importer_id])) { |
---|
664 | return ctools_export_object('feeds_importer', $result[$importer_id], $indent); |
---|
665 | } |
---|
666 | } |
---|
667 | |
---|
668 | /** |
---|
669 | * Logs to a file like /mytmp/feeds_my_domain_org.log in temporary directory. |
---|
670 | */ |
---|
671 | function feeds_dbg($msg) { |
---|
672 | if (variable_get('feeds_debug', FALSE)) { |
---|
673 | if (!is_string($msg)) { |
---|
674 | $msg = var_export($msg, TRUE); |
---|
675 | } |
---|
676 | $filename = trim(str_replace('/', '_', $_SERVER['HTTP_HOST'] . base_path()), '_'); |
---|
677 | $handle = fopen(file_directory_temp() ."/feeds_$filename.log", 'a'); |
---|
678 | fwrite($handle, date('c') ."\t$msg\n"); |
---|
679 | fclose($handle); |
---|
680 | } |
---|
681 | } |
---|
682 | |
---|
683 | /** |
---|
684 | * @} |
---|
685 | */ |
---|
686 | |
---|
687 | /** |
---|
688 | * @defgroup instantiators Instantiators |
---|
689 | * @{ |
---|
690 | */ |
---|
691 | |
---|
692 | /** |
---|
693 | * Gets an importer instance. |
---|
694 | * |
---|
695 | * @param $id |
---|
696 | * The unique id of the importer object. |
---|
697 | * |
---|
698 | * @return |
---|
699 | * A FeedsImporter object or an object of a class defined by the Drupal |
---|
700 | * variable 'feeds_importer_class'. There is only one importer object |
---|
701 | * per $id system-wide. |
---|
702 | */ |
---|
703 | function feeds_importer($id) { |
---|
704 | feeds_include('FeedsImporter'); |
---|
705 | return FeedsConfigurable::instance(variable_get('feeds_importer_class', 'FeedsImporter'), $id); |
---|
706 | } |
---|
707 | |
---|
708 | /** |
---|
709 | * Gets an instance of a source object. |
---|
710 | * |
---|
711 | * @param $importer_id |
---|
712 | * A FeedsImporter id. |
---|
713 | * @param $feed_nid |
---|
714 | * The node id of a feed node if the source is attached to a feed node. |
---|
715 | * |
---|
716 | * @return |
---|
717 | * A FeedsSource object or an object of a class defiend by the Drupal |
---|
718 | * variable 'source_class'. |
---|
719 | */ |
---|
720 | function feeds_source($importer_id, $feed_nid = 0) { |
---|
721 | feeds_include('FeedsImporter'); |
---|
722 | return FeedsSource::instance($importer_id, $feed_nid); |
---|
723 | } |
---|
724 | |
---|
725 | /** |
---|
726 | * @} |
---|
727 | */ |
---|
728 | |
---|
729 | /** |
---|
730 | * @defgroup plugins Plugin functions |
---|
731 | * @{ |
---|
732 | * |
---|
733 | * @todo Encapsulate this in a FeedsPluginHandler class, move it to includes/ |
---|
734 | * and only load it if we're manipulating plugins. |
---|
735 | */ |
---|
736 | |
---|
737 | /** |
---|
738 | * Gets all available plugins. Does not list hidden plugins. |
---|
739 | * |
---|
740 | * @return |
---|
741 | * An array where the keys are the plugin keys and the values |
---|
742 | * are the plugin info arrays as defined in hook_feeds_plugins(). |
---|
743 | */ |
---|
744 | function feeds_get_plugins() { |
---|
745 | ctools_include('plugins'); |
---|
746 | $plugins = ctools_get_plugins('feeds', 'plugins'); |
---|
747 | |
---|
748 | $result = array(); |
---|
749 | foreach ($plugins as $key => $info) { |
---|
750 | if (!empty($info['hidden'])) { |
---|
751 | continue; |
---|
752 | } |
---|
753 | $result[$key] = $info; |
---|
754 | } |
---|
755 | |
---|
756 | // Sort plugins by name and return. |
---|
757 | uasort($result, 'feeds_plugin_compare'); |
---|
758 | return $result; |
---|
759 | } |
---|
760 | |
---|
761 | /** |
---|
762 | * Sort callback for feeds_get_plugins(). |
---|
763 | */ |
---|
764 | function feeds_plugin_compare($a, $b) { |
---|
765 | return strcasecmp($a['name'], $b['name']); |
---|
766 | } |
---|
767 | |
---|
768 | /** |
---|
769 | * Gets all available plugins of a particular type. |
---|
770 | * |
---|
771 | * @param $type |
---|
772 | * 'fetcher', 'parser' or 'processor' |
---|
773 | */ |
---|
774 | function feeds_get_plugins_by_type($type) { |
---|
775 | $plugins = feeds_get_plugins(); |
---|
776 | |
---|
777 | $result = array(); |
---|
778 | foreach ($plugins as $key => $info) { |
---|
779 | if ($type == feeds_plugin_type($key)) { |
---|
780 | $result[$key] = $info; |
---|
781 | } |
---|
782 | } |
---|
783 | return $result; |
---|
784 | } |
---|
785 | |
---|
786 | /** |
---|
787 | * Gets an instance of a class for a given plugin and id. |
---|
788 | * |
---|
789 | * @param $plugin |
---|
790 | * A string that is the key of the plugin to load. |
---|
791 | * @param $id |
---|
792 | * A string that is the id of the object. |
---|
793 | * |
---|
794 | * @return |
---|
795 | * A FeedsPlugin object. |
---|
796 | * |
---|
797 | * @throws Exception |
---|
798 | * If plugin can't be instantiated. |
---|
799 | */ |
---|
800 | function feeds_plugin_instance($plugin, $id) { |
---|
801 | feeds_include('FeedsImporter'); |
---|
802 | ctools_include('plugins'); |
---|
803 | if ($class = ctools_plugin_load_class('feeds', 'plugins', $plugin, 'handler')) { |
---|
804 | return FeedsConfigurable::instance($class, $id); |
---|
805 | } |
---|
806 | $args = array( '%plugin' => $plugin, '@id' => $id); |
---|
807 | if (user_access('administer feeds')) { |
---|
808 | $args['@link'] = url('admin/build/feeds/edit/' . $id); |
---|
809 | drupal_set_message(t('Missing Feeds plugin %plugin. See <a href="@link">@id</a>. Check whether all required libraries and modules are installed properly.', $args), 'warning', FALSE); |
---|
810 | } |
---|
811 | else { |
---|
812 | drupal_set_message(t('Missing Feeds plugin %plugin. Please contact your site administrator.', $args), 'warning', FALSE); |
---|
813 | } |
---|
814 | $class = ctools_plugin_load_class('feeds', 'plugins', 'FeedsMissingPlugin', 'handler'); |
---|
815 | return FeedsConfigurable::instance($class, $id); |
---|
816 | } |
---|
817 | |
---|
818 | /** |
---|
819 | * Determines whether given plugin is derived from given base plugin. |
---|
820 | * |
---|
821 | * @param $plugin_key |
---|
822 | * String that identifies a Feeds plugin key. |
---|
823 | * @param $parent_plugin |
---|
824 | * String that identifies a Feeds plugin key to be tested against. |
---|
825 | * |
---|
826 | * @return |
---|
827 | * TRUE if $parent_plugin is directly *or indirectly* a parent of $plugin, |
---|
828 | * FALSE otherwise. |
---|
829 | */ |
---|
830 | function feeds_plugin_child($plugin_key, $parent_plugin) { |
---|
831 | ctools_include('plugins'); |
---|
832 | $plugins = ctools_get_plugins('feeds', 'plugins'); |
---|
833 | $info = $plugins[$plugin_key]; |
---|
834 | |
---|
835 | if (empty($info['handler']['parent'])) { |
---|
836 | return FALSE; |
---|
837 | } |
---|
838 | elseif ($info['handler']['parent'] == $parent_plugin) { |
---|
839 | return TRUE; |
---|
840 | } |
---|
841 | else { |
---|
842 | return feeds_plugin_child($info['handler']['parent'], $parent_plugin); |
---|
843 | } |
---|
844 | } |
---|
845 | |
---|
846 | /** |
---|
847 | * Determines the type of a plugin. |
---|
848 | * |
---|
849 | * @param $plugin_key |
---|
850 | * String that identifies a Feeds plugin key. |
---|
851 | * |
---|
852 | * @return |
---|
853 | * One of the following values: |
---|
854 | * 'fetcher' if the plugin is a fetcher |
---|
855 | * 'parser' if the plugin is a parser |
---|
856 | * 'processor' if the plugin is a processor |
---|
857 | * FALSE otherwise. |
---|
858 | */ |
---|
859 | function feeds_plugin_type($plugin_key) { |
---|
860 | if (feeds_plugin_child($plugin_key, 'FeedsFetcher')) { |
---|
861 | return 'fetcher'; |
---|
862 | } |
---|
863 | elseif (feeds_plugin_child($plugin_key, 'FeedsParser')) { |
---|
864 | return 'parser'; |
---|
865 | } |
---|
866 | elseif (feeds_plugin_child($plugin_key, 'FeedsProcessor')) { |
---|
867 | return 'processor'; |
---|
868 | } |
---|
869 | return FALSE; |
---|
870 | } |
---|
871 | |
---|
872 | /** |
---|
873 | * @} |
---|
874 | */ |
---|
875 | |
---|
876 | /** |
---|
877 | * @defgroup include Funtions for loading libraries |
---|
878 | * @{ |
---|
879 | */ |
---|
880 | |
---|
881 | /** |
---|
882 | * Includes a feeds module include file. |
---|
883 | * |
---|
884 | * @param $file |
---|
885 | * The filename without the .inc extension. |
---|
886 | * @param $directory |
---|
887 | * The directory to include the file from. Do not include files from libraries |
---|
888 | * directory. Use feeds_include_library() instead |
---|
889 | */ |
---|
890 | function feeds_include($file, $directory = 'includes') { |
---|
891 | static $included = array(); |
---|
892 | if (!isset($included[$file])) { |
---|
893 | require './'. drupal_get_path('module', 'feeds') ."/$directory/$file.inc"; |
---|
894 | } |
---|
895 | $included[$file] = TRUE; |
---|
896 | } |
---|
897 | |
---|
898 | /** |
---|
899 | * Includes a library file. |
---|
900 | * |
---|
901 | * @param $file |
---|
902 | * The filename to load from. |
---|
903 | * @param $library |
---|
904 | * The name of the library. If libraries module is installed, |
---|
905 | * feeds_include_library() will look for libraries with this name managed by |
---|
906 | * libraries module. |
---|
907 | */ |
---|
908 | function feeds_include_library($file, $library) { |
---|
909 | static $included = array(); |
---|
910 | if (!isset($included[$file])) { |
---|
911 | // Try first whether libraries module is present and load the file from |
---|
912 | // there. If this fails, require the library from the local path. |
---|
913 | if (module_exists('libraries') && file_exists(libraries_get_path($library) ."/$file")) { |
---|
914 | require libraries_get_path($library) ."/$file"; |
---|
915 | } |
---|
916 | else { |
---|
917 | require './' . drupal_get_path('module', 'feeds') . "/libraries/$file"; |
---|
918 | } |
---|
919 | } |
---|
920 | $included[$file] = TRUE; |
---|
921 | } |
---|
922 | |
---|
923 | /** |
---|
924 | * Checks whether a library is present. |
---|
925 | * |
---|
926 | * @param $file |
---|
927 | * The filename to load from. |
---|
928 | * @param $library |
---|
929 | * The name of the library. If libraries module is installed, |
---|
930 | * feeds_library_exists() will look for libraries with this name managed by |
---|
931 | * libraries module. |
---|
932 | */ |
---|
933 | function feeds_library_exists($file, $library) { |
---|
934 | if (module_exists('libraries') && file_exists(libraries_get_path($library) ."/$file")) { |
---|
935 | return TRUE; |
---|
936 | } |
---|
937 | elseif (file_exists('./' . drupal_get_path('module', 'feeds') . "/libraries/$file")) { |
---|
938 | return TRUE; |
---|
939 | } |
---|
940 | return FALSE; |
---|
941 | } |
---|
942 | |
---|
943 | /** |
---|
944 | * @} |
---|
945 | */ |
---|
946 | |
---|
947 | /** |
---|
948 | * Copy of valid_url() that supports the webcal scheme. |
---|
949 | * |
---|
950 | * @see valid_url(). |
---|
951 | * |
---|
952 | * @todo Replace with valid_url() when http://drupal.org/node/295021 is fixed. |
---|
953 | */ |
---|
954 | function feeds_valid_url($url, $absolute = FALSE) { |
---|
955 | if ($absolute) { |
---|
956 | return (bool) preg_match(" |
---|
957 | /^ # Start at the beginning of the text |
---|
958 | (?:ftp|https?|feed|webcal):\/\/ # Look for ftp, http, https, feed or webcal schemes |
---|
959 | (?: # Userinfo (optional) which is typically |
---|
960 | (?:(?:[\w\.\-\+!$&'\(\)*\+,;=]|%[0-9a-f]{2})+:)* # a username or a username and password |
---|
961 | (?:[\w\.\-\+%!$&'\(\)*\+,;=]|%[0-9a-f]{2})+@ # combination |
---|
962 | )? |
---|
963 | (?: |
---|
964 | (?:[a-z0-9\-\.]|%[0-9a-f]{2})+ # A domain name or a IPv4 address |
---|
965 | |(?:\[(?:[0-9a-f]{0,4}:)*(?:[0-9a-f]{0,4})\]) # or a well formed IPv6 address |
---|
966 | ) |
---|
967 | (?::[0-9]+)? # Server port number (optional) |
---|
968 | (?:[\/|\?] |
---|
969 | (?:[|\w#!:\.\?\+=&@$'~*,;\/\(\)\[\]\-]|%[0-9a-f]{2}) # The path and query (optional) |
---|
970 | *)? |
---|
971 | $/xi", $url); |
---|
972 | } |
---|
973 | else { |
---|
974 | return (bool) preg_match("/^(?:[\w#!:\.\?\+=&@$'~*,;\/\(\)\[\]\-]|%[0-9a-f]{2})+$/i", $url); |
---|
975 | } |
---|
976 | } |
---|