| Commit | Line | Data |
|---|---|---|
| e36af5b2 | 1 | <?php |
| bcee876d AN |
2 | |
| 3 | /** | |
| f5570608 | 4 | * @file |
| bcee876d AN |
5 | * Handle the submodules (for feed and item processing) |
| 6 | * Provide a basic management of feeds | |
| 7 | */ | |
| 8 | ||
| 51af36e6 | 9 | define('FEEDAPI_NEVER_DELETE_OLD', 0); |
| 5c770af4 | 10 | define('FEEDAPI_TIMEOUT', 1); |
| c7a0e39f AN |
11 | // Number of feeds to process for each step in cron. |
| 12 | define('FEEDAPI_CRON_FEEDS', 100); | |
| e9545db8 AN |
13 | // Default time that should elapse before a feed can be refreshed again on cron. |
| 14 | define('FEEDAPI_CRON_DEFAULT_REFRESH_TIME', 1800); | |
| 15 | // Denotes that a feed should never be refreshed. | |
| 42ec78cc AB |
16 | define('FEEDAPI_CRON_NEVER_REFRESH', -1); |
| 17 | // Denotes that a feed should be refreshed as often as possible. | |
| 18 | define('FEEDAPI_CRON_ALWAYS_REFRESH', 0); | |
| 5b9d898b AN |
19 | // Prune FeedAPI stats 4 weeks |
| 20 | define('FEEDAPI_CRON_STAT_LIFETIME', 28*24*3600); | |
| 0a743fb2 | 21 | |
| f5570608 | 22 | /** |
| efb1f865 | 23 | * Implementation of hook_help(). |
| f5570608 | 24 | */ |
| 25a3b58b AN |
25 | function feedapi_help($path, $arg) { |
| 26 | switch ($path) { | |
| efb1f865 | 27 | case 'admin/help#feedapi': |
| aa0cf13b AN |
28 | $output = '<p>'. t('Provides feed management interface and handles underlying processors and parsers for any type of feeds.') .'</p>'; |
| 29 | $output .= '<p>'. t('Feeds are based on content types. Default content types are created on install. You can create new content types on the <a href="@content-types">add content types</a> page. To do that, enable the "Is a feed content type" checkbox under the Feed API group on the content type edit form. Then choose the processors and parsers that you would like to use. At least one parser and one processor must be enabled.', array('@content-types' => url('admin/content/types/add'))) .'</p>'; | |
| 30 | return $output; | |
| 31 | case 'admin/content/feed': | |
| 32 | return '<p>'. t('Current feeds are listed below. For each FeedAPI-enabled content type, the <em>Quick create</em> block may be enabled at the <a href="@block">blocks administration page</a>.', array('@block' => url('admin/build/block'))) .'</p>'; | |
| 33 | case 'admin/content/feed/import_opml': | |
| 34 | return '<p>'. t('Feeds can be imported from a valid OPML file. You can check your OPML file at <a href="@validator">OPML Validator</a>.', array('@validator' => url('http://validator.opml.org/'))) .'</p>'; | |
| 5b9d898b | 35 | case 'admin/settings/feedapi': |
| aa0cf13b | 36 | return '<p>'. t('You can find more configuration options on the content type edit form of FeedAPI-enabled <a href="@content-types">content types</a>.', array('@content-types' => url('admin/content/types'))) .'</p>'; |
| f5570608 | 37 | } |
| f5570608 AN |
38 | } |
| 39 | ||
| 40 | /** | |
| 918f248a AN |
41 | * Implementation of hook_theme(). |
| 42 | */ | |
| 43 | function feedapi_theme() { | |
| 44 | return array( | |
| 45 | 'feedapi_export_opml' => array( | |
| 46 | 'arguments' => array('feeds' => NULL), | |
| 47 | ), | |
| 48 | ); | |
| 49 | } | |
| 50 | ||
| 51 | /** | |
| efb1f865 | 52 | * Implementation of hook_menu(). |
| f5570608 | 53 | */ |
| 25a3b58b | 54 | function feedapi_menu() { |
| 85e33999 | 55 | $items = array(); |
| 25a3b58b AN |
56 | $items['admin/content/feed'] = array( |
| 57 | 'title' => 'Feeds', | |
| 8a4f9912 | 58 | 'description' => 'Overview which content your site aggregates from other sites and see detailed statistics about the feeds.', |
| aa0cf13b | 59 | 'page callback' => 'feedapi_admin_overview', |
| d5f9ceb6 | 60 | 'access arguments' => array('administer feedapi'), |
| b8051ca0 | 61 | 'file' => 'feedapi.admin.inc', |
| 25a3b58b AN |
62 | ); |
| 63 | $items['admin/content/feed/list'] = array( | |
| 8a4f9912 | 64 | 'title' => 'List', |
| 25a3b58b | 65 | 'type' => MENU_DEFAULT_LOCAL_TASK, |
| d5f9ceb6 | 66 | 'access arguments' => array('administer feedapi'), |
| 25a3b58b AN |
67 | 'weight' => -15, |
| 68 | ); | |
| 69 | $items['admin/content/feed/import_opml'] = array( | |
| 8a4f9912 | 70 | 'title' => 'Import OPML', |
| d5f9ceb6 | 71 | 'access arguments' => array('administer feedapi'), |
| 25a3b58b | 72 | 'page callback' => 'drupal_get_form', |
| aa0cf13b | 73 | 'page arguments' => array('feedapi_import_opml'), |
| b8051ca0 | 74 | 'file' => 'feedapi.opml.inc', |
| 25a3b58b AN |
75 | ); |
| 76 | $items['admin/content/feed/export_opml'] = array( | |
| 8a4f9912 | 77 | 'title' => 'Export all feeds as OPML', |
| d5f9ceb6 | 78 | 'access arguments' => array('administer feedapi'), |
| aa0cf13b | 79 | 'page callback' => 'feedapi_export_opml', |
| b8051ca0 | 80 | 'file' => 'feedapi.opml.inc', |
| 25a3b58b AN |
81 | ); |
| 82 | $items['admin/settings/feedapi'] = array( | |
| bef1e05a AN |
83 | 'title' => 'FeedAPI', |
| 84 | 'description' => 'Configure advanced options for FeedAPI module.', | |
| 25a3b58b AN |
85 | 'page callback' => 'drupal_get_form', |
| 86 | 'page arguments' => array('feedapi_admin_settings'), | |
| d5f9ceb6 | 87 | 'access arguments' => array('administer feedapi'), |
| b8051ca0 | 88 | 'file' => 'feedapi.admin.inc', |
| 25a3b58b | 89 | ); |
| 0aa46c53 | 90 | |
| 25a3b58b | 91 | $items['node/%node/refresh'] = array( |
| 8a4f9912 | 92 | 'title' => 'Refresh', |
| 25a3b58b AN |
93 | 'page callback' => 'feedapi_refresh', |
| 94 | 'page arguments' => array(1), | |
| 95 | 'type' => MENU_LOCAL_TASK, | |
| 9ee3d054 | 96 | 'access callback' => '_feedapi_op_access', |
| 25a3b58b AN |
97 | 'access arguments' => array(1), |
| 98 | ); | |
| 99 | $items['node/%node/purge'] = array( | |
| 8a4f9912 | 100 | 'title' => 'Remove items', |
| aa0cf13b | 101 | 'page callback' => 'feedapi_invoke', |
| 25a3b58b AN |
102 | 'page arguments' => array("purge", 1, 'items'), |
| 103 | 'type' => MENU_LOCAL_TASK, | |
| 9ee3d054 | 104 | 'access callback' => '_feedapi_op_access', |
| 25a3b58b AN |
105 | 'access arguments' => array(1), |
| 106 | ); | |
| bcee876d | 107 | return $items; |
| bcee876d AN |
108 | } |
| 109 | ||
| 9ee3d054 | 110 | function _feedapi_op_access($node) { |
| aa0cf13b | 111 | if (!feedapi_enabled_type($node->type)) { |
| 9ee3d054 AN |
112 | return FALSE; |
| 113 | } | |
| 18c80d9b | 114 | global $user; |
| d5f9ceb6 | 115 | $own_feed = $node->uid == $user->uid && user_access('edit own '. $node->type .' content') ? TRUE : FALSE; |
| 25a3b58b AN |
116 | return user_access('administer feedapi') || $own_feed; |
| 117 | } | |
| 118 | ||
| bcee876d | 119 | /** |
| 5a0af529 AN |
120 | * Implementation of hook_nodeapi(). |
| 121 | */ | |
| 122 | function feedapi_nodeapi(&$node, $op, $teaser, $page) { | |
| aa0cf13b | 123 | if (isset($node->feed) || feedapi_enabled_type($node->type)) { |
| 5a0af529 | 124 | switch ($op) { |
| 87cc49ec | 125 | case 'validate': |
| 19ee1fea | 126 | $node->feed->settings = feedapi_get_settings($node->type); |
| 87cc49ec AN |
127 | $node->feed->parsers = _feedapi_format_settings($node->feed->settings, 'parsers'); |
| 128 | $node->feed->processors = _feedapi_format_settings($node->feed->settings, 'processors'); | |
| 129 | if (count($node->feed->parsers) < 1) { | |
| 130 | if (user_access('administer content types')) { | |
| bef1e05a | 131 | form_set_error('', t('There are no enabled parsers for this content type. In order to import feed items, you need to select a feed parser from the <a href="@url">content type settings</a>.', array('@url' => url("admin/content/node-type/$node->type")))); |
| 87cc49ec AN |
132 | } |
| 133 | else { | |
| 134 | form_set_error('', t('There is no parser enabled for this content-type. Contact your site administrator for help.')); | |
| 135 | } | |
| 136 | } | |
| 137 | if (count($node->feed->processors) < 1) { | |
| 138 | if (user_access('administer content types')) { | |
| bef1e05a | 139 | form_set_error('', t('There are no enabled processors for this content type. In order to import feed items, you need to select a processor from the <a href="@url">content type settings</a>.', array('@url' => url("admin/content/node-type/$node->type")))); |
| 87cc49ec AN |
140 | } |
| 141 | else { | |
| 142 | form_set_error('', t('There is no processor enabled for this content-type. Contact your site administrator for help.')); | |
| 143 | } | |
| 144 | } | |
| 145 | break; | |
| b0925d15 | 146 | case 'insert': |
| b92ecd83 | 147 | _feedapi_insert($node); |
| b0925d15 AN |
148 | break; |
| 149 | case 'update': | |
| b92ecd83 | 150 | _feedapi_update($node); |
| b0925d15 AN |
151 | break; |
| 152 | case 'load': | |
| 348a079d | 153 | if ($feed = db_fetch_object(db_query('SELECT * FROM {feedapi} WHERE vid = %d', $node->vid))) { |
| a5bed0b9 | 154 | $node->feed = $feed; |
| 348a079d | 155 | $node->feed->vid = $node->vid; |
| 25a3b58b | 156 | $node->feed->nid = $node->nid; |
| 348a079d | 157 | $node->feed->settings = feedapi_get_settings($node->type, $node->vid); |
| a5bed0b9 | 158 | // Load parsers and processors from content type |
| 18c80d9b | 159 | $node_type_settings = feedapi_get_settings($node->type); |
| a5bed0b9 AB |
160 | $node->feed->parsers = _feedapi_format_settings($node_type_settings, 'parsers'); |
| 161 | $node->feed->processors = _feedapi_format_settings($node_type_settings, 'processors'); | |
| a5bed0b9 | 162 | } |
| b0925d15 AN |
163 | break; |
| 164 | case 'delete': | |
| 165 | // Could be a performance problem - think of thousands of node feed items. | |
| ad09dcae | 166 | // This is a temporary status. See: http://drupal.org/node/195723 |
| aa0cf13b | 167 | // feedapi_invoke('purge', $node->feed); |
| d9ee1997 | 168 | db_query("DELETE FROM {feedapi_stat} WHERE id = %d", $node->nid); |
| b0925d15 AN |
169 | db_query("DELETE FROM {feedapi} WHERE nid = %d", $node->nid); |
| 170 | break; | |
| 25a3b58b | 171 | case 'presave': |
| 34be5655 AN |
172 | if (is_array($node->feedapi) || isset($node->feedapi_object)) { |
| 173 | $node->feed = isset($node->feedapi_object) ? $node->feedapi_object : _feedapi_build_feed_object($node->type, $node->feedapi['feedapi_url']); | |
| 174 | } | |
| b0925d15 | 175 | break; |
| 348a079d AN |
176 | case 'delete revision': |
| 177 | db_query("DELETE FROM {feedapi} WHERE nid = %d AND vid = %d", $node->nid, $node->vid); | |
| 178 | break; | |
| 5a0af529 AN |
179 | } |
| 180 | } | |
| 181 | } | |
| 182 | ||
| 183 | /** | |
| 13cac391 AN |
184 | * Implementation of hook_node_type(). |
| 185 | */ | |
| 186 | function feedapi_node_type($op, $info) { | |
| adc50ece | 187 | switch ($op) { |
| 13cac391 AN |
188 | case 'delete': |
| 189 | variable_del('feedapi_settings_'. $info->type); | |
| 0a08c128 | 190 | variable_del('feedapi_'. $info->type); |
| 13cac391 AN |
191 | break; |
| 192 | case 'update': | |
| 193 | if (!empty($info->old_type) && $info->old_type != $info->type) { | |
| 194 | $setting = variable_get('feedapi_settings_'. $info->old_type, array()); | |
| 195 | variable_del('feedapi_settings_'. $info->old_type); | |
| 196 | variable_set('feedapi_settings_'. $info->type, $setting); | |
| 197 | } | |
| 198 | break; | |
| 199 | } | |
| 200 | } | |
| 201 | ||
| 202 | /** | |
| 7322614e AN |
203 | * Implementation of hook_block(). |
| 204 | */ | |
| 46008934 | 205 | function feedapi_block($op = 'list', $delta = 0) { |
| d41dc8fd | 206 | $blocks = array(); |
| aa0cf13b | 207 | $names = feedapi_get_types(); |
| 7322614e AN |
208 | switch ($op) { |
| 209 | case 'list': | |
| 492a9b6c AB |
210 | foreach ($names as $type => $name) { |
| 211 | $blocks[$type]['info'] = t('FeedAPI: Quick create !preset', array('!preset' => $name)); | |
| 9ee3d054 | 212 | $blocks[$type]['cache'] = BLOCK_CACHE_GLOBAL; |
| 90797234 | 213 | } |
| d41dc8fd | 214 | break; |
| ffdd07fe | 215 | case 'view': |
| fc28480e | 216 | if (node_access('create', $delta)) { |
| adc50ece AN |
217 | $blocks['subject'] = t('Create !preset', array('!preset' => $names[$delta])); |
| 218 | $blocks['content'] = drupal_get_form('feedapi_simplified_form', $delta); | |
| fc28480e | 219 | } |
| d41dc8fd | 220 | break; |
| 7322614e | 221 | } |
| adc50ece | 222 | return $blocks; |
| 7322614e AN |
223 | } |
| 224 | ||
| 225 | /** | |
| efb1f865 AN |
226 | * Implementation of hook_perm(). |
| 227 | */ | |
| 228 | function feedapi_perm() { | |
| e46638b1 | 229 | return array('administer feedapi', 'advanced feedapi options', 'use local files as feeds'); |
| efb1f865 AN |
230 | } |
| 231 | ||
| 232 | /** | |
| 5a0af529 AN |
233 | * Implementation of hook_link(). |
| 234 | */ | |
| 46008934 | 235 | function feedapi_link($type, $node = NULL) { |
| 5a0af529 | 236 | if ($type == 'node' && isset($node->feed)) { |
| 5b9d898b | 237 | if (strlen($node->feed->link) > 0) { |
| 5a0af529 | 238 | $links['feedapi_original'] = array( |
| 760e981c | 239 | 'title' => t('Link to site'), |
| 23c9ceac | 240 | 'href' => $node->feed->link, |
| 5a0af529 | 241 | ); |
| 0a743fb2 | 242 | return $links; |
| 5a0af529 | 243 | } |
| 5a0af529 AN |
244 | } |
| 245 | } | |
| 246 | ||
| 247 | /** | |
| 264aea58 AN |
248 | * Implementation of hook_node_views(). |
| 249 | */ | |
| 250 | function feedapi_views_api() { | |
| 251 | return array( | |
| 252 | 'api' => 2, | |
| 253 | 'path' => drupal_get_path('module', 'feedapi') .'/views', | |
| 254 | ); | |
| 255 | } | |
| 256 | ||
| 257 | /** | |
| 66dada0b | 258 | * Invoke feedapi API callback functions. |
| efb1f865 | 259 | * |
| 5f19c115 | 260 | * @param $op |
| be50f51a | 261 | * "load" Load the feed items basic data into the $feed->items[] |
| 66dada0b AB |
262 | * "refresh" Re-download the feed and process newly arrived item |
| 263 | * "purge" Delete all the feed items | |
| 0aa46c53 | 264 | * |
| 5f19c115 | 265 | * @param $feed |
| d9ee1997 | 266 | * A feed object. If only the ID is known, you should pass something like this: $feed->nid = X |
| 5f19c115 AN |
267 | * @param $param |
| 268 | * Depends on the $op value. | |
| efb1f865 | 269 | */ |
| aa0cf13b | 270 | function feedapi_invoke($op, &$feed, $param = NULL) { |
| 5f19c115 AN |
271 | if (!is_object($feed)) { |
| 272 | return FALSE; | |
| 273 | } | |
| 4e683f6f AB |
274 | // The node is passed. |
| 275 | if (isset($feed->feed) && is_object($feed->feed)) { | |
| 25a3b58b AN |
276 | $feed = $feed->feed; |
| 277 | } | |
| 0a743fb2 AN |
278 | if (!isset($feed->processors)) { |
| 279 | $node = node_load($feed->nid); | |
| 280 | if (!isset($node->feed)) { | |
| 281 | return FALSE; | |
| 282 | } | |
| 283 | $feed = $node->feed; | |
| 2fae1c1b | 284 | } |
| 2fecfb04 | 285 | _feedapi_sanitize_processors($feed); |
| c7a0e39f | 286 | |
| 66dada0b | 287 | switch ($op) { |
| c7a0e39f AN |
288 | case 'refresh': |
| 289 | return _feedapi_invoke_refresh($feed, $param); | |
| 290 | case 'purge': | |
| 291 | return _feedapi_invoke_purge($feed, $param); | |
| 292 | default: // Other operations | |
| 293 | return _feedapi_invoke($op, $feed, $param); | |
| 0a743fb2 AN |
294 | } |
| 295 | } | |
| 296 | ||
| 297 | /** | |
| d5f9ceb6 | 298 | * Ask for confirmation before deleting all the items |
| 1081e1c2 | 299 | */ |
| 25a3b58b | 300 | function feedapi_purge_confirm($form_state, $node) { |
| 1081e1c2 | 301 | $output = confirm_form( |
| 4e683f6f | 302 | array('nid' => array('#type' => 'hidden', '#value' => $node->nid)), |
| 1081e1c2 | 303 | t('Delete all the feed items from !name', array('!name' => $node->title)), |
| 7f0a2057 | 304 | isset($_GET['destination']) ? $_GET['destination'] : 'node/'. $node->nid, |
| 1081e1c2 AN |
305 | t("Are you sure you want to delete all the feed items from !name?", array('!name' => $node->title)), |
| 306 | t('Yes'), t('No'), | |
| 307 | 'feedapi_purge_confirm' | |
| 308 | ); | |
| 309 | return $output; | |
| 310 | } | |
| 311 | ||
| 312 | /** | |
| 313 | * Submitted items purging form. Drop all the items. | |
| 314 | */ | |
| 25a3b58b AN |
315 | function feedapi_purge_confirm_submit($form, &$form_state) { |
| 316 | $feed->nid = $form_state['values']['nid']; | |
| 170d279e | 317 | feedapi_invoke('purge', $feed); |
| 25a3b58b | 318 | $form_state['redirect'] = 'node/'. $form_state['values']['nid']; |
| 1081e1c2 AN |
319 | } |
| 320 | ||
| 321 | /** | |
| 0a743fb2 | 322 | * Delete expired items and return informations about the feed refreshing |
| 0aa46c53 | 323 | * |
| 0a743fb2 AN |
324 | * @param $feed |
| 325 | * The feed object | |
| c7a0e39f | 326 | * @param $settings |
| d9ee1997 | 327 | * Optional feed settings |
| 0a743fb2 AN |
328 | * @return |
| 329 | * FALSE if the feed don't have to be refreshed. (forbidden if the $force is TRUE) | |
| 330 | */ | |
| 18c80d9b | 331 | function feedapi_expire($feed, $settings = NULL) { |
| c7a0e39f | 332 | // Backwards compatibility, get settings if not passed |
| f450a0db | 333 | $settings = is_null($settings) ? feedapi_get_settings(NULL, $feed->vid) : $settings; |
| 694fbb56 AN |
334 | // Each processor can have its own expiration criteria ? |
| 335 | $expired = _feedapi_invoke('expire', $feed, $settings); | |
| c7a0e39f AN |
336 | // Return the number of expired items |
| 337 | return $expired ? array_sum($expired) : 0; | |
| 338 | } | |
| 339 | ||
| 340 | /** | |
| 341 | * Callback for expired items. Does the actual deleting | |
| 342 | */ | |
| 343 | function feedapi_expire_item($feed, $item) { | |
| 344 | foreach ($feed->processors as $processor) { | |
| 345 | module_invoke($processor, 'feedapi_item', 'delete', $item, $feed->nid); | |
| f5570608 | 346 | } |
| f5570608 AN |
347 | } |
| 348 | ||
| 349 | /** | |
| 5a0af529 AN |
350 | * Implementation of hook_form_alter(). |
| 351 | */ | |
| 9ee3d054 | 352 | function feedapi_form_alter(&$form, $form_state, $form_id) { |
| 5a0af529 AN |
353 | // Content type form. |
| 354 | if ($form_id == 'node_type_form' && isset($form['identity']['type'])) { | |
| 4e683f6f | 355 | $node_type_settings = feedapi_get_settings($form['#node_type']->type); |
| 0aa46c53 | 356 | |
| ef539fbc | 357 | $form['#validate'][] = 'feedapi_content_type_validate'; |
| ae51ab8f AB |
358 | |
| 359 | // Don't blow away existing form elements. | |
| 360 | if (!isset($form['feedapi'])) { | |
| 361 | $form['feedapi'] = array(); | |
| 362 | } | |
| 363 | $form['feedapi'] += array( | |
| 5a0af529 AN |
364 | '#type' => 'fieldset', |
| 365 | '#title' => t('Feed API'), | |
| 366 | '#collapsible' => TRUE, | |
| 4e683f6f | 367 | '#collapsed' => isset($node_type_settings['enabled']) ? !($node_type_settings['enabled']) : TRUE, |
| 5a0af529 | 368 | '#tree' => TRUE, |
| d8a5a04d | 369 | ); |
| 5a0af529 AN |
370 | $form['feedapi']['enabled'] = array( |
| 371 | '#type' => 'checkbox', | |
| 372 | '#title' => t('Is a feed content type'), | |
| ae73023e | 373 | '#description' => t('Check if you want to use this content type for downloading feeds to your site.'), |
| 4e683f6f | 374 | '#default_value' => isset($node_type_settings['enabled']) ? $node_type_settings['enabled'] : FALSE, |
| 5a0af529 | 375 | '#weight' => -15, |
| d8a5a04d | 376 | ); |
| fd78e2c3 AN |
377 | $form['feedapi']['upload_method'] = array( |
| 378 | '#type' => 'radios', | |
| 379 | '#title' => t('Supply feed as'), | |
| 380 | '#description' => t('Select how a user will supply a feed. Choose URL if the user will paste a URL to a textfield, choose File upload if the user will upload a feed from the local disk.'), | |
| 381 | '#options' => array('url' => t('URL'), 'upload' => t('File upload')), | |
| 382 | '#default_value' => isset($node_type_settings['upload_method']) ? $node_type_settings['upload_method'] : 'url', | |
| 383 | '#weight' => -14, | |
| 384 | ); | |
| 3fbbab5f AB |
385 | $modules = module_implements('feedapi_settings_form'); |
| 386 | foreach ($modules as $module) { | |
| d5f9ceb6 | 387 | $form['feedapi']['defaults'] = array('#type' => 'markup', '#value' => '<strong>'. t('Default settings') .'</strong><hr/>'); |
| 3fbbab5f AB |
388 | if ($feedapi_form = module_invoke($module, 'feedapi_settings_form', 'general')) { |
| 389 | $form['feedapi'] = array_merge_recursive($form['feedapi'], $feedapi_form); | |
| 390 | } | |
| 391 | } | |
| 5a0af529 AN |
392 | $form['feedapi']['parsers'] = array( |
| 393 | '#type' => 'fieldset', | |
| 394 | '#title' => t('Parser settings'), | |
| 9a16acac | 395 | '#description' => t('Parsers turn a feed into an object ready for processing. Choose at least one.'), |
| 5a0af529 AN |
396 | '#collapsible' => FALSE, |
| 397 | '#tree' => TRUE, | |
| d8a5a04d | 398 | ); |
| 46008934 | 399 | $parsers = module_implements('feedapi_feed', TRUE); |
| 5a0af529 AN |
400 | rsort($parsers); |
| 401 | foreach ($parsers as $parser) { | |
| 402 | $form['feedapi']['parsers'][$parser] = array( | |
| 403 | '#type' => 'fieldset', | |
| d8a5a04d | 404 | '#title' => feedapi_get_natural_name($parser), |
| 5a0af529 | 405 | '#collapsible' => TRUE, |
| 4e683f6f | 406 | '#collapsed' => isset($node_type_settings['parsers'][$parser]['enabled']) ? !($node_type_settings['parsers'][$parser]['enabled']) : TRUE, |
| 5a0af529 | 407 | '#tree' => TRUE, |
| 4e683f6f | 408 | '#weight' => isset($node_type_settings['parsers'][$parser]['weight']) ? $node_type_settings['parsers'][$parser]['weight']: 0, |
| 5a0af529 AN |
409 | ); |
| 410 | $form['feedapi']['parsers'][$parser]['enabled'] = array( | |
| 411 | '#type' => 'checkbox', | |
| 412 | '#title' => t('Enable'), | |
| e080dc83 | 413 | '#description' => t('Check this box if you want to enable the @name parser on this feed.', array('@name' => $parser)), |
| 4e683f6f | 414 | '#default_value' => isset($node_type_settings['parsers'][$parser]['enabled']) ? $node_type_settings['parsers'][$parser]['enabled'] : FALSE, |
| 5a0af529 AN |
415 | '#weight' => -15, |
| 416 | ); | |
| 5a0af529 | 417 | $form['feedapi']['parsers'][$parser]['weight'] = array( |
| be50f51a AN |
418 | '#type' => 'weight', |
| 419 | '#delta' => 15, | |
| 5a0af529 | 420 | '#title' => t('Weight'), |
| 9a16acac | 421 | '#description' => t('Control the execution order. Parsers with lower weights are called before parsers with higher weights.'), |
| 4e683f6f | 422 | '#default_value' => isset($node_type_settings['parsers'][$parser]['weight']) ? $node_type_settings['parsers'][$parser]['weight'] : 0, |
| 5a0af529 AN |
423 | '#weight' => -14, |
| 424 | ); | |
| 5c770af4 | 425 | if ($parser_form = module_invoke($parser, 'feedapi_settings_form', 'parsers')) { |
| d5f9ceb6 | 426 | $form['feedapi']['parsers'][$parser]['defaults'] = array('#type' => 'markup', '#value' => '<strong>'. t('Default settings') .'</strong><hr/>'); |
| 72e4d228 | 427 | $form['feedapi']['parsers'][$parser] = array_merge_recursive($form['feedapi']['parsers'][$parser], $parser_form); |
| 5c770af4 | 428 | } |
| 5a0af529 AN |
429 | } |
| 430 | $form['feedapi']['processors'] = array( | |
| 431 | '#type' => 'fieldset', | |
| 432 | '#title' => t('Processor settings'), | |
| 433 | '#description' => t('Processors are any kind of add on modules that hook into the feed handling process on download time - you can decide here what should happen to feed items once they are downloaded and parsed.'), | |
| 434 | '#collapsible' => FALSE, | |
| 435 | '#tree' => TRUE, | |
| 46008934 AN |
436 | ); |
| 437 | $processors = module_implements('feedapi_item', TRUE); | |
| 5a0af529 AN |
438 | rsort($processors); |
| 439 | foreach ($processors as $processor) { | |
| 440 | $form['feedapi']['processors'][$processor] = array( | |
| 441 | '#type' => 'fieldset', | |
| d8a5a04d | 442 | '#title' => feedapi_get_natural_name($processor), |
| 5a0af529 | 443 | '#collapsible' => TRUE, |
| 4e683f6f | 444 | '#collapsed' => isset($node_type_settings['processors'][$processor]['enabled']) ? !($node_type_settings['processors'][$processor]['enabled']): TRUE, |
| 5a0af529 | 445 | '#tree' => TRUE, |
| 4e683f6f | 446 | '#weight' => isset($node_type_settings['processors'][$processor]['weight']) ? $node_type_settings['processors'][$processor]['weight'] : 0, |
| 5a0af529 AN |
447 | ); |
| 448 | $form['feedapi']['processors'][$processor]['enabled'] = array( | |
| 449 | '#type' => 'checkbox', | |
| 450 | '#title' => t('Enable'), | |
| e080dc83 | 451 | '#description' => t('Check this box if you want to enable the @name processor on this feed.', array('@name' => $processor)), |
| 4e683f6f | 452 | '#default_value' => isset($node_type_settings['processors'][$processor]['enabled']) ? $node_type_settings['processors'][$processor]['enabled'] : FALSE, |
| 5a0af529 | 453 | '#weight' => -15, |
| d8a5a04d | 454 | ); |
| 5a0af529 | 455 | $form['feedapi']['processors'][$processor]['weight'] = array( |
| be50f51a AN |
456 | '#type' => 'weight', |
| 457 | '#delta' => 15, | |
| 5a0af529 | 458 | '#title' => t('Weight'), |
| 9a16acac | 459 | '#description' => t('Control the execution order. Processors with lower weights are called before processors with higher weights.'), |
| 4e683f6f | 460 | '#default_value' => isset($node_type_settings['processors'][$processor]['weight']) ? $node_type_settings['processors'][$processor]['weight'] : 0, |
| 5a0af529 | 461 | '#weight' => -14, |
| d8a5a04d | 462 | ); |
| 5c770af4 | 463 | if ($processor_form = module_invoke($processor, 'feedapi_settings_form', 'processors')) { |
| d5f9ceb6 | 464 | $form['feedapi']['processors'][$processor]['defaults'] = array('#type' => 'markup', '#value' => '<strong>'. t('Default settings') .'</strong><hr/>'); |
| 5c770af4 AB |
465 | $form['feedapi']['processors'][$processor] = array_merge_recursive($form['feedapi']['processors'][$processor], $processor_form); |
| 466 | } | |
| 467 | } | |
| 468 | // Populate form with node type settings if available. | |
| 469 | if ($node_type_settings) { | |
| 470 | $form['feedapi'] = _feedapi_populate($form['feedapi'], $node_type_settings); | |
| 5a0af529 | 471 | } |
| 4e683f6f | 472 | $form['#submit'][] = 'feedapi_content_type_submit'; |
| 5a0af529 | 473 | } |
| aa0cf13b | 474 | elseif (isset($form['type']) && isset($form['#node']) && $form['type']['#value'] .'_node_form' == $form_id && feedapi_enabled_type($form['type']['#value'])) { |
| fd78e2c3 AN |
475 | // Get settings for corresponding content type |
| 476 | // Which parsers / processors are enabled is a per content-type setting. | |
| 477 | $node_type_settings = feedapi_get_settings($form['type']['#value']); | |
| 478 | ||
| 4e683f6f | 479 | // FeedAPI-enabled node form. |
| d8a5a04d | 480 | $form['title']['#required'] = FALSE; |
| e8201e4c | 481 | $form['title']['#description'] = t('This field will be populated with the feed title. You can override by filling in this field.'); |
| 34fce3a3 AN |
482 | $form['body_field']['body']['#description'] = t('This field will be populated with the feed description. You can override by filling in this field.'); |
| 483 | $form['body_field']['body']['#rows'] = 2; | |
| ae51ab8f AB |
484 | |
| 485 | // Don't blow away existing form elements. | |
| 486 | if (!isset($form['feedapi'])) { | |
| 487 | $form['feedapi'] = array(); | |
| 488 | } | |
| 489 | $form['feedapi'] += array( | |
| e8201e4c AB |
490 | '#type' => 'fieldset', |
| 491 | '#title' => t('Feed'), | |
| 492 | '#collapsible' => TRUE, | |
| 493 | '#collapsed' => FALSE, | |
| 494 | '#tree' => TRUE, | |
| 495 | ); | |
| 9ee3d054 | 496 | $feedapi_url_default = ''; |
| 4e683f6f | 497 | if (isset($form['#node']->feed->url)) { |
| 3fbedb66 AB |
498 | $feedapi_url_default = $form['#node']->feed->url; |
| 499 | } | |
| 4e683f6f | 500 | elseif (isset($form_state['values']['feedapi']['feedapi_url'])) { |
| 25a3b58b | 501 | $feedapi_url_default = $form_state['values']['feedapi']['feedapi_url']; |
| 3fbedb66 | 502 | } |
| e738b551 | 503 | if (isset($node_type_settings['upload_method']) && $node_type_settings['upload_method'] == 'upload') { |
| fd78e2c3 AN |
504 | // Makes possible to upload file via this form. |
| 505 | $form['#attributes']['enctype'] = 'multipart/form-data'; | |
| 506 | $form['feedapi']['feedapi_file'] = array( | |
| 507 | '#type' => 'file', | |
| 508 | '#title' => t('Upload a feed'), | |
| 509 | '#description' => $feedapi_url_default ? '<div class="feed-url">'. $feedapi_url_default .'</div>' : '', | |
| 510 | '#size' => 40, | |
| 511 | ); | |
| 512 | $form['feedapi']['feedapi_url'] = array( | |
| 513 | '#type' => 'value', | |
| 514 | '#value' => $feedapi_url_default, | |
| 515 | ); | |
| 516 | } | |
| 517 | else { | |
| 518 | $form['feedapi']['feedapi_url'] = array( | |
| 519 | '#type' => 'textfield', | |
| 520 | '#title' => t('Feed URL'), | |
| f7163e9a | 521 | '#description' => t('Enter feed URL. The set of supported schemas (e.g. ftp://, http://) depends on the parser that you use.'), |
| fd78e2c3 AN |
522 | '#default_value' => $feedapi_url_default, |
| 523 | '#maxlength' => 2048, | |
| 524 | ); | |
| 525 | } | |
| 3fbbab5f AB |
526 | // Show per-node-type feedapi, parser options only for users with permissions. |
| 527 | if (user_access('advanced feedapi options')) { | |
| fd78e2c3 | 528 | // retrieve forms. |
| 3fbbab5f AB |
529 | $modules = module_implements('feedapi_settings_form'); |
| 530 | foreach ($modules as $module) { | |
| 531 | if ($feedapi_form = module_invoke($module, 'feedapi_settings_form', 'general')) { | |
| 532 | $form['feedapi'] = array_merge_recursive($form['feedapi'], $feedapi_form); | |
| 5c770af4 | 533 | } |
| 5a0af529 | 534 | } |
| adc50ece | 535 | |
| 3c6ab9ac AN |
536 | $submodules_names = array( |
| 537 | 'parsers' => t('Parsers'), | |
| 538 | 'processors' => t('Processors'), | |
| 539 | ); | |
| adc50ece AN |
540 | foreach (array("parsers" => "feedapi_feed", "processors" => "feedapi_item") as $type => $requirement) { |
| 541 | $suitable_handlers = module_implements($requirement, TRUE); | |
| 542 | foreach ($suitable_handlers as $module) { | |
| 9ee3d054 | 543 | if (isset($node_type_settings[$type][$module]) && $node_type_settings[$type][$module]['enabled']) { |
| adc50ece AN |
544 | $result = array(); |
| 545 | $result = module_invoke($module, 'feedapi_settings_form', $type); | |
| 546 | if (is_array($result)) { | |
| 547 | $result['#weight'] = $node_type_settings[$type][$module]['weight']; | |
| 548 | $form['feedapi'][$type][$module] = $result; | |
| 549 | $form['feedapi'][$type][$module]['#type'] = 'fieldset'; | |
| 550 | $form['feedapi'][$type][$module]['#title'] = feedapi_get_natural_name($module); | |
| 551 | $form['feedapi'][$type][$module]['#collapsible'] = TRUE; | |
| 552 | $form['feedapi'][$type][$module]['#collapsed'] = FALSE; | |
| 553 | $form['feedapi'][$type][$module]['#tree'] = TRUE; | |
| 554 | } | |
| 3fbbab5f | 555 | } |
| 5c770af4 | 556 | } |
| adc50ece AN |
557 | if (isset($form['feedapi'][$type])) { |
| 558 | $form['feedapi'][$type]['#type'] = 'fieldset'; | |
| 3c6ab9ac | 559 | $form['feedapi'][$type]['#title'] = $submodules_names[$type]; |
| adc50ece AN |
560 | $form['feedapi'][$type]['#collapsible'] = TRUE; |
| 561 | $form['feedapi'][$type]['#collapsed'] = TRUE; | |
| 562 | $form['feedapi'][$type]['#tree'] = TRUE; | |
| 3fbbab5f AB |
563 | } |
| 564 | } | |
| 5c770af4 | 565 | } |
| 5c770af4 | 566 | // If we are on a node form, get per node settings and populate form. |
| 9ee3d054 | 567 | if (isset($form['#node']->nid)) { |
| 348a079d | 568 | $settings = feedapi_get_settings($form['type']['#value'], $form['#node']->vid); |
| 9ee3d054 | 569 | } |
| bef1e05a | 570 | elseif (isset($node_type_settings)) { |
| 3fbbab5f | 571 | $settings = $node_type_settings; |
| 5c770af4 | 572 | } |
| bef1e05a AN |
573 | if (isset($settings)) { |
| 574 | $form['feedapi'] = _feedapi_populate($form['feedapi'], $settings); | |
| 575 | } | |
| aa0cf13b | 576 | $form['#validate'][] = 'feedapi_node_validate'; |
| 5a0af529 AN |
577 | } |
| 578 | } | |
| 579 | ||
| 580 | /** | |
| 25a3b58b AN |
581 | * Build feed object on validate and submit. |
| 582 | * See feedapi_form_alter on finding out how it is called (via FormAPI) | |
| 583 | */ | |
| aa0cf13b | 584 | function feedapi_node_validate($form, &$form_state) { |
| cb9d5ece AB |
585 | // Don't validate when deleting. |
| 586 | if ($form_state['values']['op'] == t('Delete')) { | |
| 587 | return TRUE; | |
| 588 | } | |
| fd78e2c3 AN |
589 | // Upload file. |
| 590 | $feed_dir = file_directory_path() .'/feeds'; | |
| 591 | file_check_directory($feed_dir, TRUE); | |
| 592 | $file = file_save_upload('feedapi', array(), $feed_dir); | |
| 593 | $has_upload = is_object($file); | |
| 594 | ||
| 595 | // Validate and transform settings for submission. | |
| 596 | if (empty($form_state['values']['feedapi']['feedapi_url']) && !$has_upload) { | |
| 597 | form_set_error('source', t('The Feed URL or uploading a file is required.')); | |
| 25a3b58b | 598 | } |
| e46638b1 AB |
599 | else if (!empty($form_state['values']['feedapi']['feedapi_url']) && !$has_upload && (strpos($form_state['values']['feedapi']['feedapi_url'], 'file://') === 0) && !user_access('use local files as feeds')) { |
| 600 | form_set_error('source', t('You do not have sufficient permissions to use local files as feeds.')); | |
| 601 | } | |
| 6007fc2f AB |
602 | else if (strpos($form_state['values']['feedapi']['feedapi_url'], 'file://') === 0 && !file_check_location(substr($form_state['values']['feedapi']['feedapi_url'], 7), file_directory_path())) { |
| 603 | drupal_set_message(file_check_location(substr($form_state['values']['feedapi']['feedapi_url'], 7), file_directory_path())); | |
| e46638b1 AB |
604 | form_set_error('source', t('file:// is only allowed for files under the files directory.')); |
| 605 | } | |
| fd78e2c3 AN |
606 | else { |
| 607 | if ($has_upload) { | |
| 608 | $form_state['values']['feedapi']['feedapi_url'] = file_create_url($file->filepath); | |
| 609 | } | |
| 610 | $feed = _feedapi_build_feed_object($form_state['values']['type'], $form_state['values']['feedapi']['feedapi_url']); | |
| 611 | if (!isset($feed->title) && $has_upload) { | |
| 612 | $form_state['values']['feedapi']['feedapi_url'] = NULL; | |
| 613 | } | |
| 614 | // Stick feed object into feedapi form snippet - store it in submit. | |
| 615 | $form_state['values']['feedapi_object'] = $feed; | |
| dd1d80c1 AN |
616 | if ($has_upload) { |
| 617 | $form_state['values']['feedapi_object']->url = str_replace($GLOBALS['base_url'], '', $form_state['values']['feedapi_object']->url); | |
| 618 | } | |
| fd78e2c3 AN |
619 | if (empty($form_state['values']['title']) && isset($feed->title)) { |
| 620 | form_set_value($form['title'], $feed->title, $form_state); | |
| 621 | } | |
| 3e17418b | 622 | if (isset($form['body_field']) && empty($form_state['values']['body']) && isset($feed->description)) { |
| fd78e2c3 AN |
623 | form_set_value($form['body_field']['body'], $feed->description, $form_state); |
| 624 | } | |
| 625 | if (empty($form_state['values']['title'])) { | |
| 626 | if (!$has_upload) { | |
| 627 | form_set_error('title', t('Title could not be retrieved from feed.')); | |
| 628 | } | |
| 629 | else { | |
| 630 | form_set_error('title', t('Title could not be detected. Make sure that the uploaded file is a valid feed.')); | |
| 631 | } | |
| 632 | } | |
| 3e17418b AN |
633 | elseif ($has_upload) { |
| 634 | file_set_status($file, FILE_STATUS_PERMANENT); | |
| 635 | } | |
| 25a3b58b AN |
636 | } |
| 637 | } | |
| 638 | ||
| 639 | /** | |
| 640 | * Store per-content-type settings | |
| 641 | */ | |
| 642 | function feedapi_content_type_submit($form, &$form_state) { | |
| 643 | // TODO: Drupal automatically stores mutilated 'feedapi_'. $form['#node_type']->type - remove. | |
| 644 | $type = !empty($form['#node_type']->type) ? $form['#node_type']->type : $form['#post']['type']; | |
| 645 | _feedapi_store_settings(array('node_type' => $type), $form_state['values']['feedapi']); | |
| 646 | } | |
| 647 | ||
| 648 | /** | |
| 3fbbab5f | 649 | * Implementation of hook_feedapi_settings_form(). |
| 3fbbab5f AB |
650 | */ |
| 651 | function feedapi_feedapi_settings_form($type) { | |
| 652 | if ($type == 'general') { | |
| 653 | $form['refresh_on_create'] = array( | |
| 654 | '#type' => 'checkbox', | |
| 655 | '#title' => t('Refresh feed on creation'), | |
| 656 | '#description' => t('If checked, feed items will be processed immediately after a feed is created.'), | |
| 657 | '#default_value' => 0, | |
| 658 | ); | |
| df25f3e8 | 659 | $form['update_existing'] = array( |
| 3fbbab5f AB |
660 | '#type' => 'checkbox', |
| 661 | '#title' => t('Update existing feed items'), | |
| 662 | '#description' => t('If checked, existing feed items will be updated when feed is refreshed.'), | |
| 663 | '#default_value' => 1, | |
| 664 | ); | |
| 42ec78cc AB |
665 | $period = array(); |
| 666 | $period[FEEDAPI_CRON_ALWAYS_REFRESH] = t('As often as possible'); | |
| 667 | $period += drupal_map_assoc(array(900, 1800, 3600, 10800, 21600, 32400, 43200, 86400, 172800, 259200, 604800, 1209600, 2419200, 3628800, 4838400, 7257600, 15724800, 31536000), 'format_interval'); | |
| e9545db8 | 668 | $period[FEEDAPI_CRON_NEVER_REFRESH] = t('Never refresh'); |
| e9545db8 AN |
669 | $form['refresh_time'] = array( |
| 670 | '#type' => 'select', | |
| 671 | '#title' => t('Minimum refresh period'), | |
| 672 | '#description' => t('Select the minimum time that should elapse between two refreshes of the same feed. For news feeds, don\'t go under 30 minutes. Note that FeedAPI cannot guarantee that a feed will be refreshed at the rate of the selected time. The actual refresh rate depends on many factors such as number of feeds in system and your hardware.'), | |
| 673 | '#options' => $period, | |
| 674 | '#default_value' => FEEDAPI_CRON_DEFAULT_REFRESH_TIME, | |
| 14f9ba3c | 675 | ); |
| 3fbbab5f | 676 | $period = drupal_map_assoc(array(3600, 10800, 21600, 32400, 43200, 86400, 172800, 259200, 604800, 1209600, 2419200, 3628800, 4838400, 7257600, 15724800, 31536000), 'format_interval'); |
| e9545db8 | 677 | $period[FEEDAPI_NEVER_DELETE_OLD] = t('Never delete'); |
| df25f3e8 | 678 | $form['items_delete'] = array( |
| 3fbbab5f AB |
679 | '#type' => 'select', |
| 680 | '#title' => t('Delete news items older than'), | |
| 681 | '#options' => $period, | |
| 682 | '#default_value' => FEEDAPI_NEVER_DELETE_OLD, | |
| 683 | ); | |
| 684 | } | |
| 685 | return $form; | |
| 686 | } | |
| 687 | ||
| 688 | /** | |
| f746980e AN |
689 | * Implementation of hook_cron(). |
| 690 | */ | |
| 691 | function feedapi_cron() { | |
| e8ca48b2 | 692 | global $user; |
| 0aa46c53 | 693 | |
| e8ca48b2 AN |
694 | // Saves the currently logged in user and start safe impersonating |
| 695 | $original_user = $user; | |
| 696 | session_save_session(FALSE); | |
| 0aa46c53 | 697 | |
| 5b9d898b | 698 | db_query('DELETE FROM {feedapi_stat} WHERE timestamp < %d', variable_get('cron_semaphore', FALSE) - FEEDAPI_CRON_STAT_LIFETIME); |
| 0aa46c53 | 699 | |
| c7a0e39f AN |
700 | // Initialize counters |
| 701 | $count = array( | |
| 702 | '%feeds' => 0, | |
| 703 | '%expired' => 0, | |
| 704 | '%new' => 0, | |
| 705 | '%updated' => 0, | |
| 706 | ); | |
| 0aa46c53 | 707 | |
| c7a0e39f | 708 | // We get feeds in small lots, this will save memory and have the process adjusting to the |
| d9ee1997 | 709 | // time limit even when we have many thousands of them. |
| e9545db8 | 710 | $now = time(); |
| c7a0e39f AN |
711 | $process = 0; |
| 712 | // The counter process will be > 0 if we've selected less feeds | |
| 713 | while (!$process && feedapi_cron_time()) { | |
| 714 | $process = FEEDAPI_CRON_FEEDS; | |
| 42ec78cc | 715 | $result = db_query_range("SELECT f.nid, n.uid FROM {feedapi} f JOIN {node} n ON n.vid = f.vid WHERE next_refresh_time <= %d AND next_refresh_time <> %d ORDER BY next_refresh_time ASC", $now, FEEDAPI_CRON_NEVER_REFRESH, 0, FEEDAPI_CRON_FEEDS); |
| c7a0e39f AN |
716 | |
| 717 | while (feedapi_cron_time() && $feed = db_fetch_object($result)) { | |
| e8ca48b2 | 718 | $user = user_load(array('uid' => $feed->uid)); |
| c7a0e39f | 719 | // Call the refresh process for each feed and store counters |
| aa0cf13b | 720 | $counter = feedapi_invoke('refresh', $feed, TRUE); |
| c7a0e39f AN |
721 | if ($counter) { |
| 722 | foreach ($counter as $name => $value) { | |
| d5f9ceb6 | 723 | $count['%'. $name] += $value; |
| c7a0e39f AN |
724 | } |
| 725 | } | |
| 726 | $count['%feeds']++; | |
| 727 | $process--; | |
| 6e6bcb4c | 728 | } |
| c7a0e39f | 729 | } |
| 0aa46c53 | 730 | |
| e8ca48b2 AN |
731 | // Loads back the logged in user |
| 732 | $user = $original_user; | |
| 733 | session_save_session(TRUE); | |
| c7a0e39f AN |
734 | } |
| 735 | ||
| 736 | /** | |
| 737 | * Check for time limits in cron processing. | |
| 0aa46c53 | 738 | * |
| c7a0e39f AN |
739 | * @return |
| 740 | * Number of seconds left, zero if none. | |
| 741 | */ | |
| 742 | function feedapi_cron_time() { | |
| 743 | static $time_limit; | |
| 744 | ||
| 745 | if (!$time_limit) { | |
| d4e1aae1 AN |
746 | $max_exec_time = ini_get('max_execution_time') == 0 ? 120 : ini_get('max_execution_time'); |
| 747 | $time_limit = time() + (variable_get('feedapi_cron_percentage', 15) / 100) * $max_exec_time; | |
| c7a0e39f | 748 | // However, check for left time, maybe some other cron processing already occured |
| dd1d80c1 AN |
749 | $cron_semaphore = variable_get('cron_semaphore', 0); |
| 750 | if ($cron_semaphore) { | |
| d4e1aae1 | 751 | $time_limit = min($time_limit, $cron_semaphore + $max_exec_time); |
| dd1d80c1 | 752 | } |
| c7a0e39f | 753 | timer_start('feedapi_cron'); |
| f746980e | 754 | } |
| c7a0e39f | 755 | return max($time_limit - time(), 0); |
| f746980e AN |
756 | } |
| 757 | ||
| 758 | /** | |
| 23c9ceac AN |
759 | * This is shown instead of normal node form when the simplified form is chosen at the settings |
| 760 | */ | |
| 25a3b58b AN |
761 | function feedapi_simplified_form($form_state, $type) { |
| 762 | $form['node']['#tree'] = TRUE; | |
| 99a611a0 | 763 | $form['node']['type'] = array( |
| 90797234 AN |
764 | '#type' => 'hidden', |
| 765 | '#value' => $type | |
| 23c9ceac | 766 | ); |
| 3fbedb66 | 767 | $form['url'] = array( |
| 23c9ceac AN |
768 | '#title' => t('Feed URL'), |
| 769 | '#type' => 'textfield', | |
| 3fbedb66 | 770 | '#size' => 25, |
| 23c9ceac | 771 | '#required' => TRUE, |
| 14f9ba3c | 772 | '#maxlength' => 2048, |
| 23c9ceac | 773 | ); |
| 34be5655 | 774 | $form['add'] = array( |
| 23c9ceac | 775 | '#type' => 'submit', |
| adc50ece | 776 | '#value' => t('Add'), |
| 23c9ceac | 777 | ); |
| 23c9ceac AN |
778 | return $form; |
| 779 | } | |
| 780 | ||
| 781 | /** | |
| e46638b1 AB |
782 | * Validates simplified form. |
| 783 | */ | |
| 784 | function feedapi_simplified_form_validate($form, &$form_state) { | |
| 785 | if (!empty($form_state['values']['url']) && (strpos($form_state['values']['url'], 'file://') === 0) && !user_access('use local files as feeds')) { | |
| 786 | form_set_error('url', t('You do not have sufficient permissions to use local files as feeds.')); | |
| 787 | } | |
| 6007fc2f | 788 | else if (strpos($form_state['values']['url'], 'file://') === 0 && !file_check_location(substr($form_state['values']['feedapi']['feedapi_url'], 7), file_directory_path())) { |
| e46638b1 AB |
789 | form_set_error('url', t('file:// is only allowed for files under the files directory.')); |
| 790 | } | |
| 791 | } | |
| 792 | ||
| 793 | /** | |
| 23c9ceac AN |
794 | * Create the node object and save |
| 795 | */ | |
| 25a3b58b AN |
796 | function feedapi_simplified_form_submit($form, &$form_state) { |
| 797 | $node_template = (object)$form_state['values']['node']; | |
| b0748f27 | 798 | $feed_type = (string)$_POST['node']['type']; |
| aa0cf13b | 799 | $valid_types = array_keys(feedapi_get_types()); |
| b0748f27 AN |
800 | foreach ($valid_types as $type) { |
| 801 | if ($type === $feed_type) { | |
| 802 | $node_template->type = $type; | |
| 803 | } | |
| 804 | } | |
| 25a3b58b | 805 | if ($node = feedapi_create_node($node_template, $form_state['values']['url'])) { |
| 3fbedb66 | 806 | drupal_set_message(t('Feed successfully created.')); |
| 25a3b58b | 807 | $form_state['redirect'] = 'node/'. $node->nid; |
| 3fbedb66 AB |
808 | } |
| 809 | else { | |
| 810 | drupal_set_message(t('Could not retrieve title from feed.'), 'error'); | |
| 25a3b58b | 811 | $form_state['redirect'] = array('node/add/'. $node_template->type, 'feedapi_url='. urlencode($form_state['values']['url'])); |
| 9e5a9f0f | 812 | } |
| 23c9ceac AN |
813 | } |
| 814 | ||
| 815 | /** | |
| d8a5a04d | 816 | * Get the module-defined natural name of FeedAPI parser or processor |
| b495b7f1 | 817 | * Define this name in hook_help(): |
| 0aa46c53 | 818 | * |
| d8a5a04d AN |
819 | * function hook_help($section) { |
| 820 | * switch ($section) { | |
| 821 | * case 'feedapi/full_name': | |
| 822 | * return t('Natural name'); | |
| 823 | * break; | |
| 824 | * } | |
| 825 | * } | |
| 826 | */ | |
| 827 | function feedapi_get_natural_name($module) { | |
| 828 | $help = $module .'_help'; | |
| e080dc83 AN |
829 | $module_natural = function_exists($help) ? $help('feedapi/full_name', '') : $module; |
| 830 | return empty($module_natural) ? $module : $module_natural; | |
| d8a5a04d AN |
831 | } |
| 832 | ||
| 833 | /** | |
| 3fbedb66 | 834 | * Create a feedapi node programatically. |
| 0aa46c53 | 835 | * |
| d5f9ceb6 AN |
836 | * @param $param |
| 837 | * Either a feedapi - enabled node type or a $node object with at least valid $node->type. | |
| 838 | * @param $url | |
| 839 | * URI of feed. | |
| 3fbedb66 | 840 | */ |
| 99a611a0 AB |
841 | function feedapi_create_node($param, $url) { |
| 842 | if (is_object($param)) { | |
| 843 | $node = $param; | |
| 844 | } | |
| 845 | else { | |
| 846 | $node = new stdClass(); | |
| 847 | $node->type = $param; | |
| 848 | } | |
| aa0cf13b | 849 | if (!feedapi_enabled_type($node->type)) { |
| 1914e792 AB |
850 | return FALSE; |
| 851 | } | |
| 99a611a0 AB |
852 | $feed = _feedapi_build_feed_object($node->type, $url); |
| 853 | if (!$feed->title && !$node->title) { | |
| 3fbedb66 AB |
854 | return FALSE; |
| 855 | } | |
| 25a3b58b | 856 | module_load_include('inc', 'node', 'node.pages'); |
| 99a611a0 AB |
857 | $node->title = $node->title ? $node->title : $feed->title; |
| 858 | $node->body = $node->body ? $node->body : $feed->description; | |
| 25a3b58b | 859 | $node->feedapi_object = $feed; |
| dbf9ad7f | 860 | // Get the content-type settings as default |
| 18c80d9b | 861 | $node->feedapi = feedapi_get_settings($node->type); |
| 3fbedb66 AB |
862 | node_object_prepare($node); |
| 863 | global $user; | |
| 864 | $node->uid = $user->uid; | |
| 865 | node_save($node); | |
| ae73023e AB |
866 | return $node; |
| 867 | } | |
| 868 | ||
| 869 | /** | |
| ae73023e AB |
870 | * Load node by URL. |
| 871 | * @param $args | |
| d5f9ceb6 | 872 | * Currently only supported $args['url] - URL string. |
| d9ee1997 AN |
873 | * @return |
| 874 | * Node object if successful, FALSE if not. | |
| ae73023e AB |
875 | */ |
| 876 | function feedapi_load_node($args) { | |
| 02d5c016 | 877 | if ($nid = db_result(db_query("SELECT nid FROM {feedapi} WHERE url = '%s'", $args['url']))) { |
| ae73023e AB |
878 | return node_load($nid); |
| 879 | } | |
| 880 | return FALSE; | |
| 881 | } | |
| 882 | ||
| 883 | /** | |
| 99a611a0 AB |
884 | * Refresh a feed node (= run enabled processors on it). |
| 885 | * @param $node | |
| adc50ece | 886 | * A node object with a $node->feed object. |
| 99a611a0 | 887 | * @param $destination_path |
| adc50ece | 888 | * If a destination path is given, function redirects to this destination. |
| 99a611a0 | 889 | */ |
| adc50ece | 890 | function feedapi_refresh($node, $destination_path = NULL) { |
| aa0cf13b | 891 | feedapi_invoke('refresh', $node->feed, FALSE); |
| 99a611a0 AB |
892 | if ($destination_path) { |
| 893 | drupal_goto($destination_path); | |
| 894 | } | |
| 25a3b58b AN |
895 | else { |
| 896 | drupal_goto('node/'. $node->nid); | |
| 897 | } | |
| 99a611a0 AB |
898 | } |
| 899 | ||
| 900 | /** | |
| 52cb2547 AN |
901 | * Insert feedapi data to the DB when it's a new for for FeedAPI |
| 902 | */ | |
| b92ecd83 | 903 | function _feedapi_insert(&$node) { |
| e9545db8 | 904 | |
| 52cb2547 | 905 | if (isset($node->feed->url) && isset($node->feed->feed_type)) { |
| e9545db8 AN |
906 | if (isset($node->feedapi)) { |
| 907 | $values = $node->feedapi; | |
| 908 | } | |
| 909 | else { | |
| 910 | // On revert revision, settings are on $node->feed->settings | |
| 911 | // @todo: verify, settings shouldn't be NOT an array here anyway. | |
| 912 | $values = (array) $node->feed->settings; | |
| 913 | } | |
| 52cb2547 | 914 | db_query("INSERT INTO {feedapi} ( |
| 348a079d | 915 | nid, vid, url, link, feed_type, processors, |
| e9545db8 | 916 | parsers, next_refresh_time, settings) VALUES |
| 348a079d | 917 | (%d, %d, '%s', '%s', '%s', '%s', '%s', %d, '%s')", |
| dbf9ad7f | 918 | $node->nid, |
| 348a079d | 919 | $node->vid, |
| dbf9ad7f | 920 | $node->feed->url, |
| 82fc36e8 | 921 | isset($node->feed->options->link) ? $node->feed->options->link : '', |
| dbf9ad7f AN |
922 | $node->feed->feed_type, |
| 923 | serialize($node->feed->processors), | |
| 924 | serialize($node->feed->parsers), | |
| e9545db8 | 925 | $values['refresh_time'] == FEEDAPI_CRON_NEVER_REFRESH ? $values['refresh_time'] : time() + $values['refresh_time'], |
| dbf9ad7f | 926 | serialize(array()) |
| 52cb2547 | 927 | ); |
| 52cb2547 AN |
928 | // Store add on module's settings if user has permission to do so. |
| 929 | if (user_access('advanced feedapi options')) { | |
| e9545db8 | 930 | _feedapi_store_settings(array('vid' => $node->vid), $values); |
| 52cb2547 | 931 | } |
| adc50ece | 932 | // Refresh feed if the user would like to do that |
| 4f95f0ab | 933 | $settings = feedapi_get_settings($node->type, $node->vid); |
| bef1e05a AN |
934 | if (isset($settings['refresh_on_create'])) { |
| 935 | if ($settings['refresh_on_create'] == TRUE) { | |
| 936 | $node->feed->nid = $node->nid; | |
| 4f95f0ab | 937 | $node->feed->vid = $node->vid; |
| bef1e05a AN |
938 | $node->feed->settings = $settings; |
| 939 | feedapi_invoke('refresh', $node->feed); | |
| 940 | } | |
| adc50ece | 941 | } |
| 52cb2547 AN |
942 | } |
| 943 | } | |
| 944 | ||
| 945 | /** | |
| 946 | * Update feed data of an existing feed | |
| 947 | */ | |
| b92ecd83 | 948 | function _feedapi_update(&$node) { |
| 52cb2547 AN |
949 | if (isset($node->feed)) { |
| 950 | $old_config = node_load($node->nid); | |
| e9545db8 | 951 | |
| 52cb2547 | 952 | // In that case this feed has never have feed data. Should be created then, this is not really an update |
| a5bed0b9 | 953 | if (!is_numeric($old_config->feed->nid)) { |
| bfdd2698 AN |
954 | $url = isset($node->feed->url) ? $node->feed->url : $node->feedapi['feedapi_url']; |
| 955 | $node->feed = _feedapi_build_feed_object($node->type, $url); | |
| b92ecd83 | 956 | _feedapi_insert($node); |
| 52cb2547 AN |
957 | return; |
| 958 | } | |
| 7c2968ce | 959 | $old_vid = db_result(db_query_range("SELECT vid FROM {feedapi} WHERE nid = %d ORDER BY vid DESC", $node->nid, 0, 1)); |
| 348a079d | 960 | if ($old_vid !== $node->vid) { |
| b92ecd83 | 961 | _feedapi_insert($node); |
| 348a079d AN |
962 | return; |
| 963 | } | |
| 0aa46c53 | 964 | // Only change next_refresh_time if refresh_time changed |
| e9545db8 AN |
965 | // or if $next_refresh_time is FEEDAPI_CRON_NEVER_REFRESH. |
| 966 | $next_refresh_time = $old_config->feed->next_refresh_time; | |
| 967 | if (isset($node->feedapi['refresh_time'])) { | |
| 968 | if ($node->feedapi['refresh_time'] == FEEDAPI_CRON_NEVER_REFRESH) { | |
| 969 | $next_refresh_time = FEEDAPI_CRON_NEVER_REFRESH; | |
| 970 | } | |
| 971 | elseif ($old_config->feed->settings['refresh_time'] != $node->feedapi['refresh_time'] || $next_refresh_time == FEEDAPI_CRON_NEVER_REFRESH) { | |
| 972 | $next_refresh_time = time() + $node->feedapi['refresh_time']; | |
| 973 | } | |
| 974 | } | |
| 0aa46c53 | 975 | |
| 52cb2547 | 976 | db_query("UPDATE {feedapi} SET |
| 0aa46c53 AN |
977 | url = '%s', |
| 978 | feed_type = '%s', | |
| e9545db8 | 979 | processors = '%s', |
| 0aa46c53 | 980 | parsers = '%s', |
| e9545db8 AN |
981 | link = '%s', |
| 982 | next_refresh_time = %d | |
| 983 | WHERE vid = %d", | |
| 9d0266f7 AN |
984 | isset($node->feed->url) ? $node->feed->url : $node->feedapi['feedapi_url'], |
| 985 | isset($node->feed->feed_type) ? $node->feed->feed_type : '', | |
| 986 | isset($node->feed->processors) ? serialize($node->feed->processors) : serialize($old_config->feed->processors), | |
| 987 | isset($node->feed->parsers) ? serialize($node->feed->parsers) : serialize($old_config->feed->parsers), | |
| 31b6d6bc | 988 | isset($node->feed->link) ? $node->feed->link : '', |
| e9545db8 | 989 | $next_refresh_time, |
| 348a079d | 990 | $node->vid |
| 52cb2547 AN |
991 | ); |
| 992 | // Store add on module's settings if user has permission to do so. | |
| 993 | if (user_access('advanced feedapi options')) { | |
| 348a079d | 994 | _feedapi_store_settings(array('vid' => $node->vid), $node->feedapi); |
| 52cb2547 AN |
995 | } |
| 996 | } | |
| 997 | } | |
| 998 | ||
| 999 | /** | |
| efb1f865 AN |
1000 | * Execute the enabled parsers and create an unified output |
| 1001 | * | |
| 5f19c115 AN |
1002 | * @param $feed |
| 1003 | * Feed object | |
| efb1f865 AN |
1004 | * @param $parsers |
| 1005 | * Structure: array( | |
| 1006 | * "primary" => "parser_primary", | |
| 1007 | * "secondary" => array("parser1", "parser2", "parserN") | |
| 1008 | * ); | |
| 1009 | * @return | |
| 1010 | * The object of the parser data | |
| 1011 | */ | |
| a46f62fb | 1012 | function _feedapi_call_parsers($feed, $parsers, $settings) { |
| b11d2045 | 1013 | $nid = isset($feed->nid) ? $feed->nid : ''; |
| 5c770af4 AB |
1014 | $parser_primary = array_shift($parsers); |
| 1015 | $parsers_secondary = $parsers; | |
| f7163e9a AN |
1016 | // Normalize relative URLs according to the base URL (mostly for uploaded feeds), but allow other schemes too |
| 1017 | if (!valid_url($feed->url, TRUE) && valid_url($feed->url) && !strpos($feed->url, '://')) { | |
| dd1d80c1 AN |
1018 | $feed->url = $GLOBALS['base_url'] . $feed->url; |
| 1019 | } | |
| 5c770af4 | 1020 | if (module_exists($parser_primary)) { |
| 271f95d3 AN |
1021 | $settings_primary = isset($settings[$parser_primary]) ? $settings[$parser_primary] : array(); |
| 1022 | $feed->feed_type = module_invoke($parser_primary, 'feedapi_feed', 'compatible', $feed, $settings_primary); | |
| 1023 | $parser_output = module_invoke($parser_primary, 'feedapi_feed', 'parse', $feed, $settings_primary); | |
| 694fbb56 | 1024 | if ($parser_output === FALSE) { |
| bfdd2698 | 1025 | return $feed; |
| 6e6bcb4c | 1026 | } |
| 5f19c115 | 1027 | $feed = (object) array_merge((array) $feed, (array) $parser_output); |
| 79aa6dc4 | 1028 | } |
| df25f3e8 | 1029 | // Call the turned on parsers, create a union of returned options |
| 5c770af4 AB |
1030 | $parsers_secondary = is_array($parsers_secondary) ? $parsers_secondary : array(); |
| 1031 | foreach ($parsers_secondary as $parser) { | |
| 335970c3 AN |
1032 | $settings_secondary = isset($settings[$parser]) ? $settings[$parser] : array(); |
| 1033 | $feed_ext = module_invoke($parser, 'feedapi_feed', 'parse', $feed, $settings_secondary); | |
| f746980e | 1034 | $feed->options = (object) ((array) $feed->options + (array) $feed_ext->options); |
| 23c9ceac | 1035 | // Merge items' options |
| eb77ee66 AN |
1036 | if (is_array($feed_ext->items)) { |
| 1037 | foreach ($feed_ext->items as $key => $item) { | |
| 82374e1a AN |
1038 | $src = isset($feed->items[$key]) ? $feed->items[$key]->options : array(); |
| 1039 | $feed->items[$key]->options = (object) ((array) $src + (array) $item->options); | |
| eb77ee66 | 1040 | } |
| 23c9ceac | 1041 | } |
| efb1f865 | 1042 | } |
| 0a743fb2 | 1043 | $feed->nid = $nid; |
| be50f51a | 1044 | foreach (module_implements('feedapi_after_parse') as $module) { |
| d5f9ceb6 | 1045 | $func = $module .'_feedapi_after_parse'; |
| 0de24138 | 1046 | $func($feed); |
| be50f51a | 1047 | } |
| d1530694 | 1048 | // Filter bad or not allowed tags, sanitize data (currently timestamp checking) |
| be50f51a AN |
1049 | if (!variable_get('feedapi_allow_html_all', FALSE)) { |
| 1050 | $allowed = preg_split('/\s+|<|>/', variable_get('feedapi_allowed_html_tags', '<a> <b> <br> <dd> <dl> <dt> <em> <i> <li> <ol> <p> <strong> <u> <ul>'), -1, PREG_SPLIT_NO_EMPTY); | |
| 73c9a29b AN |
1051 | } |
| 1052 | else { | |
| 1053 | $allowed = TRUE; | |
| 1054 | } | |
| 1055 | foreach (array('title', 'description') as $property) { | |
| 1056 | if (isset($feed->{$property})) { | |
| 1057 | if (is_string($feed->{$property})) { | |
| 1058 | $feed->{$property} = _feedapi_process_text($feed->{$property}, $allowed); | |
| f746980e AN |
1059 | } |
| 1060 | } | |
| 73c9a29b | 1061 | } |
| 7e18b1b7 AB |
1062 | if (isset($feed->options)) { |
| 1063 | $props = array_keys(get_object_vars($feed->options)); | |
| 1064 | foreach ($props as $property) { | |
| 1065 | if (isset($feed->options->{$property})) { | |
| 1066 | if (is_string($feed->options->{$property})) { | |
| 1067 | $feed->options->{$property} = _feedapi_process_text($feed->options->{$property}, $allowed); | |
| 1068 | } | |
| 321ecc6f AN |
1069 | } |
| 1070 | } | |
| 1071 | } | |
| 0aa46c53 | 1072 | |
| bef1e05a | 1073 | if (isset($feed->items)) { |
| 04205a55 | 1074 | foreach (array_keys($feed->items) as $i) { |
| bef1e05a AN |
1075 | $feed->items[$i]->title = _feedapi_process_text($feed->items[$i]->title, array()); |
| 1076 | $feed->items[$i]->description = _feedapi_process_text($feed->items[$i]->description, $allowed); | |
| 1077 | if ($feed->items[$i]->options->timestamp == 0) { | |
| 1078 | $feed->items[$i]->options->timestamp = time(); | |
| 1079 | } | |
| be50f51a | 1080 | } |
| f746980e | 1081 | } |
| 73c9a29b | 1082 | |
| efb1f865 AN |
1083 | return $feed; |
| 1084 | } | |
| 1085 | ||
| 1086 | /** | |
| 98f54a77 AN |
1087 | * Filter texts from parsers |
| 1088 | * | |
| 1089 | * @param $text | |
| 1090 | * The text to be processed | |
| 1091 | * @param $allowed | |
| 1092 | * Allowed tags in that text | |
| 1093 | * @return | |
| 1094 | * The safe string | |
| 1095 | */ | |
| 1096 | function _feedapi_process_text($text, $allowed) { | |
| 73c9a29b AN |
1097 | if (is_array($allowed)) { |
| 1098 | $text = filter_xss($text, $allowed); | |
| 1099 | } | |
| 98f54a77 | 1100 | if (version_compare(PHP_VERSION, '5.0.0', '<')) { |
| 321ecc6f | 1101 | return trim(html_entity_decode($text, ENT_QUOTES)); |
| 98f54a77 AN |
1102 | } |
| 1103 | else { | |
| 321ecc6f | 1104 | return trim(html_entity_decode($text, ENT_QUOTES, 'UTF-8')); |
| 98f54a77 AN |
1105 | } |
| 1106 | } | |
| 1107 | ||
| 1108 | /** | |
| 5a0af529 | 1109 | * Stores settings per content type or per node. |
| adc50ece AN |
1110 | * |
| 1111 | * @param $args | |
| eae1bdd7 | 1112 | * Associative array which is $args['vid'] = N or $args['node_type'] = "content_type". Depends on what to store for |
| adc50ece AN |
1113 | * @param $settings |
| 1114 | * The settings data itself | |
| 5a0af529 AN |
1115 | */ |
| 1116 | function _feedapi_store_settings($args, $settings) { | |
| 348a079d | 1117 | if (isset($args['vid'])) { |
| e9545db8 | 1118 | db_query("UPDATE {feedapi} SET settings = '%s' WHERE vid = %d", serialize($settings), $args['vid']); |
| 348a079d | 1119 | module_invoke_all('feedapi_after_settings', $args['vid'], $settings); |
| 3622b6d0 | 1120 | // This ensures that next time, not the cached, but the updated value will be used. |
| 348a079d | 1121 | feedapi_get_settings(NULL, $args['vid'], TRUE); |
| 5a0af529 | 1122 | } |
| eae1bdd7 | 1123 | elseif (isset($args['node_type'])) { |
| 5a0af529 AN |
1124 | variable_set('feedapi_settings_'. $args['node_type'], $settings); |
| 1125 | } | |
| 5a0af529 AN |
1126 | } |
| 1127 | ||
| 1128 | /** | |
| 83888f60 AB |
1129 | * Determines wether feedapi is enabled for given node type. |
| 1130 | * If parser or processor is passed in, this function determines wether given | |
| 1131 | * parser or processor is enabled for given node type. | |
| 1132 | * @param $node_type | |
| adc50ece | 1133 | * A Drupal node type. |
| 83888f60 | 1134 | * @param $parser_or_processor |
| adc50ece | 1135 | * A parser or processor - pass in by module name. |
| 83888f60 | 1136 | * @return TRUE if enabled, FALSE if not. |
| 5a0af529 | 1137 | */ |
| aa0cf13b | 1138 | function feedapi_enabled_type($node_type, $parser_or_processor = '') { |
| 18c80d9b | 1139 | $settings = feedapi_get_settings($node_type); |
| aa0cf13b AN |
1140 | if (empty($parser_or_processor)) { |
| 1141 | if (isset($settings['enabled'])) { | |
| 1142 | return $settings['enabled'] ? TRUE : FALSE; | |
| 1143 | } | |
| 1144 | else { | |
| 1145 | return FALSE; | |
| 1146 | } | |
| 83888f60 AB |
1147 | } |
| 1148 | foreach (array('parsers', 'processors') as $stage) { | |
| 4e683f6f | 1149 | if (isset($settings[$stage][$parser_or_processor]['enabled'])) { |
| aa0cf13b AN |
1150 | if ($settings[$stage][$parser_or_processor]['enabled'] == TRUE) { |
| 1151 | return TRUE; | |
| 1152 | } | |
| 83888f60 AB |
1153 | } |
| 1154 | } | |
| 1155 | return FALSE; | |
| 5a0af529 AN |
1156 | } |
| 1157 | ||
| 1158 | /** | |
| 66dada0b | 1159 | * Helper function for feedapi_invoke(). |
| 0aa46c53 | 1160 | * |
| c7a0e39f AN |
1161 | * Generic operations, collects results and returns array |
| 1162 | */ | |
| 1163 | function _feedapi_invoke($op, &$feed, $param) { | |
| 1164 | $output = array(); | |
| 1165 | foreach ($feed->processors as $processor) { | |
| 1166 | $result = module_invoke($processor, 'feedapi_item', $op, $feed, $param); | |
| 1167 | // Result may be a list of items or single values (count) | |
| 1168 | if ($result) { | |
| 1169 | if (is_array($result)) { | |
| 1170 | $output = array_merge($output, $result); | |
| d5f9ceb6 AN |
1171 | } |
| 1172 | else { | |
| c7a0e39f AN |
1173 | $output[] = $result; |
| 1174 | } | |
| 1175 | } | |
| 1176 | } | |
| 1177 | return $output; | |
| 1178 | } | |
| 1179 | ||
| 1180 | /** | |
| 1181 | * Helper function for feedapi_invoke(). | |
| 66dada0b AB |
1182 | * Refresh the feed, call the proper parsers and processors' hooks. |
| 1183 | * Don't call this function directly, use feedapi_refresh() instead. | |
| 0aa46c53 | 1184 | * |
| c7a0e39f | 1185 | * @ TODO Fix: This may loop forever when a feed has no processors |
| adc50ece | 1186 | */ |
| 66dada0b | 1187 | function _feedapi_invoke_refresh(&$feed, $param) { |
| c7a0e39f | 1188 | $timestamp = variable_get('cron_semaphore', FALSE) !== FALSE ? variable_get('cron_semaphore', FALSE) : time(); |
| 0aa46c53 | 1189 | |
| c7a0e39f | 1190 | $counter = array(); |
| adc50ece | 1191 | timer_start('feedapi_'. $feed->nid); |
| ff3eca2a | 1192 | $memory_usage_before = function_exists('memory_get_usage') ? memory_get_usage() : 0; |
| adc50ece | 1193 | $cron = $param; |
| 0aa46c53 | 1194 | |
| 47fded66 | 1195 | // Step 0: Check processors and grab settings |
| adc50ece AN |
1196 | if (!is_array($feed->processors) || count($feed->processors) == 0) { |
| 1197 | if (!$cron) { | |
| 1198 | drupal_set_message(t("No processors specified for URL %url. Could not refresh.", array('%url' => $feed->url)), "error"); | |
| 1199 | drupal_goto('node/'. $feed->nid); | |
| 1200 | } | |
| c7a0e39f | 1201 | return 0; |
| adc50ece | 1202 | } |
| 348a079d | 1203 | $settings = feedapi_get_settings(NULL, $feed->vid); |
| 0aa46c53 | 1204 | |
| 694fbb56 | 1205 | // Step 1: Force processors to delete old items and determine the max. create elements. |
| f450a0db | 1206 | $counter['expired'] = feedapi_expire($feed, $settings); |
| c7a0e39f | 1207 | |
| 694fbb56 | 1208 | // Step 2: Get feed. |
| adc50ece | 1209 | $nid = $feed->nid; |
| 82374e1a | 1210 | $hash_old = isset($feed->hash) ? $feed->hash : ''; |
| a46f62fb | 1211 | $feed = _feedapi_call_parsers($feed, $feed->parsers, $settings['parsers']); |
| 694fbb56 AN |
1212 | if (is_object($feed)) { |
| 1213 | $feed->hash = md5(serialize($feed->items)); | |
| 1214 | } | |
| 0aa46c53 | 1215 | |
| 694fbb56 | 1216 | // Step 3: See, whether feed has been modified. |
| bfdd2698 | 1217 | if (!isset($feed->items) || $hash_old == $feed->hash) { |
| e9545db8 AN |
1218 | // Updated the next_refresh_time field in any case. |
| 1219 | db_query("UPDATE {feedapi} SET next_refresh_time = %d, half_done = %d WHERE nid = %d", time() + $settings['refresh_time'], FALSE, $nid); | |
| adc50ece | 1220 | if (!$cron) { |
| 669c9aae | 1221 | if (is_object($feed) && $hash_old == $feed->hash) { |
| 502b57ef AN |
1222 | drupal_set_message(t('There are no new items in the feed.'), 'status'); |
| 1223 | } | |
| 1224 | else { | |
| 1225 | drupal_set_message(t('Could not refresh feed.'), 'error'); | |
| 1226 | } | |
| adc50ece | 1227 | } |
| c7a0e39f | 1228 | return $counter; |
| adc50ece AN |
1229 | } |
| 1230 | ||
| c7a0e39f AN |
1231 | // Step 4: Walk through the items and check duplicates, then save or update |
| 1232 | $items = $feed->items; | |
| adc50ece AN |
1233 | $updated = 0; |
| 1234 | $new = 0; | |
| 1235 | $half_done = FALSE; | |
| c7a0e39f AN |
1236 | |
| 1237 | // We check for time-out after each item | |
| 502b57ef | 1238 | foreach ($items as $index => $item) { |
| d9ee1997 | 1239 | // Call each item parser. |
| 502b57ef AN |
1240 | $item->is_updated = FALSE; |
| 1241 | $item->is_new = FALSE; | |
| adc50ece | 1242 | foreach ($feed->processors as $processor) { |
| e9545db8 AN |
1243 | $unique = module_invoke($processor, 'feedapi_item', 'unique', $item, $feed->nid, $settings['processors'][$processor]); |
| 1244 | if ($unique === FALSE || is_numeric($unique)) { | |
| adc50ece | 1245 | if ($settings['update_existing'] == TRUE) { |
| e9545db8 | 1246 | module_invoke($processor, 'feedapi_item', 'update', $item, $feed->nid, $settings['processors'][$processor], $unique); |
| 502b57ef | 1247 | $item->is_updated = TRUE; |
| adc50ece AN |
1248 | } |
| 1249 | } | |
| 1250 | else { | |
| c7a0e39f | 1251 | // We have checked before for expired items, so just save it. |
| adc50ece | 1252 | // if the item is already expired then do nothing |
| adc50ece | 1253 | $items_delete = $settings['items_delete']; |
| c7a0e39f | 1254 | $diff = abs(time() - (isset($item->options->timestamp) ? $item->options->timestamp : time())); |
| adc50ece AN |
1255 | if ($diff > $items_delete && ($items_delete > FEEDAPI_NEVER_DELETE_OLD)) { |
| 1256 | break; | |
| 1257 | } | |
| 0c979d50 AN |
1258 | $result = module_invoke($processor, 'feedapi_item', 'save', $item, $feed->nid, $settings['processors'][$processor]); |
| 1259 | if ($result !== FALSE) { | |
| 1260 | $item->is_new = TRUE; | |
| 1261 | } | |
| adc50ece AN |
1262 | } |
| 1263 | } | |
| 502b57ef AN |
1264 | $new = $item->is_new ? $new + 1 : $new; |
| 1265 | $updated = ($item->is_updated && !$item->is_new) ? $updated + 1 : $updated; | |
| 0aa46c53 | 1266 | |
| c7a0e39f AN |
1267 | // Decision on time. If the exec time is greather than the user-set percentage of php max execution time |
| 1268 | if ($cron && !feedapi_cron_time()) { | |
| adc50ece AN |
1269 | $half_done = ($new + $updated) == count($items) ? FALSE : TRUE; |
| 1270 | break; | |
| 1271 | } | |
| 502b57ef AN |
1272 | // Save the item status for further processing |
| 1273 | $feed->items[$index] = $item; | |
| adc50ece | 1274 | } |
| 0aa46c53 | 1275 | |
| ffbf6fa2 | 1276 | // Closing step: Call after refresh and update feed statistics |
| dd1d80c1 AN |
1277 | foreach (module_implements('feedapi_after_refresh') as $module) { |
| 1278 | $func = $module .'_feedapi_after_refresh'; | |
| 1279 | $func($feed); | |
| ffbf6fa2 | 1280 | } |
| e9545db8 AN |
1281 | |
| 1282 | // Set next_refresh_time to FEEDAPI_CRON_NEVER_REFRESH if refresh_time is FEEDAPI_CRON_NEVER_REFRESH. | |
| 1283 | $next_refresh_time = $settings['refresh_time'] == FEEDAPI_CRON_NEVER_REFRESH ? $settings['refresh_time'] : (time() + $settings['refresh_time']); | |
| 1284 | db_query("UPDATE {feedapi} SET next_refresh_time = %d, half_done = %d, hash = '%s' WHERE nid = %d", $next_refresh_time, $half_done, $feed->hash, $feed->nid); | |
| 1285 | ||
| de5ecd59 | 1286 | // Log statistics. |
| ff3eca2a | 1287 | $memory_usage_after = function_exists('memory_get_usage') ? memory_get_usage() : 0; |
| de5ecd59 AN |
1288 | _feedapi_store_stat($nid, 'update_times', time(), $timestamp); |
| 1289 | _feedapi_store_stat($nid, 'new', $new, $timestamp); | |
| 1290 | _feedapi_store_stat($nid, 'download_num', count($items), $timestamp); | |
| 1291 | _feedapi_store_stat($nid, 'process_time', timer_read('feedapi_'. $feed->nid), $timestamp); | |
| ff3eca2a | 1292 | _feedapi_store_stat($nid, 'memory_increase', $memory_usage_after - $memory_usage_before, $timestamp); |
| de5ecd59 AN |
1293 | _feedapi_store_stat($nid, 'next_refresh_time', $next_refresh_time, $timestamp); |
| 1294 | ||
| adc50ece | 1295 | if (!$cron) { |
| 502b57ef AN |
1296 | if ($new == 0 && $updated == 0) { |
| 1297 | drupal_set_message(t('There are no new items in the feed.'), 'status'); | |
| d5f9ceb6 AN |
1298 | } |
| 1299 | else { | |
| 502b57ef AN |
1300 | drupal_set_message(t("%new new item(s) were saved. %updated existing item(s) were updated.", array("%new" => $new, "%updated" => $updated))); |
| 1301 | } | |
| c7a0e39f | 1302 | // @ TODO what value to return here? |
| d5f9ceb6 AN |
1303 | } |
| 1304 | else { | |
| c7a0e39f AN |
1305 | // Update and return counter |
| 1306 | $counter['new'] = $new; | |
| 1307 | $counter['updated'] = $updated; | |
| 1308 | return $counter; | |
| adc50ece AN |
1309 | } |
| 1310 | } | |
| 1311 | ||
| 1312 | /** | |
| 66dada0b | 1313 | * Helper function for feedapi_invoke(). |
| adc50ece AN |
1314 | * Delete all feed items of a feed. |
| 1315 | */ | |
| 66dada0b | 1316 | function _feedapi_invoke_purge(&$feed, $param) { |
| adc50ece | 1317 | $node = node_load($feed->nid); |
| 0aa46c53 | 1318 | |
| adc50ece AN |
1319 | if ($param == 'items') { |
| 1320 | return drupal_get_form('feedapi_purge_confirm', $node); | |
| 1321 | } | |
| adc50ece AN |
1322 | |
| 1323 | // Delete items from the processors | |
| 170d279e AN |
1324 | foreach ($feed->processors as $processor) { |
| 1325 | // FIXME: it's possible now to accidentally delete an item from another processor | |
| 1326 | module_invoke($processor, 'feedapi_item', 'purge', $feed); | |
| adc50ece | 1327 | } |
| eb91c99c AN |
1328 | |
| 1329 | // Closing step: Call after purge hook | |
| 1330 | foreach (module_implements('feedapi_after_purge') as $module) { | |
| 1331 | $func = $module .'_feedapi_after_purge'; | |
| 1332 | $func($feed); | |
| 1333 | } | |
| 1334 | ||
| 170d279e AN |
1335 | // Reset hash. |
| 1336 | db_query("UPDATE {feedapi} SET hash = 0 WHERE nid = %d", $feed->nid); | |
| adc50ece AN |
1337 | } |
| 1338 | ||
| 1339 | /** | |
| 5c770af4 AB |
1340 | * Builds feed object ready to be sticked onto node. |
| 1341 | */ | |
| 1342 | function _feedapi_build_feed_object($node_type, $url) { | |
| 1343 | $feed = new stdClass(); | |
| 1344 | $feed->url = $url; | |
| 18c80d9b | 1345 | $node_type_settings = feedapi_get_settings($node_type); |
| 5c770af4 AB |
1346 | $feed->processors = _feedapi_format_settings($node_type_settings, 'processors'); |
| 1347 | $feed->parsers = _feedapi_format_settings($node_type_settings, 'parsers'); | |
| 9ee3d054 | 1348 | if (isset($feed->url)) { |
| a46f62fb | 1349 | $feed = _feedapi_call_parsers($feed, $feed->parsers, $node_type_settings['parsers']); |
| 5c770af4 | 1350 | } |
| 9ee3d054 | 1351 | $feed->link = isset($feed->options->link) ? $feed->options->link : ''; |
| 5c770af4 AB |
1352 | return $feed; |
| 1353 | } | |
| 1354 | ||
| 1355 | /** | |
| 1356 | * Returns per content type settings ordered by weight | |
| 1357 | * and only those that are turned on. | |
| 1358 | * @param $node_type_settings | |
| d5f9ceb6 | 1359 | * Content type settings retrieved with feedapi_get_settings(). |
| 5c770af4 | 1360 | * @param $stage_type |
| d5f9ceb6 | 1361 | * 'parsers' or 'processors' |
| 5c770af4 AB |
1362 | */ |
| 1363 | function _feedapi_format_settings($node_type_settings, $stage_type) { | |
| 1364 | $result = array(); | |
| 1365 | $settings = $node_type_settings[$stage_type]; | |
| 87cc49ec | 1366 | if (!is_array($settings)) { |
| 669c9aae | 1367 | return $result; |
| 87cc49ec | 1368 | } |
| 5c770af4 | 1369 | foreach ($settings as $name => $properties) { |
| aa0cf13b AN |
1370 | if (isset($properties['enabled'])) { |
| 1371 | if ($properties['enabled'] == TRUE) { | |
| 1372 | $result[$properties['weight']] = $name; | |
| 85e33999 | 1373 | } |
| 5c770af4 AB |
1374 | } |
| 1375 | } | |
| 1376 | ksort($result); | |
| 1377 | return $result; | |
| 1378 | } | |
| 1379 | ||
| 1380 | /** | |
| 5a0af529 | 1381 | * Retrieve settings per content type or per node. |
| 0aa46c53 | 1382 | * |
| 18c80d9b | 1383 | * @param $node_type |
| 592bd7ee | 1384 | * Content type name or NULL if per node |
| 348a079d AN |
1385 | * @param $vid |
| 1386 | * Node vid or NULL if per content type | |
| 592bd7ee AN |
1387 | * @param $reset |
| 1388 | * If TRUE, the data is returned from the database. | |
| 18c80d9b AN |
1389 | * @return |
| 1390 | * The associative array of feedapi settings | |
| 0aa46c53 | 1391 | * |
| d9ee1997 AN |
1392 | * @todo: Use node type settings for pulling on/off and weight of |
| 1393 | * parsers/processors, use per node settings to override their | |
| 1394 | * configuration, this allows us a more predictable | |
| 75c6d700 | 1395 | * presets/settings behaviour. See d. o. #191692 |
| 83888f60 AB |
1396 | * Watch out: cache permutations of node_type or node_type+nid or nid. |
| 1397 | * Watch out: changes within page load likely. | |
| 5a0af529 | 1398 | */ |
| 348a079d | 1399 | function feedapi_get_settings($node_type, $vid = FALSE, $reset = FALSE) { |
| c7a0e39f | 1400 | static $node_settings; |
| 0aa46c53 | 1401 | |
| 348a079d AN |
1402 | if (is_numeric($vid)) { |
| 1403 | if (!isset($node_settings[$vid]) || $reset) { | |
| 1404 | if ($settings = db_fetch_object(db_query('SELECT settings FROM {feedapi} WHERE vid = %d', $vid))) { | |
| c7a0e39f | 1405 | $settings = unserialize($settings->settings); |
| 82374e1a AN |
1406 | // If parsers don't have any settings, create an empty array |
| 1407 | if (!isset($settings['parsers'])) { | |
| 1408 | $settings['parsers'] = array(); | |
| 1409 | } | |
| 1410 | // If processors don't have any settings, create an empty array | |
| 1411 | if (!isset($settings['processors'])) { | |
| 1412 | $settings['processors'] = array(); | |
| 1413 | } | |
| 5a0af529 | 1414 | } |
| 669c9aae | 1415 | if (is_array($settings) && count($settings['processors']) == 0 && count($settings['parsers']) == 0) { |
| bef1e05a AN |
1416 | $settings = NULL; |
| 1417 | } | |
| 348a079d | 1418 | $node_settings[$vid] = !empty($settings) && is_array($settings) ? $settings : FALSE; |
| de473ec0 | 1419 | } |
| 348a079d | 1420 | if (!is_array($node_settings[$vid])) { |
| 18c80d9b AN |
1421 | if (empty($node_type)) { |
| 1422 | // In normal case, this shouldn't happen. This is an emergency branch | |
| 348a079d | 1423 | $node_type = db_result(db_query("SELECT type FROM {node} WHERE vid = %d", $vid)); |
| de473ec0 AN |
1424 | } |
| 1425 | } | |
| 1426 | else { | |
| 348a079d | 1427 | return $node_settings[$vid]; |
| 5a0af529 AN |
1428 | } |
| 1429 | } | |
| 0aa46c53 | 1430 | |
| 5a0af529 | 1431 | // Fallback: node_type. |
| 18c80d9b | 1432 | if (isset($node_type) && is_string($node_type)) { |
| f897c7d8 | 1433 | if (($settings = variable_get('feedapi_settings_'. $node_type, FALSE)) && ($settings['enabled'] == 1)) { |
| bef1e05a AN |
1434 | // Sanitize data right now, tricky users may turned off the module |
| 1435 | foreach (array('parsers', 'processors') as $type) { | |
| e738b551 AN |
1436 | if (isset($settings[$type]) && is_array($settings[$type])) { |
| 1437 | $modules = array_keys($settings[$type]); | |
| 1438 | foreach ($modules as $module) { | |
| 1439 | if (!module_exists($module)) { | |
| 1440 | unset($settings['parsers'][$module]); | |
| 1441 | } | |
| 1442 | } | |
| 1443 | } | |
| 1444 | else { | |
| 1445 | // Missing parser or processor, set error message. | |
| 1446 | if (user_access('administer content types')) { | |
| c7226064 | 1447 | drupal_set_message(t('There are no !type defined for this content type. Go to !edit_page and enable at least one.', array('!type' => $type, '!edit_page' => l('admin/content/node-type/'. $node_type, 'admin/content/node-type/'. $node_type))), 'warning', FALSE); |
| e738b551 AN |
1448 | } |
| 1449 | else { | |
| c7226064 | 1450 | drupal_set_message(t('There are no !type defined for this content type. Contact your site administrator.', array('!type' => $type)), 'warning', FALSE); |
| bef1e05a AN |
1451 | } |
| 1452 | } | |
| 1453 | } | |
| 5a0af529 AN |
1454 | return $settings; |
| 1455 | } | |
| 1456 | } | |
| 1457 | return FALSE; | |
| 1458 | } | |
| 1459 | ||
| 1460 | /** | |
| 1461 | * Set default value of $form elements if present in $settings. | |
| 1462 | */ | |
| 1463 | function _feedapi_populate($form, $settings) { | |
| 5a0af529 AN |
1464 | foreach ($form as $k => $v) { |
| 1465 | if (is_array($v)) { | |
| 1466 | if (array_key_exists('#default_value', $v)) { | |
| 3fbbab5f AB |
1467 | // Don't prepopulate feedapi_url slot, not stored in settings |
| 1468 | // Might be overwritten otherwise by users without advanced feedapi options permissions. | |
| 1469 | // Todo: stick all settings form elements that are not in 'parsers' or 'processors' in 'general' - | |
| 1470 | // This is kind of tricky though without breaking sites out there. | |
| 27c70840 | 1471 | if ($k != 'feedapi_url') { |
| 4e683f6f | 1472 | if (isset($form[$k]['#parents']) && is_array($form[$k]['#parents'])) { |
| 27c70840 AN |
1473 | // respect #parents if set |
| 1474 | $form[$k]['#default_value'] = _feedapi_populate_get_setting($form[$k]['#parents'], $settings); | |
| 1475 | } | |
| 4e683f6f | 1476 | elseif (isset($settings[$k])) { |
| 27c70840 AN |
1477 | $form[$k]['#default_value'] = $settings[$k]; |
| 1478 | } | |
| 5a0af529 AN |
1479 | } |
| 1480 | } | |
| 85e33999 AN |
1481 | elseif (isset($settings[$k])) { |
| 1482 | $form[$k] = _feedapi_populate($form[$k], $settings[$k]); | |
| 5a0af529 AN |
1483 | } |
| 1484 | } | |
| 1485 | } | |
| 5a0af529 AN |
1486 | return $form; |
| 1487 | } | |
| 1488 | ||
| 1489 | /** | |
| 27c70840 AN |
1490 | * Gets the setting for '#parent' |
| 1491 | * (there must be a more efficent way) | |
| 1492 | */ | |
| 1493 | function _feedapi_populate_get_setting($parents, $settings) { | |
| 1494 | if (is_array($parents) && count($parents)) { | |
| 1495 | $this_parent = array_shift($parents); | |
| aa0cf13b | 1496 | return _feedapi_populate_get_setting($parents, $settings[$this_parent]); |
| 27c70840 AN |
1497 | } |
| 1498 | else { | |
| aa0cf13b | 1499 | return $settings[$parents]; |
| 27c70840 AN |
1500 | } |
| 1501 | } | |
| 1502 | ||
| 1503 | /** | |
| 6e6bcb4c AN |
1504 | * Calculate the average between-update time |
| 1505 | */ | |
| 1506 | function _feedapi_update_rate($update_times) { | |
| b11d2045 | 1507 | $between = array(); |
| 6e6bcb4c AN |
1508 | for ($i = 0; $i < count($update_times) - 1; $i++) { |
| 1509 | $between[] = abs($update_times[$i] - $update_times[$i + 1]); | |
| 1510 | } | |
| 1511 | return (count($between) > 0) ? round(array_sum($between) / count($between), 2) : t('No data yet'); | |
| 1512 | } | |
| 1513 | ||
| 1514 | /** | |
| f746980e AN |
1515 | * Remove non-existing processors from the processors arrays |
| 1516 | */ | |
| 1517 | function _feedapi_sanitize_processors(&$feed) { | |
| 5c770af4 AB |
1518 | if (is_array($feed->processors)) { |
| 1519 | foreach ($feed->processors as $key => $processor) { | |
| 1520 | if (!module_exists($processor)) { | |
| 1521 | unset($feed->processors[$key]); | |
| f746980e AN |
1522 | } |
| 1523 | } | |
| 1524 | } | |
| 1525 | } | |
| fd4a571c EB |
1526 | |
| 1527 | /** | |
| c7a0e39f AN |
1528 | * Store statistics information |
| 1529 | * | |
| 1530 | * @param $id | |
| 1531 | * A numerical id | |
| 1532 | * @param $type | |
| 1533 | * A string which describes what we want to store. This is an identifier, think of as a variable name | |
| 1534 | * @param $val | |
| 1535 | * This is the variable value | |
| 1536 | * @param $timestamp | |
| 1537 | * Timestamp for the value | |
| 1538 | * @param $time | |
| 1539 | * Optional, a string equivalent to the $timestamp | |
| 1540 | * @param $update | |
| 1541 | * Boolean, TRUE if you'd like to modify an existing entry in the stat table | |
| 1542 | */ | |
| d9ee1997 | 1543 | function _feedapi_store_stat($id, $type, $val, $timestamp, $time = NULL, $update = FALSE) { |
| c7a0e39f AN |
1544 | if (!$time) { |
| 1545 | $time = date("Y-m-d H:i", $timestamp); | |
| 1546 | } | |
| 1547 | if ($update) { | |
| 02d5c016 | 1548 | db_query("UPDATE {feedapi_stat} SET value = %d, timestamp = %d WHERE time = '%s' AND type = '%s' AND id = %d", $val, $timestamp, $time, $type, $id); |
| c7a0e39f | 1549 | } |
| 502b57ef | 1550 | if (!$update || !db_affected_rows()) { |
| 02d5c016 | 1551 | db_query("INSERT INTO {feedapi_stat} (id, value, time, timestamp, type) VALUES (%d, %d, '%s', %d, '%s')", $id, $val, $time, $timestamp, $type); |
| c7a0e39f AN |
1552 | } |
| 1553 | } | |
| 1554 | ||
| 1555 | /** | |
| 1556 | * Return the type-specific statistics data | |
| 1557 | * | |
| 1558 | * @param $id | |
| 1559 | * A numerical id | |
| 1560 | * @param $type | |
| 1561 | * Name of the type (variable) | |
| 1562 | * @name $only_val | |
| d9ee1997 | 1563 | * If TRUE, only the values are returned, no more. |
| c7a0e39f AN |
1564 | * @return |
| 1565 | * $only_val = FALSE -> array("timestamp" => array(), "time" => array(), "value" => array()); | |
| 1566 | */ | |
| 1567 | function _feedapi_get_stat($id, $type, $only_val = FALSE) { | |
| b11d2045 | 1568 | $stat = array(); |
| 0a039293 | 1569 | $result = db_query("SELECT timestamp, time, value FROM {feedapi_stat} WHERE type = '%s' AND id = %d", $type, $id); |
| c7a0e39f AN |
1570 | while ($row = db_fetch_array($result)) { |
| 1571 | if ($only_val) { | |
| 1572 | $stat[] = $row['value']; | |
| 1573 | } | |
| 1574 | else { | |
| 1575 | foreach (array('timestamp', 'time', 'value') as $member) { | |
| 1576 | $stat[$member][] = $row[$member]; | |
| 1577 | } | |
| 1578 | } | |
| 1579 | } | |
| 1580 | return $stat; | |
| 1581 | } | |
| 1582 | ||
| 1583 | /** | |
| 1d6ac378 AN |
1584 | * Return a list of FeedAPI-enabled content-types list, ready-to-use for #options at FormsAPI |
| 1585 | */ | |
| aa0cf13b | 1586 | function feedapi_get_types() { |
| 1d6ac378 | 1587 | $names = node_get_types('names'); |
| 71103b77 | 1588 | foreach ($names as $type => $name) { |
| aa0cf13b | 1589 | if (!feedapi_enabled_type($type)) { |
| 1d6ac378 AN |
1590 | unset($names[$type]); |
| 1591 | } | |
| 1592 | } | |
| 1593 | return $names; | |
| e10a56c6 AN |
1594 | } |
| 1595 | ||
| 1596 | /** | |
| 1597 | * Prevent users to use the same weight for two or more parsers and processors | |
| 1598 | * because FeedAPI cannot handle this. And this is not neccessary too. | |
| 1599 | */ | |
| ef539fbc | 1600 | function feedapi_content_type_validate($form, &$form_state) { |
| 1d61d962 AN |
1601 | if ($form_state['values']['feedapi']['enabled'] == FALSE) { |
| 1602 | return; | |
| 1603 | } | |
| e10a56c6 AN |
1604 | $parsers = module_implements('feedapi_feed', TRUE); |
| 1605 | rsort($parsers); | |
| 1606 | $processors = module_implements('feedapi_item', TRUE); | |
| 1607 | rsort($processors); | |
| 87cc49ec AN |
1608 | $count_enabled_per_type = array(); |
| 1609 | $count_enabled_per_type['parsers'] = 0; | |
| 1610 | $count_enabled_per_type['processors'] = 0; | |
| e10a56c6 AN |
1611 | foreach (array('processors', 'parsers') as $type) { |
| 1612 | $proc_weight = array(); | |
| 82fc36e8 | 1613 | foreach (${$type} as $stuff) { |
| ef539fbc | 1614 | if (isset($form_state['values']['feedapi'][$type][$stuff]) && $form_state['values']['feedapi'][$type][$stuff]['enabled'] == TRUE) { |
| 87cc49ec | 1615 | $count_enabled_per_type[$type]++; |
| 82fc36e8 AN |
1616 | $weight = $form_state['values']['feedapi'][$type][$stuff]['weight']; |
| 1617 | if (!isset($proc_weight[$weight])) { | |
| 1618 | $proc_weight[$weight] = 0; | |
| 1619 | } | |
| 1620 | if (++$proc_weight[$weight] > 1) { | |
| e10a56c6 AN |
1621 | form_error($form, t('Two enabled processors or parsers cannot have the same weight.'), 'error'); |
| 1622 | } | |
| 1623 | } | |
| 1624 | } | |
| 1625 | } | |
| 87cc49ec AN |
1626 | if ($count_enabled_per_type['parsers'] == 0) { |
| 1627 | form_error($form, t('Using FeedAPI for this content-type requires at least one enabled parser.')); | |
| 1628 | } | |
| 1629 | if ($count_enabled_per_type['processors'] == 0) { | |
| 1630 | form_error($form, t('Using FeedAPI for this content-type requires at least one enabled processor.')); | |
| 1631 | } | |
| e10a56c6 | 1632 | } |