Browse Source

security update for uuid xmlsitemap file_field_path

Bachir Soussi Chiadmi 11 months ago
parent
commit
a163542966
100 changed files with 5234 additions and 1897 deletions
  1. 121 0
      sites/all/modules/filefield_paths/.travis.yml
  2. 100 1
      sites/all/modules/filefield_paths/CHANGELOG.txt
  3. 47 34
      sites/all/modules/filefield_paths/README.txt
  4. 49 0
      sites/all/modules/filefield_paths/filefield_paths.admin.inc
  5. 71 0
      sites/all/modules/filefield_paths/filefield_paths.api.php
  6. 29 14
      sites/all/modules/filefield_paths/modules/filefield_paths.drush.inc
  7. 18 15
      sites/all/modules/filefield_paths/filefield_paths.info
  8. 58 35
      sites/all/modules/filefield_paths/filefield_paths.install
  9. 430 133
      sites/all/modules/filefield_paths/filefield_paths.module
  10. 72 0
      sites/all/modules/filefield_paths/filefield_paths.tokens.inc
  11. 57 0
      sites/all/modules/filefield_paths/filefield_paths.variable.inc
  12. 18 2
      sites/all/modules/filefield_paths/modules/features.inc
  13. 4 1
      sites/all/modules/filefield_paths/modules/file.inc
  14. 91 42
      sites/all/modules/filefield_paths/modules/filefield_paths.inc
  15. 17 0
      sites/all/modules/filefield_paths/modules/image.inc
  16. 0 63
      sites/all/modules/filefield_paths/modules/token.inc
  17. 3 0
      sites/all/modules/filefield_paths/modules/video.inc
  18. 327 0
      sites/all/modules/filefield_paths/tests/filefield_paths.general.test
  19. 90 0
      sites/all/modules/filefield_paths/tests/filefield_paths.test
  20. 104 0
      sites/all/modules/filefield_paths/tests/filefield_paths.text_replace.test
  21. 103 0
      sites/all/modules/filefield_paths/tests/filefield_paths.tokens.test
  22. 81 0
      sites/all/modules/filefield_paths/tests/filefield_paths.update.test
  23. 12 0
      sites/all/modules/filefield_paths/tests/filefield_paths_test.info
  24. 16 0
      sites/all/modules/filefield_paths/tests/filefield_paths_test.module
  25. 84 0
      sites/all/modules/filefield_paths/tests/pathauto.test
  26. 90 0
      sites/all/modules/filefield_paths/tests/redirect.test
  27. 82 0
      sites/all/modules/filefield_paths/tests/transliteration.test
  28. 74 0
      sites/all/modules/uuid/.travis.yml
  29. 1 0
      sites/all/modules/uuid/README.txt
  30. 17 2
      sites/all/modules/uuid/plugins/arguments/entity_uuid.inc
  31. 2 0
      sites/all/modules/uuid/uuid.admin.inc
  32. 13 39
      sites/all/modules/uuid/uuid.api.php
  33. 134 98
      sites/all/modules/uuid/uuid.core.inc
  34. 2 2
      sites/all/modules/uuid/uuid.drush.inc
  35. 132 38
      sites/all/modules/uuid/uuid.entity.inc
  36. 17 5
      sites/all/modules/uuid/uuid.features.inc
  37. 112 41
      sites/all/modules/uuid/uuid.inc
  38. 5 4
      sites/all/modules/uuid/uuid.info
  39. 83 22
      sites/all/modules/uuid/uuid.install
  40. 24 21
      sites/all/modules/uuid/uuid.module
  41. 303 287
      sites/all/modules/uuid/uuid.test
  42. 14 0
      sites/all/modules/uuid/uuid.tokens.inc
  43. 2 1
      sites/all/modules/uuid/uuid.views.inc
  44. 0 402
      sites/all/modules/uuid/uuid_default_entities_example/uuid_default_entities_example.features.uuid_entities.inc
  45. 0 15
      sites/all/modules/uuid/uuid_default_entities_example/uuid_default_entities_example.info
  46. 0 6
      sites/all/modules/uuid/uuid_default_entities_example/uuid_default_entities_example.module
  47. 3 4
      sites/all/modules/uuid/uuid_path/uuid_path.info
  48. 7 8
      sites/all/modules/uuid/uuid_path/uuid_path.module
  49. 0 12
      sites/all/modules/uuid/uuid_services/resources/field_collection.resource.inc
  50. 30 0
      sites/all/modules/uuid/uuid_services/uuid_services.admin.inc
  51. 150 0
      sites/all/modules/uuid/uuid_services/uuid_services.file_services.test
  52. 9 4
      sites/all/modules/uuid/uuid_services/uuid_services.info
  53. 14 0
      sites/all/modules/uuid/uuid_services/uuid_services.install
  54. 60 24
      sites/all/modules/uuid/uuid_services/uuid_services.module
  55. 1 0
      sites/all/modules/uuid/uuid_services_example/uuid_services_example.features.inc
  56. 15 14
      sites/all/modules/uuid/uuid_services_example/uuid_services_example.info
  57. 2 1
      sites/all/modules/uuid/uuid_services_example/uuid_services_example.module
  58. 21 9
      sites/all/modules/uuid/uuid_services_example/uuid_services_example.services.inc
  59. 43 18
      sites/all/modules/xmlsitemap/README.txt
  60. 91 55
      sites/all/modules/xmlsitemap/xmlsitemap.admin.inc
  61. 43 13
      sites/all/modules/xmlsitemap/xmlsitemap.api.php
  62. 14 4
      sites/all/modules/xmlsitemap/xmlsitemap.drush.inc
  63. 57 24
      sites/all/modules/xmlsitemap/xmlsitemap.generate.inc
  64. 9 6
      sites/all/modules/xmlsitemap/xmlsitemap.inc
  65. 4 11
      sites/all/modules/xmlsitemap/xmlsitemap.info
  66. 24 8
      sites/all/modules/xmlsitemap/xmlsitemap.install
  67. 217 83
      sites/all/modules/xmlsitemap/xmlsitemap.module
  68. 6 4
      sites/all/modules/xmlsitemap/xmlsitemap.pages.inc
  69. 262 83
      sites/all/modules/xmlsitemap/xmlsitemap.test
  70. 97 14
      sites/all/modules/xmlsitemap/xmlsitemap.xmlsitemap.inc
  71. 76 0
      sites/all/modules/xmlsitemap/xmlsitemap_custom/README.txt
  72. 27 5
      sites/all/modules/xmlsitemap/xmlsitemap_custom/xmlsitemap_custom.admin.inc
  73. 3 7
      sites/all/modules/xmlsitemap/xmlsitemap_custom/xmlsitemap_custom.info
  74. 6 1
      sites/all/modules/xmlsitemap/xmlsitemap_custom/xmlsitemap_custom.module
  75. 36 11
      sites/all/modules/xmlsitemap/xmlsitemap_custom/xmlsitemap_custom.test
  76. 79 0
      sites/all/modules/xmlsitemap/xmlsitemap_engines/README.txt
  77. 86 11
      sites/all/modules/xmlsitemap/xmlsitemap_engines/tests/xmlsitemap_engines.test
  78. 3 6
      sites/all/modules/xmlsitemap/xmlsitemap_engines/tests/xmlsitemap_engines_test.info
  79. 9 0
      sites/all/modules/xmlsitemap/xmlsitemap_engines/tests/xmlsitemap_engines_test.module
  80. 19 2
      sites/all/modules/xmlsitemap/xmlsitemap_engines/xmlsitemap_engines.admin.inc
  81. 1 1
      sites/all/modules/xmlsitemap/xmlsitemap_engines/xmlsitemap_engines.api.php
  82. 3 7
      sites/all/modules/xmlsitemap/xmlsitemap_engines/xmlsitemap_engines.info
  83. 36 24
      sites/all/modules/xmlsitemap/xmlsitemap_engines/xmlsitemap_engines.module
  84. 3 5
      sites/all/modules/xmlsitemap/xmlsitemap_i18n/xmlsitemap_i18n.info
  85. 11 1
      sites/all/modules/xmlsitemap/xmlsitemap_i18n/xmlsitemap_i18n.module
  86. 43 6
      sites/all/modules/xmlsitemap/xmlsitemap_i18n/xmlsitemap_i18n.test
  87. 79 0
      sites/all/modules/xmlsitemap/xmlsitemap_menu/README.txt
  88. 3 6
      sites/all/modules/xmlsitemap/xmlsitemap_menu/xmlsitemap_menu.info
  89. 44 18
      sites/all/modules/xmlsitemap/xmlsitemap_menu/xmlsitemap_menu.module
  90. 32 4
      sites/all/modules/xmlsitemap/xmlsitemap_menu/xmlsitemap_menu.test
  91. 3 5
      sites/all/modules/xmlsitemap/xmlsitemap_modal/xmlsitemap_modal.info
  92. 7 2
      sites/all/modules/xmlsitemap/xmlsitemap_modal/xmlsitemap_modal.module
  93. 81 0
      sites/all/modules/xmlsitemap/xmlsitemap_node/README.txt
  94. 3 6
      sites/all/modules/xmlsitemap/xmlsitemap_node/xmlsitemap_node.info
  95. 69 23
      sites/all/modules/xmlsitemap/xmlsitemap_node/xmlsitemap_node.module
  96. 97 17
      sites/all/modules/xmlsitemap/xmlsitemap_node/xmlsitemap_node.test
  97. 83 0
      sites/all/modules/xmlsitemap/xmlsitemap_taxonomy/README.txt
  98. 3 6
      sites/all/modules/xmlsitemap/xmlsitemap_taxonomy/xmlsitemap_taxonomy.info
  99. 1 1
      sites/all/modules/xmlsitemap/xmlsitemap_taxonomy/xmlsitemap_taxonomy.install
  100. 0 0
      sites/all/modules/xmlsitemap/xmlsitemap_taxonomy/xmlsitemap_taxonomy.module

+ 121 - 0
sites/all/modules/filefield_paths/.travis.yml

@@ -0,0 +1,121 @@
+# @file
+# .travis.yml - Drupal for Travis CI Integration
+#
+# Template provided by https://github.com/LionsAd/drupal_ti.
+#
+# Based for simpletest upon:
+#   https://github.com/sonnym/travis-ci-drupal-module-example
+
+language: php
+
+sudo: false
+
+php:
+  - 5.3
+  - 5.4
+  - 5.5
+  - 5.6
+  - 7
+
+matrix:
+  fast_finish: true
+  allow_failures:
+    - php: 7
+
+env:
+  global:
+    # add composer's global bin directory to the path
+    # see: https://github.com/drush-ops/drush#install---composer
+    - PATH="$PATH:$HOME/.composer/vendor/bin"
+
+    # Configuration variables.
+    - DRUPAL_TI_MODULE_NAME="filefield_paths"
+    - DRUPAL_TI_SIMPLETEST_GROUP="File (Field) Paths"
+
+    # Define runners and environment vars to include before and after the
+    # main runners / environment vars.
+    #- DRUPAL_TI_SCRIPT_DIR_BEFORE="./drupal_ti/before"
+    #- DRUPAL_TI_SCRIPT_DIR_AFTER="./drupal_ti/after"
+
+    # The environment to use, supported are: drupal-7, drupal-8
+    - DRUPAL_TI_ENVIRONMENT="drupal-7"
+
+    # Drupal specific variables.
+    - DRUPAL_TI_DB="drupal_travis_db"
+    - DRUPAL_TI_DB_URL="mysql://root:@127.0.0.1/drupal_travis_db"
+    # Note: Do not add a trailing slash here.
+    - DRUPAL_TI_WEBSERVER_URL="http://127.0.0.1"
+    - DRUPAL_TI_WEBSERVER_PORT="8080"
+
+    # Simpletest specific commandline arguments, the DRUPAL_TI_SIMPLETEST_GROUP is appended at the end.
+    - DRUPAL_TI_SIMPLETEST_ARGS="--verbose --color --concurrency 4 --url $DRUPAL_TI_WEBSERVER_URL:$DRUPAL_TI_WEBSERVER_PORT"
+
+    # === Behat specific variables.
+    # This is relative to $TRAVIS_BUILD_DIR
+    - DRUPAL_TI_BEHAT_DIR="./tests/behat"
+    # These arguments are passed to the bin/behat command.
+    - DRUPAL_TI_BEHAT_ARGS=""
+    # Specify the filename of the behat.yml with the $DRUPAL_TI_DRUPAL_DIR variables.
+    - DRUPAL_TI_BEHAT_YML="behat.yml.dist"
+    # This is used to setup Xvfb.
+    - DRUPAL_TI_BEHAT_SCREENSIZE_COLOR="1280x1024x16"
+    # The version of seleniumthat should be used.
+    - DRUPAL_TI_BEHAT_SELENIUM_VERSION="2.44"
+    # Set DRUPAL_TI_BEHAT_DRIVER to "selenium" to use "firefox" or "chrome" here.
+    - DRUPAL_TI_BEHAT_DRIVER="phantomjs"
+    - DRUPAL_TI_BEHAT_BROWSER="firefox"
+
+    # PHPUnit specific commandline arguments.
+    - DRUPAL_TI_PHPUNIT_ARGS=""
+    # Specifying the phpunit-core src/ directory is useful when e.g. a vendor/
+    # directory is present in the module directory, which phpunit would then
+    # try to find tests in. This option is relative to $TRAVIS_BUILD_DIR.
+    #- DRUPAL_TI_PHPUNIT_CORE_SRC_DIRECTORY="./tests/src"
+
+    # Code coverage via coveralls.io
+    - DRUPAL_TI_COVERAGE="satooshi/php-coveralls:0.6.*"
+    # This needs to match your .coveralls.yml file.
+    - DRUPAL_TI_COVERAGE_FILE="build/logs/clover.xml"
+
+    # Debug options
+    #- DRUPAL_TI_DEBUG="-x -v"
+    # Set to "all" to output all files, set to e.g. "xvfb selenium" or "selenium",
+    # etc. to only output those channels.
+    #- DRUPAL_TI_DEBUG_FILE_OUTPUT="selenium xvfb webserver"
+
+  matrix:
+    # [[[ SELECT ANY OR MORE OPTIONS ]]]
+    #- DRUPAL_TI_RUNNERS="phpunit"
+    #- DRUPAL_TI_RUNNERS="simpletest"
+    #- DRUPAL_TI_RUNNERS="behat"
+    #- DRUPAL_TI_RUNNERS="phpunit simpletest behat"
+    # Use phpunit-core to test modules with phpunit with Drupal 8 core.
+    #- DRUPAL_TI_RUNNERS="phpunit-core"
+    - DRUPAL_TI_RUNNERS="simpletest"
+
+mysql:
+  database: drupal_travis_db
+  username: root
+  encoding: utf8
+
+before_install:
+  - composer self-update
+  - cd ./tests
+  - composer global require "lionsad/drupal_ti:1.*"
+  - drupal-ti before_install
+
+install:
+  - drupal-ti install
+
+before_script:
+  - drupal-ti before_script
+  - drush dl pathauto-7.x redirect-7.x token-7.x transliteration-7.x --destination="$TRAVIS_BUILD_DIR/../drupal-7/drupal/sites/all/modules"
+
+script:
+  - drupal-ti script
+
+after_script:
+  - drupal-ti after_script
+
+notifications:
+  email: false

