Projet

Général

Profil

Paste
Télécharger (11,7 ko) Statistiques
| Branche: | Révision:

root / drupal7 / sites / all / modules / feeds / plugins / FeedsFileFetcher.inc @ 41cc1b08

1
<?php
2

    
3
/**
4
 * @file
5
 * Home of the FeedsFileFetcher and related classes.
6
 */
7

    
8
/**
9
 * Definition of the import batch object created on the fetching stage by
10
 * FeedsFileFetcher.
11
 */
12
class FeedsFileFetcherResult extends FeedsFetcherResult {
13
  /**
14
   * Constructor.
15
   */
16
  public function __construct($file_path) {
17
    parent::__construct('');
18
    $this->file_path = $file_path;
19
  }
20

    
21
  /**
22
   * Overrides parent::getRaw().
23
   */
24
  public function getRaw() {
25
    return $this->sanitizeRaw(file_get_contents($this->file_path));
26
  }
27

    
28
  /**
29
   * Overrides parent::getFilePath().
30
   */
31
  public function getFilePath() {
32
    if (!is_readable($this->file_path)) {
33
      throw new Exception(t('File @filepath is not accessible.', array('@filepath' => $this->file_path)));
34
    }
35
    return $this->sanitizeFile($this->file_path);
36
  }
37
}
38

    
39
/**
40
 * Fetches data via HTTP.
41
 */
