From f96f6a853579310434d67390d3952edbc7e75e2f Mon Sep 17 00:00:00 2001
From: Lorenz Schori <lo@znerol.ch>
Date: Fri, 23 Dec 2011 13:02:42 +0100
Subject: [PATCH] Configurable upload directory for FeedsFileFetcher

Expose a new configuration setting in FeedsFileFetcher which lets the
user choose the directory where files get uploaded to. Also let the
administrator choose allowed url schemes for direct upload.
---
 plugins/FeedsFileFetcher.inc |   90 +++++++++++++++++++++++++++++++++--------
 1 files changed, 72 insertions(+), 18 deletions(-)

diff --git a/plugins/FeedsFileFetcher.inc b/plugins/FeedsFileFetcher.inc
index 5d11129..7dec722 100644
--- a/plugins/FeedsFileFetcher.inc
+++ b/plugins/FeedsFileFetcher.inc
@@ -118,7 +118,7 @@ class FeedsFileFetcher extends FeedsFetcher {
       $form['source'] = array(
         '#type' => 'textfield',
         '#title' => t('File'),
-        '#description' => t('Specify a path to a file or a directory. Path must start with @scheme://', array('@scheme' => file_default_scheme())),
+        '#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']))),
         '#default_value' => empty($source_config['source']) ? '' : $source_config['source'],
       );
     }
@@ -129,24 +129,30 @@ class FeedsFileFetcher extends FeedsFetcher {
    * Override parent::sourceFormValidate().
    */
   public function sourceFormValidate(&$values) {
-    $feed_dir = 'public://feeds';
-    file_prepare_directory($feed_dir, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS);
-
-    // If there is a file uploaded, save it, otherwise validate input on
-    // file.
-    // @todo: Track usage of file, remove file when removing source.
-    if ($file = file_save_upload('feeds', array('file_validate_extensions' => array(0 => $this->config['allowed_extensions'])), $feed_dir)) {
-      $values['source'] = $file->uri;
-      $values['file'] = $file;
-    }
-    elseif (empty($values['source'])) {
-      form_set_error('feeds][source', t('Upload a file first.'));
+    if (empty($this->config['direct'])) {
+      $feed_dir = $this->config['directory'];
+      if (!file_prepare_directory($feed_dir, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS)) {
+        form_set_error('feeds][FeedsFileFetcher][source', t('Upload failed.'));
+        watchdog('feeds', 'The upload directory %directory for the feed fetcher !name 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, '!name' => 'FIXME: figure out name of importer'));
+      }
+      // Validate and save uploaded file
+      else if ($file = file_save_upload('feeds', array('file_validate_extensions' => array(0 => $this->config['allowed_extensions'])), $feed_dir)) {
+        $values['source'] = $file->uri;
+        $values['file'] = $file;
+      }
+      else {
+        form_set_error('feeds][FeedsFileFetcher][source', t('Upload a file first.'));
+      }
     }
-    // If a file has not been uploaded and $values['source'] is not empty, make
-    // sure that this file is within Drupal's files directory as otherwise
-    // potentially any file that the web server has access to could be exposed.
-    elseif (strpos($values['source'], file_default_scheme()) !== 0) {
-      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())));
+    else {
+      // Check if choosen url scheme is allowed
+      if (!in_array(reset(explode(':', $values['source'])), $this->config['allowed_schemes'])) {
+        form_set_error('feeds][FeedsFileFetcher][source', t('File needs to reside within the site\'s file directory, its path needs to start with scheme://. Available schemes: @schemes', array('@schemes' => implode(', ', $this->config['allowed_schemes']))));
+      }
+      // Verify that the given directory is accessible.
+      if (!file_prepare_directory($values['source'])) {
+        form_set_error('feeds[FeedsFileFetcher][source', t('Failed to access the specified directory.'));
+      }
     }
   }
 
@@ -187,9 +193,14 @@ class FeedsFileFetcher extends FeedsFetcher {
    * Override parent::configDefaults().
    */
   public function configDefaults() {
+    $schemes = $this->getSchemes();
+    $scheme = in_array('private', $schemes) ? 'private' : 'public';
+
     return array(
       'allowed_extensions' => 'txt csv tsv xml opml',
       'direct' => FALSE,
+      'directory' => $scheme . '://feeds',
+      'allowed_schemes' => $schemes,
     );
   }
 
@@ -197,6 +208,11 @@ class FeedsFileFetcher extends FeedsFetcher {
    * Override parent::configForm().
    */
   public function configForm(&$form_state) {
+    $options = array();
+    foreach (file_get_stream_wrappers(STREAM_WRAPPERS_WRITE_VISIBLE) as $scheme => $info) {
+      $options[$scheme] = check_plain($scheme . ': ' . $info['description']);
+    }
+
     $form = array();
     $form['allowed_extensions'] = array(
       '#type' => 'textfield',
@@ -204,6 +220,12 @@ class FeedsFileFetcher extends FeedsFetcher {
       '#description' => t('Allowed file extensions for upload.'),
       '#default_value' => $this->config['allowed_extensions'],
     );
+    $form['directory'] = array(
+      '#type' => 'textfield',
+      '#title' => t('Upload directory'),
+      '#description' => t('Directory where uploaded files get stored. Prefix the path with a scheme. Available schemes: @schemes', array('@schemes' => implode(', ', $this->getSchemes()))),
+      '#default_value' => $this->config['directory'],
+    );
     $form['direct'] = array(
       '#type' => 'checkbox',
       '#title' => t('Supply path to file or directory directly'),
@@ -212,10 +234,35 @@ class FeedsFileFetcher extends FeedsFetcher {
         are already on the server.'),
       '#default_value' => $this->config['direct'],
     );
+    if (!empty($options)) {
+      $form['allowed_schemes'] = array(
+        '#type' => 'checkboxes',
+        '#title' => t('Allowed schemes'),
+        '#default_value' => $this->config['allowed_schemes'],
+        '#options' => $options,
+        '#description' => t('Select the schemes you want to allow for direct upload'),
+      );
+    }
+
     return $form;
   }
 
   /**
+   * Override parent::configForm().
+   *
+   * Ensure that the choosen directory is accessible.
+   */
+  public function configFormValidate(&$values) {
+    $ok = file_prepare_directory($values['directory'], FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS);
+    if (!$ok) {
+      form_set_error('feeds][directory', t('The choosen directory does not exist and/or attempts to create it failed.'));
+    }
+
+    // Remove zeroes from array
+    $values['allowed_schemes'] = array_filter($values['allowed_schemes']);
+  }
+
+  /**
    * Helper. Deletes a file.
    */
   protected function deleteFile($fid, $feed_nid) {
@@ -224,4 +271,11 @@ class FeedsFileFetcher extends FeedsFetcher {
       file_delete($file);
     }
   }
+
+  /**
+   * Helper function: return available schemes.
+   */
+  protected function getSchemes() {
+    return array_keys(file_get_stream_wrappers(STREAM_WRAPPERS_WRITE_VISIBLE));
+  }
 }
-- 
1.7.4.1