+ 100 - 1
sites/all/modules/filefield_paths/CHANGELOG.txt

@@ -1,4 +1,100 @@
-File (Field) Paths 7.x-1.x-dev, xxxx-xx-xx (development release)
+File (Field) Paths 7.x-1.1, 2018-08-14
+--------------------------------------------------------------------------------
+
+- Fixed security issue
+- #2643026: Fixed issue when anonymous function not created.
+
+
+
+File (Field) Paths 7.x-1.0, 2015-11-17
+--------------------------------------------------------------------------------
+
+- #2615704 by morenstrat: Fixed issue with temporary path and schemes.
+- Added variable module integration.
+
+
+
+File (Field) Paths 7.x-1.0-rc3, 2015-11-11
+--------------------------------------------------------------------------------
+
+- #2612396: Added watchdog message for unmoved files.
+- #2607302: Added configurable temporary file location.
+
+
+
+File (Field) Paths 7.x-1.0-rc2, 2015-10-28
+--------------------------------------------------------------------------------
+
+- #2592519: Fixed issue with temporary upload location and field collections.
+- #2576547: Fixed issue with Media Youtube files being processed.
+- #2570127: Added stricter checks for filefield_paths_form_alter().
+- #2569589: Fixed issue with unicode characters in pathauto processing.
+
+
+
+File (Field) Paths 7.x-1.0-rc1, 2015-09-15
+--------------------------------------------------------------------------------
+
+- #2551187: Changed token tree to dialog.
+- #2514874 by Deciphered, smithmilner: Fixed issue with Drush command and
+    permissions.
+- #2468547 by Deciphered, smithmilner: Added Redirect module integration.
+- #2398411: Added default value of file_directory to file path.
+- #2395903: Added truncating of long file paths.
+- #2383527: Changed unprocessed destination to temporary://.
+- #2276435 by Deciphered, david_garcia, rajmataj: Fixed issue with retroactive
+    updates and instance settings.
+- #2271595 by Deciphered, tstoeckler, Reg: Fixed issue with empty batch.
+- #2214409: Updated field form layout.
+- #2211665: Added File path validation to remove unnecessary slashes.
+- #2103151 by Deciphered, david_garcia, Sumeet.Pareek, kitikonti, kolier:
+    Fixed deprecated /e modifier in preg_replace().
+- #2185755 by treksler: Fixed issue with regex, backreferences and numbers.
+- #2119789: Fixed issue with complex characters in regex functionality.
+- #2068365 by Deciphered, vinmassaro: Fixed issue when no instance in update.
+- #2047835: Fixed issue with pathauto replacemenets and periods (.).
+- #2019723 by Deciphered, szantog, jcandan, justafish: Fixed issue with remote
+    file stream wrappers.
+- #1985650: Fixed issue with moved Drush file in .info.
+- #1985280 by Deciphered, drasgardian: Fixed issue with hook_entity_update()
+    foreach loop.
+- #1942720 by Deciphered, kaare, alex.designworks, pp, BWPanda: Added slash
+    cleanup functionality.
+- #1854450: Updated file processing logic.
+- #1495716/#1986472: Fixed issue with D6 -> D7 upgrade path.
+- #1292436 by maximpodorov, InternetDevels: Fixed issue pathinfo and UTF files.
+- Added tests.
+- Removed dependency on the Token module.
+
+
+
+File (Field) Paths 7.x-1.0-beta4, 2013-04-25
+--------------------------------------------------------------------------------
+
+- #1945148: Fixed issue with File path cleanup process.
+- #1942720 by pp: Fixed issue with cleanup wehn token contains a '/' character.
+- #1925298 by David_Rothstein: Fixed issue with Image derivatives.
+- #1866450: Fixed issue with foreign characters in Regex.
+- #1714596: Fixed issue with file_attach_update() introduced in Drupal 7.15.
+- #1705298: Fixed extra beggining slash.
+- #1601104 by tseven: Fixed notice when no extension present.
+- #1572206: Add ability to disable for certain fields.
+- #1549474: Fixed path replacement in summary text.
+- #1512466 by maximpodorov, perarnet: Fixed issue with multi-value updates.
+- #1499442 by hass: Improved translatable strings.
+- #1464404: Fixed issue with field_attach_update().
+- #1438290 by xtfer: Fixed D6 to D7 upgrade path.
+- #1432200: Fixed issue with missing langcode variable.
+- #1420700: Fixed issue with pass-by-reference issue.
+- #1364492 by j0rd, Deciphered: Improved require_once routine.
+- #1361884 by burningdog, dwkitchen, Deciphered: Fixed processing remote files.
+- #1262828 by Pablo Gosse: Fixed issue with regex.
+- #860848 by mostou: Fixed File name field length.
+- Fixed issue with malformed URIs.
+
+
+
+File (Field) Paths 7.x-1.0-beta3, 2012-02-08
 --------------------------------------------------------------------------------
 
 - #1429238 by ocanzillon: Fixed Drush command syntax error.
@@ -6,6 +102,7 @@ File (Field) Paths 7.x-1.x-dev, xxxx-xx-xx (development release)
 - Changed a large chunk of core functionality to simplify.
 
 
+
 File (Field) Paths 7.x-1.0-beta2, 2012-02-05
 --------------------------------------------------------------------------------
 
@@ -16,6 +113,7 @@ File (Field) Paths 7.x-1.0-beta2, 2012-02-05
 - Added support for Video module.
 
 
+
 File (Field) Paths 7.x-1.0-beta1, 2011-11-08
 --------------------------------------------------------------------------------
 
@@ -36,5 +134,6 @@ File (Field) Paths 7.x-1.0-beta1, 2011-11-08
 - Added Entity support.
 
 
+
 File (Field) Paths 7.x-1.0-alpha1, 2011-06-12
 --------------------------------------------------------------------------------

+ 47 - 34
sites/all/modules/filefield_paths/README.txt

