diff --git a/feeds.module b/feeds.module index 7cded50..78d9485 100644 --- a/feeds.module +++ b/feeds.module @@ -427,26 +427,34 @@ function feeds_theme() { * Node object or FeedsImporter id. */ function feeds_access($action, $param) { + $importer_ids = array(); if (!in_array($action, array('import', 'clear', 'unlock'))) { // If $action is not one of the supported actions, we return access denied. return FALSE; } - $importer_id = FALSE; if (is_string($param)) { - $importer_id = $param; + // If checking the access of a single importer build the array manually. + $importer_ids[$param] = $param; } elseif ($param instanceof FeedsImporter) { - $importer_id = $param->id; + $importer_ids[$param->id] = $param->id; } elseif ($param->type) { - $importer_id = feeds_get_importer_id($param->type); - } - - // Check for permissions if feed id is present, otherwise return FALSE. - if ($importer_id) { - if (user_access('administer feeds') || user_access("{$action} {$importer_id} feeds")) { - return TRUE; + // If checking the access for a content type, + // check all importers available for it. + $importer_ids = feeds_get_importer_ids($param->type); + } + + // Loop through all importers. if the user has access to one, + // they have access to the item. + $administer_feeds = user_access('administer feeds'); + foreach ($importer_ids as $importer_id) { + // Check for permissions if feed id is present, otherwise return FALSE. + if ($importer_id) { + if ($administer_feeds || user_access($action . ' ' . $importer_id . ' feeds')) { + return TRUE; + } } } return FALSE; @@ -486,7 +494,7 @@ function feeds_exit() { foreach ($jobs as $job) { if (!isset($job['fetcher']) || !isset($job['source'])) { continue; - } + } $job['fetcher']->subscribe($job['source']); } @@ -581,10 +589,24 @@ function feeds_entity_delete($entity, $type) { } /** + * Implements hook_node_prepare(). + */ +function feeds_node_prepare($node) { + if ($importer_ids = feeds_get_importer_ids($node->type)) { + $node->feeds = array(); + foreach ($importer_ids as $importer_id) { + $source = feeds_source($importer_id, empty($node->nid) ? 0 : $node->nid); + $node->feeds[$importer_id] = array(); + $node->feeds[$importer_id] += $source->configDefaults(); + } + } +} + +/** * Implements hook_node_validate(). */ function feeds_node_validate($node, $form, &$form_state) { - if (!$importer_id = feeds_get_importer_id($node->type)) { + if (!$importer_ids = feeds_get_importer_ids($node->type)) { return; } // Keep a copy of the title for subsequent node creation stages. @@ -593,32 +615,35 @@ function feeds_node_validate($node, $form, &$form_state) { $last_title = &drupal_static('feeds_node_last_title'); $last_feeds = &drupal_static('feeds_node_last_feeds'); + // Node module magically moved $form['feeds'] to $node->feeds :P. + // configFormValidate may modify $last_feed, smuggle it to update/insert stage + // through a static variable. + $last_feeds = isset($node->feeds) ? $node->feeds : array(); + + $trimmed_node_title = trim($node->title); // On validation stage we are working with a FeedsSource object that is // not tied to a nid - when creating a new node there is no // $node->nid at this stage. $source = feeds_source($importer_id); - - // Node module magically moved $form['feeds'] to $node->feeds :P. - // configFormValidate may modify $last_feed, smuggle it to update/insert stage - // through a static variable. - $last_feeds = $node->feeds; - $source->configFormValidate($last_feeds); - - // Check if title form element is hidden. - $title_hidden = (isset($form['title']['#access']) && !$form['title']['#access']); - - // If the node title is empty and the title form element wasn't hidden, try to - // retrieve the title from the feed. - if (isset($node->title) && trim($node->title) == '' && !$title_hidden) { - try { - $source->addConfig($last_feeds); - if (!$last_title = $source->preview()->title) { - throw new Exception(); + foreach ($importer_ids as $importer_id) { + $class = get_class(feeds_importer($importer_id)->fetcher); + if (!$form['feeds'][$importer_id][$class]['source']['#required']) break; + + $source = feeds_source($importer_id); + $source->configFormValidate($last_feeds[$importer_id]); + + // If node title is empty, try to retrieve title from feed. + if ($trimmed_node_title == '') { + try { + $source->addConfig($last_feeds[$importer_id]); + if (!$last_title = $source->preview()->title) { + throw new Exception(t('Could not retrieve title from feed')); + } + } + catch (Exception $e) { + drupal_set_message($e->getMessage(), 'error'); + form_set_error('title', t('Could not retrieve title from feed.'), array('error' => array('title'))); } - } - catch (Exception $e) { - drupal_set_message($e->getMessage(), 'error'); - form_set_error('title', t('Could not retrieve title from feed.')); } } } @@ -651,14 +676,16 @@ function feeds_node_presave($node) { function feeds_node_insert($node) { // Source attached to node. feeds_node_update($node); - if (isset($node->feeds) && $importer_id = feeds_get_importer_id($node->type)) { - $source = feeds_source($importer_id, $node->nid); - // Start import if requested. - if (feeds_importer($importer_id)->config['import_on_create'] && !isset($node->feeds['suppress_import'])) { - $source->startImport(); + if (isset($node->feeds) && ($importer_ids = feeds_get_importer_ids($node->type, $node->nid))) { + foreach ($importer_ids as $importer_id) { + $source = feeds_source($importer_id, $node->nid); + // Start import if requested. + if (feeds_importer($importer_id)->config['import_on_create'] && !isset($node->feeds['suppress_import'])) { + $source->startImport(); + } + // Schedule the source. + $source->schedule(); } - // Schedule the source. - $source->schedule(); } } @@ -667,10 +694,15 @@ function feeds_node_insert($node) { */ function feeds_node_update($node) { // Source attached to node. - if (isset($node->feeds) && $importer_id = feeds_get_importer_id($node->type)) { - $source = feeds_source($importer_id, $node->nid); - $source->addConfig($node->feeds); - $source->save(); + if (isset($node->feeds) && ($importer_ids = feeds_get_importer_ids($node->type))) { + foreach ($importer_ids as $importer_id) { + $source = feeds_source($importer_id, $node->nid); + // Config may be empty if defined so by importer. + if ($node->feeds[$importer_id]) { + $source->addConfig($node->feeds[$importer_id]); + } + $source->save(); + } } } @@ -682,8 +714,10 @@ function feeds_node_delete($node) { // Make sure we don't leave any orphans behind: Do not use // feeds_get_importer_id() to determine importer id as the importer may have // been deleted. - if ($importer_id = db_query("SELECT id FROM {feeds_source} WHERE feed_nid = :nid", array(':nid' => $node->nid))->fetchField()) { - feeds_source($importer_id, $node->nid)->delete(); + if ($importer_ids = db_query("SELECT id FROM {feeds_source} WHERE feed_nid = :nid", array(':nid' => $node->nid))) { + foreach ($importer_ids as $row) { + feeds_source($row->id, $node->nid)->delete(); + } } } @@ -691,20 +725,34 @@ function feeds_node_delete($node) { * Implements hook_form_BASE_FORM_ID_alter(). */ function feeds_form_node_form_alter(&$form, $form_state) { - if ($importer_id = feeds_get_importer_id($form['#node']->type)) { - // Enable uploads. - $form['#attributes']['enctype'] = 'multipart/form-data'; - - // Build form. - $source = feeds_source($importer_id, empty($form['#node']->nid) ? 0 : $form['#node']->nid); + if ($importer_ids = feeds_get_importer_ids($form['#node']->type)) { $form['feeds'] = array( '#type' => 'fieldset', '#title' => t('Feed'), '#tree' => TRUE, '#weight' => 0, ); - $form['feeds'] += $source->configForm($form_state); - $form['#feed_id'] = $importer_id; + + // Enable uploads. + if (count($importer_ids)) { + $form['#attributes']['enctype'] = 'multipart/form-data'; + } + + foreach ($importer_ids as $importer_id) { + // Set title to not required, try to retrieve it from feed. + if (isset($form['title'])) { + $form['title']['#required'] = FALSE; + } + + // Build form. + $source = feeds_source($importer_id, empty($form['#node']->nid) ? 0 : $form['#node']->nid); + $form['feeds'][$importer_id] = $source->configForm($form_state); + $class = get_class(feeds_importer($importer_id)->fetcher); + $form['feeds'][$importer_id][$class]['source']['#title'] = $form['feeds'][$importer_id][$class]['source']['#title'] + . ' - ' + . $source->importer->config['name']; + $form['feeds'][$importer_id][$class]['source']['#required'] = FALSE; + } // If the parser has support for delivering a source title, set node title // to not required and try to retrieve it from the source if the node title @@ -927,11 +975,11 @@ function feeds_menu_local_tasks_alter(&$data, $router_item, $root_path) { /** * Loads all importers. * - * @param $load_disabled + * @param bool $load_disabled * Pass TRUE to load all importers, enabled or disabled, pass FALSE to only * retrieve enabled importers. * - * @return + * @return array * An array of all feed configurations available. */ function feeds_importer_load_all($load_disabled = FALSE) { @@ -981,10 +1029,10 @@ function feeds_enabled_importers() { /** * Gets an enabled importer configuration by content type. * - * @param $content_type + * @param string $content_type * A node type string. * - * @return + * @return array * A FeedsImporter id if there is an importer for the given content type, * FALSE otherwise. */ @@ -994,6 +1042,57 @@ function feeds_get_importer_id($content_type) { } /** + * Gets an enabled importer configuration by content type. + * + * @param string $content_type + * A node type string. + * @param int $feed_nid + * Nid for feed. + * + * @return array + * A list of FeedsImporters attached to the given content type. + */ +function feeds_get_importer_ids($content_type, $feed_nid = NULL) { + $all_importers = _feeds_importer_digest(); + $importers = array(); + foreach ($all_importers as $importer => $type) { + if ($type == $content_type) { + $importers[$importer] = $importer; + } + } + // Sort those importers by weight. + if (!empty($importers)) { + $weights = _feeds_get_importer_weights($importers); + // Sort these arrays by key, then sort together. + ksort($weights); + ksort($importers); + array_multisort($weights, $importers); + } + return $importers; +} + +/** + * Get importer. + * + * @param array $importers + * @param bool|TRUE $sorted + * + * @return mixed + */ +function _feeds_get_importer_weights($importers, $sorted = TRUE) { + foreach (feeds_importer_load_all() as $importer) { + if (isset($importers[$importer->id])) { + $importer_weights[$importer->id] = isset($importer->config['weight']) ? + $importer->config['weight'] : '0'; + } + } + if ($sorted) { + asort($importer_weights); + } + return $importer_weights; +} + +/** * Helper function for feeds_get_importer_id() and feeds_enabled_importers(). */ function _feeds_importer_digest() { @@ -1005,7 +1104,8 @@ function _feeds_importer_digest() { else { $importers = array(); foreach (feeds_importer_load_all() as $importer) { - $importers[$importer->id] = isset($importer->config['content_type']) ? $importer->config['content_type'] : ''; + $importers[$importer->id] = isset($importer->config['content_type']) ? + $importer->config['content_type'] : ''; } cache_set(__FUNCTION__, $importers); } @@ -1259,6 +1359,9 @@ function feeds_include_library($file, $library) { * The name of the library. If libraries module is installed, * feeds_library_exists() will look for libraries with this name managed by * libraries module. + * + * @return bool + * If library exists or not. */ function feeds_library_exists($file, $library) { $path = module_exists('libraries') ? libraries_get_path($library) : FALSE; @@ -1280,7 +1383,7 @@ function feeds_library_exists($file, $library) { return FALSE; } - /** +/** * Checks whether simplepie exists. */ function feeds_simplepie_exists() { @@ -1345,7 +1448,7 @@ function feeds_alter($type, &$data) { /** * Copy of valid_url() that supports the webcal scheme. * - * @see valid_url(). + * @see valid_url() * * @todo Replace with valid_url() when http://drupal.org/node/295021 is fixed. */ @@ -1380,7 +1483,7 @@ function feeds_valid_url($url, $absolute = FALSE) { * Information about a new job to queue; or if set to NULL (default), leaves * the current queued jobs unchanged. * - * @return + * @return array * An array of subscribe jobs to process. * * @see feeds_exit() @@ -1397,7 +1500,7 @@ function feeds_set_subscription_job(array $job = NULL) { /** * Returns the list of queued jobs to be run. * - * @return + * @return array * An array of subscribe jobs to process. * * @see feeds_set_subscription_job() @@ -1431,7 +1534,7 @@ function feeds_entity_property_info_alter(&$info) { * Gets the feed_nid for an entity for use in entity metadata. */ function feeds_get_feed_nid_entity_callback($entity, array $options, $name, $entity_type) { - list($entity_id, , ) = entity_extract_ids($entity_type, $entity); + list($entity_id, ,) = entity_extract_ids($entity_type, $entity); $feed_nid = NULL; if ($entity_id) { @@ -1459,7 +1562,7 @@ function feeds_file_download($uri) { return; } - // Get the file record based on the URI. If not in the database just return. + // Get the file record based on the URI. If not in the database just return. $files = file_load_multiple(array(), array('uri' => $uri)); foreach ($files as $item) { // Since some database servers sometimes use a case-insensitive comparison diff --git a/feeds.pages.inc b/feeds.pages.inc index 6eddf47..cb4e6d3 100644 --- a/feeds.pages.inc +++ b/feeds.pages.inc @@ -131,25 +131,64 @@ function feeds_import_form_submit($form, &$form_state) { * Render a feeds import form on node/id/import pages. */ function feeds_import_tab_form($form, &$form_state, $node) { - $importer_id = feeds_get_importer_id($node->type); - $source = feeds_source($importer_id, $node->nid); + $total_progress = 0; + + $importer_ids = feeds_get_importer_ids($node->type, $node->nid); $form = array(); - $form['#feed_nid'] = $node->nid; - $form['#importer_id'] = $importer_id; - $form['#redirect'] = 'node/' . $node->nid; - $form['source_status'] = array( - '#type' => 'fieldset', - '#title' => t('Status'), - '#tree' => TRUE, - '#value' => feeds_source_status($source), - ); - $form = confirm_form($form, t('Import all content from source?'), 'node/' . $node->nid, '', t('Import'), t('Cancel'), 'confirm feeds update'); - $progress = $source->progressImporting(); - if ($progress !== FEEDS_BATCH_COMPLETE) { - $form['actions']['submit']['#disabled'] = TRUE; - $form['actions']['submit']['#value'] = - t('Importing (@progress %)', array('@progress' => number_format(100 * $progress, 0))); + if ($importer_ids) { + $form['#feed_nid'] = $node->nid; + $form['#redirect'] = 'node/' . $node->nid; + $form = confirm_form($form, t('Import all content from source?'), + 'node/' . $node->nid, '', t('Import'), t('Cancel'), + 'confirm feeds update'); + foreach ($importer_ids as $importer_id => $weight) { + $source = feeds_source($importer_id, $node->nid); + $form[$importer_id]['source_status'] = array( + '#type' => 'fieldset', + '#title' => t('@source_name: Status', array( + '@source_name' => $source->importer->config['name'], + )), + '#tree' => TRUE, + '#value' => feeds_source_status($source), + ); + $progress = $source->progressImporting(); + $total_progress += $progress; + } + if (count($importer_ids) == 1) { + $form['importer_ids'] = array( + '#type' => 'value', + '#value' => array($importer_id), + ); + } + else { + $options = array(); + foreach ($importer_ids as $importer_id => $weight) { + $source = feeds_source($importer_id, $node->nid); + $options[$importer_id] = $source->importer->config['name']; + } + $form['importer_ids'] = array( + '#type' => 'checkboxes', + '#options' => $options, + '#default_value' => array_keys($options), + '#title' => t('Sources'), + '#description' => t('Select the sources to import.'), + ); + } + + if (count($importer_ids)) { + $progress = $total_progress / count($importer_ids); + if ($progress !== FEEDS_BATCH_COMPLETE) { + $form['actions']['submit']['#disabled'] = TRUE; + $form['actions']['submit']['#value'] = t('Importing (@progress %)', + array('@progress' => number_format(100 * $progress, 0))); + } + } + } + else { + $form['no_source'] = array( + '#markup' => t('No feeds sources added to node.'), + ); } return $form; } @@ -159,7 +198,9 @@ function feeds_import_tab_form($form, &$form_state, $node) { */ function feeds_import_tab_form_submit($form, &$form_state) { $form_state['redirect'] = $form['#redirect']; - feeds_source($form['#importer_id'], $form['#feed_nid'])->startImport(); + foreach (array_filter($form_state['values']['importer_ids']) as $importer_id) { + feeds_source($importer_id, $form['#feed_nid'])->startImport(); + } } /** @@ -169,30 +210,86 @@ function feeds_import_tab_form_submit($form, &$form_state) { * Therefore $node may be missing. */ function feeds_delete_tab_form(array $form, array &$form_state, FeedsImporter $importer = NULL, $node = NULL) { + $total_progress = 0; if (empty($node)) { $source = feeds_source($importer->id); $form['#redirect'] = 'import/' . $source->id; + $importer_ids = array($importer->id); } else { - $importer_id = feeds_get_importer_id($node->type); - $source = feeds_source($importer_id, $node->nid); - $form['#redirect'] = 'node/' . $source->feed_nid; + $importer_ids = feeds_get_importer_ids($node->type, $node->nid); + $form['#redirect'] = 'node/' . $node->nid; } - // Form cannot pass on source object. - $form['#importer_id'] = $source->id; - $form['#feed_nid'] = $source->feed_nid; - $form['source_status'] = array( - '#type' => 'fieldset', - '#title' => t('Status'), - '#tree' => TRUE, - '#value' => feeds_source_status($source), - ); - $form = confirm_form($form, t('Delete all items from source?'), $form['#redirect'], '', t('Delete'), t('Cancel'), 'confirm feeds update'); - $progress = $source->progressClearing(); - if ($progress !== FEEDS_BATCH_COMPLETE) { - $form['actions']['submit']['#disabled'] = TRUE; - $form['actions']['submit']['#value'] = - t('Deleting (@progress %)', array('@progress' => number_format(100 * $progress, 0))); + if ($importer_ids) { + // Form cannot pass on source object. + $form['#feed_nid'] = empty($node) ? '' : $node->nid; + foreach ($importer_ids as $import_id => $weight) { + $source = empty($node) ? $source : feeds_source($import_id, $node->nid); + if (!empty($node)) { + $form[$import_id]['source_status'] = array( + '#type' => 'fieldset', + '#title' => t('@source_name: Status', array( + '@source_name' => $source->importer->config['name'], + )), + '#tree' => TRUE, + '#value' => feeds_source_status($source), + ); + } + else { + $form['source_status'] = array( + '#type' => 'fieldset', + '#title' => t('@source_name: Status', array( + '@source_name' => $source->importer->config['name'], + )), + '#tree' => TRUE, + '#value' => feeds_source_status($source), + ); + } + $progress = $source->progressClearing(); + $total_progress += $progress; + } + + // Set importer ids. + // If this is a stand-alone form then importer will be passed. + if ($importer) { + $form['importer_ids'] = array( + '#type' => 'value', + '#value' => array($importer->id => $importer->id), + ); + } + else { + $options = array(); + foreach ($importer_ids as $importer_id => $weight) { + $source = feeds_source($importer_id, $node->nid); + $options[$importer_id] = $source->importer->config['name']; + } + $form['importer_ids'] = array( + '#type' => 'checkboxes', + '#options' => $options, + '#default_value' => array_keys($options), + '#title' => t('Sources'), + '#description' => t('Select the sources to delete items from.'), + ); + } + + $form = confirm_form($form, t('Delete all items from source?'), + $form['#redirect'], '', t('Delete'), t('Cancel'), 'confirm feeds update'); + + if (count($importer_ids)) { + $progress = $total_progress / count($importer_ids); + if ($progress !== FEEDS_BATCH_COMPLETE) { + $form['actions']['submit']['#disabled'] = TRUE; + $form['actions']['submit']['#value'] = t('Deleting (@progress %)', + array( + '@progress' => number_format(100 * $progress, 0), + )); + } + } + } + else { + $form['no_source'] = array( + '#markup' => t('No feeds sources added to node.'), + ); } return $form; } @@ -203,7 +300,9 @@ function feeds_delete_tab_form(array $form, array &$form_state, FeedsImporter $i function feeds_delete_tab_form_submit($form, &$form_state) { $form_state['redirect'] = $form['#redirect']; $feed_nid = empty($form['#feed_nid']) ? 0 : $form['#feed_nid']; - feeds_source($form['#importer_id'], $feed_nid)->startClear(); + foreach (array_filter($form_state['values']['importer_ids']) as $importer_id) { + feeds_source($importer_id, $feed_nid)->startClear(); + } } /** @@ -231,7 +330,8 @@ function feeds_unlock_tab_form($form, &$form_state, FeedsImporter $importer = NU '#tree' => TRUE, '#value' => feeds_source_status($source), ); - $form = confirm_form($form, t('Unlock this importer?'), $form['#redirect'], '', t('Delete'), t('Cancel'), 'confirm feeds update'); + $form = confirm_form($form, t('Unlock this importer?'), + $form['#redirect'], '', t('Delete'), t('Cancel'), 'confirm feeds update'); if ($source->progressImporting() == FEEDS_BATCH_COMPLETE && $source->progressClearing() == FEEDS_BATCH_COMPLETE) { $form['source_locked'] = array( '#type' => 'markup', @@ -257,7 +357,7 @@ function feeds_unlock_tab_form_submit($form, &$form_state) { $feed_nid = empty($form['#feed_nid']) ? 0 : $form['#feed_nid']; $importer_id = $form['#importer_id']; - //Is there a more API-friendly way to set the state? + // Is there a more API-friendly way to set the state? db_update('feeds_source') ->condition('id', $importer_id) ->condition('feed_nid', $feed_nid) @@ -281,7 +381,7 @@ function feeds_fetcher_callback($importer, $feed_nid = 0) { } /** - * Template generation + * Template generation. */ function feeds_importer_template(FeedsImporter $importer) { if ($importer->parser instanceof FeedsCSVParser) { @@ -318,18 +418,26 @@ function theme_feeds_source_status($v) { $items = array(); if ($v['progress_importing']) { $progress = number_format(100.0 * $v['progress_importing'], 0); - $items[] = t('Importing - @progress % complete.', array('@progress' => $progress)); + $items[] = t('Importing - @progress % complete.', array( + '@progress' => $progress, + )); } if ($v['progress_clearing']) { $progress = number_format(100.0 * $v['progress_clearing'], 0); - $items[] = t('Deleting items - @progress % complete.', array('@progress' => $progress)); + $items[] = t('Deleting items - @progress % complete.', array( + '@progress' => $progress, + )); } if (!count($items)) { if ($v['count']) { if ($v['imported']) { - $items[] = t('Last import: @ago ago.', array('@ago' => format_interval(REQUEST_TIME - $v['imported'], 1))); + $items[] = t('Last import: @ago ago.', array( + '@ago' => format_interval(REQUEST_TIME - $v['imported'], 1), + )); } - $items[] = t('@count imported items total.', array('@count' => $v['count'])); + $items[] = t('@count imported items total.', array( + '@count' => $v['count'], + )); } else { $items[] = t('No imported items.'); @@ -357,7 +465,9 @@ function theme_feeds_upload($variables) { $summary .= l($file->filename, $wrapper->getExternalUrl()); } else { - $summary .= t('URI scheme %scheme not available.', array('%scheme' => file_uri_scheme($uri))); + $summary .= t('URI scheme %scheme not available.', array( + '%scheme' => file_uri_scheme($uri), + )); } $summary .= ''; $summary .= '