42
class FeedsFileFetcher extends FeedsFetcher {
43

    
44
  /**
45
   * Implements FeedsFetcher::fetch().
46
   */
47
  public function fetch(FeedsSource $source) {
48
    $source_config = $source->getConfigFor($this);
49

    
50
    // Just return a file fetcher result if this is a file.
51
    if (is_file($source_config['source'])) {
52
      return new FeedsFileFetcherResult($source_config['source']);
53
    }
54

    
55
    // Batch if this is a directory.
56
    $state = $source->state(FEEDS_FETCH);
57
    $files = array();
58
    if (!isset($state->files)) {
59
      $state->files = $this->listFiles($source_config['source']);
60
      $state->total = count($state->files);
61
    }
62
    if (count($state->files)) {
63
      $file = array_shift($state->files);
64
      $state->progress($state->total, $state->total - count($state->files));
65
      return new FeedsFileFetcherResult($file);
66
    }
67

    
68
    throw new Exception(t('Resource is not a file or it is an empty directory: %source', array('%source' => $source_config['source'])));
69
  }
70

    
71
  /**
72
   * Returns an array of files in a directory.
73
   *
74
   * @param string $dir
75
   *   A stream wreapper URI that is a directory.
76
   *
77
   * @return array
78
   *   An array of stream wrapper URIs pointing to files. The array is empty if
79
   *   no files could be found. Never contains directories.
80
   */
81
  protected function listFiles($dir) {
82
    $dir = file_stream_wrapper_uri_normalize($dir);
83
    $files = array();
84
    if ($items = @scandir($dir)) {
85
      foreach ($items as $item) {
86
        if (is_file("$dir/$item") && strpos($item, '.') !== 0) {
87
          $files[] = "$dir/$item";
88
        }
89
      }
90
    }
91
    return $files;
92
  }
93

    
94
  /**
95
   * Source form.
96
   */
97
  public function sourceForm($source_config) {
98
    $form = array();
99
    $form['fid'] = array(
100
      '#type' => 'value',
101
      '#value' => empty($source_config['fid']) ? 0 : $source_config['fid'],
102
    );
103
    if (empty($this->config['direct'])) {
104
      $form['source'] = array(
105
        '#type' => 'value',
106
        '#value' => empty($source_config['source']) ? '' : $source_config['source'],
107
      );
108
      $form['upload'] = array(
109
        '#type' => 'file',
110
        '#title' => empty($this->config['direct']) ? t('File') : NULL,
111
        '#description' => empty($source_config['source']) ? t('Select a file from your local system.') : t('Select a different file from your local system.'),
112
        '#theme_wrappers' => array('feeds_upload'),
113
        '#file_info' => empty($source_config['fid']) ? NULL : file_load($source_config['fid']),
114
        '#size' => 10,
115
      );
116
    }
117
    else {
118
      $form['source'] = array(
119
        '#type' => 'textfield',
120
        '#title' => t('File'),
121
        '#description' => t('Specify a path to a file or a directory. Prefix the path with a scheme. Available schemes: @schemes.', array('@schemes' => implode(', ', $this->config['allowed_schemes']))),
122
        '#default_value' => empty($source_config['source']) ? '' : $source_config['source'],
123
      );
124
    }
125
    return $form;
126
  }
127

    
128
  /**
129
   * Overrides parent::sourceFormValidate().
130
   */
131
  public function sourceFormValidate(&$values) {
132
    $values['source'] = trim($values['source']);
133

    
134
    if (empty($this->config['direct'])) {
135

    
136
      $feed_dir = $this->config['directory'];
137

    
138
      if (!file_prepare_directory($feed_dir, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS)) {
139
        if (user_access('administer feeds')) {
140
          $plugin_key = feeds_importer($this->id)->config[$this->pluginType()]['plugin_key'];
141
          $link = url('admin/structure/feeds/' . $this->id . '/settings/' . $plugin_key);
142
          form_set_error('feeds][FeedsFileFetcher][source', t('Upload failed. Please check the upload <a href="@link">settings.</a>', array('@link' => $link)));
143
        }
144
        else {
145
          form_set_error('feeds][FeedsFileFetcher][source', t('Upload failed. Please contact your site administrator.'));
146
        }
147
        watchdog('feeds', 'The upload directory %directory required by a feed could not be created or is not accessible. A newly uploaded file could not be saved in this directory as a consequence, and the upload was canceled.', array('%directory' => $feed_dir));
148
      }
149
      // Validate and save uploaded file.
150
      elseif ($file = file_save_upload('feeds', array('file_validate_extensions' => array(0 => $this->config['allowed_extensions'])), $feed_dir)) {
151
        $values['source'] = $file->uri;
152
        $values['file'] = $file;
153
      }
154
      elseif (empty($values['source'])) {
155
        form_set_error('feeds][FeedsFileFetcher][source', t('Please upload a file.'));
156
      }
157
      else {
158
        // File present from previous upload. Nothing to validate.
159
      }
160
    }
161
    else {
162
      // Check if chosen url scheme is allowed.
163
      $scheme = file_uri_scheme($values['source']);
164
      if (!$scheme || !in_array($scheme, $this->config['allowed_schemes'])) {
165
        form_set_error('feeds][FeedsFileFetcher][source', t("The file needs to reside within the site's files directory, its path needs to start with scheme://. Available schemes: @schemes.", array('@schemes' => implode(', ', $this->config['allowed_schemes']))));
166
      }
167
      // Check whether the given path is readable.
168
      elseif (!is_readable($values['source'])) {
169
        form_set_error('feeds][FeedsFileFetcher][source', t('The specified file or directory does not exist.'));
170
      }
171
    }
172
  }
173

    
174
  /**
175
   * Overrides parent::sourceSave().
176
   */
177
  public function sourceSave(FeedsSource $source) {
178
    $source_config = $source->getConfigFor($this);
179

    
180
    // If a new file is present, delete the old one and replace it with the new
181
    // one.
182
    if (isset($source_config['file'])) {
183
      $file = $source_config['file'];
184
      if (isset($source_config['fid'])) {
185
        $this->deleteFile($source_config['fid'], $source->feed_nid);
186
      }
187
      $file->status = FILE_STATUS_PERMANENT;
188
      file_save($file);
189
      file_usage_add($file, 'feeds', get_class($this), $source->feed_nid);
190

    
191
      $source_config['fid'] = $file->fid;
192
      unset($source_config['file']);
193
      $source->setConfigFor($this, $source_config);
194
    }
195
  }
196

    
197
  /**
198
   * Overrides parent::sourceDelete().
199
   */
200
  public function sourceDelete(FeedsSource $source) {
201
    $source_config = $source->getConfigFor($this);
202
    if (isset($source_config['fid'])) {
203
      $this->deleteFile($source_config['fid'], $source->feed_nid);
204
    }
205
  }
206

    
207
  /**
208
   * Overrides parent::configDefaults().
209
   */
210
  public function configDefaults() {
211
    $schemes = $this->getSchemes();
212
    $scheme = in_array('private', $schemes) ? 'private' : 'public';
213

    
214
    return array(
215
      'allowed_extensions' => 'txt csv tsv xml opml',
216
      'direct' => FALSE,
217
      'directory' => $scheme . '://feeds',
218
      'allowed_schemes' => $schemes,
219
    );
220
  }
221

    
222
  /**
223
   * Overrides parent::configForm().
224
   */
225
  public function configForm(&$form_state) {
226
    $form = array();
227
    $form['allowed_extensions'] = array(
228
      '#type' => 'textfield',
229
      '#title' => t('Allowed file extensions'),
230
      '#description' => t('Allowed file extensions for upload.'),
231
      '#default_value' => $this->config['allowed_extensions'],
232
    );
233
    $form['direct'] = array(
234
      '#type' => 'checkbox',
235
      '#title' => t('Supply path to file or directory directly'),
236
      '#description' => t('For experts. Lets users specify a path to a file <em>or a directory of files</em> directly,
237
        instead of a file upload through the browser. This is useful when the files that need to be imported
238
        are already on the server.'),
239
      '#default_value' => $this->config['direct'],
240
    );
241
    $form['directory'] = array(
242
      '#type' => 'textfield',
243
      '#title' => t('Upload directory'),
244
      '#description' => t('Directory where uploaded files get stored. Prefix the path with a scheme. Available schemes: @schemes.', array('@schemes' => implode(', ', $this->getSchemes()))),
245
      '#default_value' => $this->config['directory'],
246
      '#states' => array(
247
        'visible' => array(':input[name="direct"]' => array('checked' => FALSE)),
248
        'required' => array(':input[name="direct"]' => array('checked' => FALSE)),
249
      ),
250
    );
251
    if ($options = $this->getSchemeOptions()) {
252
      $form['allowed_schemes'] = array(
253
        '#type' => 'checkboxes',
254
        '#title' => t('Allowed schemes'),
255
        '#default_value' => $this->config['allowed_schemes'],
256
        '#options' => $options,
257
        '#description' => t('Select the schemes you want to allow for direct upload.'),
258
        '#states' => array(
259
          'visible' => array(':input[name="direct"]' => array('checked' => TRUE)),
260
        ),
261
      );
262
    }
263

    
264
    return $form;
265
  }
266

    
267
  /**
268
   * Overrides parent::configFormValidate().
269
   *
270
   * Ensure that the chosen directory is accessible.
271
   */
272
  public function configFormValidate(&$values) {
273

    
274
    $values['directory'] = trim($values['directory']);
275
    $values['allowed_schemes'] = array_filter($values['allowed_schemes']);
276

    
277
    if (!$values['direct']) {
278
      // Ensure that the upload directory field is not empty when not in
279
      // direct-mode.
280
      if (!$values['directory']) {
281
        form_set_error('directory', t('Please specify an upload directory.'));
282
        // Do not continue validating the directory if none was specified.
283
        return;
284
      }
285

    
286
      // Validate the URI scheme of the upload directory.
287
      $scheme = file_uri_scheme($values['directory']);
288
      if (!$scheme || !in_array($scheme, $this->getSchemes())) {
289
        form_set_error('directory', t('Please enter a valid scheme into the directory location.'));
290

    
291
        // Return here so that attempts to create the directory below don't
292
        // throw warnings.
293
        return;
294
      }
295

    
296
      // Ensure that the upload directory exists.
297
      if (!file_prepare_directory($values['directory'], FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS)) {
298
        form_set_error('directory', t('The chosen directory does not exist and attempts to create it failed.'));
299
      }
300
    }
301
  }
302

    
303
  /**
304
   * Deletes a file.
305
   *
306
   * @param int $fid
307
   *   The file id.
308
   * @param int $feed_nid
309
   *   The feed node's id, or 0 if a standalone feed.
310
   *
311
   * @return bool|array
312
   *   TRUE for success, FALSE in the event of an error, or an array if the file
313
   *   is being used by any modules.
314
   *
315
   * @see file_delete()
316
   */
317
  protected function deleteFile($fid, $feed_nid) {
318
    if ($file = file_load($fid)) {
319
      file_usage_delete($file, 'feeds', get_class($this), $feed_nid);
320
      return file_delete($file);
321
    }
322
    return FALSE;
323
  }
324

    
325
  /**
326
   * Returns available schemes.
327
   *
328
   * @return array
329
   *   The available schemes.
330
   */
331
  protected function getSchemes() {
332
    return array_keys(file_get_stream_wrappers(STREAM_WRAPPERS_WRITE_VISIBLE));
333
  }
334

    
335
  /**
336
   * Returns available scheme options for use in checkboxes or select list.
337
   *
338
   * @return array
339
   *   The available scheme array keyed scheme => description
340
   */
341
  protected function getSchemeOptions() {
342
    $options = array();
343
    foreach (file_get_stream_wrappers(STREAM_WRAPPERS_WRITE_VISIBLE) as $scheme => $info) {
344
      $options[$scheme] = check_plain($scheme . ': ' . $info['description']);
345
    }
346
    return $options;
347
  }
348

    
349
}