FeedsFileFetcher.inc 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  1. <?php
  2. /**
  3. * @file
  4. * Home of the FeedsFileFetcher and related classes.
  5. */
  6. /**
  7. * Definition of the import batch object created on the fetching stage by
  8. * FeedsFileFetcher.
  9. */
  10. class FeedsFileFetcherResult extends FeedsFetcherResult {
  11. /**
  12. * Constructor.
  13. */
  14. public function __construct($file_path) {
  15. parent::__construct('');
  16. $this->file_path = $file_path;
  17. }
  18. /**
  19. * Overrides parent::getRaw();
  20. */
  21. public function getRaw() {
  22. return $this->sanitizeRaw(file_get_contents($this->file_path));
  23. }
  24. /**
  25. * Overrides parent::getFilePath().
  26. */
  27. public function getFilePath() {
  28. if (!file_exists($this->file_path)) {
  29. throw new Exception(t('File @filepath is not accessible.', array('@filepath' => $this->file_path)));
  30. }
  31. return $this->sanitizeFile($this->file_path);
  32. }
  33. }
  34. /**
  35. * Fetches data via HTTP.
  36. */
  37. class FeedsFileFetcher extends FeedsFetcher {
  38. /**
  39. * Implements FeedsFetcher::fetch().
  40. */
  41. public function fetch(FeedsSource $source) {
  42. $source_config = $source->getConfigFor($this);
  43. // Just return a file fetcher result if this is a file.
  44. if (is_file($source_config['source'])) {
  45. return new FeedsFileFetcherResult($source_config['source']);
  46. }
  47. // Batch if this is a directory.
  48. $state = $source->state(FEEDS_FETCH);
  49. $files = array();
  50. if (!isset($state->files)) {
  51. $state->files = $this->listFiles($source_config['source']);
  52. $state->total = count($state->files);
  53. }
  54. if (count($state->files)) {
  55. $file = array_shift($state->files);
  56. $state->progress($state->total, $state->total - count($state->files));
  57. return new FeedsFileFetcherResult($file);
  58. }
  59. throw new Exception(t('Resource is not a file or it is an empty directory: %source', array('%source' => $source_config['source'])));
  60. }
  61. /**
  62. * Return an array of files in a directory.
  63. *
  64. * @param $dir
  65. * A stream wreapper URI that is a directory.
  66. *
  67. * @return
  68. * An array of stream wrapper URIs pointing to files. The array is empty
  69. * if no files could be found. Never contains directories.
  70. */
  71. protected function listFiles($dir) {
  72. $dir = file_stream_wrapper_uri_normalize($dir);
  73. $files = array();
  74. if ($items = @scandir($dir)) {
  75. foreach ($items as $item) {
  76. if (is_file("$dir/$item") && strpos($item, '.') !== 0) {
  77. $files[] = "$dir/$item";
  78. }
  79. }
  80. }
  81. return $files;
  82. }
  83. /**
  84. * Source form.
  85. */
  86. public function sourceForm($source_config) {
  87. $form = array();
  88. $form['fid'] = array(
  89. '#type' => 'value',
  90. '#value' => empty($source_config['fid']) ? 0 : $source_config['fid'],
  91. );
  92. if (empty($this->config['direct'])) {
  93. $form['source'] = array(
  94. '#type' => 'value',
  95. '#value' => empty($source_config['source']) ? '' : $source_config['source'],
  96. );
  97. $form['upload'] = array(
  98. '#type' => 'file',
  99. '#title' => empty($this->config['direct']) ? t('File') : NULL,
  100. '#description' => empty($source_config['source']) ? t('Select a file from your local system.') : t('Select a different file from your local system.'),
  101. '#theme' => 'feeds_upload',
  102. '#file_info' => empty($source_config['fid']) ? NULL : file_load($source_config['fid']),
  103. '#size' => 10,
  104. );
  105. }
  106. else {
  107. $form['source'] = array(
  108. '#type' => 'textfield',
  109. '#title' => t('File'),
  110. '#description' => t('Specify a path to a file or a directory. Path must start with @scheme://', array('@scheme' => file_default_scheme())),
  111. '#default_value' => empty($source_config['source']) ? '' : $source_config['source'],
  112. );
  113. }
  114. return $form;
  115. }
  116. /**
  117. * Override parent::sourceFormValidate().
  118. */
  119. public function sourceFormValidate(&$values) {
  120. $values['source'] = trim($values['source']);
  121. $feed_dir = 'public://feeds';
  122. file_prepare_directory($feed_dir, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS);
  123. // If there is a file uploaded, save it, otherwise validate input on
  124. // file.
  125. // @todo: Track usage of file, remove file when removing source.
  126. if ($file = file_save_upload('feeds', array('file_validate_extensions' => array(0 => $this->config['allowed_extensions'])), $feed_dir)) {
  127. $values['source'] = $file->uri;
  128. $values['file'] = $file;
  129. }
  130. elseif (empty($values['source'])) {
  131. form_set_error('feeds][source', t('Upload a file first.'));
  132. }
  133. // If a file has not been uploaded and $values['source'] is not empty, make
  134. // sure that this file is within Drupal's files directory as otherwise
  135. // potentially any file that the web server has access to could be exposed.
  136. elseif (strpos($values['source'], file_default_scheme()) !== 0) {
  137. form_set_error('feeds][source', t('File needs to reside within the site\'s file directory, its path needs to start with @scheme://.', array('@scheme' => file_default_scheme())));
  138. }
  139. }
  140. /**
  141. * Override parent::sourceSave().
  142. */
  143. public function sourceSave(FeedsSource $source) {
  144. $source_config = $source->getConfigFor($this);
  145. // If a new file is present, delete the old one and replace it with the new
  146. // one.
  147. if (isset($source_config['file'])) {
  148. $file = $source_config['file'];
  149. if (isset($source_config['fid'])) {
  150. $this->deleteFile($source_config['fid'], $source->feed_nid);
  151. }
  152. $file->status = FILE_STATUS_PERMANENT;
  153. file_save($file);
  154. file_usage_add($file, 'feeds', get_class($this), $source->feed_nid);
  155. $source_config['fid'] = $file->fid;
  156. unset($source_config['file']);
  157. $source->setConfigFor($this, $source_config);
  158. }
  159. }
  160. /**
  161. * Override parent::sourceDelete().
  162. */
  163. public function sourceDelete(FeedsSource $source) {
  164. $source_config = $source->getConfigFor($this);
  165. if (isset($source_config['fid'])) {
  166. $this->deleteFile($source_config['fid'], $source->feed_nid);
  167. }
  168. }
  169. /**
  170. * Override parent::configDefaults().
  171. */
  172. public function configDefaults() {
  173. return array(
  174. 'allowed_extensions' => 'txt csv tsv xml opml',
  175. 'direct' => FALSE,
  176. );
  177. }
  178. /**
  179. * Override parent::configForm().
  180. */
  181. public function configForm(&$form_state) {
  182. $form = array();
  183. $form['allowed_extensions'] = array(
  184. '#type' => 'textfield',
  185. '#title' => t('Allowed file extensions'),
  186. '#description' => t('Allowed file extensions for upload.'),
  187. '#default_value' => $this->config['allowed_extensions'],
  188. );
  189. $form['direct'] = array(
  190. '#type' => 'checkbox',
  191. '#title' => t('Supply path to file or directory directly'),
  192. '#description' => t('For experts. Lets users specify a path to a file <em>or a directory of files</em> directly,
  193. instead of a file upload through the browser. This is useful when the files that need to be imported
  194. are already on the server.'),
  195. '#default_value' => $this->config['direct'],
  196. );
  197. return $form;
  198. }
  199. /**
  200. * Helper. Deletes a file.
  201. */
  202. protected function deleteFile($fid, $feed_nid) {
  203. if ($file = file_load($fid)) {
  204. file_usage_delete($file, 'feeds', get_class($this), $feed_nid);
  205. file_delete($file);
  206. }
  207. }
  208. }