FeedsImporter.inc 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295
  1. <?php
  2. /**
  3. * @file
  4. * FeedsImporter class and related.
  5. */
  6. /**
  7. * A FeedsImporter object describes how an external source should be fetched,
  8. * parsed and processed. Feeds can manage an arbitrary amount of importers.
  9. *
  10. * A FeedsImporter holds a pointer to a FeedsFetcher, a FeedsParser and a
  11. * FeedsProcessor plugin. It further contains the configuration for itself and
  12. * each of the three plugins.
  13. *
  14. * Its most important responsibilities are configuration management, interfacing
  15. * with the job scheduler and expiring of all items produced by this
  16. * importer.
  17. *
  18. * When a FeedsImporter is instantiated, it loads its configuration. Then it
  19. * instantiates one fetcher, one parser and one processor plugin depending on
  20. * the configuration information. After instantiating them, it sets them to
  21. * the configuration information it holds for them.
  22. */
  23. class FeedsImporter extends FeedsConfigurable {
  24. // Every feed has a fetcher, a parser and a processor.
  25. // These variable names match the possible return values of
  26. // FeedsPlugin::typeOf().
  27. protected $fetcher, $parser, $processor;
  28. // This array defines the variable names of the plugins above.
  29. protected $plugin_types = array('fetcher', 'parser', 'processor');
  30. /**
  31. * Instantiate class variables, initialize and configure
  32. * plugins.
  33. */
  34. protected function __construct($id) {
  35. parent::__construct($id);
  36. // Try to load information from database.
  37. $this->load();
  38. // Instantiate fetcher, parser and processor, set their configuration if
  39. // stored info is available.
  40. foreach ($this->plugin_types as $type) {
  41. $plugin = feeds_plugin($this->config[$type]['plugin_key'], $this->id);
  42. if (isset($this->config[$type]['config'])) {
  43. $plugin->setConfig($this->config[$type]['config']);
  44. }
  45. $this->$type = $plugin;
  46. }
  47. }
  48. /**
  49. * Report how many items *should* be created on one page load by this
  50. * importer.
  51. *
  52. * Note:
  53. *
  54. * It depends on whether parser implements batching if this limit is actually
  55. * respected. Further, if no limit is reported it doesn't mean that the
  56. * number of items that can be created on one page load is actually without
  57. * limit.
  58. *
  59. * @return
  60. * A positive number defining the number of items that can be created on
  61. * one page load. 0 if this number is unlimited.
  62. */
  63. public function getLimit() {
  64. return $this->processor->getLimit();
  65. }
  66. /**
  67. * Save configuration.
  68. */
  69. public function save() {
  70. $save = new stdClass();
  71. $save->id = $this->id;
  72. $save->config = $this->getConfig();
  73. if ($config = db_query("SELECT config FROM {feeds_importer} WHERE id = :id", array(':id' => $this->id))->fetchField()) {
  74. drupal_write_record('feeds_importer', $save, 'id');
  75. // Only rebuild menu if content_type has changed. Don't worry about
  76. // rebuilding menus when creating a new importer since it will default
  77. // to the standalone page.
  78. $config = unserialize($config);
  79. if ($config['content_type'] != $save->config['content_type']) {
  80. variable_set('menu_rebuild_needed', TRUE);
  81. }
  82. }
  83. else {
  84. drupal_write_record('feeds_importer', $save);
  85. }
  86. }
  87. /**
  88. * Load configuration and unpack.
  89. */
  90. public function load() {
  91. ctools_include('export');
  92. if ($config = ctools_export_load_object('feeds_importer', 'conditions', array('id' => $this->id))) {
  93. $config = array_shift($config);
  94. $this->export_type = $config->export_type;
  95. $this->disabled = isset($config->disabled) ? $config->disabled : FALSE;
  96. $this->config = $config->config;
  97. return TRUE;
  98. }
  99. return FALSE;
  100. }
  101. /**
  102. * Deletes configuration.
  103. *
  104. * Removes configuration information from database, does not delete
  105. * configuration itself.
  106. */
  107. public function delete() {
  108. db_delete('feeds_importer')
  109. ->condition('id', $this->id)
  110. ->execute();
  111. feeds_reschedule($this->id);
  112. }
  113. /**
  114. * Set plugin.
  115. *
  116. * @param $plugin_key
  117. * A fetcher, parser or processor plugin.
  118. *
  119. * @todo Error handling, handle setting to the same plugin.
  120. */
  121. public function setPlugin($plugin_key) {
  122. // $plugin_type can be either 'fetcher', 'parser' or 'processor'
  123. if ($plugin_type = FeedsPlugin::typeOf($plugin_key)) {
  124. if ($plugin = feeds_plugin($plugin_key, $this->id)) {
  125. // Unset existing plugin, switch to new plugin.
  126. unset($this->$plugin_type);
  127. $this->$plugin_type = $plugin;
  128. // Set configuration information, blow away any previous information on
  129. // this spot.
  130. $this->config[$plugin_type] = array('plugin_key' => $plugin_key);
  131. }
  132. }
  133. }
  134. /**
  135. * Copy a FeedsImporter configuration into this importer.
  136. *
  137. * @param FeedsImporter $importer
  138. * The feeds importer object to copy from.
  139. */
  140. public function copy(FeedsConfigurable $configurable) {
  141. parent::copy($configurable);
  142. if ($configurable instanceof FeedsImporter) {
  143. // Instantiate new fetcher, parser and processor and initialize their
  144. // configurations.
  145. foreach ($this->plugin_types as $plugin_type) {
  146. $this->setPlugin($configurable->config[$plugin_type]['plugin_key']);
  147. $this->$plugin_type->setConfig($configurable->config[$plugin_type]['config']);
  148. }
  149. }
  150. }
  151. /**
  152. * Get configuration of this feed.
  153. */
  154. public function getConfig() {
  155. foreach (array('fetcher', 'parser', 'processor') as $type) {
  156. $this->config[$type]['config'] = $this->$type->getConfig();
  157. }
  158. return parent::getConfig();
  159. }
  160. /**
  161. * Return defaults for feed configuration.
  162. */
  163. public function configDefaults() {
  164. return array(
  165. 'name' => '',
  166. 'description' => '',
  167. 'fetcher' => array(
  168. 'plugin_key' => 'FeedsHTTPFetcher',
  169. ),
  170. 'parser' => array(
  171. 'plugin_key' => 'FeedsSyndicationParser',
  172. ),
  173. 'processor' => array(
  174. 'plugin_key' => 'FeedsNodeProcessor',
  175. ),
  176. 'content_type' => '',
  177. 'update' => 0,
  178. 'import_period' => 1800, // Refresh every 30 minutes by default.
  179. 'expire_period' => 3600, // Expire every hour by default, this is a hidden setting.
  180. 'import_on_create' => TRUE, // Import on submission.
  181. 'process_in_background' => FALSE,
  182. );
  183. }
  184. /**
  185. * Override parent::configForm().
  186. */
  187. public function configForm(&$form_state) {
  188. $config = $this->getConfig();
  189. $form = array();
  190. $form['name'] = array(
  191. '#type' => 'textfield',
  192. '#title' => t('Name'),
  193. '#description' => t('A human readable name of this importer.'),
  194. '#default_value' => $config['name'],
  195. '#required' => TRUE,
  196. );
  197. $form['description'] = array(
  198. '#type' => 'textfield',
  199. '#title' => t('Description'),
  200. '#description' => t('A description of this importer.'),
  201. '#default_value' => $config['description'],
  202. );
  203. $node_types = node_type_get_names();
  204. array_walk($node_types, 'check_plain');
  205. $form['content_type'] = array(
  206. '#type' => 'select',
  207. '#title' => t('Attach to content type'),
  208. '#description' => t('If "Use standalone form" is selected a source is imported by using a form under !import_form.
  209. If a content type is selected a source is imported by creating a node of that content type.',
  210. array('!import_form' => l(url('import', array('absolute' => TRUE)), 'import', array('attributes' => array('target' => '_new'))))),
  211. '#options' => array('' => t('Use standalone form')) + $node_types,
  212. '#default_value' => $config['content_type'],
  213. );
  214. $cron_required = ' ' . l(t('Requires cron to be configured.'), 'http://drupal.org/cron', array('attributes' => array('target' => '_new')));
  215. $period = drupal_map_assoc(array(900, 1800, 3600, 10800, 21600, 43200, 86400, 259200, 604800, 2419200), 'format_interval');
  216. foreach ($period as &$p) {
  217. $p = t('Every !p', array('!p' => $p));
  218. }
  219. $period = array(
  220. FEEDS_SCHEDULE_NEVER => t('Off'),
  221. 0 => t('As often as possible'),
  222. ) + $period;
  223. $form['import_period'] = array(
  224. '#type' => 'select',
  225. '#title' => t('Periodic import'),
  226. '#options' => $period,
  227. '#description' => t('Choose how often a source should be imported periodically.') . $cron_required,
  228. '#default_value' => $config['import_period'],
  229. );
  230. $form['import_on_create'] = array(
  231. '#type' => 'checkbox',
  232. '#title' => t('Import on submission'),
  233. '#description' => t('Check if import should be started at the moment a standalone form or node form is submitted.'),
  234. '#default_value' => $config['import_on_create'],
  235. );
  236. $form['process_in_background'] = array(
  237. '#type' => 'checkbox',
  238. '#title' => t('Process in background'),
  239. '#description' => t('For very large imports. If checked, import and delete tasks started from the web UI will be handled by a cron task in the background rather than by the browser. This does not affect periodic imports, they are handled by a cron task in any case.') . $cron_required,
  240. '#default_value' => $config['process_in_background'],
  241. );
  242. return $form;
  243. }
  244. /**
  245. * Reschedule if import period changes.
  246. */
  247. public function configFormSubmit(&$values) {
  248. if ($this->config['import_period'] != $values['import_period']) {
  249. feeds_reschedule($this->id);
  250. }
  251. parent::configFormSubmit($values);
  252. }
  253. /**
  254. * Implements FeedsConfigurable::dependencies().
  255. */
  256. public function dependencies() {
  257. $dependencies = parent::dependencies();
  258. foreach ($this->plugin_types as $plugin_type) {
  259. $dependencies = array_merge($dependencies, $this->$plugin_type->dependencies());
  260. }
  261. return $dependencies;
  262. }
  263. }
  264. /**
  265. * Helper, see FeedsDataProcessor class.
  266. */
  267. function feeds_format_expire($timestamp) {
  268. if ($timestamp == FEEDS_EXPIRE_NEVER) {
  269. return t('Never');
  270. }
  271. return t('after !time', array('!time' => format_interval($timestamp)));
  272. }