@@ -1,63 +1,66 @@
-The File (Field) Paths module extends the default functionality of Drupals core
+File (Field) Paths
+==================
+
+[![Build Status](https://travis-ci.org/Decipher/filefield_paths.svg)](https://travis-ci.org/Decipher/filefield_paths)
+
+The File (Field) Paths module extends the default functionality of Drupal's core
 File module, Image module and many other File upload modules, by adding the
-ability to use entity based tokens in destination paths and filenames.
+ability to use entity based tokens in destination paths and file names.
 
 In simple terms, File (Field) Paths allows you to automatically sort and rename
 your uploaded files using token based replacement patterns to maintain a nice
 clean filesystem.
 
-File (Field) Paths was written and is maintained by Stuart Clark (deciphered).
-- http://stuar.tc/lark
-- http://twitter.com/Decipher
 
 
 Features
---------------------------------------------------------------------------------
+--------
 
 * Configurable file paths now use entity tokens in addition to user tokens.
-* Configurable filenames.
-* Support for:
-  * Drupal core File module.
-  * Drupal core Image module.
-  * Video module.
+* Configurable file names.
+* Support for file based fields, including but not limited to:
+    * Drupal core File module.
+    * Drupal core Image module.
+    * Video module.
 * File path and filename cleanup options:
-  * Filter out words and punctuation by taking advantage of the Pathauto module.
-  * Convert unicode characters into US-ASCII with the Transliteration module.
+    * Remove slashes from tokens.
+    * Filter out words and punctuation by taking advantage of the Pathauto
+      module.
+    * Convert unicode characters into US-ASCII with the Transliteration module.
 * Automatically updates unprocessed file paths in any Text fields on the entity.
-* Retroactive updates - rename and/or move previously uploaded files (Use with
-  caution)
-
+* Retroactive updates - rename and/or move previously uploaded files.
+* Active updating - actively rename and/or move previously uploaded files.
+* Create redirect - automatically create a redirect when moving uploaded files,
+  using the Redirect module.
 
-Required Modules
---------------------------------------------------------------------------------
-
-* Token           - http://drupal.org/project/token
 
 
 Recommended Modules
---------------------------------------------------------------------------------
+-------------------
+
+* [Pathauto](https://www.drupal.org/project/pathauto)
+* [Redirect](https://www.drupal.org/project/redirect)
+* [Token](https://www.drupal.org/project/token)
+* [Transliteration](https://www.drupal.org/project/transliteration)
 
-* Pathauto        - http://drupal.org/project/pathauto
-* Transliteration - http://drupal.org/project/transliteration
 
 
 Usage/Configuration
---------------------------------------------------------------------------------
+-------------------
 
 Once installed, File (Field) Paths needs to be configured for each file field
-you wish to use.
-
-* Drupal core File/Image and other Field based supported modules
-  Settings an be found on the fields configuration page.
+you wish to use. Settings can be found on the settings form of any supported
+file based field.
 
-  Example:
+  *Example:*
+  
     Administration > Structure > Content types > Article > Manage fields > Image
-    http://[www.yoursite.com/path/to/drupal]/admin/structure/types/manage/article/fields/field_image
+    http://example.com/admin/structure/types/manage/article/fields/field_image
 
 
 
 Frequently Asked Questions
---------------------------------------------------------------------------------
+--------------------------
 
 Q. Aren't tokens already supported in the File module?
 
@@ -71,9 +74,9 @@ Q. Why aren't my files in the correct folder?
 
 A. When you are creating or updating an entity the full values for the tokens
    may not yet be known by Drupal, so the File (Field) Paths module will upload
-   your files to the Fields old file path temporarily and then once you save the
-   entity and Drupal is provided with the tokens values the file will be moved
-   to the appropriate location.
+   your files to a temporary location and then once you save the entity and
+   Drupal is provided with the tokens values the file will be moved to the
+   appropriate location.
 
 
 Q. Why is there a warning on the 'Retroactive updates' feature?
@@ -85,3 +88,13 @@ A. Retroactive updates will go through every single entity of the particular
    possible that the moving/renaming of these files could break links. It is
    strongly advised that you only use this functionality on your developmental
    servers so that you can make sure not to introduce any linking issues.
+
+
+
+History and Maintainers
+-----------------------
+
+File (Field) Paths was written and is maintained by Stuart Clark (deciphered).
+
+* http://stuar.tc/lark
+* http://twitter.com/Decipher

+ 49 - 0
sites/all/modules/filefield_paths/filefield_paths.admin.inc

@@ -0,0 +1,49 @@
+<?php
+
+/**
+ * @file
+ *
+ * Administration functions for the File (Field) Paths module.
+ */
+
+/**
+ * @param $form
+ * @param $form_state
+ * @return mixed
+ */
+function filefield_paths_settings_form($form, $form_state) {
+  $form['filefield_paths_temp_location'] = array(
+    '#title'            => t('Temporary file location'),
+    '#type'             => 'textfield',
+    '#default_value'    => variable_get('filefield_paths_temp_location', 'public://filefield_paths'),
+    '#description'      => t('The location that unprocessed files will be uploaded priot to being processed by File (Field) Paths.<br />It is recommended that you use the temporary file system (temporary://) if your server configuration allows for that.'),
+    '#element_validate' => array('filefield_paths_settings_form_temp_location_validate'),
+  );
+
+  return system_settings_form($form);
+}
+
+/**
+ * Validation callback for 'Temporary file location' setting.
+ *
+ * @param $element
+ * @param $form_state
+ * @return bool
+ */
+function filefield_paths_settings_form_temp_location_validate($element, $form_state) {
+  $scheme = file_uri_scheme($element['#value']);
+  if (!$scheme) {
+    form_error($element, t('Invalid file location. You must include a file stream wrapper (e.g., public://).'));
+    return FALSE;
+  }
+
+  if (!file_stream_wrapper_valid_scheme($scheme)) {
+    form_error($element, t('Invalid file stream wrapper.'));
+    return FALSE;
+  }
+
+  if ((!is_dir($element['#value']) || !is_writable($element['#value'])) && !file_prepare_directory($element['#value'], FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS)) {
+    form_error($element, t('File location can not be created or is not writable.'));
+    return FALSE;
+  }
+}

+ 71 - 0
sites/all/modules/filefield_paths/filefield_paths.api.php

@@ -0,0 +1,71 @@
+<?php
+
+/**
+ * @file
+ * Hooks provided by the File (Field) Paths module.
+ */
+
+/**
+ * Define field(s) to be displayed on the File (Field) Paths settings form and
+ * used during the processing of uploaded files.
+ *
+ * @param $field
+ *   The field definition this File (Field) Paths settings field applies to.
+ * @param $instance
+ *   The field instance this File (Field) Paths settings field applies to.
+ *
+ * @return array
+ *   An array whose keys are field names and whose values are arrays defining
+ *   the field, with the following key/value pairs:
+ *   - title: The title fo the field.
+ *   - form: A keyed array of Form API elements.
+ *
+ * @see hook_filefield_paths_process_file().
+ */
+function hook_filefield_paths_field_settings($field, $instance) {
+  return array(
+    'file_path' => array(
+      'title' => 'File path',
+      'form'  => array(
+        'value' => array(
+          '#type'             => 'textfield',
+          '#title'            => t('File path'),
+          '#maxlength'        => 512,
+          '#size'             => 128,
+          '#element_validate' => array('_file_generic_settings_file_directory_validate'),
+          '#default_value'    => $instance['settings']['file_directory'],
+        ),
+      ),
+    ),
+  );
+}
+
+/**
+ * Declare a compatible field type for use with File (Field) Paths.
+ *
+ * @return array
+ */
+function hook_filefield_paths_field_type_info() {
+  return array('file');
+}
+
+/**
+ * Process the uploaded files.
+ *
+ * @param $type
+ *   The entity type containing the files for processing.
+ * @param $entity
+ *   The entity containing the files for processing.
+ * @param $field
+ *   The definition of the field containing the files for processing.
+ * @param $instance
+ *   The instance of the field containing the files for processing.
+ * @param $langcode
+ *   The language code of the field containing the files for processing.
+ * @param $items
+ *   A pass-by-reference array of all the files for processing.
+ *
+ * @see filefield_paths_filefield_paths_process_file().
+ */
+function hook_filefield_paths_process_file($type, $entity, $field, $instance, $langcode, &$items) {
+}

+ 29 - 14
sites/all/modules/filefield_paths/modules/filefield_paths.drush.inc

@@ -1,4 +1,5 @@
 <?php
+
 /**
  * @file
  * Drush integration.
@@ -12,20 +13,20 @@ function filefield_paths_drush_command() {
 
   $items['ffp-update'] = array(
     'description' => 'Retroactively updates all File (Field) Paths of a chosen field instance.',
-    'arguments' => array(
+    'arguments'   => array(
       'entity_type' => 'Entity type.',
       'bundle_name' => 'Bundle name.',
-      'field_name' => 'Field name.'
+      'field_name'  => 'Field name.'
     ),
-    'options' => array(
+    'options'     => array(
       'all' => 'Retroactively update all File (Field) Paths.',
     ),
-    'examples' => array(
-      'drush ffp-update' => 'Retroactively updates the File (Field) Paths of the instances choosen via an interactive menu.',
+    'examples'    => array(
+      'drush ffp-update'                          => 'Retroactively updates the File (Field) Paths of the instances choosen via an interactive menu.',
       'drush ffp-update node article field_image' => 'Retroactively updates the File (Field) Paths of all instances of the Article content types Image field.',
-      'drush ffp-update --all' => 'Retroactively update all File (Field) Paths.',
+      'drush ffp-update --all'                    => 'Retroactively update all File (Field) Paths.',
     ),
-    'aliases' => array('ffpu'),
+    'aliases'     => array('ffpu'),
   );
 
   return $items;
@@ -33,17 +34,23 @@ function filefield_paths_drush_command() {
 
 /**
  * Retroactively updates all File (Field) Paths of a chosen field instance.
+ *
+ * @param null $entity_type
+ * @param null $bundle_name
+ * @param null $field_name
+ *
+ * @return string
  */
 function drush_filefield_paths_ffp_update($entity_type = NULL, $bundle_name = NULL, $field_name = NULL) {
   // Build array of information of all entity types, bundle names and field
   // names.
   $field_types = array_keys(_filefield_paths_get_field_types());
-  $info = array();
+  $info        = array();
   foreach (field_info_fields() as $field) {
     if (in_array($field['type'], $field_types)) {
       foreach ($field['bundles'] as $entity_type_name => $bundles) {
         if (!isset($info[$entity_type_name])) {
-          $entity_type_info = entity_get_info($entity_type_name);
+          $entity_type_info        = entity_get_info($entity_type_name);
           $info[$entity_type_name] = array(
             '#label' => "{$entity_type_info['label']} ({$entity_type_name})",
           );
@@ -56,6 +63,7 @@ function drush_filefield_paths_ffp_update($entity_type = NULL, $bundle_name = NU
             );
           }
           $field = field_info_instance($entity_type_name, $field['field_name'], $bundle);
+
           $info[$entity_type_name][$bundle][$field['field_name']] = "{$field['label']} ({$field['field_name']})";
         }
       }
@@ -72,6 +80,7 @@ function drush_filefield_paths_ffp_update($entity_type = NULL, $bundle_name = NU
       }
     }
     _filefield_paths_drush_ffp_update($instances);
+
     return '';
   }
 
@@ -96,6 +105,7 @@ function drush_filefield_paths_ffp_update($entity_type = NULL, $bundle_name = NU
       }
     }
     _filefield_paths_drush_ffp_update($instances);
+
     return '';
   }
 
@@ -118,6 +128,7 @@ function drush_filefield_paths_ffp_update($entity_type = NULL, $bundle_name = NU
       }
     }
     _filefield_paths_drush_ffp_update($instances);
+
     return '';
   }
 
@@ -138,6 +149,7 @@ function drush_filefield_paths_ffp_update($entity_type = NULL, $bundle_name = NU
       $instances[] = field_info_instance($entity_type, $field_name, $bundle_name);
     }
     _filefield_paths_drush_ffp_update($instances);
+
     return '';
   }
 
@@ -148,13 +160,16 @@ function drush_filefield_paths_ffp_update($entity_type = NULL, $bundle_name = NU
 
 /**
  * Helper function; Invokes File (Field) Paths Retroactive updates.
+ *
+ * @param $instances
  */
 function _filefield_paths_drush_ffp_update($instances) {
   foreach ($instances as $instance) {
-    filefield_paths_batch_update($instance);
-    $batch =& batch_get();
-    $batch['progressive'] = FALSE;
-    drush_backend_batch_process();
-    drush_log(dt('!field_name File (Field) Paths updated.', array('!field_name' => "{$instance['label']} ({$instance['entity_type']}-{$instance['bundle']}-{$instance['field_name']})")), 'success');
+    if (filefield_paths_batch_update($instance)) {
+      $batch                =& batch_get();
+      $batch['progressive'] = FALSE;
+      drush_backend_batch_process();
+      drush_log(dt('!field_name File (Field) Paths updated.', array('!field_name' => "{$instance['label']} ({$instance['entity_type']}-{$instance['bundle']}-{$instance['field_name']})")), 'success');
+    }
   }
 }

+ 18 - 15
sites/all/modules/filefield_paths/filefield_paths.info

@@ -1,25 +1,28 @@
 name = File (Field) Paths
 description = Adds improved Token based file sorting and renaming functionalities.
-dependencies[] = token
 package = Fields
+test_dependencies[] = pathauto
+test_dependencies[] = redirect
+test_dependencies[] = token
+test_dependencies[] = transliteration
+configure = admin/config/media/file-system/filefield-paths
 core = 7.x
 
-files[] = filefield_paths.install
-files[] = filefield_paths.module
 
-files[] = modules/features.inc
-files[] = modules/file.inc
-files[] = modules/filefield_paths.drush.inc
-files[] = modules/filefield_paths.inc
-files[] = modules/image.inc
-files[] = modules/token.inc
-files[] = modules/video.inc
 
-;files[] = tests/filefield_paths.test
+; Simpletest files.
 
-; Information added by drupal.org packaging script on 2012-02-07
-version = "7.x-1.0-beta3"
+files[] = tests/filefield_paths.test
+files[] = tests/filefield_paths.general.test
+files[] = tests/filefield_paths.text_replace.test
+files[] = tests/filefield_paths.tokens.test
+files[] = tests/filefield_paths.update.test
+files[] = tests/pathauto.test
+files[] = tests/redirect.test
+files[] = tests/transliteration.test
+
+; Information added by Drupal.org packaging script on 2018-08-14
+version = "7.x-1.1"
 core = "7.x"
 project = "filefield_paths"
-datestamp = "1328655041"
-
+datestamp = "1534256584"

+ 58 - 35
sites/all/modules/filefield_paths/filefield_paths.install

@@ -1,4 +1,5 @@
 <?php
+
 /**
  * @file
  * Install, update and uninstall functions for the File (Field) Paths module.
@@ -13,10 +14,10 @@
 function filefield_paths_schema_alter(&$schema) {
   $schema['file_managed']['fields']['origname'] = array(
     'description' => 'Original name of the file.',
-    'type' => 'varchar',
-    'length' => 255,
-    'not null' => TRUE,
-    'default' => '',
+    'type'        => 'varchar',
+    'length'      => 255,
+    'not null'    => TRUE,
+    'default'     => '',
   );
 }
 
@@ -28,14 +29,12 @@ function filefield_paths_install() {
   // filenames.
   db_add_field('file_managed', 'origname', array(
     'description' => 'Original name of the file with no path components. Used by the filefield_paths module.',
-    'type' => 'varchar',
-    'length' => 255,
-    'not null' => TRUE,
-    'default' => '',
+    'type'        => 'varchar',
+    'length'      => 255,
+    'not null'    => TRUE,
+    'default'     => '',
   ));
-  db_update('file_managed')
-    ->expression('origname', 'filename')
-    ->execute();
+  db_update('file_managed')->expression('origname', 'filename')->execute();
 }
 
 /**
@@ -76,10 +75,10 @@ function filefield_paths_update_7103() {
   if (!db_field_exists('file_managed', 'origname')) {
     db_add_field('file_managed', 'origname', array(
       'description' => 'Original name of the file with no path components. Used by the filefield_paths module.',
-      'type' => 'varchar',
-      'length' => 255,
-      'not null' => TRUE,
-      'default' => '',
+      'type'        => 'varchar',
+      'length'      => 255,
+      'not null'    => TRUE,
+      'default'     => '',
     ));
   }
   db_update('file_managed')
@@ -93,10 +92,10 @@ function filefield_paths_update_7103() {
  */
 function filefield_paths_update_7104() {
   db_add_field('filefield_paths', 'active_updating', array(
-    'type' => 'int',
-    'size' => 'tiny',
+    'type'     => 'int',
+    'size'     => 'tiny',
     'not null' => TRUE,
-    'default' => '0'
+    'default'  => '0'
   ));
 
   // migrate variable to filefield_paths table
@@ -123,10 +122,10 @@ function filefield_paths_update_7104() {
  */
 function filefield_paths_update_7105() {
   db_change_field('filefield_paths', 'active_updating', 'active_updating', array(
-    'type' => 'int',
-    'size' => 'tiny',
+    'type'     => 'int',
+    'size'     => 'tiny',
     'not null' => TRUE,
-    'default' => 0
+    'default'  => 0
   ));
 }
 
@@ -135,16 +134,16 @@ function filefield_paths_update_7105() {
  */
 function filefield_paths_update_7106() {
   db_change_field('filefield_paths', 'type', 'type', array(
-    'type' => 'varchar',
-    'length' => 128,
+    'type'     => 'varchar',
+    'length'   => 128,
     'not null' => TRUE,
-    'default' => ''
+    'default'  => ''
   ));
   db_change_field('filefield_paths', 'field', 'field', array(
-    'type' => 'varchar',
-    'length' => 128,
+    'type'     => 'varchar',
+    'length'   => 128,
     'not null' => TRUE,
-    'default' => ''
+    'default'  => ''
   ));
 }
 
@@ -152,33 +151,57 @@ function filefield_paths_update_7106() {
  * Removed filefield_paths table/schema.
  */
 function filefield_paths_update_7107() {
+  $results = db_select('filefield_paths', 'ffp')->fields('ffp')->execute();
+  foreach ($results as $result) {
+    $instance = field_info_instance('node', $result->field, $result->type);
+    if (!is_null($instance) && isset($instance["ffp_{$result->field}}"])) {
+      $filepath = unserialize($result->filepath);
+      $filename = unserialize($result->filename);
+
+      $instance["ffp_{$result->field}"] = array(
+        'file_path'         => $filepath['value'],
+        'file_path_cleanup' => array(
+          'file_path_pathauto'      => $filepath['pathauto'],
+          'file_path_transliterate' => $filepath['transliterate'],
+        ),
+        'file_name'         => $filename['value'],
+        'file_name_cleanup' => array(
+          'file_name_pathauto'      => $filename['pathauto'],
+          'file_name_transliterate' => $filename['transliterate'],
+        ),
+        'active_updating'   => $result->active_updating,
+      );
+      field_update_instance($instance);
+    }
+  }
+
   // Remove filefield_paths table/schema.
   db_drop_table('filefield_paths');
 
   // Update field instance settings.
+  drupal_load('module', 'filefield_paths');
   $field_types = array_keys(_filefield_paths_get_field_types());
   foreach (field_info_fields() as $field) {
     if (in_array($field['type'], $field_types)) {
       foreach ($field['bundles'] as $entity_type => $bundles) {
         foreach ($bundles as $bundle_name) {
           $instance = field_info_instance($entity_type, $field['field_name'], $bundle_name);
-          if ($instance["ffp_{$field['field_name']}"] && !isset($instance['settings']['filefield_paths'])) {
+          if (isset($instance["ffp_{$field['field_name']}"]) && !isset($instance['settings']['filefield_paths'])) {
             $instance['settings']['filefield_paths'] = array(
-              'file_path' => array(
-                'value' => $instance["ffp_{$field['field_name']}"]['file_path'],
+              'file_path'       => array(
+                'value'   => $instance["ffp_{$field['field_name']}"]['file_path'],
                 'options' => array(
-                  'pathauto' => $instance["ffp_{$field['field_name']}"]['file_path_cleanup']['file_path_pathauto'],
+                  'pathauto'      => $instance["ffp_{$field['field_name']}"]['file_path_cleanup']['file_path_pathauto'],
                   'transliterate' => $instance["ffp_{$field['field_name']}"]['file_path_cleanup']['file_path_transliterate'],
                 ),
               ),
-              'file_name' => array(
-                'value' => $instance["ffp_{$field['field_name']}"]['file_name'],
+              'file_name'       => array(
+                'value'   => $instance["ffp_{$field['field_name']}"]['file_name'],
                 'options' => array(
-                  'pathauto' => $instance["ffp_{$field['field_name']}"]['file_name_cleanup']['file_name_pathauto'],
+                  'pathauto'      => $instance["ffp_{$field['field_name']}"]['file_name_cleanup']['file_name_pathauto'],
                   'transliterate' => $instance["ffp_{$field['field_name']}"]['file_name_cleanup']['file_name_transliterate'],
                 ),
               ),
-              'retroactive_update' => $instance["ffp_{$field['field_name']}"]['retroactive_update'],
               'active_updating' => $instance["ffp_{$field['field_name']}"]['active_updating'],
             );
             unset($instance["ffp_{$field['field_name']}"]);

+ 430 - 133
sites/all/modules/filefield_paths/filefield_paths.module

@@ -1,4 +1,5 @@
 <?php
+
 /**
  * @file
  * Contains core functions for the File (Field) Paths module.
@@ -7,36 +8,78 @@
 /**
  * Include additional files.
  */
+$dirname  = dirname(__FILE__) . "/modules";
+$includes = file_scan_directory($dirname, '/.inc$/');
 foreach (module_list() as $module) {
-  if (file_exists($file = dirname(__FILE__) . "/modules/{$module}.inc")) {
+  if (isset($includes[$file = "{$dirname}/{$module}.inc"])) {
     require_once $file;
   }
 }
 
 /**
+ * Implements hook_menu().
+ */
+function filefield_paths_menu() {
+  $items['admin/config/media/file-system/filefield-paths'] = array(
+    'title'            => t('File (Field) Paths settings'),
+    'page callback'    => 'drupal_get_form',
+    'page arguments'   => array('filefield_paths_settings_form'),
+    'access arguments' => array('administer site configuration'),
+    'type'             => MENU_NORMAL_ITEM,
+    'file'             => 'filefield_paths.admin.inc',
+  );
+
+  return $items;
+}
+
+/**
  * Implements hook_form_alter().
+ *
+ * @param $form
  */
-function filefield_paths_form_alter(&$form, $form_state, $form_id) {
+function filefield_paths_form_alter(&$form) {
+  // Force all File (Field) Paths uploads to go to the temporary file system
+  // prior to being processed.
+  if (isset($form['#entity']) && isset($form['#entity_type']) && isset($form['#bundle']) && $form['#type'] == 'form') {
+    filefield_paths_temporary_upload_location($form);
+  }
+
   $field_types = _filefield_paths_get_field_types();
   if (isset($form['#field']) && in_array($form['#field']['type'], array_keys($field_types))) {
     $entity_info = entity_get_info($form['#instance']['entity_type']);
-    $settings = isset($form['#instance']['settings']['filefield_paths']) ? $form['#instance']['settings']['filefield_paths'] : array();
+    $settings    = isset($form['#instance']['settings']['filefield_paths']) ? $form['#instance']['settings']['filefield_paths'] : array();
+
+    $form['instance']['settings']['filefield_paths_enabled'] = array(
+      '#type'          => 'checkbox',
+      '#title'         => t('Enable File (Field) Paths?'),
+      '#default_value' => isset($form['#instance']['settings']['filefield_paths_enabled']) ? $form['#instance']['settings']['filefield_paths_enabled'] : TRUE,
+      '#weight'        => 2,
+    );
 
     // Hide standard File directory field.
-    $form['instance']['settings']['file_directory']['#access'] = FALSE;
+    $form['instance']['settings']['file_directory']['#states'] = array(
+      'visible' => array(
+        ':input[name="instance[settings][filefield_paths_enabled]"]' => array('checked' => FALSE),
+      ),
+    );
 
     // File (Field) Paths fieldset element.
     $form['instance']['settings']['filefield_paths'] = array(
-      '#type' => 'fieldset',
-      '#title' => t('File (Field) Path settings'),
+      '#type'        => 'fieldset',
+      '#title'       => t('File (Field) Path settings'),
       '#collapsible' => TRUE,
-      '#collapsed' => TRUE,
-      '#weight' => 1,
-      '#tree' => TRUE,
+      '#collapsed'   => TRUE,
+      '#weight'      => 3,
+      '#tree'        => TRUE,
+      '#states'      => array(
+        'visible' => array(
+          ':input[name="instance[settings][filefield_paths_enabled]"]' => array('checked' => TRUE),
+        ),
+      ),
     );
 
     // Additional File (Field) Paths widget fields.
-    $fields = module_invoke_all('filefield_paths_field_settings');
+    $fields = module_invoke_all('filefield_paths_field_settings', $form['#field'], $form['#instance']);
     foreach ($fields as $name => $field) {
       // Attach widget fields.
       $form['instance']['settings']['filefield_paths'][$name] = array(
@@ -46,13 +89,14 @@ function filefield_paths_form_alter(&$form, $form_state, $form_id) {
       // Attach widget field form elements.
       if (isset($field['form']) && is_array($field['form'])) {
         foreach (array_keys($field['form']) as $delta => $key) {
-          $form['instance']['settings']['filefield_paths'][$name][$key] = array_merge(
-            $field['form'][$key],
-            array(
-              '#element_validate' => array('token_element_validate'),
-              '#token_types' => array('file', $entity_info['token type']),
-            )
-          );
+          $form['instance']['settings']['filefield_paths'][$name][$key] = $field['form'][$key];
+          if (module_exists('token')) {
+            $form['instance']['settings']['filefield_paths'][$name][$key]['#element_validate'][] = 'token_element_validate';
+            $form['instance']['settings']['filefield_paths'][$name][$key]['#token_types']        = array(
+              'file',
+              $entity_info['token type']
+            );
+          }
 
           // Fetch stored value from instance.
           if (isset($settings[$name][$key])) {
@@ -62,67 +106,82 @@ function filefield_paths_form_alter(&$form, $form_state, $form_id) {
 
         // Field options.
         $form['instance']['settings']['filefield_paths'][$name]['options'] = array(
-          '#type' => 'fieldset',
-          '#title' => t('@title options', array('@title' => $field['title'])),
+          '#type'        => 'fieldset',
+          '#title'       => t('@title options', array('@title' => t($field['title']))),
           '#collapsible' => TRUE,
-          '#collapsed' => TRUE,
-          '#weight' => 1,
-          '#attributes' => array(
+          '#collapsed'   => TRUE,
+          '#weight'      => 1,
+          '#attributes'  => array(
             'class' => array("{$name} cleanup")
           ),
         );
-        // @TODO - Make this more modular.
+        // Cleanup slashes (/).
+        $form['instance']['settings']['filefield_paths'][$name]['options']['slashes'] = array(
+          '#type'          => 'checkbox',
+          '#title'         => t('Remove slashes (/) from tokens'),
+          '#default_value' => isset($settings[$name]['options']['slashes']) ? $settings[$name]['options']['slashes'] : FALSE,
+          '#description'   => t('If checked, any slashes (/) in tokens will be removed from %title.', array('%title' => t($field['title']))),
+        );
+
         // Cleanup field with Pathauto module.
         $form['instance']['settings']['filefield_paths'][$name]['options']['pathauto'] = array(
-          '#type' => 'checkbox',
-          '#title' => t('Cleanup using Pathauto') . '.',
-          '#default_value' => isset($settings[$name]['options']['pathauto']) && module_exists('pathauto')
-            ? $settings[$name]['options']['pathauto']
-            : FALSE,
-          '#description' => t('Cleanup @title using', array('@title' => $field['title'])) . ' ' . l(t('Pathauto settings'), 'admin/config/search/path/settings'),
-          '#disabled' => !module_exists('pathauto'),
+          '#type'          => 'checkbox',
+          '#title'         => t('Cleanup using Pathauto'),
+          '#default_value' => isset($settings[$name]['options']['pathauto']) && module_exists('pathauto') ? $settings[$name]['options']['pathauto'] : FALSE,
+          '#description'   => t('Cleanup %title using <a href="@pathauto">Pathauto settings</a>.', array(
+            '%title'    => t($field['title']),
+            '@pathauto' => url('admin/config/search/path/settings')
+          )),
+          '#disabled'      => !module_exists('pathauto'),
         );
 
         // Transliterate field with Transliteration module.
         $form['instance']['settings']['filefield_paths'][$name]['options']['transliterate'] = array(
-          '#type' => 'checkbox',
-          '#title' => t('Transliterate') . '.',
-          '#default_value' => isset($settings[$name]['options']['transliterate']) && module_exists('transliteration')
-            ? $settings[$name]['options']['transliterate']
-            : 0,
-          '#description' => t('Transliterate @title', array('@title' => $field['title'])) . '.',
-          '#disabled' => !module_exists('transliteration'),
+          '#type'          => 'checkbox',
+          '#title'         => t('Transliterate'),
+          '#default_value' => isset($settings[$name]['options']['transliterate']) && module_exists('transliteration') ? $settings[$name]['options']['transliterate'] : 0,
+          '#description'   => t('Provides one-way string transliteration (romanization) and cleans the %title during upload by replacing unwanted characters.', array('%title' => t($field['title']))),
+          '#disabled'      => !module_exists('transliteration'),
         );
 
         // Replacement patterns for field.
-        $form['instance']['settings']['filefield_paths']['token_tree'] = array(
-          '#type' => 'fieldset',
-          '#title' => t('Replacement patterns'),
-          '#collapsible' => TRUE,
-          '#collapsed' => TRUE,
-          '#description' => theme('token_tree', array('token_types' => array('file', $entity_info['token type']))),
-          '#weight' => 10,
+        if (module_exists('token')) {
+          $form['instance']['settings']['filefield_paths']['token_tree'] = array(
+            '#theme'       => 'token_tree',
+            '#token_types' => array('file', $entity_info['token type']),
+            '#dialog'      => TRUE,
+            '#weight'      => 10,
+          );
+        }
+
+        // Redirect
+        $form['instance']['settings']['filefield_paths']['redirect'] = array(
+          '#type'          => 'checkbox',
+          '#title'         => t('Create Redirect'),
+          '#description'   => t('Create a redirect to the new location when a previously uploaded file is moved.'),
+          '#default_value' => isset($settings['redirect']) ? $settings['redirect'] : FALSE,
+          '#weight'        => 11,
         );
+        if (!module_exists('redirect')) {
+          $form['instance']['settings']['filefield_paths']['redirect']['#disabled'] = TRUE;
+          $form['instance']['settings']['filefield_paths']['redirect']['#description'] .= '<br />' . t('Requires the <a href="https://drupal.org/project/redirect" target="_blank">Redirect</a> module.');
+        }
 
         // Retroactive updates.
         $form['instance']['settings']['filefield_paths']['retroactive_update'] = array(
-          '#type' => 'checkbox',
-          '#title' => t('Retroactive update'),
-          '#description' => t('Move and rename previously uploaded files') . '.' .
-            '<br /> <strong style="color: #FF0000;">' . t('Warning') . ':</strong> ' .
-            t('This feature should only be used on developmental servers or with extreme caution') . '.',
-          '#weight' => 11,
+          '#type'        => 'checkbox',
+          '#title'       => t('Retroactive update'),
+          '#description' => t('Move and rename previously uploaded files.') . '<div>' . t('<strong class="warning">Warning:</strong> This feature should only be used on developmental servers or with extreme caution.') . '</div>',
+          '#weight'      => 12,
         );
 
         // Active updating.
         $form['instance']['settings']['filefield_paths']['active_updating'] = array(
-          '#type' => 'checkbox',
-          '#title' => t('Active updating'),
+          '#type'          => 'checkbox',
+          '#title'         => t('Active updating'),
           '#default_value' => isset($settings['active_updating']) ? $settings['active_updating'] : FALSE,
-          '#description' => t('Actively move and rename previously uploaded files as required') . '.' .
-            '<br /> <strong style="color: #FF0000;">' . t('Warning') . ':</strong> ' .
-            t('This feature should only be used on developmental servers or with extreme caution') . '.',
-          '#weight' => 12
+          '#description'   => t('Actively move and rename previously uploaded files as required.') . '<div>' . t('<strong class="warning">Warning:</strong> This feature should only be used on developmental servers or with extreme caution.') . '</div>',
+          '#weight'        => 13
         );
       }
     }
@@ -132,13 +191,37 @@ function filefield_paths_form_alter(&$form, $form_state, $form_id) {
 }
 
 /**
+ * Recursively set temporary upload location of all File (Field) Paths enabled
+ * managed file fields.
+ *
+ * @param $element
+ */
+function filefield_paths_temporary_upload_location(&$element) {
+  if (isset($element['#type']) && $element['#type'] == 'managed_file' && isset($element['#entity_type']) && isset($element['#field_name']) && isset($element['#bundle'])) {
+    $instance = field_info_instance($element['#entity_type'], $element['#field_name'], $element['#bundle']);
+    if (isset($instance['settings']['filefield_paths_enabled']) && $instance['settings']['filefield_paths_enabled']) {
+      $element['#upload_location'] = variable_get('filefield_paths_temp_location', 'public://filefield_paths');
+    }
+    return;
+  }
+  foreach (element_children($element) as $child) {
+    filefield_paths_temporary_upload_location($element[$child]);
+  }
+}
+
+
+/**
  * Submit callback for File (Field) Paths settings form.
+ *
+ * @param $form
+ * @param $form_state
  */
 function filefield_paths_form_submit($form, &$form_state) {
   // Retroactive updates.
-  if ($form_state['values']['instance']['settings']['filefield_paths']['retroactive_update']) {
-    filefield_paths_batch_update($form_state['values']['instance']);
-    batch_process($form_state['redirect']);
+  if ($form_state['values']['instance']['settings']['filefield_paths_enabled'] && $form_state['values']['instance']['settings']['filefield_paths']['retroactive_update']) {
+    if (filefield_paths_batch_update($form_state['values']['instance'])) {
+      batch_process($form_state['redirect']);
+    }
   }
 }
 
@@ -146,40 +229,58 @@ function filefield_paths_form_submit($form, &$form_state) {
  * Set batch process to update File (Field) Paths.
  *
  * @param $instance
+ *
+ * @return bool
  */
 function filefield_paths_batch_update($instance) {
-  $query = new EntityFieldQuery();
+  $query  = new EntityFieldQuery();
   $result = $query->entityCondition('entity_type', $instance['entity_type'])
     ->entityCondition('bundle', array($instance['bundle']))
     ->fieldCondition($instance['field_name'])
+    ->addTag('DANGEROUS_ACCESS_CHECK_OPT_OUT')
     ->execute();
-  $objects = array_keys($result[$instance['entity_type']]);
+
+  // If there are no results, do not set a batch as there is nothing to process.
+  if (empty($result[$instance['entity_type']])) {
+    return FALSE;
+  }
+
+  $objects  = array_keys($result[$instance['entity_type']]);
+  $instance = field_info_instance($instance['entity_type'], $instance['field_name'], $instance['bundle']);
 
   // Create batch.
   $batch = array(
-    'title' => t('Updating File (Field) Paths'),
+    'title'      => t('Updating File (Field) Paths'),
     'operations' => array(
       array('_filefield_paths_batch_update_process', array($objects, $instance))
     ),
   );
   batch_set($batch);
+
+  return TRUE;
 }
 
 /**
+ * Batch callback for File (Field) Paths retroactive updates.
+ *
+ * @param $objects
+ * @param $instance
+ * @param $context
  *
+ * @throws FieldException
  */
 function _filefield_paths_batch_update_process($objects, $instance, &$context) {
   if (!isset($context['sandbox']['progress'])) {
     $context['sandbox']['progress'] = 0;
-    $context['sandbox']['max'] = count($objects);
-    $context['sandbox']['objects'] = $objects;
+    $context['sandbox']['max']      = count($objects);
+    $context['sandbox']['objects']  = $objects;
   }
 
   // Process nodes by groups of 5.
   $count = min(5, count($context['sandbox']['objects']));
   for ($i = 1; $i <= $count; $i++) {
     // For each oid, load the object, update the files and save it.
-    $oid = array_shift($context['sandbox']['objects']);
+    $oid    = array_shift($context['sandbox']['objects']);
     $entity = current(entity_load($instance['entity_type'], array($oid)));
 
     // Enable active updating if it isn't already enabled.
@@ -189,8 +290,8 @@ function _filefield_paths_batch_update_process($objects, $instance, &$context) {
       field_update_instance($instance);
     }
 
-    // Invoke File (Field) Paths implementation of hook_entity_update().
-    filefield_paths_entity_update($entity, $instance['entity_type']);
+    // Invoke field_attach_update().
+    field_attach_update($instance['entity_type'], $entity);
 
     // Restore active updating to it's previous state if necessary.
     if (!$active_updating) {
@@ -210,93 +311,161 @@ function _filefield_paths_batch_update_process($objects, $instance, &$context) {
 }
 
 /**
- * Implements hook_entity_insert().
+ * Implements hook_field_storage_pre_insert().
+ *
+ * @param $entity_type
+ * @param $entity
  */
-function filefield_paths_entity_insert($entity, $type) {
-  filefield_paths_entity_update($entity, $type);
+function filefield_paths_field_storage_pre_insert($entity_type, $entity) {
+  filefield_paths_field_storage_pre_update($entity_type, $entity);
 }
 
 /**
- * Implements hook_entity_update().
+ * Implements hook_field_storage_pre_update().
+ *
+ * @param $entity_type
+ * @param $entity
  */
-function filefield_paths_entity_update($entity, $type) {
+function filefield_paths_field_storage_pre_update($entity_type, $entity) {
   $field_types = _filefield_paths_get_field_types();
-  $entity_info = entity_get_info($type);
-  $bundle_name = !empty($entity_info['entity keys']['bundle']) ? $entity->{$entity_info['entity keys']['bundle']} : $type;
+  $entity_info = entity_get_info($entity_type);
+  list(, , $bundle) = entity_extract_ids($entity_type, $entity);
   if ($entity_info['fieldable']) {
-    foreach (field_info_fields($type, $bundle_name) as $field) {
+    foreach (field_info_fields() as $field) {
       if (in_array($field['type'], array_keys($field_types))) {
-        $files = array();
-        $instance = field_info_instance($type, $field['field_name'], $bundle_name);
-        if (isset($entity->{$field['field_name']})) {
+        $files    = array();
+        $instance = field_info_instance($entity_type, $field['field_name'], $bundle);
+        $enabled  = (isset($instance['settings']['filefield_paths_enabled']) && $instance['settings']['filefield_paths_enabled']) || !isset($instance['settings']['filefield_paths_enabled']);
+        if ($enabled && isset($entity->{$field['field_name']}) && is_array($entity->{$field['field_name']})) {
           foreach ($entity->{$field['field_name']} as $langcode => &$deltas) {
             foreach ($deltas as $delta => &$file) {
               // Prepare file.
               if (function_exists($function = "{$field['module']}_field_load")) {
                 $items = array(array(&$file));
-                $function($type, array($entity), $field, array($instance), $langcode, $items, FIELD_LOAD_CURRENT);
+                $function($entity_type, array($entity), $field, array($instance), $langcode, $items, FIELD_LOAD_CURRENT);
               }
               $files[] = &$file;
             }
-          }
-          // Invoke hook_filefield_paths_process_file().
-          foreach (module_implements('filefield_paths_process_file') as $module) {
-            if (function_exists($function = "{$module}_filefield_paths_process_file")) {
-              $function($type, $entity, $field, $instance, $langcode, $files);
+            // Invoke hook_filefield_paths_process_file().
+            foreach (module_implements('filefield_paths_process_file') as $module) {
+              if (function_exists($function = "{$module}_filefield_paths_process_file")) {
+                $function($entity_type, $entity, $field, $instance, $langcode, $files);
+              }
             }
           }
         }
       }
     }
-
-    if (isset($entity->revision)) {
-      // Remember revision flag.
-      $revision = $entity->revision;
-      // Remove revision flag as long as fields already processed it, and no need
-      // to create new revision for moved files.
-      $entity->revision = FALSE;
-    }
-    // Save any changes back to the database.
-    field_attach_update($type, $entity);
-    if (isset($entity->revision)) {
-      // Restore revision flag so that other modules can process it if needed.
-      $entity->revision = $revision;
-    }
   }
 }
 
 /**
  * Implements hook_file_presave().
+ *
+ * @param $file
  */
 function filefield_paths_file_presave($file) {
   // Store original filename in the database.
-  if (empty($file->origname)) {
+  if (empty($file->origname) && isset($file->filename)) {
     $file->origname = $file->filename;
   }
 }
 
 /**
+ * Creates a redirect for a moved File field.
+ *
+ * @param $source
+ * @param $path
+ *
+ * @throws Exception
+ */
+function _filefield_paths_create_redirect($source, $path) {
+  global $base_path;
+  watchdog('filefield_paths', 'Creating redirect from @source to @path', array(
+    '@source' => $source,
+    '@path'   => $path
+  ), WATCHDOG_DEBUG);
+
+  $redirect = new stdClass();
+  redirect_object_prepare($redirect);
+
+  $parsed_source = parse_url(file_create_url($source), PHP_URL_PATH);
+  $parsed_path   = parse_url(file_create_url($path), PHP_URL_PATH);
+
+  $redirect->source   = drupal_substr(urldecode($parsed_source), drupal_strlen($base_path));
+  $redirect->redirect = drupal_substr(urldecode($parsed_path), drupal_strlen($base_path));
+
+  // Check if the redirect exists before saving.
+  $hash = redirect_hash($redirect);
+  if (!redirect_load_by_hash($hash)) {
+    redirect_save($redirect);
+  }
+}
+
+/**
  * Run regular expression over all available text-based fields.
+ *
+ * @param $old
+ * @param $new
+ * @param $entity
  */
 function _filefield_paths_replace_path($old, $new, $entity) {
-  // Build regular expression.
   $info = parse_url($old);
-  $info['path'] = !empty($info['path']) ? $info['path'] : '';
-  $absolute = str_replace("{$info['host']}{$info['path']}", '', file_create_url($old));
-  $relative = parse_url($absolute, PHP_URL_PATH);
-  $regex = str_replace('/', '\/', "({$absolute}|{$relative}|{$info['scheme']}://)(styles/.*?/{$info['scheme']}/|)({$info['host']}{$info['path']})");
+  if (isset($info['path'])) {
+    $info['host'] .= $info['path'];
+  }
+
+  // Generate all path prefix variations.
+  $prefixes = _filefield_paths_replace_path_get_prefixes($info['scheme'], TRUE);
+  $prefixes = implode('|', $prefixes);
+
+  // Generate all image style path variations.
+  $styles['raw']       = "styles/REGEX/{$info['scheme']}/";
+  $styles['urlencode'] = urlencode($styles['raw']);
+  foreach ($styles as &$style) {
+    $style = str_replace(array('/', 'REGEX'), array('\/', '(.*?)'), $style);
+  }
+  $styles = implode('|', $styles);
 
-  // Build replacement.
-  $info = parse_url($new);
-  $info['path'] = !empty($info['path']) ? $info['path'] : '';
-  $replacement = "_filefield_paths_replace_path_uri_scheme('\\1', '{$old}', '{$new}') . '\\2{$info['host']}{$info['path']}'";
+  // General all path variations.
+  $paths['raw']                = preg_quote($info['host'], '/');
+  $paths['urlencode']          = preg_quote(urlencode($info['host']), '/');
+  $paths['drupal_encode_path'] = preg_quote(drupal_encode_path($info['host']), '/');
+
+  $paths = implode('|', $paths);
+
+  // Newer versions of the Image module add an 8 character token which is
+  // required if the image style hasn't been generated yet.
+  $itok = '';
+  if (defined('IMAGE_DERIVATIVE_TOKEN')) {
+    $itok = '((?:[\?|&](?:\S+?&)*|(?:%3F|%26)(?:\S+?%26)*)' . IMAGE_DERIVATIVE_TOKEN . '(?:=|%3D)(\S{8}))*';
+  }
+
+  // Build regular expression pattern.
+  $pattern = "/({$prefixes})({$styles})*({$paths}){$itok}/";
+
+  // Create an anonymous function for the replacement via preg_replace_callback.
+  $callback = function ($matches) use ($new, $old) {
+    return filefield_paths_replace_path_callback($matches, $new, $old);
+  };
+  if (!$callback) {
+    watchdog('filefield_paths', 'Unable to create an anonymous function to find references of %old and replace with %new.', array(
+      '%old' => $old,
+      '%new' => $new,
+    ));
+    return;
+  }
 
   $fields = field_info_fields();
   foreach ($fields as $name => $field) {
     if ($field['module'] == 'text' && isset($entity->{$field['field_name']}) && is_array($entity->{$field['field_name']})) {
       foreach ($entity->{$field['field_name']} as &$language) {
         foreach ($language as &$item) {
-          $item['value'] = preg_replace("/$regex/e", $replacement, $item['value']);
+          foreach (array('value', 'summary') as $column) {
+            if (isset($item[$column])) {
+              $item[$column] = preg_replace_callback($pattern, $callback, $item[$column]);
+            }
+          }
         }
       }
     }
@@ -304,49 +473,172 @@ function _filefield_paths_replace_path($old, $new, $entity) {
 }
 
 /**
- * Helper function for File (Field) Paths URI updater regular expression.
+ * Helper function; Returns all variations of the file path prefix.
  *
- * Determines what format the old URI prefix was and returns the new URI prefix
- * in the same format.
+ * @param            $scheme
+ * @param bool|FALSE $preg_quote
+ * @param bool|FALSE $reset
+ *
+ * @return mixed
  */
-function _filefield_paths_replace_path_uri_scheme($prefix, $old, $new) {
-  switch (TRUE) {
-    case $prefix == file_uri_scheme($old) . '://':
-      return file_uri_scheme($new) . '://';
+function _filefield_paths_replace_path_get_prefixes($scheme, $preg_quote = FALSE, $reset = FALSE) {
+  $prefixes =& drupal_static(__FUNCTION__, array());
+
+  // Force clean urls on.
+  $clean_url = $GLOBALS['conf']['clean_url'];
+  $GLOBALS['conf']['clean_url'] = TRUE;
+
+  $id = $scheme . '::' . (string) $preg_quote;
+  if (!isset($prefixes[$id]) || $reset) {
+    $prefixes[$id]['uri']      = "{$scheme}://";
+    $prefixes[$id]['absolute'] = file_create_url($prefixes[$id]['uri']);
+    $prefixes[$id]['relative'] = parse_url($prefixes[$id]['absolute'], PHP_URL_PATH);
+    $prefixes[$id]['unclean']  = '?q=' . drupal_substr($prefixes[$id]['relative'], drupal_strlen(base_path()));
+
+    foreach ($prefixes[$id] as $key => $prefix) {
+      $prefixes[$id]["{$key}-urlencode"]          = urlencode($prefix);
+      $prefixes[$id]["{$key}-drupal_encode_path"] = drupal_encode_path($prefix);
+    }
 
-    case $prefix == file_create_url(file_uri_scheme($old) . '://'):
-      return file_create_url(file_uri_scheme($new) . '://');
+    if ($preg_quote) {
+      foreach ($prefixes[$id] as $key => $prefix) {
+        $prefixes[$id][$key] = preg_quote($prefixes[$id][$key], '/');
+      }
+    }
+  }
+
+  // Restore clean url settings.
+  $GLOBALS['conf']['clean_url'] = $clean_url;
 
-    case $prefix == parse_url(file_create_url(file_uri_scheme($old) . '://'), PHP_URL_PATH):
-      return parse_url(file_create_url(file_uri_scheme($new) . '://'), PHP_URL_PATH);
+  return $prefixes[$id];
+}
+
+/**
+ * Callback for regex string replacement functionality.
+ *
+ * @param $matches
+ * @param $new
+ * @param $old
+ *
+ * @return string
+ */
+function filefield_paths_replace_path_callback($matches, $new, $old) {
+  $prefix = $matches[1];
+  $styles = $matches[2];
+  $query  = isset($matches[6]) ? $matches[6] : '';
+
+  // Get file path info for old file.
+  $old_info = parse_url($old);
+  if (isset($old_info['path'])) {
+    $old_info['host'] .= $old_info['path'];
   }
 
-  return $prefix;
+  // Determine the file path variation type/modifier.
+  $old_prefixes = _filefield_paths_replace_path_get_prefixes($old_info['scheme']);
+  $modifier     = NULL;
+  foreach ($old_prefixes as $key => $old_prefix) {
+    if ($prefix == $old_prefix) {
+      $parts    = explode('-', $key);
+      $modifier = isset($parts[1]) ? $parts[1] : NULL;
+      break;
+    }
+  }
+
+  // Get file path info for new file.
+  $new_info = parse_url($new);
+  if (isset($new_info['path'])) {
+    $new_info['host'] .= $new_info['path'];
+  }
+
+  // Replace prefix.
+  $prefixes = _filefield_paths_replace_path_get_prefixes($new_info['scheme']);
+  if (isset($key) && isset($prefixes[$key])) {
+    $prefix = $prefixes[$key];
+  }
+
+  // Replace styles directory.
+  if (!empty($styles)) {
+    $styles = str_replace($old_info['scheme'], $new_info['scheme'], $styles);
+
+    // Newer versions of the Image module add an 8 character token which is
+    // required if the image style hasn't been generated yet.
+    if (defined('IMAGE_DERIVATIVE_TOKEN') && isset($matches[7])) {
+      $image_style = !empty($matches[3]) ? $matches[3] : $matches[4];
+      // Only replace the token if the old one was valid.
+      if ($matches[7] == image_style_path_token($image_style, $old)) {
+        $query = substr_replace($query, image_style_path_token($image_style, $new), -strlen($matches[7]));
+      }
+    }
+  }
+
+  // Replace path.
+  $path = $new_info['host'];
+  if (!is_null($modifier) && function_exists($modifier)) {
+    $path = call_user_func($modifier, $path);
+  }
+
+  return $prefix . $styles . $path . $query;
 }
 
 /**
  * Process and cleanup strings.
+ *
+ * @param       $value
+ * @param       $data
+ * @param array $settings
+ *
+ * @return mixed|string
  */
 function filefield_paths_process_string($value, $data, $settings = array()) {
-  $transliterate = module_exists('transliteration') && isset($settings['transliterate']) && $settings['transliterate'];
-  $pathauto = module_exists('pathauto') && isset($settings['pathauto']) && $settings['pathauto'] == TRUE;
+  $transliterate  = module_exists('transliteration') && isset($settings['transliterate']) && $settings['transliterate'];
+  $pathauto       = module_exists('pathauto') && isset($settings['pathauto']) && $settings['pathauto'] == TRUE;
+  $remove_slashes = !empty($settings['slashes']);
+
   if ($pathauto == TRUE) {
     module_load_include('inc', 'pathauto');
   }
 
+  // If '/' is to be removed from tokens, token replacement need to happen after
+  // splitting the paths to subdirs, otherwise tokens containing '/' will be
+  // part of the final path.
+  if (!$remove_slashes) {
+    $value = token_replace($value, $data, array('clear' => TRUE));
+  }
   $paths = explode('/', $value);
-  foreach ($paths as &$path) {
-
-    // Process string tokens.
-    $path = token_replace($path, $data, array('clear' => TRUE));
 
-    // Cleanup with pathauto.
+  foreach ($paths as $i => &$path) {
+    if ($remove_slashes) {
+      $path = token_replace($path, $data, array('clear' => TRUE));
+    }
     if ($pathauto == TRUE) {
-      $path_parts = explode('.', $path);
-      foreach ($path_parts as &$path_part) {
-        $path_part = pathauto_cleanstring($path_part);
+      if ('file_name' == $settings['context'] && count($paths) == $i + 1) {
+        $pathinfo             = pathinfo($path);
+        $basename             = drupal_basename($path);
+        $extension            = preg_match('/\.[^.]+$/', $basename, $matches) ? $matches[0] : NULL;
+        $pathinfo['filename'] = !is_null($extension) ? drupal_substr($basename, 0, drupal_strlen($basename) - drupal_strlen($extension)) : $basename;
+
+        if ($remove_slashes) {
+          $path = '';
+          if (!empty($pathinfo['dirname']) && $pathinfo['dirname'] !== '.') {
+            $path .= $pathinfo['dirname'] . '/';
+          }
+          $path .= $pathinfo['filename'];
+          $path = pathauto_cleanstring($path);
+          if (!empty($pathinfo['extension'])) {
+            $path .= '.' . pathauto_cleanstring($pathinfo['extension']);
+          }
+          $path = str_replace('/', '', $path);
+        }
+        else {
+          $path = str_replace($pathinfo['filename'], pathauto_cleanstring($pathinfo['filename']), $path);
+        }
+      }
+      else {
+        $path = pathauto_cleanstring($path);
       }
-      $path = implode('.', $path_parts);
+    }
+    elseif ($remove_slashes) {
+      $path = str_replace('/', '', $path);
     }
 
     // Transliterate string.
@@ -363,7 +655,11 @@ function filefield_paths_process_string($value, $data, $settings = array()) {
 }
 
 /**
+ * Provides a list of all available field types for use with File (Field) Paths.
+ *
+ * @param bool|FALSE $reset
  *
+ * @return array
  */
 function _filefield_paths_get_field_types($reset = FALSE) {
   $field_types = &drupal_static(__FUNCTION__);
@@ -372,7 +668,7 @@ function _filefield_paths_get_field_types($reset = FALSE) {
     $field_types = module_invoke_all('filefield_paths_field_type_info');
     $field_types = array_flip($field_types);
     foreach (array_keys($field_types) as $type) {
-      $info = field_info_field_types($type);
+      $info               = field_info_field_types($type);
       $field_types[$type] = array(
         'label' => $info['label']
       );
@@ -381,3 +677,4 @@ function _filefield_paths_get_field_types($reset = FALSE) {
 
   return $field_types;
 }
+

+ 72 - 0
sites/all/modules/filefield_paths/filefield_paths.tokens.inc

@@ -0,0 +1,72 @@
+<?php
+
+/**
+ * @file
+ * Tokens for the File (Field) Paths module.
+ */
+
+/**
+ * Implements hook_token_info().
+ *
+ * @return mixed
+ */
+function filefield_paths_token_info() {
+  $info['tokens']['file']['ffp-name-only']          = array(
+    'name'        => t("File name"),
+    'description' => t("File name without extension."),
+  );
+  $info['tokens']['file']['ffp-name-only-original'] = array(
+    'name'        => t("File name - original"),
+    'description' => t("File name without extension - original."),
+  );
+  $info['tokens']['file']['ffp-extension-original'] = array(
+    'name'        => t("File extension - original"),
+    'description' => t("File extension - original."),
+  );
+
+  return $info;
+}
+
+/**
+ * Implements hook_tokens().
+ *
+ * @param       $type
+ * @param       $tokens
+ * @param array $data
+ *
+ * @return array
+ */
+function filefield_paths_tokens($type, $tokens, array $data = array()) {
+  $url_options = array('absolute' => TRUE);
+  if (isset($language)) {
+    $url_options['language'] = $language;
+  }
+
+  $replacements = array();
+
+  if ($type == 'file' && !empty($data['file'])) {
+    $file = $data['file'];
+
+    foreach ($tokens as $name => $original) {
+      switch ($name) {
+        case 'ffp-name-only':
+          $basename                = drupal_basename($file->filename);
+          $extension               = preg_match('/\.[^.]+$/', $basename, $matches) ? $matches[0] : NULL;
+          $replacements[$original] = !is_null($extension) ? drupal_substr($basename, 0, drupal_strlen($basename) - drupal_strlen($extension)) : $basename;
+          break;
+
+        case 'ffp-name-only-original':
+          $basename                = drupal_basename($file->origname);
+          $extension               = preg_match('/\.[^.]+$/', $basename, $matches) ? $matches[0] : NULL;
+          $replacements[$original] = !is_null($extension) ? drupal_substr($basename, 0, drupal_strlen($basename) - drupal_strlen($extension)) : $basename;
+          break;
+
+        case 'ffp-extension-original':
+          $replacements[$original] = preg_match('/[^.]+$/', drupal_basename($file->origname), $matches) ? $matches[0] : NULL;
+          break;
+      }
+    }
+  }
+
+  return $replacements;
+}

+ 57 - 0
sites/all/modules/filefield_paths/filefield_paths.variable.inc

@@ -0,0 +1,57 @@
+<?php
+
+/**
+ * @file
+ * Contains Variable functions for the File (Field) Paths module.
+ */
+
+/**
+ * Implements hook_variable_info().
+ *
+ * @return mixed
+ */
+function filefield_paths_variable_info() {
+  $variables['filefield_paths_temp_location'] = array(
+    'title'             => t('Temporary file location'),
+    'type'              => 'string',
+    'default'           => 'public://filefield_paths',
+    'description'       => t('The location that unprocessed files will be uploaded priot to being processed by File (Field) Paths.<br />It is recommended that you use the temporary file system (temporary://) if your server configuration allows for that.'),
+    'validate callback' => 'filefield_paths_variable_temp_location_validate',
+    'group'             => 'filefield_paths',
+  );
+
+  return $variables;
+}
+
+/**
+ * Validate callback for 'Temporary file location' variable.
+ *
+ * @param $element
+ */
+function filefield_paths_variable_temp_location_validate($element) {
+  // Add FAPI element keys for standard validation callback.
+  $element['#parents'] = array('filefield_paths_temp_location');
+  $element['#value'] = $element['value'];
+
+  // Pass element through standard validation callback.
+  module_load_include('admin.inc', 'filefield_paths');
+  filefield_paths_settings_form_temp_location_validate($element);
+}
+
+/**
+ * Implements hook_variable_group_info().
+ *
+ * @return mixed
+ */
+function filefield_paths_variable_group_info() {
+  $groups['filefield_paths'] = array(
+    'title'       => t('File (Field) Paths'),
+    'description' => t('File (Field) Paths settings.'),
+    'access'      => 'administer site configuration',
+    'path'        => array(
+      'admin/config/media/file-system/filefield-paths'
+    ),
+  );
+
+  return $groups;
+}

+ 18 - 2
sites/all/modules/filefield_paths/modules/features.inc

@@ -1,16 +1,21 @@
 <?php
+
 /**
  * @file
  * Features module integration.
  */
 
 /**
- * Implements hook_features_pipe_field_alter().
+ * Implements hook_features_pipe_field_instance_alter().
  *
  * This determines whether exported fields contain File (Field) Paths settings
  * and if so adds File (Field) Paths as a dependency.
+ *
+ * @param $pipe
+ * @param $data
+ * @param $export
  */
-function filefield_paths_features_pipe_field_alter(&$pipe, $data, &$export) {
+function filefield_paths_features_pipe_field_instance_alter(&$pipe, $data, &$export) {
   foreach ($data as $field_identifier) {
     list($entity_type, $bundle_name, $field_name) = explode('-', $field_identifier);
     $instance = field_info_instance($entity_type, $field_name, $bundle_name);
@@ -19,3 +24,14 @@ function filefield_paths_features_pipe_field_alter(&$pipe, $data, &$export) {
     }
   }
 }
+
+/**
+ * Implements hook_features_pipe_field_alter().
+ *
+ * @param $pipe
+ * @param $data
+ * @param $export
+ */
+function filefield_paths_features_pipe_field_alter(&$pipe, $data, &$export) {
+  filefield_paths_features_pipe_field_instance_alter($pipe, $data, $export);
+}

+ 4 - 1
sites/all/modules/filefield_paths/modules/file.inc

@@ -1,11 +1,14 @@
 <?php
+
 /**
  * @file
  * File module integration.
  */
 
 /**
- * Implements hook_filefield_paths_filed_type_info() on behalf of file.module.
+ * Implements hook_filefield_paths_field_type_info() on behalf of file.module.
+ *
+ * @return array
  */
 function file_filefield_paths_field_type_info() {
   return array('file');

+ 91 - 42
sites/all/modules/filefield_paths/modules/filefield_paths.inc

@@ -1,4 +1,5 @@
 <?php
+
 /**
  * @file
  * File (Field) Paths module integration.
@@ -6,31 +7,35 @@
 
 /**
  * Implements hook_filefield_paths_field_settings().
+ *
+ * @param $field
+ * @param $instance
+ *
+ * @return array
  */
-function filefield_paths_filefield_paths_field_settings() {
+function filefield_paths_filefield_paths_field_settings($field, $instance) {
   return array(
     'file_path' => array(
       'title' => 'File path',
-      'sql' => 'filepath',
-
-      'form' => array(
+      'form'  => array(
         'value' => array(
-          '#type' => 'textfield',
-          '#title' => t('File path'),
-          '#maxlength' => 512,
-          '#size' => 128,
+          '#type'             => 'textfield',
+          '#title'            => t('File path'),
+          '#maxlength'        => 512,
+          '#size'             => 128,
+          '#element_validate' => array('_file_generic_settings_file_directory_validate'),
+          '#default_value'    => $instance['settings']['file_directory'],
         ),
       ),
     ),
-
     'file_name' => array(
       'title' => 'File name',
-      'sql' => 'filename',
-
-      'form' => array(
+      'form'  => array(
         'value' => array(
-          '#type' => 'textfield',
-          '#title' => t('File name'),
+          '#type'          => 'textfield',
+          '#title'         => t('File name'),
+          '#maxlength'     => 512,
+          '#size'          => 128,
           '#default_value' => '[file:ffp-name-only-original].[file:ffp-extension-original]',
         ),
       ),
@@ -40,42 +45,86 @@ function filefield_paths_filefield_paths_field_settings() {
 
 /**
  * Implements hook_filefield_paths_process_file().
+ *
+ * @param $type
+ * @param $entity
+ * @param $field
+ * @param $instance
+ * @param $langcode
+ * @param $items
  */
 function filefield_paths_filefield_paths_process_file($type, $entity, $field, $instance, $langcode, &$items) {
-  $settings = $instance['settings']['filefield_paths'];
-  foreach ($items as &$file) {
-    if ($file['timestamp'] == REQUEST_TIME || $settings['active_updating']) {
-      $token_data = array(
-        'file' => (object) $file,
-        $type => $entity
-      );
+  if (isset($instance['settings']['filefield_paths'])) {
+    $settings = $instance['settings']['filefield_paths'];
+
+    // Check that the destination is writeable.
+    $wrappers = file_get_stream_wrappers(STREAM_WRAPPERS_WRITE);
+    foreach ($items as &$file) {
+      $source_scheme = file_uri_scheme($file['uri']);
+      $temporary_scheme = file_uri_scheme(variable_get('filefield_paths_temp_location', 'public://filefield_paths'));
+      $destination_scheme = $field['settings']['uri_scheme'];
+      if (in_array($source_scheme, array($temporary_scheme, $destination_scheme)) && !empty($wrappers[$destination_scheme])) {
+        // Process file if this is a new entity, 'Active updating' is set or
+        // file wasn't previously attached to the entity.
+        if (isset($entity->original) && empty($settings['active_updating']) && !empty($entity->original->{$field['field_name']}[$langcode])) {
+          foreach ($entity->original->{$field['field_name']}[$langcode] as $original_file) {
+            if ($original_file['fid'] == $file['fid']) {
+              continue(2);
+            }
+          }
+        }
+
+        $token_data = array(
+          'file' => (object) $file,
+          $type  => $entity
+        );
 
-      // Copy the original file for comparision purposes.
-      $old_file = $file;
+        // Copy the original file for comparison purposes.
+        $old_file = $file;
 
-      // Process filename.
-      $file['filename'] = !empty($settings['file_name']['value'])
-        ? filefield_paths_process_string($settings['file_name']['value'], $token_data, $settings['file_name']['options'])
-        : $file['filename'];
+        // Process filename.
+        $settings['file_name']['options']['context'] = 'file_name';
+        $file['filename']                            = !empty($settings['file_name']['value']) ? filefield_paths_process_string($settings['file_name']['value'], $token_data, $settings['file_name']['options']) : $file['filename'];
 
-      // Process filepath.
-      $file['uri'] = "{$field['settings']['uri_scheme']}://" . filefield_paths_process_string($settings['file_path']['value'] . "/{$file['filename']}", $token_data, $settings['file_path']['options']);
+        // Process filepath.
+        $settings['file_path']['options']['context'] = 'file_path';
+        $path                                        = filefield_paths_process_string($settings['file_path']['value'], $token_data, $settings['file_path']['options']);
+        $file['uri']                                 = file_stream_wrapper_uri_normalize("{$destination_scheme}://{$path}/{$file['filename']}");
+
+        // Ensure file uri is no more than 255 characters.
+        if (drupal_strlen($file['uri']) > 255) {
+          watchdog('filefield_paths', 'File path was truncated.', array(), WATCHDOG_INFO);
+          $pathinfo    = pathinfo($file['uri']);
+          $file['uri'] = drupal_substr($file['uri'], 0, 254 - drupal_strlen($pathinfo['extension'])) . ".{$pathinfo['extension']}";
+        }
 
-      // Finalize file if necessary.
-      if ($file !== $old_file) {
-        if (file_prepare_directory(drupal_dirname($file['uri']), FILE_CREATE_DIRECTORY) && file_move((object) $old_file, $file['uri'])) {
-          // Process regular expression.
-          _filefield_paths_replace_path($old_file['uri'], $file['uri'], $entity);
+        // Finalize file if necessary.
+        if ($file !== $old_file) {
+          $dirname = drupal_dirname($file['uri']);
+          if (file_prepare_directory($dirname, FILE_CREATE_DIRECTORY) && $new_file = file_move((object) $old_file, $file['uri'])) {
+            // Process regular expression.
+            _filefield_paths_replace_path($old_file['uri'], $file['uri'], $entity);
 
-          // Remove any old empty directories.
-          $scheme = file_uri_scheme($old_file['uri']);
-          $paths = explode('/', str_replace("{$scheme}://", '', drupal_dirname($old_file['uri'])));
-          while ($paths) {
-            if (@drupal_rmdir("{$scheme}://" . implode('/', $paths)) == TRUE) {
-              array_pop($paths);
-              continue;
+            // Create redirect from old location.
+            if (module_exists('redirect') && !empty($settings['redirect']) && $settings['active_updating']) {
+              _filefield_paths_create_redirect($old_file['uri'], $new_file->uri);
             }
-            break;
+
+            // Remove any old empty directories.
+            $paths  = explode('/', str_replace("{$source_scheme}://", '', drupal_dirname($old_file['uri'])));
+            while ($paths) {
+              if (@drupal_rmdir("{$source_scheme}://" . implode('/', $paths)) == TRUE) {
+                array_pop($paths);
+                continue;
+              }
+              break;
+            }
+          }
+          else {
+            watchdog('filefield_paths', 'The file %old could not be moved to the destination of %new. Ensure your permissions are set correctly.', array(
+              '%old' => $old_file['uri'],
+              '%new' => $file['uri'],
+            ));
           }
         }
       }

+ 17 - 0
sites/all/modules/filefield_paths/modules/image.inc

@@ -1,4 +1,5 @@
 <?php
+
 /**
  * @file
  * Image module integration.
@@ -6,7 +7,23 @@
 
 /**
  * Implements hook_filefield_paths_field_type_info() on behalf of image.module.
+ *
+ * @return array
  */
 function image_filefield_paths_field_type_info() {
   return array('image');
 }
+
+/**
+ * Implements hook_menu_alter().
+ *
+ * @param $items
+ */
+function filefield_paths_menu_alter(&$items) {
+  // Workaround for issue with 'temporary://' image styles not being generated
+  // correctly in Drupal core Image module.
+  // @see https://www.drupal.org/node/2560139
+  if (!isset($items['system/temporary/styles/%image_style']) && isset($items['system/files/styles/%image_style'])) {
+    $items['system/temporary/styles/%image_style'] = $items['system/files/styles/%image_style'];
+  }
+}

+ 0 - 63
sites/all/modules/filefield_paths/modules/token.inc

@@ -1,63 +0,0 @@
-<?php
-/**
- * @file
- * Token module integration.
- */
-
-/**
- * Implements hook_token_info().
- */
-function filefield_paths_token_info() {
-  $info['tokens']['file']['ffp-name-only'] = array(
-    'name' => t("File name"),
-    'description' => t("File name without extension."),
-  );
-  $info['tokens']['file']['ffp-name-only-original'] = array(
-    'name' => t("File name - original"),
-    'description' => t("File name without extension - original."),
-  );
-  $info['tokens']['file']['ffp-extension-original'] = array(
-    'name' => t("File extension - original"),
-    'description' => t("File extension - original."),
-  );
-
-  return $info;
-}
-
-/**
- * Implements hook_tokens().
- */
-function filefield_paths_tokens($type, $tokens, array $data = array(), array $options = array()) {
-  $url_options = array('absolute' => TRUE);
-  if (isset($language)) {
-    $url_options['language'] = $language;
-  }
-  $sanitize = !empty($options['sanitize']);
-
-  $replacements = array();
-
-  if ($type == 'file' && !empty($data['file'])) {
-    $file = $data['file'];
-
-    foreach ($tokens as $name => $original) {
-      switch ($name) {
-        case 'ffp-name-only':
-          $info = pathinfo($file->filename);
-          $replacements[$original] = $info['filename'];
-          break;
-
-        case 'ffp-name-only-original':
-          $info = pathinfo($file->origname);
-          $replacements[$original] = $info['filename'];
-          break;
-
-        case 'ffp-extension-original':
-          $info = pathinfo($file->origname);
-          $replacements[$original] = $info['extension'];
-          break;
-      }
-    }
-  }
-
-  return $replacements;
-}

+ 3 - 0
sites/all/modules/filefield_paths/modules/video.inc

@@ -1,4 +1,5 @@
 <?php
+
 /**
  * @file
  * Video module integration.
@@ -6,6 +7,8 @@
 
 /**
  * Implements hook_filefield_paths_field_type_info() on behalf of video.module.
+ *
+ * @return array
  */
 function video_filefield_paths_field_type_info() {
   return array('video');

+ 327 - 0
sites/all/modules/filefield_paths/tests/filefield_paths.general.test

@@ -0,0 +1,327 @@
+<?php
+
+/**
+ * @file
+ * Tests for the File (Field) Paths module.
+ */
+
+/**
+ * Class FileFieldPathsGeneralTestCase
+ */
+class FileFieldPathsGeneralTestCase extends FileFieldPathsTestCase {
+  /**
+   * @inheritdoc
+   */
+  public static function getInfo() {
+    return array(
+      'name'        => 'General functionality',
+      'description' => 'Test general functionality.',
+      'group'       => 'File (Field) Paths',
+    );
+  }
+
+  /**
+   * Test that the File (Field) Paths UI works as expected.
+   */
+  public function testAddField() {
+    // Create a File field.
+    $field_name        = drupal_strtolower($this->randomName());
+    $instance_settings = array('file_directory' => "fields/{$field_name}");
+    $this->createFileField($field_name, $this->content_type, array(), $instance_settings);
+
+    // Ensure File (Field) Paths settings are present.
+    $this->drupalGet("admin/structure/types/manage/{$this->content_type}/fields/{$field_name}");
+    $this->assertText('Enable File (Field) Paths?', t('File (Field) Path settings are present.'));
+
+    // Ensure that 'Enable File (Field) Paths?' is a direct sibling of
+    // 'File (Field) Path settings'.
+    $element = $this->xpath('//div[contains(@class, :class)]/following-sibling::*[1]/@id', array(':class' => 'form-item-instance-settings-filefield-paths-enabled'));
+    $this->assert(isset($element[0]) && 'edit-instance-settings-filefield-paths' == (string) $element[0], t('Enable checkbox is next to settings fieldset.'));
+
+    // Ensure that the File path used the File directory as it's default value.
+    $this->assertFieldByName('instance[settings][filefield_paths][file_path][value]', "fields/{$field_name}");
+  }
+
+  /**
+   * Test File (Field) Paths works as normal when no file uploaded.
+   */
+  public function testNoFile() {
+    // Create a File field.
+    $field_name                                                 = drupal_strtolower($this->randomName());
+    $instance_settings['filefield_paths']['file_path']['value'] = 'node/[node:nid]';
+    $instance_settings['filefield_paths']['file_name']['value'] = '[node:nid].[file:ffp-extension-original]';
+    $this->createFileField($field_name, $this->content_type, array(), $instance_settings);
+
+    // Create a node without a file attached.
+    $this->drupalCreateNode(array('type' => $this->content_type));
+  }
+
+  /**
+   * Test a basic file upload with File (Field) Paths.
+   */
+  public function testUploadFile() {
+    $langcode = LANGUAGE_NONE;
+
+    // Create a File field with 'node/[node:nid]' as the File path and
+    // '[node:nid].[file:ffp-extension-original]' as the File name.
+    $field_name                                                 = drupal_strtolower($this->randomName());
+    $instance_settings['filefield_paths']['file_path']['value'] = 'node/[node:nid]';
+    $instance_settings['filefield_paths']['file_name']['value'] = '[node:nid].[file:ffp-extension-original]';
+    $this->createFileField($field_name, $this->content_type, array(), $instance_settings);
+
+    $schemes = array('public', 'private');
+    foreach ($schemes as $scheme) {
+      // Set the field URI scheme.
+      $this->drupalPost("admin/structure/types/manage/{$this->content_type}/fields/{$field_name}", array('field[settings][uri_scheme]' => $scheme), t('Save settings'));
+
+      // Upload a file to a node.
+      $test_file = $this->getTestFile('text');
+      $this->drupalGet("node/add/{$this->content_type}");
+      $edit['title'] = $this->randomName();
+      $edit["files[{$field_name}_{$langcode}_0]"] = $test_file->uri;
+      $this->drupalPost(NULL, $edit, t('Upload'));
+
+      // Ensure that the file was put into the Temporary file location.
+      $temp_location = variable_get('filefield_paths_temp_location', 'public://filefield_paths');
+      $this->assertRaw(file_create_url("{$temp_location}/{$test_file->filename}"), t('File has been uploaded to the temporary file location.'));
+
+      // Save the node.
+      $this->drupalPost(NULL, array(), t('Save'));
+
+      // Get created Node ID.
+      $matches = array();
+      preg_match('/node\/([0-9]+)/', $this->getUrl(), $matches);
+      $nid = $matches[1];
+
+      // Ensure that the File path has been processed correctly.
+      $uri = file_create_url("{$scheme}://node/{$nid}/{$nid}.txt");
+      $this->assertRaw($uri, t('The File path has been processed correctly.'));
+
+      // Delete the node so we can change the URI scheme.
+      node_delete($nid);
+    }
+  }
+
+  /**
+   * Tests a multivalue file upload with File (Field) Paths.
+   */
+  public function testUploadFileMultivalue() {
+    $langcode = LANGUAGE_NONE;
+
+    // Create a multivalue File field with 'node/[node:nid]' as the File path
+    // and '[file:fid].txt' as the File name.
+    $field_name                                                 = drupal_strtolower($this->randomName());
+    $field_settings['cardinality']                              = FIELD_CARDINALITY_UNLIMITED;
+    $instance_settings['filefield_paths']['file_path']['value'] = 'node/[node:nid]';
+    $instance_settings['filefield_paths']['file_name']['value'] = '[file:fid].txt';
+    $this->createFileField($field_name, $this->content_type, $field_settings, $instance_settings);
+
+    // Create a node with three (3) test files.
+    $text_files = $this->drupalGetTestFiles('text');
+    $this->drupalGet("node/add/{$this->content_type}");
+    $this->drupalPost(NULL, array("files[{$field_name}_{$langcode}_0]" => drupal_realpath($text_files[0]->uri)), t('Upload'));
+    $this->drupalPost(NULL, array("files[{$field_name}_{$langcode}_1]" => drupal_realpath($text_files[1]->uri)), t('Upload'));
+    $edit = array(
+      'title'                              => $this->randomName(),
+      "files[{$field_name}_{$langcode}_2]" => drupal_realpath($text_files[1]->uri),
+    );
+    $this->drupalPost(NULL, $edit, t('Save'));
+
+    // Get created Node ID.
+    $matches = array();
+    preg_match('/node\/([0-9]+)/', $this->getUrl(), $matches);
+    $nid = $matches[1];
+
+    // Ensure that the File path has been processed correctly.
+    $this->assertRaw("{$this->public_files_directory}/node/{$nid}/1.txt", t('The first File path has been processed correctly.'));
+    $this->assertRaw("{$this->public_files_directory}/node/{$nid}/2.txt", t('The second File path has been processed correctly.'));
+    $this->assertRaw("{$this->public_files_directory}/node/{$nid}/3.txt", t('The third File path has been processed correctly.'));
+  }
+
+  /**
+   * Test File (Field) Paths with a very long path.
+   */
+  public function testLongPath() {
+    // Create a File field with 'node/[random:hash:sha256]' as the File path.
+    $field_name                                                 = drupal_strtolower($this->randomName());
+    $instance_settings['filefield_paths']['file_path']['value'] = 'node/[random:hash:sha512]/[random:hash:sha512]';
+    $this->createFileField($field_name, $this->content_type, array(), $instance_settings);
+
+    // Create a node with a test file.
+    $test_file = $this->getTestFile('text');
+    $nid       = $this->uploadNodeFile($test_file, $field_name, $this->content_type);
+
+    // Ensure file path is no more than 255 characters.
+    $node = node_load($nid, NULL, TRUE);
+    $this->assert(drupal_strlen($node->{$field_name}[LANGUAGE_NONE][0]['uri']) <= 255, t('File path is no more than 255 characters'));
+  }
+
+  /**
+   * Test File (Field) Paths on a programmatically added file.
+   */
+  public function testProgrammaticAttach() {
+    // Create a File field with 'node/[node:nid]' as the File path and
+    // '[node:nid].[file:ffp-extension-original]' as the File name.
+    $field_name                                                 = drupal_strtolower($this->randomName());
+    $instance_settings['filefield_paths']['file_path']['value'] = 'node/[node:nid]';
+    $instance_settings['filefield_paths']['file_name']['value'] = '[node:nid].[file:ffp-extension-original]';
+    $this->createFileField($field_name, $this->content_type, array(), $instance_settings);
+
+    // Create a node without an attached file.
+    $node = $this->drupalCreateNode(array('type' => $this->content_type));
+
+    // Create a file object.
+    $test_file = $this->getTestFile('text');
+
+    $file           = new stdClass();
+    $file->fid      = NULL;
+    $file->uri      = $test_file->uri;
+    $file->filename = basename($file->uri);
+    $file->filemime = file_get_mimetype($file->uri);
+    $file->uid      = $GLOBALS['user']->uid;
+    $file->status   = FILE_STATUS_PERMANENT;
+    $file->display  = TRUE;
+    file_save($file);
+
+    // Adjust timestamp to simulate real-world experience.
+    $file->timestamp = REQUEST_TIME - 60;
+
+    // Attach the file to the node.
+    $node->{$field_name}[LANGUAGE_NONE][0] = (array) $file;
+    node_save($node);
+
+    // Ensure that the File path has been processed correctly.
+    $node = node_load($node->nid, NULL, TRUE);
+    $this->assertEqual("public://node/{$node->nid}/{$node->nid}.txt", $node->{$field_name}[LANGUAGE_NONE][0]['uri'], t('The File path has been processed correctly.'));
+  }
+
+  /**
+   * Test File (Field) Paths slashes cleanup functionality.
+   */
+  public function testSlashes() {
+    $langcode = LANGUAGE_NONE;
+
+    // Create a File field with 'node/[node:title]' as the File path and
+    // '[node:title].[file:ffp-extension-original]' as the File name.
+    $field_name                                                 = drupal_strtolower($this->randomName());
+    $instance_settings['filefield_paths']['file_path']['value'] = 'node/[node:title]';
+    $instance_settings['filefield_paths']['file_name']['value'] = '[node:title].[file:ffp-extension-original]';
+    $this->createFileField($field_name, $this->content_type, array(), $instance_settings);
+
+    // Create a node with a test file.
+    $test_file = $this->getTestFile('text');
+
+    $title                                      = "{$this->randomName()}/{$this->randomName()}";
+    $edit['title']                              = $title;
+    $edit["body[{$langcode}][0][value]"]        = '';
+    $edit["files[{$field_name}_{$langcode}_0]"] = drupal_realpath($test_file->uri);
+    $this->drupalPost("node/add/{$this->content_type}", $edit, t('Save'));
+
+    // Get created Node ID.
+    $matches = array();
+    preg_match('/node\/([0-9]+)/', $this->getUrl(), $matches);
+    $nid = $matches[1];
+
+    // Ensure slashes are present in file path and name.
+    $node = node_load($nid);
+    $this->assertEqual("public://node/{$title}/{$title}.txt", $node->{$field_name}[$langcode][0]['uri']);
+
+    // Remove slashes.
+    $edit = array(
+      'instance[settings][filefield_paths][file_path][options][slashes]' => TRUE,
+      'instance[settings][filefield_paths][file_name][options][slashes]' => TRUE,
+      'instance[settings][filefield_paths][retroactive_update]'          => TRUE,
+    );
+    $this->drupalPost("admin/structure/types/manage/{$this->content_type}/fields/{$field_name}", $edit, t('Save settings'));
+
+    // Ensure slashes are not present in file path and name.
+    $node  = node_load($nid, NULL, TRUE);
+    $title = str_replace('/', '', $title);
+    $this->assertEqual("public://node/{$title}/{$title}.txt", $node->{$field_name}[$langcode][0]['uri']);
+  }
+
+  /**
+   * Test a file usage of a basic file upload with File (Field) Paths.
+   */
+  public function testFileUsage() {
+    // Create a File field with 'node/[node:nid]' as the File path.
+    $field_name                                                 = drupal_strtolower($this->randomName());
+    $instance_settings['filefield_paths']['file_path']['value'] = 'node/[node:nid]';
+    $this->createFileField($field_name, $this->content_type, array(), $instance_settings);
+
+    // Create a node with a test file.
+    $test_file = $this->getTestFile('text');
+    $nid       = $this->uploadNodeFile($test_file, $field_name, $this->content_type);
+
+    // Get file usage for uploaded file.
+    $node  = node_load($nid, NULL, TRUE);
+    $items = field_get_items('node', $node, $field_name);
+    $file  = file_load($items[0]['fid']);
+    $usage = file_usage_list($file);
+
+    // Ensure file usage count for new node is correct.
+    $this->assert(isset($usage['file']['node'][$nid]) && $usage['file']['node'][$nid] == 1, t('File usage count for new node is correct.'));
+
+    // Update node.
+    $this->drupalPost("node/{$nid}/edit", array(), t('Save'));
+    $usage = file_usage_list($file);
+
+    // Ensure file usage count for updated node is correct.
+    $this->assert(isset($usage['file']['node'][$nid]) && $usage['file']['node'][$nid] == 1, t('File usage count for updated node is correct.'));
+
+    // Update node with revision.
+    $this->drupalPost("node/{$nid}/edit", array('revision' => TRUE), t('Save'));
+    $usage = file_usage_list($file);
+
+    // Ensure file usage count for updated node with revision is correct.
+    $this->assert(isset($usage['file']['node'][$nid]) && $usage['file']['node'][$nid] == 2, t('File usage count for updated node with revision is correct.'));
+  }
+
+  /**
+   * Test File (Field) Paths works with read-only stream wrappers.
+   */
+  public function testReadOnly() {
+    // Create a File field.
+    $field_name        = drupal_strtolower($this->randomName());
+    $field_settings    = array('uri_scheme' => 'ffp');
+    $instance_settings = array('file_directory' => "fields/{$field_name}");
+    $this->createFileField($field_name, $this->content_type, $field_settings, $instance_settings);
+
+    // Get a test file.
+    $file = $this->getTestFile('image');
+
+    // Prepare the file for the test 'ffp://' read-only stream wrapper.
+    $file->uri = str_replace('public', 'ffp', $file->uri);
+    $uri       = file_stream_wrapper_uri_normalize($file->uri);
+
+    // Create a file object.
+    $file           = new stdClass();
+    $file->fid      = NULL;
+    $file->uri      = $uri;
+    $file->filename = basename($file->uri);
+    $file->filemime = file_get_mimetype($file->uri);
+    $file->uid      = $GLOBALS['user']->uid;
+    $file->status   = FILE_STATUS_PERMANENT;
+    $file->display  = TRUE;
+    file_save($file);
+
+    // Attach the file to a node.
+    $node                                = array();
+    $node['type']                        = $this->content_type;
+    $node[$field_name][LANGUAGE_NONE][0] = (array) $file;
+
+    $node = $this->drupalCreateNode($node);
+
+    // Ensure file has been attached to a node.
+    $this->assert(isset($node->{$field_name}[LANGUAGE_NONE][0]) && !empty($node->{$field_name}[LANGUAGE_NONE][0]), t('Read-only file is correctly attached to a node.'));
+
+    $edit                                                            = array();
+    $edit['instance[settings][filefield_paths][retroactive_update]'] = TRUE;
+    $edit['instance[settings][filefield_paths][file_path][value]']   = 'node/[node:nid]';
+    $this->drupalPost("admin/structure/types/manage/{$this->content_type}/fields/{$field_name}", $edit, t('Save settings'));
+
+    // Ensure file is still in original location.
+    $this->drupalGet("node/{$node->nid}");
+    $this->assertRaw("{$this->public_files_directory}/{$file->filename}", t('Read-only file not affected by Retroactive updates.'));
+  }
+}

+ 90 - 0
sites/all/modules/filefield_paths/tests/filefield_paths.test

@@ -0,0 +1,90 @@
+<?php
+
+/**
+ * @file
+ * Tests for the File (Field) Paths module.
+ */
+
+/**
+ * Class FileFieldPathsTestCase
+ */
+class FileFieldPathsTestCase extends FileFieldTestCase {
+  var $content_type = NULL;
+  var $public_files_directory = NULL;
+
+  /**
+   * @inheritdoc
+   */
+  function setUp() {
+    // Setup required modules.
+    $modules = func_get_args();
+    if (isset($modules[0]) && is_array($modules[0])) {
+      $modules = $modules[0];
+    }
+    $modules[] = 'filefield_paths_test';
+    $modules[] = 'image';
+    $modules[] = 'token';
+    parent::setUp($modules);
+
+    // Include all optional dependency files.
+    $dirname  = dirname(__FILE__) . "/../modules";
+    $includes = file_scan_directory($dirname, '/.inc$/');
+    foreach (array_keys($includes) as $file) {
+      require_once $file;
+    }
+
+    // Create a content type.
+    $content_type       = $this->drupalCreateContentType();
+    $this->content_type = $content_type->name;
+  }
+
+  /**
+   * @inheritdoc
+   */
+  function createFileField($name, $type_name, $field_settings = array(), $instance_settings = array(), $widget_settings = array()) {
+    parent::createFileField($name, $type_name, $field_settings, $instance_settings, $widget_settings);
+    $this->drupalPost("admin/structure/types/manage/{$this->content_type}/fields/{$name}", array(), t('Save settings'));
+  }
+
+  /**
+   * Creates a new image field.
+   *
+   * @param $name
+   *   The name of the new field (all lowercase), exclude the "field_" prefix.
+   * @param $type_name
+   *   The node type that this field will be added to.
+   * @param $field_settings
+   *   A list of field settings that will be added to the defaults.
+   * @param $instance_settings
+   *   A list of instance settings that will be added to the instance defaults.
+   * @param $widget_settings
+   *   A list of widget settings that will be added to the widget defaults.
+   */
+  function createImageField($name, $type_name, $field_settings = array(), $instance_settings = array(), $widget_settings = array()) {
+    $field             = array(
+      'field_name'  => $name,
+      'type'        => 'image',
+      'settings'    => array(),
+      'cardinality' => !empty($field_settings['cardinality']) ? $field_settings['cardinality'] : 1,
+    );
+    $field['settings'] = array_merge($field['settings'], $field_settings);
+    field_create_field($field);
+
+    $instance                       = array(
+      'field_name'  => $name,
+      'label'       => $name,
+      'entity_type' => 'node',
+      'bundle'      => $type_name,
+      'required'    => !empty($instance_settings['required']),
+      'settings'    => array(),
+      'widget'      => array(
+        'type'     => 'image_image',
+        'settings' => array(),
+      ),
+    );
+    $instance['settings']           = array_merge($instance['settings'], $instance_settings);
+    $instance['widget']['settings'] = array_merge($instance['widget']['settings'], $widget_settings);
+    field_create_instance($instance);
+    $this->drupalPost("admin/structure/types/manage/{$this->content_type}/fields/{$name}", array(), t('Save settings'));
+  }
+}

+ 104 - 0
sites/all/modules/filefield_paths/tests/filefield_paths.text_replace.test

@@ -0,0 +1,104 @@
+<?php
+
+/**
+ * @file
+ * Tests for the File (Field) Paths module.
+ */
+
+/**
+ * Class FileFieldPathsTextReplaceTestCase
+ */
+class FileFieldPathsTextReplaceTestCase extends FileFieldPathsTestCase {
+  /**
+   * @inheritdoc
+   */
+  public static function getInfo() {
+    return array(
+      'name'        => 'Text replace functionality',
+      'description' => 'Tests text replace functionality.',
+      'group'       => 'File (Field) Paths',
+    );
+  }
+
+  /**
+   * Generates all variations of the URI for text replacement.
+   *
+   * @param        $uri
+   * @param string $type
+   *
+   * @return mixed
+   */
+  protected function getPathVariations($uri, $type = 'image') {
+    // Force clean urls on.
+    $GLOBALS['conf']['clean_url'] = TRUE;
+
+    $variations['uri']      = $uri;
+    $variations['absolute'] = urldecode(file_create_url($uri));
+    $variations['relative'] = parse_url($variations['absolute'], PHP_URL_PATH);
+
+    if ($type == 'image') {
+      $variations['image_style']          = urldecode(image_style_url('thumbnail', $uri));
+      $variations['image_style_relative'] = parse_url($variations['image_style'], PHP_URL_PATH) . '?' . parse_url($variations['image_style'], PHP_URL_QUERY);
+    }
+
+    foreach ($variations as $key => $value) {
+      $variations["{$key}_urlencode"]          = urlencode($value);
+      $variations["{$key}_drupal_encode_path"] = drupal_encode_path($value);
+    }
+
+    return $variations;
+  }
+
+  /**
+   * Test text replace with multiple file uploads.
+   */
+  public function testTextReplace() {
+    $langcode = LANGUAGE_NONE;
+
+    // Create a File field with 'node/[node:nid]' as the File path and
+    // '[node:nid].png’ as the File name,
+    $field_name                                                 = drupal_strtolower($this->randomName());
+    $instance_settings['filefield_paths']['file_path']['value'] = 'node/[node:nid]';
+    $instance_settings['filefield_paths']['file_name']['value'] = '[node:nid].png';
+    $this->createImageField($field_name, $this->content_type, array(), $instance_settings);
+
+    // Prepare test files.
+    $test_files['basic_image']   = $this->getTestFile('image');
+    $test_files['complex_image'] = $this->getTestFile('image');
+    file_unmanaged_copy($test_files['complex_image']->uri, 'public://test image.png');
+    $files                       = file_scan_directory('public://', '/test image\.png/');
+    $test_files['complex_image'] = current($files);
+
+    // Iterate over each test file.
+    foreach ($test_files as $type => $test_file) {
+      // Get the available file paths for the test file.
+      $uri          = str_replace('public://', variable_get('filefield_paths_temp_location', 'public://filefield_paths') . '/', $test_file->uri);
+      $source_paths = $this->getPathVariations($uri);
+
+      // Upload a file and reference the original path(s) to the file in the body
+      // field.
+      $edit['title']                              = $this->randomName();
+      $edit["body[{$langcode}][0][value]"]        = '';
+      $edit["files[{$field_name}_{$langcode}_0]"] = drupal_realpath($test_file->uri);
+      foreach ($source_paths as $key => $value) {
+        $edit["body[{$langcode}][0][value]"] .= "{$key}: {$value}\n";
+      }
+      $this->drupalPost("node/add/{$this->content_type}", $edit, t('Save'));
+
+      // Get created Node ID.
+      $matches = array();
+      preg_match('/node\/([0-9]+)/', $this->getUrl(), $matches);
+      $nid = $matches[1];
+
+      // Ensure body field has updated file path.
+      $node              = node_load($nid);
+      $destination_paths = $this->getPathVariations($node->{$field_name}[$langcode][0]['uri']);
+      foreach ($destination_paths as $key => $value) {
+        $this->assert($source_paths[$key] !== $destination_paths[$key] && strpos($node->body[$langcode][0]['value'], "{$key}: {$value}") !== FALSE, t('@type %key file path replaced successfully.', array(
+          '@type' => str_replace('_', ' ', drupal_ucfirst($type)),
+          '%key'  => $key
+        )));
+      }
+    }
+  }
+}

+ 103 - 0
sites/all/modules/filefield_paths/tests/filefield_paths.tokens.test