Browse Source

updated mailgun, mailsystem, honeypot, googleanalitycs, features, content_taxonomy

Bachir Soussi Chiadmi 5 years ago
parent
commit
e08a2639c6
54 changed files with 1860 additions and 391 deletions
  1. 3 3
      sites/all/modules/contrib/admin/features/features.admin.inc
  2. 1 1
      sites/all/modules/contrib/admin/features/features.drush.inc
  3. 13 12
      sites/all/modules/contrib/admin/features/features.export.inc
  4. 3 4
      sites/all/modules/contrib/admin/features/features.info
  5. 1 0
      sites/all/modules/contrib/admin/features/features.module
  6. 4 4
      sites/all/modules/contrib/admin/features/includes/features.field.inc
  7. 8 2
      sites/all/modules/contrib/admin/features/includes/features.image.inc
  8. 8 4
      sites/all/modules/contrib/admin/features/includes/features.menu.inc
  9. 3 0
      sites/all/modules/contrib/admin/features/tests/features.test
  10. 3 4
      sites/all/modules/contrib/admin/features/tests/features_test/features_test.info
  11. 7 7
      sites/all/modules/contrib/admin/google_analytics/README.txt
  12. 1 1
      sites/all/modules/contrib/admin/google_analytics/googleanalytics.admin.inc
  13. 3 3
      sites/all/modules/contrib/admin/google_analytics/googleanalytics.debug.js
  14. 3 4
      sites/all/modules/contrib/admin/google_analytics/googleanalytics.info
  15. 21 15
      sites/all/modules/contrib/admin/google_analytics/googleanalytics.install
  16. 3 3
      sites/all/modules/contrib/admin/google_analytics/googleanalytics.js
  17. 32 13
      sites/all/modules/contrib/admin/google_analytics/googleanalytics.module
  18. 61 13
      sites/all/modules/contrib/admin/google_analytics/googleanalytics.test
  19. 8 6
      sites/all/modules/contrib/admin/google_analytics/googleanalytics.test.js
  20. 0 0
      sites/all/modules/contrib/admin/google_analytics/googleanalytics.variable.inc
  21. 42 0
      sites/all/modules/contrib/fields/content_taxonomy/CHANGELOG.txt
  22. 42 0
      sites/all/modules/contrib/fields/content_taxonomy/content_taxonomy.api.php
  23. 3 3
      sites/all/modules/contrib/fields/content_taxonomy/content_taxonomy.info
  24. 94 0
      sites/all/modules/contrib/fields/content_taxonomy/content_taxonomy.install
  25. 49 7
      sites/all/modules/contrib/fields/content_taxonomy/content_taxonomy.module
  26. 3 3
      sites/all/modules/contrib/fields/content_taxonomy/content_taxonomy_autocomplete.info
  27. 39 8
      sites/all/modules/contrib/fields/content_taxonomy/content_taxonomy_autocomplete.module
  28. 15 0
      sites/all/modules/contrib/fields/content_taxonomy/content_taxonomy_et.info
  29. 404 0
      sites/all/modules/contrib/fields/content_taxonomy/content_taxonomy_et.module
  30. 4 6
      sites/all/modules/contrib/fields/content_taxonomy/content_taxonomy_migrate.info
  31. 79 8
      sites/all/modules/contrib/fields/content_taxonomy/content_taxonomy_migrate.module
  32. 58 0
      sites/all/modules/contrib/form/honeypot/README.md
  33. 0 43
      sites/all/modules/contrib/form/honeypot/README.txt
  34. 32 0
      sites/all/modules/contrib/form/honeypot/docker-compose.yml
  35. 20 2
      sites/all/modules/contrib/form/honeypot/honeypot.admin.inc
  36. 3 4
      sites/all/modules/contrib/form/honeypot/honeypot.info
  37. 2 2
      sites/all/modules/contrib/form/honeypot/honeypot.install
  38. 125 8
      sites/all/modules/contrib/form/honeypot/honeypot.module
  39. 55 0
      sites/all/modules/contrib/form/honeypot/honeypot.test
  40. 37 0
      sites/all/modules/contrib/form/honeypot/js/honeypot.js
  41. 3 4
      sites/all/modules/contrib/form/honeypot/tests/honeypot_test.info
  42. 1 0
      sites/all/modules/contrib/mail/mailgun/README.txt
  43. 12 0
      sites/all/modules/contrib/mail/mailgun/composer.json
  44. 112 59
      sites/all/modules/contrib/mail/mailgun/mailgun.admin.inc
  45. 8 7
      sites/all/modules/contrib/mail/mailgun/mailgun.info
  46. 38 2
      sites/all/modules/contrib/mail/mailgun/mailgun.install
  47. 77 23
      sites/all/modules/contrib/mail/mailgun/mailgun.mail.inc
  48. 188 64
      sites/all/modules/contrib/mail/mailgun/mailgun.module
  49. 28 0
      sites/all/modules/contrib/mail/mailgun/templates/mailgun-message.tpl.php
  50. 5 4
      sites/all/modules/contrib/mail/mailsystem/html_to_text.inc
  51. 9 8
      sites/all/modules/contrib/mail/mailsystem/mailsystem.admin.inc
  52. 3 5
      sites/all/modules/contrib/mail/mailsystem/mailsystem.info
  53. 35 15
      sites/all/modules/contrib/mail/mailsystem/mailsystem.module
  54. 49 7
      sites/all/modules/contrib/mail/mailsystem/mailsystem.theme.inc

+ 3 - 3
sites/all/modules/contrib/admin/features/features.admin.inc

@@ -871,7 +871,7 @@ function features_export_build_form_submit($form, &$form_state) {
   $feature = $form['#feature'];
   $export = _features_export_build($feature, $form_state);
   $export = _features_export_generate($export, $form_state, $feature);
-  $generate = ($form_state['values']['op'] == $form_state['values']['generate']);
+  $generate = isset($form_state['values']['generate']) && ($form_state['values']['op'] == $form_state['values']['generate']);
   $module_name = $form_state['values']['module_name'];
 
   if ($generate && !user_access('generate features')) {
@@ -1628,9 +1628,9 @@ function _features_get_features_list() {
 
   $cache = cache_get('features:features_list');
   if ($cache) {
-    $features = $cache->data;  
+    $features = $cache->data;
   }
-  
+
   if (empty($features)) {
     // Clear & rebuild key caches
     features_get_info(NULL, NULL, TRUE);

+ 1 - 1
sites/all/modules/contrib/admin/features/features.drush.inc

@@ -519,7 +519,7 @@ function drush_features_export() {
         drush_die('Aborting.');
       }
       $export = _drush_features_generate_export($items, $module);
-      _features_populate($items, $export[info], $export[name]);
+      _features_populate($items, $export['info'], $export['name']);
       _drush_features_export($export['info'], $module, $directory);
     }
   }

+ 13 - 12
sites/all/modules/contrib/admin/features/features.export.inc

@@ -315,7 +315,7 @@ function features_export_render($export, $module_name, $reset = FALSE) {
   $code = array_filter($code);
   foreach ($code as $filename => $contents) {
     if ($filename != '_files') {
-      $code[$filename] = "<?php\n/**\n * @file\n * {$module_name}.{$filename}.inc\n */\n\n". implode("\n\n", $contents) ."\n";
+      $code[$filename] = "<?php\n\n/**\n * @file\n * {$module_name}.{$filename}.inc\n */\n\n". implode("\n\n", $contents) ."\n";
     }
   }
 
@@ -332,8 +332,8 @@ function features_export_render($export, $module_name, $reset = FALSE) {
   $code['info'] = features_export_info($export);
 
   // Used to create or manipulate the generated .module for features.inc.
-  $modulefile_features_inc = "<?php\n/**\n * @file\n * Code for the {$export['name']} feature.\n */\n\ninclude_once '{$module_name}.features.inc';\n";
-  $modulefile_blank = "<?php\n/**\n * @file\n * Drupal needs this blank file.\n */\n";
+  $modulefile_features_inc = "<?php\n\n/**\n * @file\n * Code for the {$export['name']} feature.\n */\n\ninclude_once '{$module_name}.features.inc';\n";
+  $modulefile_blank = "<?php\n\n/**\n * @file\n * Drupal needs this blank file.\n */\n";
 
   // Prepare the module
   // If module exists, let it be and include it in the files
@@ -1088,13 +1088,6 @@ function _features_is_assoc($array) {
  *   returns a copy of the object or array with recursion removed
  */
 function features_remove_recursion($o) {
-  static $replace;
-  if (!isset($replace)) {
-    $replace = create_function(
-      '$m',
-      '$r="\x00{$m[1]}ecursion_features";return \'s:\'.strlen($r.$m[2]).\':"\'.$r.$m[2].\'";\';'
-    );
-  }
   if (is_array($o) || is_object($o)) {
     $re = '#(r|R):([0-9]+);#';
     $serialize = serialize($o);
@@ -1104,7 +1097,7 @@ function features_remove_recursion($o) {
         $chunk = substr($serialize, $last, $pos - $last);
         if (preg_match($re, $chunk)) {
           $length = strlen($chunk);
-          $chunk = preg_replace_callback($re, $replace, $chunk);
+          $chunk = preg_replace_callback($re, '_features_remove_recursion', $chunk);
           $serialize = substr($serialize, 0, $last) . $chunk . substr($serialize, $last + ($pos - $last));
           $pos += strlen($chunk) - $length;
         }
@@ -1114,13 +1107,21 @@ function features_remove_recursion($o) {
         $last += 4 + $length;
         $pos = $last;
       }
-      $serialize = substr($serialize, 0, $last) . preg_replace_callback($re, $replace, substr($serialize, $last));
+      $serialize = substr($serialize, 0, $last) . preg_replace_callback($re, '_features_remove_recursion', substr($serialize, $last));
       $o = unserialize($serialize);
     }
   }
   return $o;
 }
 
+/**
+ * Callback function for preg_replace_callback() to remove recursion.
+ */
+function _features_remove_recursion($m) {
+  $r = "\x00{$m[1]}ecursion_features";
+  return 's:' . strlen($r . $m[2]) . ':"' . $r . $m[2] . '";';
+}
+
 /**
  * Helper to removes a set of keys an object/array.
  *

+ 3 - 4
sites/all/modules/contrib/admin/features/features.info

@@ -10,9 +10,8 @@ test_dependencies[] = views
 
 configure = admin/structure/features/settings
 
-; Information added by Drupal.org packaging script on 2016-04-18
-version = "7.x-2.10"
+; Information added by Drupal.org packaging script on 2018-11-01
+version = "7.x-2.11"
 core = "7.x"
 project = "features"
-datestamp = "1461011641"
-
+datestamp = "1541050686"

+ 1 - 0
sites/all/modules/contrib/admin/features/features.module

@@ -503,6 +503,7 @@ function features_load_feature($name, $reset = FALSE) {
         $features[$name]->name = $name;
         $features[$name]->filename = drupal_get_path('module', $name) . '/' . $name . '.module';
         $features[$name]->type = 'module';
+        $features[$name]->status = module_exists($name);
         $features[$name]->info = $info + $defaults;
       }
     }

+ 4 - 4
sites/all/modules/contrib/admin/features/includes/features.field.inc

@@ -274,7 +274,7 @@ function field_base_features_rebuild($module) {
       // Create or update field.
       if (isset($existing_fields[$field['field_name']])) {
         $existing_field = $existing_fields[$field['field_name']];
-        $array_diff_result = drupal_array_diff_assoc_recursive($field + $existing_field, $existing_field);
+        $array_diff_result = features_array_diff_assoc_recursive($field + $existing_field, $existing_field);
         if (!empty($array_diff_result)) {
           try {
             field_update_field($field);
@@ -319,7 +319,7 @@ function field_instance_features_rebuild($module) {
       // Create or update field instance.
       if (isset($existing_instances[$field_instance['entity_type']][$field_instance['bundle']][$field_instance['field_name']])) {
         $existing_instance = $existing_instances[$field_instance['entity_type']][$field_instance['bundle']][$field_instance['field_name']];
-        if ($field_instance + $existing_instance !== $existing_instance) {
+        if ($field_instance + $existing_instance != $existing_instance) {
           try {
             field_update_instance($field_instance);
           }
@@ -494,7 +494,7 @@ function field_features_rebuild($module) {
       $field_config = $field['field_config'];
       if (isset($existing_fields[$field_config['field_name']])) {
         $existing_field = $existing_fields[$field_config['field_name']];
-        $array_diff_result = drupal_array_diff_assoc_recursive($field_config + $existing_field, $existing_field);
+        $array_diff_result = features_array_diff_assoc_recursive($field_config + $existing_field, $existing_field);
         if (!empty($array_diff_result)) {
           try {
             field_update_field($field_config);
@@ -518,7 +518,7 @@ function field_features_rebuild($module) {
       $field_instance = $field['field_instance'];
       if (isset($existing_instances[$field_instance['entity_type']][$field_instance['bundle']][$field_instance['field_name']])) {
         $existing_instance = $existing_instances[$field_instance['entity_type']][$field_instance['bundle']][$field_instance['field_name']];
-        if ($field_instance + $existing_instance !== $existing_instance) {
+        if ($field_instance + $existing_instance != $existing_instance) {
           field_update_instance($field_instance);
         }
       }

+ 8 - 2
sites/all/modules/contrib/admin/features/includes/features.image.inc

@@ -73,11 +73,17 @@ function image_features_export_render($module_name, $data, $export = NULL) {
  */
 function image_features_revert($module) {
   if ($default_styles = features_get_default('image', $module)) {
-    foreach (array_keys($default_styles) as $default_style) {
-      if ($style = image_style_load($default_style)) {
+    foreach ($default_styles as $default_style_name => $default_style) {
+      if ($style = image_style_load($default_style_name)) {
         if ($style['storage'] != IMAGE_STORAGE_DEFAULT) {
           image_default_style_revert($style);
         }
+        else {
+          // Verify that the loaded style still matches what's in code.
+          if ($default_style['effects'] !== $style['effects']) {
+            image_default_style_revert($style);
+          }
+        }
       }
     }
   }

+ 8 - 4
sites/all/modules/contrib/admin/features/includes/features.menu.inc

@@ -420,8 +420,12 @@ function features_menu_link_load($identifier) {
  * Returns a lowercase clean string with only letters, numbers and dashes
  */
 function features_clean_title($str) {
-  return strtolower(preg_replace_callback('/(\s)|([^a-zA-Z\-0-9])/i', create_function(
-          '$matches',
-          'return $matches[1]?"-":"";'
-      ), $str));
+  return strtolower(preg_replace_callback('/(\s)|([^a-zA-Z\-0-9])/i', '_features_clean_title', $str));
+}
+
+/**
+ * Callback function for preg_replace_callback() to clean a string.
+ */
+function _features_clean_title($matches) {
+  return $matches[1] ? '-' : '';
 }

+ 3 - 0
sites/all/modules/contrib/admin/features/tests/features.test

@@ -14,6 +14,7 @@ class FeaturesUserTestCase extends DrupalWebTestCase {
       'name' => t('Component tests'),
       'description' => t('Run tests for components of Features.') ,
       'group' => t('Features'),
+      'dependencies' => array('views', 'strongarm'),
     );
   }
 
@@ -182,6 +183,7 @@ class FeaturesEnableTestCase extends DrupalWebTestCase {
       'name' => t('Features enable tests'),
       'description' => t('Run tests for enabling of features.') ,
       'group' => t('Features'),
+      'dependencies' => array('views', 'strongarm'),
     );
   }
 
@@ -231,6 +233,7 @@ class FeaturesCtoolsIntegrationTest extends DrupalWebTestCase {
       'name' => t('Features Chaos Tools integration'),
       'description' => t('Run tests for ctool integration of features.') ,
       'group' => t('Features'),
+      'dependencies' => array('views', 'strongarm'),
     );
   }
 

+ 3 - 4
sites/all/modules/contrib/admin/features/tests/features_test/features_test.info

@@ -21,9 +21,8 @@ features[user_permission][] = create features_test content
 features[views_view][] = features_test
 hidden = 1
 
-; Information added by Drupal.org packaging script on 2016-04-18
-version = "7.x-2.10"
+; Information added by Drupal.org packaging script on 2018-11-01
+version = "7.x-2.11"
 core = "7.x"
 project = "features"
-datestamp = "1461011641"
-
+datestamp = "1541050686"

+ 7 - 7
sites/all/modules/contrib/admin/google_analytics/README.txt

@@ -1,6 +1,6 @@
 
 Module: Google Analytics
-Author: Alexander Hass <http://drupal.org/user/85918>
+Author: Alexander Hass <https://drupal.org/user/85918>
 
 
 Description
@@ -53,15 +53,15 @@ user with 'Administer Google Analytics' permission.
 Like the blocks visibility settings in Drupal core, there is a choice for
 "Add if the following PHP code returns TRUE." Sample PHP snippets that can be
 used in this textarea can be found on the handbook page "Overview-approach to
-block visibility" at http://drupal.org/node/64135.
+block visibility" at https://drupal.org/node/64135.
 
 Custom dimensions and metrics
 =============================
 One example for custom dimensions tracking is the "User roles" tracking.
 
-1. In the Google Analytics Management Interface (http://www.google.com/analytics/)
-   you need to setup Dimension #1 with name e.g. "User roles". This step is
-   required. Do not miss it, please.
+1. In the Google Analytics (https://marketingplatform.google.com/about/analytics/)
+   Management Interface you need to setup Dimension #1 with name 
+   e.g. "User roles". This step is required. Do not miss it, please.
 
 2. Enter the below configuration data into the Drupal custom dimensions settings
    form under admin/config/system/googleanalytics. You can also choose another
@@ -77,7 +77,7 @@ Advanced Settings
 =================
 You can include additional JavaScript snippets in the custom javascript
 code textarea. These can be found on the official Google Analytics pages
-and a few examples at http://drupal.org/node/248699. Support is not
+and a few examples at https://drupal.org/node/248699. Support is not
 provided for any customisations you include.
 
 To speed up page loading you may also cache the Google Analytics "analytics.js"
@@ -97,7 +97,7 @@ Body:
   <li><a href="mailto:foo@example.com">Mailto</a></li>
   <li><a href="/files/test.txt">Download file</a></li>
   <li><a class="colorbox" href="#">Open colorbox</a></li>
-  <li><a href="http://example.com/">External link</a></li>
+  <li><a href="https://example.com/">External link</a></li>
   <li><a href="/go/test">Go link</a></li>
 </ul>
 

File diff suppressed because it is too large
+ 1 - 1
sites/all/modules/contrib/admin/google_analytics/googleanalytics.admin.inc


+ 3 - 3
sites/all/modules/contrib/admin/google_analytics/googleanalytics.debug.js

@@ -122,7 +122,7 @@ $(document).ready(function() {
 Drupal.googleanalytics.isCrossDomain = function (hostname, crossDomains) {
   /**
    * jQuery < 1.6.3 bug: $.inArray crushes IE6 and Chrome if second argument is
-   * `null` or `undefined`, http://bugs.jquery.com/ticket/10076,
+   * `null` or `undefined`, https://bugs.jquery.com/ticket/10076,
    * https://github.com/jquery/jquery/commit/a839af034db2bd934e4d4fa6758a3fed8de74174
    *
    * @todo: Remove/Refactor in D8
@@ -181,8 +181,8 @@ Drupal.googleanalytics.isInternalSpecial = function (url) {
  * Extract the relative internal URL from an absolute internal URL.
  *
  * Examples:
- * - http://mydomain.com/node/1 -> /node/1
- * - http://example.com/foo/bar -> http://example.com/foo/bar
+ * - https://mydomain.com/node/1 -> /node/1
+ * - https://example.com/foo/bar -> https://example.com/foo/bar
  *
  * @param string url
  *   The web url to check.

+ 3 - 4
sites/all/modules/contrib/admin/google_analytics/googleanalytics.info

@@ -5,9 +5,8 @@ package = Statistics
 configure = admin/config/system/googleanalytics
 files[] = googleanalytics.test
 test_dependencies[] = token
-; Information added by Drupal.org packaging script on 2016-08-09
-version = "7.x-2.3"
+; Information added by Drupal.org packaging script on 2019-01-31
+version = "7.x-2.6"
 core = "7.x"
 project = "google_analytics"
-datestamp = "1470779953"
-
+datestamp = "1548968597"

File diff suppressed because it is too large
+ 21 - 15
sites/all/modules/contrib/admin/google_analytics/googleanalytics.install


+ 3 - 3
sites/all/modules/contrib/admin/google_analytics/googleanalytics.js

@@ -104,7 +104,7 @@ $(document).ready(function() {
 Drupal.googleanalytics.isCrossDomain = function (hostname, crossDomains) {
   /**
    * jQuery < 1.6.3 bug: $.inArray crushes IE6 and Chrome if second argument is
-   * `null` or `undefined`, http://bugs.jquery.com/ticket/10076,
+   * `null` or `undefined`, https://bugs.jquery.com/ticket/10076,
    * https://github.com/jquery/jquery/commit/a839af034db2bd934e4d4fa6758a3fed8de74174
    *
    * @todo: Remove/Refactor in D8
@@ -163,8 +163,8 @@ Drupal.googleanalytics.isInternalSpecial = function (url) {
  * Extract the relative internal URL from an absolute internal URL.
  *
  * Examples:
- * - http://mydomain.com/node/1 -> /node/1
- * - http://example.com/foo/bar -> http://example.com/foo/bar
+ * - https://mydomain.com/node/1 -> /node/1
+ * - https://example.com/foo/bar -> https://example.com/foo/bar
  *
  * @param string url
  *   The web url to check.

+ 32 - 13
sites/all/modules/contrib/admin/google_analytics/googleanalytics.module

@@ -7,7 +7,7 @@
  * Adds the required Javascript to all your Drupal pages to allow tracking by
  * the Google Analytics statistics package.
  *
- * @author: Alexander Hass <http://drupal.org/user/85918>
+ * @author: Alexander Hass <https://drupal.org/user/85918>
  */
 
 /**
@@ -17,7 +17,7 @@ define('GOOGLEANALYTICS_TRACKFILES_EXTENSIONS', '7z|aac|arc|arj|asf|asx|avi|bin|
 
 /**
  * Define default path exclusion list to remove tracking from admin pages,
- * see http://drupal.org/node/34970 for more information.
+ * see https://drupal.org/node/34970 for more information.
  */
 define('GOOGLEANALYTICS_PAGES', "admin\nadmin/*\nbatch\nnode/add*\nnode/*/*\nuser/*/*");
 
@@ -36,7 +36,7 @@ function googleanalytics_api() {
 function googleanalytics_help($path, $arg) {
   switch ($path) {
     case 'admin/config/system/googleanalytics':
-      return t('<a href="@ga_url">Google Analytics</a> is a free (registration required) website traffic and marketing effectiveness service.', array('@ga_url' => 'http://www.google.com/analytics/'));
+      return t('<a href="@ga_url">Google Analytics</a> is a free (registration required) website traffic and marketing effectiveness service.', array('@ga_url' => 'https://marketingplatform.google.com/about/analytics/'));
   }
 }
 
@@ -98,7 +98,7 @@ function googleanalytics_menu() {
  * Implements hook_page_alter() to insert JavaScript to the appropriate scope/region of the page.
  */
 function googleanalytics_page_alter(&$page) {
-  global $user;
+  global $base_path, $user;
 
   $id = variable_get('googleanalytics_account', '');
 
@@ -134,7 +134,7 @@ function googleanalytics_page_alter(&$page) {
       $link_settings['trackColorbox'] = $track_colorbox;
     }
     if ($track_domain_mode = variable_get('googleanalytics_domain_mode', 0)) {
-      $link_settings['trackDomainMode'] = $track_domain_mode;
+      $link_settings['trackDomainMode'] = (int) $track_domain_mode;
     }
     if ($track_cross_domains = variable_get('googleanalytics_cross_domains', '')) {
       $link_settings['trackCrossDomains'] = preg_split('/(\r\n?|\n)/', $track_cross_domains);
@@ -201,11 +201,23 @@ function googleanalytics_page_alter(&$page) {
 
     // Track access denied (403) and file not found (404) pages.
     if ($status == '403 Forbidden') {
-      // See http://www.google.com/support/analytics/bin/answer.py?answer=86927
-      $url_custom = '"/403.html?page=" + document.location.pathname + document.location.search + "&from=" + document.referrer';
+      // See https://www.google.com/support/analytics/bin/answer.py?answer=86927
+      $url_custom = '"' . $base_path . '403.html?page=" + document.location.pathname + document.location.search + "&from=" + document.referrer';
     }
     elseif ($status == '404 Not Found') {
-      $url_custom = '"/404.html?page=" + document.location.pathname + document.location.search + "&from=" + document.referrer';
+      $url_custom = '"' . $base_path . '404.html?page=" + document.location.pathname + document.location.search + "&from=" + document.referrer';
+    }
+
+    // #2693595: User has entered an invalid login and clicked on forgot
+    // password link. This link contains the username or email address and may
+    // get send to Google if we do not override it. Override only if 'name'
+    // query param exists. Last custom url condition, this need to win.
+    //
+    // URLs to protect are:
+    // - user/password?name=username
+    // - user/password?name=foo@example.com
+    if (arg(0) == 'user' && arg(1) == 'password' && array_key_exists('name', drupal_get_query_parameters())) {
+      $url_custom = '"' . $base_path . 'user/password"';
     }
 
     // Add custom dimensions and metrics.
@@ -256,11 +268,10 @@ function googleanalytics_page_alter(&$page) {
     $script .= '})(window,document,"script",';
 
     // Which version of the tracking library should be used?
-    $library_tracker_url = '//www.google-analytics.com/' . ($debug ? 'analytics_debug.js' : 'analytics.js');
-    $library_cache_url = 'http:' . $library_tracker_url;
+    $library_tracker_url = 'https://www.google-analytics.com/' . ($debug ? 'analytics_debug.js' : 'analytics.js');
 
     // Should a local cached copy of analytics.js be used?
-    if (variable_get('googleanalytics_cache', 0) && $url = _googleanalytics_cache($library_cache_url)) {
+    if (variable_get('googleanalytics_cache', 0) && $url = _googleanalytics_cache($library_tracker_url)) {
       // A dummy query-string is added to filenames, to gain control over
       // browser-caching. The string changes on every update or full cache
       // flush, forcing browsers to load a new copy of the files, as the
@@ -462,7 +473,7 @@ function googleanalytics_user_presave(&$edit, $account, $category) {
 function googleanalytics_cron() {
   // Regenerate the tracking code file every day.
   if (REQUEST_TIME - variable_get('googleanalytics_last_cache', 0) >= 86400 && variable_get('googleanalytics_cache', 0)) {
-    _googleanalytics_cache('http://www.google-analytics.com/analytics.js', TRUE);
+    _googleanalytics_cache('https://www.google-analytics.com/analytics.js', TRUE);
     variable_set('googleanalytics_last_cache', REQUEST_TIME);
   }
 }
@@ -485,7 +496,7 @@ function googleanalytics_preprocess_search_results(&$variables) {
 /**
  * Helper function for grabbing search keys. Function is missing in D7.
  *
- * http://api.drupal.org/api/function/search_get_keys/6
+ * https://api.drupal.org/api/function/search_get_keys/6
  */
 function googleanalytics_search_get_keys() {
   static $return;
@@ -526,6 +537,10 @@ function _googleanalytics_cache($location, $synchronize = FALSE) {
         if ($data_hash_local != $data_hash_remote && file_prepare_directory($path)) {
           // Save updated tracking code file to disk.
           file_unmanaged_save_data($result->data, $file_destination, FILE_EXISTS_REPLACE);
+          // Based on Drupal Core drupal_build_css_cache().
+          if (variable_get('css_gzip_compression', TRUE) && variable_get('clean_url', 0) && extension_loaded('zlib')) {
+            file_unmanaged_save_data(gzencode($result->data, 9, FORCE_GZIP), $file_destination . '.gz', FILE_EXISTS_REPLACE);
+          }
           watchdog('googleanalytics', 'Locally cached tracking code file has been updated.', array(), WATCHDOG_INFO);
 
           // Change query-strings on css/js files to enforce reload for all users.
@@ -538,6 +553,10 @@ function _googleanalytics_cache($location, $synchronize = FALSE) {
           // There is no need to flush JS here as core refreshes JS caches
           // automatically, if new files are added.
           file_unmanaged_save_data($result->data, $file_destination, FILE_EXISTS_REPLACE);
+          // Based on Drupal Core drupal_build_css_cache().
+          if (variable_get('css_gzip_compression', TRUE) && variable_get('clean_url', 0) && extension_loaded('zlib')) {
+            file_unmanaged_save_data(gzencode($result->data, 9, FORCE_GZIP), $file_destination . '.gz', FILE_EXISTS_REPLACE);
+          }
           watchdog('googleanalytics', 'Locally cached tracking code file has been saved.', array(), WATCHDOG_INFO);
 
           // Return the local JS file path.

+ 61 - 13
sites/all/modules/contrib/admin/google_analytics/googleanalytics.test

@@ -83,7 +83,7 @@ class GoogleAnalyticsBasicTest extends DrupalWebTestCase {
     // Verify that no tracking code is embedded into the webpage; if there is
     // only the module installed, but UA code not configured. See #2246991.
     $this->drupalGet('');
-    $this->assertNoRaw('//www.google-analytics.com/analytics.js', '[testGoogleAnalyticsPageVisibility]: Tracking code is not displayed without UA code configured.');
+    $this->assertNoRaw('https://www.google-analytics.com/analytics.js', '[testGoogleAnalyticsPageVisibility]: Tracking code is not displayed without UA code configured.');
 
     $ua_code = 'UA-123456-1';
     variable_set('googleanalytics_account', $ua_code);
@@ -104,7 +104,7 @@ class GoogleAnalyticsBasicTest extends DrupalWebTestCase {
     $this->assertNoRaw($ua_code, '[testGoogleAnalyticsPageVisibility]: Tracking code is not displayed on admin page.');
     $this->drupalGet('admin/config/system/googleanalytics');
     // Checking for tracking code URI here, as $ua_code is displayed in the form.
-    $this->assertNoRaw('//www.google-analytics.com/analytics.js', '[testGoogleAnalyticsPageVisibility]: Tracking code is not displayed on admin subpage.');
+    $this->assertNoRaw('https://www.google-analytics.com/analytics.js', '[testGoogleAnalyticsPageVisibility]: Tracking code is not displayed on admin subpage.');
 
     // Test whether tracking code display is properly flipped.
     variable_set('googleanalytics_visibility_pages', 1);
@@ -112,7 +112,7 @@ class GoogleAnalyticsBasicTest extends DrupalWebTestCase {
     $this->assertRaw($ua_code, '[testGoogleAnalyticsPageVisibility]: Tracking code is displayed on admin page.');
     $this->drupalGet('admin/config/system/googleanalytics');
     // Checking for tracking code URI here, as $ua_code is displayed in the form.
-    $this->assertRaw('//www.google-analytics.com/analytics.js', '[testGoogleAnalyticsPageVisibility]: Tracking code is displayed on admin subpage.');
+    $this->assertRaw('https://www.google-analytics.com/analytics.js', '[testGoogleAnalyticsPageVisibility]: Tracking code is displayed on admin subpage.');
     $this->drupalGet('');
     $this->assertNoRaw($ua_code, '[testGoogleAnalyticsPageVisibility]: Tracking code is NOT displayed on front page.');
 
@@ -126,13 +126,15 @@ class GoogleAnalyticsBasicTest extends DrupalWebTestCase {
     // Enable tracking code for all user roles.
     variable_set('googleanalytics_roles', array());
 
+    $base_path = base_path();
+
     // Test whether 403 forbidden tracking code is shown if user has no access.
     $this->drupalGet('admin');
-    $this->assertRaw('/403.html', '[testGoogleAnalyticsPageVisibility]: 403 Forbidden tracking code shown if user has no access.');
+    $this->assertRaw($base_path . '403.html', '[testGoogleAnalyticsPageVisibility]: 403 Forbidden tracking code shown if user has no access.');
 
     // Test whether 404 not found tracking code is shown on non-existent pages.
     $this->drupalGet($this->randomName(64));
-    $this->assertRaw('/404.html', '[testGoogleAnalyticsPageVisibility]: 404 Not Found tracking code shown on non-existent page.');
+    $this->assertRaw($base_path . '404.html', '[testGoogleAnalyticsPageVisibility]: 404 Not Found tracking code shown on non-existent page.');
 
     // DNT Tests:
     // Enable system internal page cache for anonymous users.
@@ -168,7 +170,7 @@ class GoogleAnalyticsBasicTest extends DrupalWebTestCase {
     (function(q,u,i,c,k){window['GoogleAnalyticsObject']=q;
     window[q]=window[q]||function(){(window[q].q=window[q].q||[]).push(arguments)},
     window[q].l=1*new Date();c=i.createElement(u),k=i.getElementsByTagName(u)[0];
-    c.async=true;c.src='//www.google-analytics.com/analytics.js';
+    c.async=true;c.src='https://www.google-analytics.com/analytics.js';
     k.parentNode.insertBefore(c,k)})('ga','script',document);
     ga('create', 'UA-123456-7');
     ga('send', 'pageview');
@@ -179,7 +181,7 @@ class GoogleAnalyticsBasicTest extends DrupalWebTestCase {
     // Test whether tracking code uses latest JS.
     variable_set('googleanalytics_cache', 0);
     $this->drupalGet('');
-    $this->assertRaw('//www.google-analytics.com/analytics.js', '[testGoogleAnalyticsTrackingCode]: Latest tracking code used.');
+    $this->assertRaw('https://www.google-analytics.com/analytics.js', '[testGoogleAnalyticsTrackingCode]: Latest tracking code used.');
 
     // Test whether anonymize visitors IP address feature has been enabled.
     variable_set('googleanalytics_tracker_anonymizeip', 0);
@@ -256,13 +258,14 @@ class GoogleAnalyticsBasicTest extends DrupalWebTestCase {
     $this->assertRaw('ga("create", "' . $ua_code . '", {"cookieDomain":"auto","allowLinker":true', '[testGoogleAnalyticsTrackingCode]: "allowLinker" has been found. Cross domain tracking is active.');
     $this->assertRaw('ga("require", "linker");', '[testGoogleAnalyticsTrackingCode]: Require linker has been found. Cross domain tracking is active.');
     $this->assertRaw('ga("linker:autoLink", ["www.example.com","www.example.net"]);', '[testGoogleAnalyticsTrackingCode]: "linker:autoLink" has been found. Cross domain tracking is active.');
+    $this->assertRaw('"trackDomainMode":2,', '[testGoogleAnalyticsTrackingCode]: Domain mode value is of type integer.');
     $this->assertRaw('"trackCrossDomains":["www.example.com","www.example.net"]', '[testGoogleAnalyticsTrackingCode]: Cross domain tracking with www.example.com and www.example.net is active.');
     variable_set('googleanalytics_domain_mode', 0);
 
     // Test whether debugging script has been enabled.
     variable_set('googleanalytics_debug', 1);
     $this->drupalGet('');
-    $this->assertRaw('//www.google-analytics.com/analytics_debug.js', '[testGoogleAnalyticsTrackingCode]: Google debugging script has been enabled.');
+    $this->assertRaw('https://www.google-analytics.com/analytics_debug.js', '[testGoogleAnalyticsTrackingCode]: Google debugging script has been enabled.');
 
     // Check if text and link is shown on 'Status Reports' page.
     // Requires 'administer site configuration' permission.
@@ -272,7 +275,7 @@ class GoogleAnalyticsBasicTest extends DrupalWebTestCase {
     // Test whether debugging script has been disabled.
     variable_set('googleanalytics_debug', 0);
     $this->drupalGet('');
-    $this->assertRaw('//www.google-analytics.com/analytics.js', '[testGoogleAnalyticsTrackingCode]: Google debugging script has been disabled.');
+    $this->assertRaw('https://www.google-analytics.com/analytics.js', '[testGoogleAnalyticsTrackingCode]: Google debugging script has been disabled.');
 
     // Test whether the CREATE and BEFORE and AFTER code is added to the tracker.
     $codesnippet_create = array(
@@ -473,6 +476,51 @@ class GoogleAnalyticsCustomDimensionsAndMetricsTest extends DrupalWebTestCase {
   }
 }
 
+/**
+ * Test custom url functionality of Google Analytics module.
+ */
+class GoogleAnalyticsCustomUrls extends DrupalWebTestCase {
+
+  public static function getInfo() {
+    return array(
+      'name' => 'Google Analytics custom url tests',
+      'description' => 'Test custom url functionality of Google Analytics module.',
+      'group' => 'Google Analytics',
+    );
+  }
+
+  function setUp() {
+    parent::setUp('googleanalytics');
+
+    $permissions = array(
+      'access administration pages',
+      'administer google analytics',
+    );
+
+    // User to set up google_analytics.
+    $this->admin_user = $this->drupalCreateUser($permissions);
+  }
+
+  /**
+   * Tests if user password page urls are overridden.
+   */
+  public function testGoogleAnalyticsUserPasswordPage() {
+    $base_path = base_path();
+    $ua_code = 'UA-123456-4';
+    variable_set('googleanalytics_account', $ua_code);
+
+    $this->drupalGet('user/password', array('query' => array('name' => 'foo')));
+    $this->assertRaw('ga("set", "page", "' . $base_path . 'user/password"');
+
+    $this->drupalGet('user/password', array('query' => array('name' => 'foo@example.com')));
+    $this->assertRaw('ga("set", "page", "' . $base_path . 'user/password"');
+
+    $this->drupalGet('user/password');
+    $this->assertNoRaw('ga("set", "page",', '[testGoogleAnalyticsCustomUrls]: Custom url not set.');
+  }
+
+}
+
 class GoogleAnalyticsStatusMessagesTest extends DrupalWebTestCase {
 
   public static function getInfo() {
@@ -510,7 +558,7 @@ class GoogleAnalyticsStatusMessagesTest extends DrupalWebTestCase {
     //drupal_set_message('Example status message.', 'status');
     //drupal_set_message('Example warning message.', 'warning');
     //drupal_set_message('Example error message.', 'error');
-    //drupal_set_message('Example error <em>message</em> with html tags and <a href="http://example.com/">link</a>.', 'error');
+    //drupal_set_message('Example error <em>message</em> with html tags and <a href="https://example.com/">link</a>.', 'error');
     //$this->drupalGet('');
     //$this->assertNoRaw('ga("send", "event", "Messages", "Status message", "Example status message.");', '[testGoogleAnalyticsStatusMessages]: Example status message is not enabled for tracking.');
     //$this->assertNoRaw('ga("send", "event", "Messages", "Warning message", "Example warning message.");', '[testGoogleAnalyticsStatusMessages]: Example warning message is not enabled for tracking.');
@@ -734,13 +782,13 @@ class GoogleAnalyticsPhpFilterTest extends DrupalWebTestCase {
     // Check tracking code visibility.
     variable_set('googleanalytics_pages', '<?php return TRUE; ?>');
     $this->drupalGet('');
-    $this->assertRaw('//www.google-analytics.com/analytics.js', '[testGoogleAnalyticsPhpFilter]: Tracking is displayed on frontpage page.');
+    $this->assertRaw('https://www.google-analytics.com/analytics.js', '[testGoogleAnalyticsPhpFilter]: Tracking is displayed on frontpage page.');
     $this->drupalGet('admin');
-    $this->assertRaw('//www.google-analytics.com/analytics.js', '[testGoogleAnalyticsPhpFilter]: Tracking is displayed on admin page.');
+    $this->assertRaw('https://www.google-analytics.com/analytics.js', '[testGoogleAnalyticsPhpFilter]: Tracking is displayed on admin page.');
 
     variable_set('googleanalytics_pages', '<?php return FALSE; ?>');
     $this->drupalGet('');
-    $this->assertNoRaw('//www.google-analytics.com/analytics.js', '[testGoogleAnalyticsPhpFilter]: Tracking is not displayed on frontpage page.');
+    $this->assertNoRaw('https://www.google-analytics.com/analytics.js', '[testGoogleAnalyticsPhpFilter]: Tracking is not displayed on frontpage page.');
 
     // Test administration form.
     variable_set('googleanalytics_pages', '<?php return TRUE; ?>');

+ 8 - 6
sites/all/modules/contrib/admin/google_analytics/googleanalytics.test.js

@@ -77,7 +77,7 @@ $(document).ready(function() {
   Drupal.googleanalytics.test.assertTrue(Drupal.googleanalytics.isInternal(base_url + Drupal.settings.basePath + 'node/1?foo=bar'), "Link '" + base_url + Drupal.settings.basePath + "node/1?foo=bar' has been detected as internal link.");
   Drupal.googleanalytics.test.assertTrue(Drupal.googleanalytics.isInternal(base_url + Drupal.settings.basePath + 'node/1?foo=bar#foo'), "Link '" + base_url + Drupal.settings.basePath + "node/1?foo=bar#foo' has been detected as internal link.");
   Drupal.googleanalytics.test.assertTrue(Drupal.googleanalytics.isInternal(base_url + Drupal.settings.basePath + 'go/foo'), "Link '" + base_url + Drupal.settings.basePath + "go/foo' has been detected as internal link.");
-  Drupal.googleanalytics.test.assertFalse(Drupal.googleanalytics.isInternal('http://example.com/node/3'), "Link 'http://example.com/node/3' has been detected as external link.");
+  Drupal.googleanalytics.test.assertFalse(Drupal.googleanalytics.isInternal('https://example.com/node/3'), "Link 'https://example.com/node/3' has been detected as external link.");
   console.groupEnd();
 
   console.group("Test 'isInternalSpecial':");
@@ -86,9 +86,11 @@ $(document).ready(function() {
   console.groupEnd();
 
   console.group("Test 'getPageUrl':");
-  Drupal.googleanalytics.test.assertSame(base_path, Drupal.googleanalytics.getPageUrl(base_url + Drupal.settings.basePath + 'node/1'), "Absolute internal URL '" +  Drupal.settings.basePath + "node/1' has been extracted from full qualified url '" + base_url + base_path + "'.");
-  Drupal.googleanalytics.test.assertSame(base_path, Drupal.googleanalytics.getPageUrl(Drupal.settings.basePath + 'node/1'), "Absolute internal URL '" +  Drupal.settings.basePath + "node/1' has been extracted from absolute url '" +  base_path + "'.");
-  Drupal.googleanalytics.test.assertSame('http://example.com/node/2', Drupal.googleanalytics.getPageUrl('http://example.com/node/2'), "Full qualified external url 'http://example.com/node/2' has been extracted.");
+  Drupal.google_analytics.test.assertSame(base_path, Drupal.google_analytics.getPageUrl(window.location.href), "Absolute internal URL '" + base_path + "' has been extracted from full qualified url '" + window.location.href + "'.");
+  Drupal.google_analytics.test.assertSame(base_path, Drupal.google_analytics.getPageUrl(base_path), "Absolute internal URL '" + base_path + "' has been extracted from absolute url '" + base_path + "'.");
+  //Drupal.googleanalytics.test.assertSame(base_path, Drupal.googleanalytics.getPageUrl(base_url + Drupal.settings.basePath + 'node/1'), "Absolute internal URL '" +  Drupal.settings.basePath + "node/1' has been extracted from full qualified url '" + base_url + base_path + "'.");
+  //Drupal.googleanalytics.test.assertSame(base_path, Drupal.googleanalytics.getPageUrl(Drupal.settings.basePath + 'node/1'), "Absolute internal URL '" +  Drupal.settings.basePath + "node/1' has been extracted from absolute url '" +  base_path + "'.");
+  Drupal.googleanalytics.test.assertSame('https://example.com/node/2', Drupal.googleanalytics.getPageUrl('https://example.com/node/2'), "Full qualified external url 'https://example.com/node/2' has been extracted.");
   Drupal.googleanalytics.test.assertSame('//example.com/node/2', Drupal.googleanalytics.getPageUrl('//example.com/node/2'), "Full qualified external url '//example.com/node/2' has been extracted.");
   console.groupEnd();
 
@@ -105,9 +107,9 @@ $(document).ready(function() {
   if (Drupal.settings.googleanalytics.trackCrossDomains) {
     console.dir(Drupal.settings.googleanalytics.trackCrossDomains);
     Drupal.googleanalytics.test.assertTrue(Drupal.googleanalytics.isCrossDomain('example.com', Drupal.settings.googleanalytics.trackCrossDomains), "URL 'example.com' has been found in cross domain list.");
-    Drupal.googleanalytics.test.assertTrue(Drupal.googleanalytics.isCrossDomain('example.net', Drupal.settings.googleanalytics.trackCrossDomains), "URL 'example.com' has been found in cross domain list.");
+    Drupal.googleanalytics.test.assertTrue(Drupal.googleanalytics.isCrossDomain('example.net', Drupal.settings.googleanalytics.trackCrossDomains), "URL 'example.net' has been found in cross domain list.");
     Drupal.googleanalytics.test.assertFalse(Drupal.googleanalytics.isCrossDomain('www.example.com', Drupal.settings.googleanalytics.trackCrossDomains), "URL 'www.example.com' not found in cross domain list.");
-    Drupal.googleanalytics.test.assertFalse(Drupal.googleanalytics.isCrossDomain('www.example.net', Drupal.settings.googleanalytics.trackCrossDomains), "URL 'www.example.com' not found in cross domain list.");
+    Drupal.googleanalytics.test.assertFalse(Drupal.googleanalytics.isCrossDomain('www.example.net', Drupal.settings.googleanalytics.trackCrossDomains), "URL 'www.example.net' not found in cross domain list.");
   }
   else {
     console.warn('Cross domain tracking is not enabled. Tests skipped.');

File diff suppressed because it is too large
+ 0 - 0
sites/all/modules/contrib/admin/google_analytics/googleanalytics.variable.inc


+ 42 - 0
sites/all/modules/contrib/fields/content_taxonomy/CHANGELOG.txt

@@ -0,0 +1,42 @@
+Content_Taxonomy 7.x-1.0-rc1, 2016-03-22
+----------------------------------------
+#1373716 by Owen Barton: Added hook_disable() to disable the custom field
+  widget and prevent site from breaking.
+By mh86: Added hook for altering the term tree callback used in the allowed
+  values function.
+By mh86: Added new submodule that supports the Entity Translation mode for
+  taxonomy terms.
+By mh86: Fixed translated term names on initial autocomplete deluxe suggestions.
+#1208164 by skein, mrded, rbayliss, drzraf, DamienMcKenna: Fix field migration.
+#2580251 by michel.settembrino, DamienMcKenna: Added EntityReference
+  autocomplete support.
+#2379611 by justanothermark: Wording change on autocomplete error message.
+#1160146 by moskito: Added ActiveTags support.
+#1395276 by bigjim: Fixed options field on migration.
+#2348363 by james.williams, DamienMcKenna: Only alter field instances for
+  content_taxonomy fields.
+#1558050 by bdimaggio, DamienMcKenna: The 'array_parents' element might not
+  exist.
+#1643858 by DamienMcKenna, hatuhay, BrightBold, marthinal, benjy, mikhailian,
+  MakaziMtingwa, zhilevan, klidifia, pappuksingh, cameronbprince: Fix invalid
+  default values.
+
+
+Content_Taxonomy 7.x-1.0-beta2, 2013-01-21
+------------------------------------------
+By mh86: Fixed validate handlers for new terms.
+By mh86: Added Search API Postprocessor for filtering out moderated terms from
+  facets.
+#1631000 by klausi: Fixed Strict warning: Declaration of
+  ContentTaxonomyAutocompleteModeratedTermsSearchAPIProcessor.
+By mh86: Added option for selects lists with opt groups
+
+
+Content_Taxonomy 7.x-1.0-beta1, 2011-06-22
+------------------------------------------
+By mh86: Initial port to D7.
+#1079846 by mh86: Fixed error in content_taxonomy_autocomplete when using
+  without autocomplete_deluxe.
+#1079938 by mh86: Fixed error in migration module.
+#1086228 by mh86: Support for upgrading Hierarchical Select fields.
+By mh86: Added option for the tree depth.

+ 42 - 0
sites/all/modules/contrib/fields/content_taxonomy/content_taxonomy.api.php

@@ -0,0 +1,42 @@
+<?php
+
+/**
+ * @file
+ * This file contains no working PHP code; it exists to provide additional
+ * documentation for doxygen as well as to document hooks in the standard
+ * Drupal manner.
+ */
+
+
+/**
+ * @addtogroup hooks
+ * @{
+ */
+
+/**
+ * Alter the term tree callback that is used for content taxonomy options lists.
+ *
+ * By default taxonomy_get_tree is used to retrieve the list of terms. It is
+ * important that any provided tree callback must have the same function
+ * signature as hook_content_taxonomy_tree_callback_alter().
+ * For example this hook can be used to exchange the callback with a language
+ * specific tree function.
+ *
+ * @param $tree_callback
+ *   The current callback that can be overridden.
+ * @param $field
+ *   The term reference field info array.
+ * @param $vocabulary
+ *   The vocabulary object for which the term list should be retrieved. One
+ *   field can have multiple vocabularies attached, which leads to multiple
+ *   invocations of this function.
+ */
+function hook_content_taxonomy_tree_callback_alter(&$tree_callback, $field, $vocabulary) {
+  if ($vocabulary->machine_name == 'my_voc') {
+    $tree_callback = 'my_tree_callback';
+  }
+}
+
+/**
+ * @} End of "addtogroup hooks".
+ */

+ 3 - 3
sites/all/modules/contrib/fields/content_taxonomy/content_taxonomy.info

@@ -8,9 +8,9 @@ dependencies[] = taxonomy
 
 
 
-; Information added by drupal.org packaging script on 2013-02-13
-version = "7.x-1.0-beta2"
+; Information added by Drupal.org packaging script on 2016-03-22
+version = "7.x-1.0-rc1"
 core = "7.x"
 project = "content_taxonomy"
-datestamp = "1360767812"
+datestamp = "1458667740"
 

+ 94 - 0
sites/all/modules/contrib/fields/content_taxonomy/content_taxonomy.install

@@ -0,0 +1,94 @@
+<?php
+
+/**
+ * @file
+ * (Un-) installation tasks for content taxonomy.
+ */
+
+/**
+ * Implements hook_disable().
+ *
+ * Allow uninstall of content taxonomy by removing the callback in field
+ * configuration. Re-enabling requires to save the field configs to re-insert
+ * the callback.
+ */
+function content_taxonomy_disable() {
+  $fields = field_read_fields();
+  foreach ($fields as $field) {
+    if (isset($field['settings']['options_list_callback']) && $field['settings']['options_list_callback'] == 'content_taxonomy_allowed_values') {
+      // We cannot unset this value, because field_update_field() merges in
+      // prior settings before saving. Setting it to NULL works.
+      $field['settings']['options_list_callback'] = NULL;
+    }
+    field_update_field($field);
+  }
+}
+
+/**
+ * Implementations of hook_update_N().
+ */
+
+/**
+ * Fix the default values converted from D6. These values had an empty 'value'
+ * value which could cause errors on D7.
+ */
+function content_taxonomy_update_7100() {
+  $params = array(
+    'type' => 'taxonomy_term_reference',
+  );
+  foreach (field_read_fields($params) as $field) {
+    foreach (field_read_instances(array('field_name' => $field['field_name'])) as $instance) {
+      // If the 'default_value' item doesn't exist there's no point in
+      // continuing.
+      if (!isset($instance['default_value'])) {
+        continue;
+      }
+
+      // Keep track of whether fields are actually changed.
+      $updated = FALSE;
+
+      // Fix each of the default values.
+      foreach ($instance['default_value'] as $key => $defaults) {
+        // Need to check isset() and is_null() because the value could be NULL.
+        if (isset($instance['default_value'][$key]['value']) || is_null($instance['default_value'][$key]['value'])) {
+          // Remove any empty 'value' strings.
+          if (empty($instance['default_value'][$key]['value'])) {
+            unset($instance['default_value'][$key]['value']);
+            $updated = TRUE;
+          }
+
+          // Rename the 'value' string to 'tid'.
+          elseif (!isset($instance_value['default_value'][$key]['tid'])) {
+            $instance_value['default_value'][$key]['tid'] = $instance_value['default_value'][$key]['value'];
+            unset($instance_value['default_value'][$key]['value']);
+            $updated = TRUE;
+          }
+
+          // Look for a junk value carried over from D6.
+          if (isset($instance['default_value'][$key]['_error_element'])) {
+            unset($instance['default_value'][$key]['_error_element']);
+            $updated = TRUE;
+          }
+
+          // If the array is empty, just remove it.
+          if (empty($instance['default_value'][$key])) {
+            unset($instance['default_value'][$key]);
+            $updated = TRUE;
+          }
+        }
+      }
+
+      // If there are no default values left, just remove it.
+      if (empty($instance['default_value'])) {
+        unset($instance['default_value']);
+        $updated = TRUE;
+      }
+
+      // If the field's definition was changed, save it.
+      if ($updated) {
+        field_update_instance($instance);
+        drupal_set_message(t('Fixed configuration of the "@field_name" field ("@type" content type).', array('@field_name' => $instance['field_name'], '@type' => $instance['bundle'])));
+      }
+    }
+  }
+}

+ 49 - 7
sites/all/modules/contrib/fields/content_taxonomy/content_taxonomy.module

@@ -7,12 +7,14 @@ function content_taxonomy_form_field_ui_field_edit_form_alter(&$form, &$form_sta
   $field = $form['#field'];
   $instance = $form['#instance'];
 
-  // Add parent selector to term reference fields,
-  // except to the autocomplete widget, as it ignores the parent setting.
+  // Add parent selector to term reference fields, except to the autocomplete
+  // widget, as it ignores the parent setting.
   if ($field['type'] == 'taxonomy_term_reference'
-    && !($instance['widget']['type'] == 'taxonomy_autocomplete' || $instance['widget']['type'] == 'autocomplete_deluxe_taxonomy')) {
-
-    // add parent form.
+      && $instance['widget']['type'] != 'taxonomy_autocomplete'
+      && $instance['widget']['type'] != 'autocomplete_deluxe_taxonomy'
+      && $instance['widget']['type'] != 'entityreference_autocomplete'
+      && $instance['widget']['type'] != 'entityreference_autocomplete_tags') {
+    // Add parent form.
     foreach ($field['settings']['allowed_values'] as $delta => $tree) {
       $options[0] = '---';
       // todo this might break with huge vocs
@@ -76,7 +78,8 @@ function content_taxonomy_allowed_values($field) {
   foreach ($field['settings']['allowed_values'] as $tree) {
     if ($vocabulary = taxonomy_vocabulary_machine_name_load($tree['vocabulary'])) {
       $max_depth = (isset($tree['depth']) && !empty($tree['depth'])) ? $tree['depth'] : NULL;
-      if ($terms = taxonomy_get_tree($vocabulary->vid, $tree['parent'], $max_depth)) {
+      $terms = content_taxonomy_get_terms($field, $vocabulary, $tree['parent'], $max_depth);
+      if (!empty($terms) && is_array($terms)) {
         foreach ($terms as $term) {
           $options[$term->tid] = str_repeat('- ', $term->depth) . $term->name;
         }
@@ -86,6 +89,44 @@ function content_taxonomy_allowed_values($field) {
   return $options;
 }
 
+/**
+ * Returns an array of terms that can be used in an options list.
+ *
+ * By default taxonomy_get_tree is used to retrieve the list of terms, but an
+ * alteration via hook_content_taxonomy_tree_callback_alter() is possible. It
+ * is important that any provided tree callback must have the same function
+ * signature as hook_content_taxonomy_tree_callback_alter().
+ * For example this hook can be used to exchange the callback with a language
+ * specific tree function.
+ *
+ * Although we could change the options list callback via the field definitions,
+ * it is easier to do this via the alteration hook provided by this function.
+ *
+ * @param $field
+ *   The term reference field info array.
+ * @param $vocabulary
+ *   The vocabulary object for which the term list should be retrieved. One
+ *   field can have multiple vocabularies attached, which leads to multiple
+ *   invocations of this function.
+ * @param $parent
+ *   The parent term id. Use 0 for the root level.
+ * @param $max_depth
+ *   The maximum depth. Use NULL for non limitation.
+ *
+ * @return array
+ */
+function content_taxonomy_get_terms($field, $vocabulary, $parent, $max_depth) {
+  $terms = array();
+
+  $tree_callback = 'taxonomy_get_tree';
+  drupal_alter('content_taxonomy_tree_callback', $tree_callback, $field, $vocabulary);
+  if (function_exists($tree_callback)) {
+    $terms = $tree_callback($vocabulary->vid, $parent, $max_depth, FALSE);
+  }
+
+  return $terms;
+}
+
 /**
  * Implements hook_field_widget_info_alter().
  */
@@ -126,7 +167,8 @@ function content_taxonomy_allowed_values_opt_groups($field) {
   $options = array();
   foreach ($field['settings']['allowed_values'] as $tree) {
     if ($vocabulary = taxonomy_vocabulary_machine_name_load($tree['vocabulary'])) {
-      if ($terms = taxonomy_get_tree($vocabulary->vid, 0, 2)) {
+      $terms = content_taxonomy_get_terms($field, $vocabulary, 0, 2);
+      if (!empty($terms) && is_array($terms)) {
         $current_group_term = NULL;
         foreach ($terms as $term) {
           if ($term->depth == 0) {

+ 3 - 3
sites/all/modules/contrib/fields/content_taxonomy/content_taxonomy_autocomplete.info

@@ -10,9 +10,9 @@ files[] = includes/content_taxonomy_autocomplete_moderated_terms.inc
 
 
 
-; Information added by drupal.org packaging script on 2013-02-13
-version = "7.x-1.0-beta2"
+; Information added by Drupal.org packaging script on 2016-03-22
+version = "7.x-1.0-rc1"
 core = "7.x"
 project = "content_taxonomy"
-datestamp = "1360767812"
+datestamp = "1458667740"
 

+ 39 - 8
sites/all/modules/contrib/fields/content_taxonomy/content_taxonomy_autocomplete.module

@@ -10,10 +10,25 @@ function content_taxonomy_autocomplete_field_widget_info_alter(&$info) {
   );
   $info['taxonomy_autocomplete']['settings'] += $ct_settings;
 
+  // Add this to the Active Tags as well, if enabled.
+  if (isset($info['active_tags']['settings'])) {
+    $info['active_tags']['settings'] += $ct_settings;
+  }
+
   // Add this to the autocomplete deluxe as well, if enabled.
   if (isset($info['autocomplete_deluxe_taxonomy']['settings'])) {
     $info['autocomplete_deluxe_taxonomy']['settings'] += $ct_settings;
   }
+
+  // Add this to the entityreference autocomplete as well, if enabled.
+  if (isset($info['entityreference_autocomplete']['settings'])) {
+    $info['entityreference_autocomplete']['settings'] += $ct_settings;
+  }
+
+  // Add this to the entityreference autocomplete (tags) as well, if enabled.
+  if (isset($info['entityreference_autocomplete_tags']['settings'])) {
+    $info['entityreference_autocomplete_tags']['settings'] += $ct_settings;
+  }
 }
 
 /**
@@ -22,7 +37,11 @@ function content_taxonomy_autocomplete_field_widget_info_alter(&$info) {
 function content_taxonomy_autocomplete_form_field_ui_field_edit_form_alter(&$form, &$form_state, $form_id) {
   $field = $form['#field'];
   $instance = $form['#instance'];
-  if ($instance['widget']['type'] == 'taxonomy_autocomplete' || $instance['widget']['type'] == 'autocomplete_deluxe_taxonomy') {
+  if ($instance['widget']['type'] == 'taxonomy_autocomplete'
+      || $instance['widget']['type'] == 'active_tags_taxonomy_autocomplete'
+      || $instance['widget']['type'] == 'autocomplete_deluxe_taxonomy'
+      || $instance['widget']['type'] == 'entityreference_autocomplete'
+      || $instance['widget']['type'] == 'entityreference_autocomplete_tags') {
     // Add a setting form for validating new terms.
     $options = array(
       'allow' => t('Allow and insert new terms'),
@@ -39,7 +58,14 @@ function content_taxonomy_autocomplete_form_field_ui_field_edit_form_alter(&$for
   }
 
   // Show form for second vocabulary.
-  if (isset($instance['widget']['settings']['content_taxonomy_autocomplete_new_terms']) && $instance['widget']['settings']['content_taxonomy_autocomplete_new_terms'] == 'moderate') {
+  if (($instance['widget']['type'] == 'taxonomy_autocomplete'
+      || $instance['widget']['type'] == 'active_tags_taxonomy_autocomplete'
+      || $instance['widget']['type'] == 'autocomplete_deluxe_taxonomy'
+      || $instance['widget']['type'] == 'entityreference_autocomplete'
+      || $instance['widget']['type'] == 'entityreference_autocomplete_tags')
+    && isset($form[$instance['field_name']])
+    && isset($instance['widget']['settings']['content_taxonomy_autocomplete_new_terms'])) {
+
     // Initialize settings, if not set.
     if (!isset($field['settings']['allowed_values'][1])) {
       $field['settings']['allowed_values'][1] = array(
@@ -83,8 +109,11 @@ function content_taxonomy_autocomplete_field_attach_form($entity_type, $entity,
   // Add validation function to taxonomy_autocompletes, if necessary.
   $instances = field_info_instances($form['#entity_type'], $form['#bundle']);
   foreach ($instances as $instance) {
-    if (($instance['widget']['type'] == 'taxonomy_autocomplete' || $instance['widget']['type'] == 'autocomplete_deluxe_taxonomy') 
-      && isset($form[$instance['field_name']]) 
+    if (($instance['widget']['type'] == 'taxonomy_autocomplete'
+        || $instance['widget']['type'] == 'autocomplete_deluxe_taxonomy'
+        || $instance['widget']['type'] == 'entityreference_autocomplete'
+        || $instance['widget']['type'] == 'entityreference_autocomplete_tags')
+      && isset($form[$instance['field_name']])
       && isset($instance['widget']['settings']['content_taxonomy_autocomplete_new_terms'])) {
 
       // Use the language that is used in this form (which doesn't necessarily
@@ -105,12 +134,14 @@ function content_taxonomy_autocomplete_field_attach_form($entity_type, $entity,
  */
 function content_taxonomy_autocomplete_validate_deny_new_terms($element, &$form_state, $form) {
   $values = $form_state['values'];
-  foreach ($element['#array_parents'] as $parent) {
-    $values = $values[$parent];
+  if (!empty($element['#array_parents'])) {
+    foreach ($element['#array_parents'] as $parent) {
+      $values = $values[$parent];
+    }
   }
   foreach ($values as $delta => $value) {
-    if ($value['tid'] == 'autocreate') {
-      form_error($element, t('%name: new terms are not allowed. Please choose from the given list.', array('%name' => $element['#title'])));
+    if (isset($value['tid']) && $value['tid'] == 'autocreate') {
+      form_error($element, t('%name: new terms are not allowed. Please choose from the suggested options.', array('%name' => $element['#title'])));
     }
   }
 }

+ 15 - 0
sites/all/modules/contrib/fields/content_taxonomy/content_taxonomy_et.info

@@ -0,0 +1,15 @@
+name = Content Taxonomy Entity Translations
+description = Adds support for terms that are translated via the Entity Translation and Title module.
+core = 7.x
+package = Fields
+
+dependencies[] = content_taxonomy
+dependencies[] = entity_translation
+dependencies[] = title
+
+; Information added by Drupal.org packaging script on 2016-03-22
+version = "7.x-1.0-rc1"
+core = "7.x"
+project = "content_taxonomy"
+datestamp = "1458667740"
+

+ 404 - 0
sites/all/modules/contrib/fields/content_taxonomy/content_taxonomy_et.module

@@ -0,0 +1,404 @@
+<?php
+
+/**
+ * @file
+ * Entity Translation integration for taxonomy terms.
+ */
+
+/**
+ * Implements hook_content_taxonomy_tree_callback_alter().
+ */
+function content_taxonomy_et_content_taxonomy_tree_callback_alter(&$tree_callback, $field, $vocabulary) {
+  // Checks whether the Entity Translation mode with the title field is enabled,
+  // if so, replace the tree callback with our custom language specific
+  // implementation.
+  if (title_field_replacement_enabled('taxonomy_term', $vocabulary->machine_name, 'name')) {
+    $tree_callback = 'content_taxonomy_et_get_tree';
+  }
+}
+
+/**
+ * Implements hook_menu_alter().
+ */
+function content_taxonomy_et_menu_alter(&$items) {
+  // Localize autocompletes.
+  $items['taxonomy/autocomplete']['page callback'] = 'content_taxonomy_et_autocomplete';
+  if (isset($items['autocomplete_deluxe/taxonomy'])) {
+    $items['autocomplete_deluxe/taxonomy']['page callback'] = 'content_taxonomy_et_autocomplete_deluxe';
+  }
+}
+
+/**
+ * Implements hook_field_widget_form_alter().
+ */
+function content_taxonomy_et_field_widget_form_alter(&$element, &$form_state, $context) {
+  // Change validation callback for autocompletes in order to fix the
+  // retrieving of terms in the right language.
+  if (in_array($context['instance']['widget']['type'], array('taxonomy_autocomplete', 'autocomplete_deluxe_taxonomy'))) {
+    foreach ($element['#element_validate'] as $key => $validate_callback) {
+      if ($validate_callback == 'taxonomy_autocomplete_validate') {
+        $element['#element_validate'][$key] = 'content_taxonomy_et_autocomplete_validate';
+        break;
+      }
+    }
+  }
+}
+
+/**
+ * Replacement for taxonomy_get_tree().
+ *
+ * Exchanges the term name property with the title field value in the current
+ * language.
+ */
+function content_taxonomy_et_get_tree($vid, $parent = 0, $max_depth = NULL, $load_entities = FALSE) {
+  global $language;
+
+  $children = &drupal_static(__FUNCTION__, array());
+  $parents = &drupal_static(__FUNCTION__ . ':parents', array());
+  $terms = &drupal_static(__FUNCTION__ . ':terms', array());
+
+  // We cache trees, so it's not CPU-intensive to call taxonomy_get_tree() on a
+  // term and its children, too.
+  if (!isset($children[$vid])) {
+    $children[$vid] = array();
+    $parents[$vid] = array();
+    $terms[$vid] = array();
+
+    $query = db_select('taxonomy_term_data', 't');
+    $query->join('taxonomy_term_hierarchy', 'h', 'h.tid = t.tid');
+    $query->leftJoin('field_data_name_field', 'fd', 'fd.entity_id = t.tid AND fd.entity_type = :type AND fd.language = :language',
+      array(':type' => 'taxonomy_term', ':language' => $language->language));
+    $result = $query
+      ->addTag('translatable')
+      ->addTag('term_access')
+      ->fields('t')
+      ->fields('h', array('parent'))
+      ->fields('fd', array('name_field_value'))
+      ->condition('t.vid', $vid)
+      ->orderBy('t.weight')
+      ->orderBy('t.name')
+      ->execute();
+
+    foreach ($result as $term) {
+      $children[$vid][$term->parent][] = $term->tid;
+      $parents[$vid][$term->tid][] = $term->parent;
+      $terms[$vid][$term->tid] = $term;
+    }
+  }
+
+  // Load full entities, if necessary. The entity controller statically
+  // caches the results.
+  if ($load_entities) {
+    $term_entities = taxonomy_term_load_multiple(array_keys($terms[$vid]));
+  }
+
+  $max_depth = (!isset($max_depth)) ? count($children[$vid]) : $max_depth;
+  $tree = array();
+
+  // Keeps track of the parents we have to process, the last entry is used
+  // for the next processing step.
+  $process_parents = array();
+  $process_parents[] = $parent;
+
+  // Loops over the parent terms and adds its children to the tree array.
+  // Uses a loop instead of a recursion, because it's more efficient.
+  while (count($process_parents)) {
+    $parent = array_pop($process_parents);
+    // The number of parents determines the current depth.
+    $depth = count($process_parents);
+    if ($max_depth > $depth && !empty($children[$vid][$parent])) {
+      $has_children = FALSE;
+      $child = current($children[$vid][$parent]);
+      do {
+        if (empty($child)) {
+          break;
+        }
+        $term = $load_entities ? $term_entities[$child] : $terms[$vid][$child];
+        if (isset($parents[$vid][$term->tid])) {
+          // Clone the term so that the depth attribute remains correct
+          // in the event of multiple parents.
+          $term = clone $term;
+        }
+
+        // Use translation if it exists in the name property.
+        if (!empty($term->name_field_value)) {
+          $term->name = $term->name_field_value;
+        }
+
+        $term->depth = $depth;
+        unset($term->parent);
+        $term->parents = $parents[$vid][$term->tid];
+        $tree[] = $term;
+        if (!empty($children[$vid][$term->tid])) {
+          $has_children = TRUE;
+
+          // We have to continue with this parent later.
+          $process_parents[] = $parent;
+          // Use the current term as parent for the next iteration.
+          $process_parents[] = $term->tid;
+
+          // Reset pointers for child lists because we step in there more often
+          // with multi parents.
+          reset($children[$vid][$term->tid]);
+          // Move pointer so that we get the correct term the next time.
+          next($children[$vid][$parent]);
+          break;
+        }
+      } while ($child = next($children[$vid][$parent]));
+
+      if (!$has_children) {
+        // We processed all terms in this hierarchy-level, reset pointer
+        // so that this function works the next time it gets called.
+        reset($children[$vid][$parent]);
+      }
+    }
+  }
+
+  return $tree;
+}
+
+/**
+ * Replacement for form validate handler taxonomy_autocomplete_validate().
+ *
+ * Uses content_taxonomy_et_get_first_possible_term() to retrieve the right term
+ * in the right language, instead of term_load_multiple().
+ */
+function content_taxonomy_et_autocomplete_validate($element, &$form_state) {
+  // Autocomplete widgets do not send their tids in the form, so we must detect
+  // them here and process them independently.
+  $value = array();
+  if ($tags = $element['#value']) {
+    // Collect candidate vocabularies.
+    $field = field_widget_field($element, $form_state);
+    $vocabularies = array();
+    foreach ($field['settings']['allowed_values'] as $tree) {
+      if ($vocabulary = taxonomy_vocabulary_machine_name_load($tree['vocabulary'])) {
+        $vocabularies[$vocabulary->vid] = $vocabulary;
+      }
+    }
+
+    // Translate term names into actual terms.
+    $typed_terms = drupal_explode_tags($tags);
+    foreach ($typed_terms as $typed_term) {
+      // See if the term exists in the chosen vocabulary and return the tid;
+      // otherwise, create a new 'autocreate' term for insert/update.
+      $term = content_taxonomy_et_get_first_possible_term(trim($typed_term), array_keys($vocabularies));
+      if (!$term) {
+        $vocabulary = reset($vocabularies);
+        $term = array(
+          'tid' => 'autocreate',
+          'vid' => $vocabulary->vid,
+          'name' => $typed_term,
+          'vocabulary_machine_name' => $vocabulary->machine_name,
+        );
+      }
+      $value[] = (array)$term;
+    }
+  }
+
+  form_set_value($element, $value, $form_state);
+}
+
+/**
+ * Replacement for taxonomy_autocomplete().
+ *
+ *  Adds an additional left join to the translatable title field.
+ */
+function content_taxonomy_et_autocomplete($field_name = '', $tags_typed = '') {
+  global $language;
+
+  // If the request has a '/' in the search text, then the menu system will have
+  // split it into multiple arguments, recover the intended $tags_typed.
+  $args = func_get_args();
+  // Shift off the $field_name argument.
+  array_shift($args);
+  $tags_typed = implode('/', $args);
+
+  // Make sure the field exists and is a taxonomy field.
+  if (!($field = field_info_field($field_name)) || $field['type'] !== 'taxonomy_term_reference') {
+    // Error string. The JavaScript handler will realize this is not JSON and
+    // will display it as debugging information.
+    print t('Taxonomy field @field_name not found.', array('@field_name' => $field_name));
+    exit;
+  }
+
+  // The user enters a comma-separated list of tags. We only autocomplete the last tag.
+  $tags_typed = drupal_explode_tags($tags_typed);
+  $tag_last = drupal_strtolower(array_pop($tags_typed));
+
+  $term_matches = array();
+  if ($tag_last != '') {
+
+    // Part of the criteria for the query come from the field's own settings.
+    $vids = array();
+    $vocabularies = taxonomy_vocabulary_get_names();
+    foreach ($field['settings']['allowed_values'] as $tree) {
+      $vids[] = $vocabularies[$tree['vocabulary']]->vid;
+    }
+
+    $query = db_select('taxonomy_term_data', 't');
+    $query->addTag('translatable');
+    $query->addTag('term_access');
+
+    $query->leftJoin('field_data_name_field', 'fd', 'fd.entity_id = t.tid AND fd.entity_type = :type AND fd.language = :language',
+      array(':type' => 'taxonomy_term', ':language' => $language->language));
+
+    // Do not select already entered terms.
+    if (!empty($tags_typed)) {
+      $query->condition('t.name', $tags_typed, 'NOT IN');
+    }
+
+    // Select rows that match by term name.
+    $tags_return = $query
+      ->fields('t', array('tid', 'name'))
+      ->fields('fd', array('name_field_value'))
+      ->condition('t.vid', $vids)
+      ->condition(db_or()->condition('t.name', '%' . db_like($tag_last) . '%', 'LIKE')->condition('fd.name_field_value', '%' . db_like($tag_last) . '%', 'LIKE'))
+      ->range(0, 10)
+      ->execute();
+
+    $prefix = count($tags_typed) ? drupal_implode_tags($tags_typed) . ', ' : '';
+
+    foreach ($tags_return as $record) {
+      $name = !empty($record->name_field_value) ? $record->name_field_value : $record->name;
+      $n = $name;
+      // Term names containing commas or quotes must be wrapped in quotes.
+      if (strpos($name, ',') !== FALSE || strpos($name, '"') !== FALSE) {
+        $n = '"' . str_replace('"', '""', $name) . '"';
+      }
+      $term_matches[$prefix . $n] = check_plain($name);
+    }
+  }
+
+  drupal_json_output($term_matches);
+}
+
+
+/**
+ * Replacement for taxonomy_autocomplete_deluxe().
+ *
+ * Adds an additional left join to the translatable title field.
+ *
+ * @todo
+ *   Merge with content_taxonomy_et_autocomplete().
+ */
+function content_taxonomy_et_autocomplete_deluxe($field_name, $tags_typed = '', $limit = 10) {
+  global $language;
+
+  $field = field_info_field($field_name);
+  $use_synonyms = !empty($_GET['synonyms']);
+
+  // The user enters a comma-separated list of tags. We only autocomplete the last tag.
+  $tags_typed = drupal_explode_tags($tags_typed);
+  $tag_last = drupal_strtolower(array_pop($tags_typed));
+
+  $matches = array();
+
+  // Part of the criteria for the query come from the field's own settings.
+  $vids = array();
+  $vocabularies = taxonomy_vocabulary_get_names();
+  foreach ($field['settings']['allowed_values'] as $tree) {
+    // If the content taxonomy setting content_taxonomy_ignore_in_suggestions
+    // is set, then the vocabulary is ignored.
+    if (empty($tree['content_taxonomy_ignore_in_suggestions'])) {
+      $vids[] = $vocabularies[$tree['vocabulary']]->vid;
+    }
+  }
+
+  $query = db_select('taxonomy_term_data', 't');
+  $query->addTag('translatable');
+  $query->addTag('term_access');
+
+  $query->leftJoin('field_data_name_field', 'fd', 'fd.entity_id = t.tid AND fd.entity_type = :type AND fd.language = :language',
+    array(':type' => 'taxonomy_term', ':language' => $language->language));
+
+  if (module_exists('synonyms') && !empty($use_synonyms)) {
+    $query->leftJoin('field_data_synonyms_synonym', 'fdss', 'fdss.entity_id = t.tid');
+  }
+
+  if ($tag_last != '') {
+    // Do not select already entered terms.
+    if (!empty($tags_typed)) {
+      $query->condition('t.name', $tags_typed, 'NOT IN');
+    }
+    // Select rows that match by term name.
+    $query
+      ->fields('t', array('tid', 'name'))
+      ->fields('fd', array('name_field_value'))
+      ->condition('t.vid', $vids);
+
+    if (module_exists('synonyms') && !empty($use_synonyms)) {
+      $or = db_or();
+      $or->condition('fdss.synonyms_synonym_value', '%' . db_like($tag_last) . '%', 'LIKE');
+      $or->condition('t.name', '%' . db_like($tag_last) . '%', 'LIKE');
+      $or->condition('fd.name_field_value', '%' . db_like($tag_last) . '%', 'LIKE');
+      $query->condition($or);
+    }
+    else {
+      $query->condition(db_or()->condition('t.name', '%' . db_like($tag_last) . '%', 'LIKE')->condition('fd.name_field_value', '%' . db_like($tag_last) . '%', 'LIKE'));
+
+    }
+
+    if (isset($limit) && $limit > 0) {
+      $query->range(0, $limit);
+    }
+
+    $tags_return = $query->execute();
+  }
+  else {
+    $query
+      ->fields('t', array('tid', 'name'))
+      ->fields('fd', array('name_field_value'))
+      ->condition('t.vid', $vids);
+
+    if (isset($limit) && $limit > 0) {
+      $query->range(0, $limit);
+    }
+
+    $tags_return = $query->execute();
+  }
+
+  $prefix = count($tags_typed) ? drupal_implode_tags($tags_typed) . ', ' : '';
+
+  $term_matches = array();
+  foreach ($tags_return as $record) {
+    $name = !empty($record->name_field_value) ? $record->name_field_value : $record->name;
+    $n = $name;
+    // Term names containing commas or quotes must be wrapped in quotes.
+    if (strpos($name, ',') !== FALSE || strpos($name, '"') !== FALSE) {
+      $n = '"' . str_replace('"', '""', $name) . '"';
+    }
+    $term_matches[$prefix . $n] = check_plain($name);
+  }
+
+  drupal_json_output($term_matches);
+}
+
+/**
+ * Helper function that retrieves the first possible term object for a given
+ * term name and and array of vocabulary ids.
+ *
+ * Used in content_taxonomy_et_autocomplete_validate().
+ */
+function content_taxonomy_et_get_first_possible_term($term_name, $vids) {
+  global $language;
+
+  // EFQ does not work here, as we do not have OR condition possibilities.
+  $query = db_select('taxonomy_term_data', 't');
+  $query->addTag('translatable');
+  $query->addTag('term_access');
+  $query->leftJoin('field_data_name_field', 'fd', 'fd.entity_id = t.tid AND fd.entity_type = :type AND fd.language = :language',
+    array(':type' => 'taxonomy_term', ':language' => $language->language));
+  $query->fields('t', array('tid'))
+    ->condition('t.vid', $vids, 'IN')
+    ->condition(db_or()->condition('t.name', $term_name)->condition('fd.name_field_value', $term_name))
+    ->orderBy('t.tid', 'ASC')
+    ->execute();
+
+  $first_tid = $query->execute()->fetchColumn();
+  if (!empty($first_tid)) {
+    $term = taxonomy_term_load($first_tid);
+    return $term;
+  }
+  return FALSE;
+}

+ 4 - 6
sites/all/modules/contrib/fields/content_taxonomy/content_taxonomy_migrate.info

@@ -1,5 +1,5 @@
 name = Content Taxonomy Migrate
-description = Migration from Content Taxonomy to Term Reference Fields
+description = Migration from Content Taxonomy to Term Reference fields.
 
 core = 7.x
 package = Fields
@@ -7,11 +7,9 @@ package = Fields
 dependencies[] = taxonomy
 dependencies[] = content_migrate
 
-
-
-; Information added by drupal.org packaging script on 2013-02-13
-version = "7.x-1.0-beta2"
+; Information added by Drupal.org packaging script on 2016-03-22
+version = "7.x-1.0-rc1"
 core = "7.x"
 project = "content_taxonomy"
-datestamp = "1360767812"
+datestamp = "1458667740"
 

+ 79 - 8
sites/all/modules/contrib/fields/content_taxonomy/content_taxonomy_migrate.module

@@ -1,11 +1,18 @@
 <?php
+/**
+ * @file
+ *
+ */
 
+/**
+ * Implements hook_content_migrate_field_alter().
+ */
 function content_taxonomy_migrate_content_migrate_field_alter(&$field_value, $instance_value) {
   if ($field_value['type'] == 'content_taxonomy') {
     $field_value['type'] = 'taxonomy_term_reference';
     $field_value['module'] = 'taxonomy';
     
-    // transform field settings
+    // Transform field settings.
     $old_settings = $field_value['settings'];
     $vocabulary = taxonomy_vocabulary_load($old_settings['vid']);
     $field_value['settings'] = array();
@@ -14,38 +21,102 @@ function content_taxonomy_migrate_content_migrate_field_alter(&$field_value, $in
     $field_value['settings']['allowed_values'][0]['parent'] = $old_settings['parent'];
   }
 }
-  
+
+/**
+ * Implements hook_content_migrate_instance_alter().
+ */
 function content_taxonomy_migrate_content_migrate_instance_alter(&$instance_value, $field_value) {
+  // Only work on content_taxonomy fields.
+  if ($field_value['type'] != 'content_taxonomy') {
+    return;
+  }
+
+  // Track whether fields are managed by this module.
+  $fix_this_instance = FALSE;
+
+  // Fix the widget.
   if ($instance_value['widget_type'] == "content_taxonomy_autocomplete") {
+    $fix_this_instance = TRUE;
     $instance_value['widget_type'] = 'taxonomy_autocomplete';
     $instance_value['widget']['type'] = 'taxonomy_autocomplete';
     $instance_value['widget']['module'] = 'taxonomy';
   }
-  else if ($instance_value['widget_type'] == "content_taxonomy_options" || $instance_value['widget_type'] == "content_taxonomy_tree") {
+  elseif ($instance_value['widget_type'] == "content_taxonomy_options" || $instance_value['widget_type'] == "content_taxonomy_tree") {
+    $fix_this_instance = TRUE;
     $instance_value['widget_type'] = 'options_buttons';
     $instance_value['widget']['type'] = 'options_buttons';
     $instance_value['widget']['module'] = 'options';
   }
-  else if ($instance_value['widget_type'] == "content_taxonomy_select" || $instance_value['widget_type'] == 'content_taxonomy_hs') {
+  elseif ($instance_value['widget_type'] == "content_taxonomy_select" || $instance_value['widget_type'] == 'content_taxonomy_hs') {
+    $fix_this_instance = TRUE;
     $instance_value['widget_type'] = 'options_select';
     $instance_value['widget']['type'] = 'options_select';
     $instance_value['widget']['module'] = 'options';
   }
   
-  // fix formatter
+  // Fix the formatter.
   foreach ($instance_value['display'] as $context => $settings) {
     if ($instance_value['display'][$context]['type'] == 'default') {
+      $fix_this_instance = TRUE;
       $instance_value['display'][$context]['type'] = 'taxonomy_term_reference_plain';
     }
-    else if ($instance_value['display'][$context]['type'] == 'link') {
+    elseif ($instance_value['display'][$context]['type'] == 'link') {
+      $fix_this_instance = TRUE;
       $instance_value['display'][$context]['type'] = 'taxonomy_term_reference_link';
     }
+    if ($instance_value['display'][$context]['module'] == 'content_taxonomy_options') {
+      $fix_this_instance = TRUE;
+      $instance_value['display'][$context]['module'] = 'options';
+    }
+  }
+
+  // Fix the defaults.
+  if ($fix_this_instance && !empty($instance_value['default_value'])) {
+    foreach ($instance_value['default_value'] as $key => $default) {
+      // Need to check isset() and is_null() because the value could be NULL.
+      if (isset($instance_value['default_value'][$key]['value']) || is_null($instance_value['default_value'][$key]['value'])) {
+        // Remove any empty 'value' strings.
+        if (empty($instance_value['default_value'][$key]['value'])) {
+          unset($instance_value['default_value'][$key]['value']);
+        }
+
+        // Rename the 'value' string as 'tid'.
+        else {
+          $instance_value['default_value'][$key]['tid'] = $instance_value['default_value'][$key]['value'];
+          unset($instance_value['default_value'][$key]['value']);
+        }
+
+        // Remove a junk value carried over from D6.
+        if (isset($instance_value['default_value'][$key]['_error_element'])) {
+          unset($instance_value['default_value'][$key]['_error_element']);
+          $updated = TRUE;
+        }
+
+        // If the array is empty, just remove it.
+        if (empty($instance_value['default_value'][$key])) {
+          unset($instance_value['default_value'][$key]);
+        }
+      }
+
+      // There are no default values left.
+      if (empty($instance_value['default_value'])) {
+        $instance_value['default_value'] = NULL;
+      }
+    }
   }
 }
 
+/**
+ * Implements hook_content_migrate_data_record_alter().
+ */
 function content_taxonomy_migrate_content_migrate_data_record_alter(&$record, $field) {
-  // fill the taxonomy_index
   if ($field['type'] == 'taxonomy_term_reference') {
+    // Copy field_FIELD_NAME_value (D6) to field_FIELD_NAME_tid (D7).
+    if (isset($record[$field['field_name'] . '_value']) && !isset($record[$field['field_name'] . '_tid'])) {
+      $record[$field['field_name'] . '_tid'] = $record[$field['field_name'] . '_value'];
+    }
+
+    // Fill the taxonomy_index.
     if (variable_get('taxonomy_maintain_index_table', TRUE) && $field['storage']['type'] == 'field_sql_storage' && $record['entity_type'] == 'node') {
       if (isset($record[$field['field_name'] . "_tid"]) && $record[$field['field_name'] . "_tid"]) {
         $entity = node_load($record['entity_id']);
@@ -61,4 +132,4 @@ function content_taxonomy_migrate_content_migrate_data_record_alter(&$record, $f
       }
     }
   }
-}
+}

+ 58 - 0
sites/all/modules/contrib/form/honeypot/README.md

@@ -0,0 +1,58 @@
+
+# Honeypot
+
+[![Build Status](https://travis-ci.org/geerlingguy/drupal-honeypot.svg?branch=7.x-1.x)](https://travis-ci.org/geerlingguy/drupal-honeypot)
+
+
+## Installation
+
+To install this module, place it in your sites/all/modules folder and enable it
+on the modules page.
+
+
+## Configuration
+
+All settings for this module are on the Honeypot configuration page, under the
+Configuration section, in the Content authoring settings. You can visit the
+configuration page directly at admin/config/content/honeypot.
+
+Note that, when testing Honeypot on your website, make sure you're not logged in
+as an administrative user or user 1; Honeypot allows administrative users to
+bypass Honeypot protection, so by default, Honeypot will not be added to forms
+accessed by site administrators.
+
+
+## Use in Your Own Forms
+
+If you want to add honeypot to your own forms, or to any form through your own
+module's hook_form_alter's, you can simply place the following function call
+inside your form builder function (or inside a hook_form_alter):
+
+    honeypot_add_form_protection(
+      $form,
+      $form_state,
+      array('honeypot', 'time_restriction')
+    );
+
+Note that you can enable or disable either the honeypot field, or the time
+restriction on the form by including or not including the option in the array.
+
+
+## Testing
+
+Honeypot includes a `docker-compose.yml` file that can be used for testing purposes. To build a Drupal 8 environment for local testing, do the following:
+
+  1. Make sure you have Docker for Mac (or for whatever OS you're using) installed.
+  2. Add the following entry to your `/etc/hosts` file: `192.168.22.33   local.drupalhoneypot.com`
+  3. Run `docker-compose up -d` in this directory.
+  4. Install Drupal: `docker exec honeypot install-drupal 7.x` (optionally provide a version after `install-drupal`).
+  5. Link the honeypot module directory into the Drupal modules directory: `docker exec honeypot ln -s /opt/honeypot/ /var/www/drupalvm/drupal/web/sites/all/modules/honeypot`
+  6. Visit `http://local.drupalhoneypot.com/user` and log in using the admin credentials Drush displayed.
+
+> Note: If you're using a Mac, you may also need to perform additional steps to get the hostname working; see [Managing your hosts file](http://docs.drupalvm.com/en/latest/other/docker/#managing-your-hosts-file) in the Drupal VM documentation.
+
+
+## Credit
+
+The Honeypot module was originally developed by Jeff Geerling of Midwestern Mac,
+LLC (midwesternmac.com), and sponsored by Flocknote (flocknote.com).

+ 0 - 43
sites/all/modules/contrib/form/honeypot/README.txt

@@ -1,43 +0,0 @@
-
-Honeypot Module Readme
-----------------------
-
-
-Installation
-------------
-
-To install this module, place it in your sites/all/modules folder and enable it
-on the modules page.
-
-
-Configuration
--------------
-
-All settings for this module are on the Honeypot configuration page, under the
-Configuration section, in the Content authoring settings. You can visit the
-configuration page directly at admin/config/content/honeypot.
-
-Note that, when testing Honeypot on your website, make sure you're not logged in
-as an administrative user or user 1; Honeypot allows administrative users to
-bypass Honeypot protection, so by default, Honeypot will not be added to forms
-accessed by site administrators.
-
-
-Use in Your Own Forms
----------------------
-
-If you want to add honeypot to your own forms, or to any form through your own
-module's hook_form_alter's, you can simply place the following function call
-inside your form builder function (or inside a hook_form_alter):
-
-honeypot_add_form_protection($form, $form_state, array('honeypot', 'time_restriction'));
-
-Note that you can enable or disable either the honeypot field, or the time
-restriction on the form by including or not including the option in the array.
-
-
-Credit
-------
-
-The Honeypot module was originally developed by Jeff Geerling of Midwestern Mac,
-LLC (midwesternmac.com), and sponsored by flockNote (flocknote.com).

+ 32 - 0
sites/all/modules/contrib/form/honeypot/docker-compose.yml

@@ -0,0 +1,32 @@
+version: "3"
+
+services:
+
+  honeypot:
+    image: geerlingguy/drupal-vm
+    container_name: honeypot
+    ports:
+      - 80:80
+      - 443:443
+    privileged: true
+    extra_hosts:
+      local.drupalhoneypot.com: 127.0.0.1
+    dns:
+      - 8.8.8.8
+      - 8.8.4.4
+    volumes:
+      - ./:/opt/honeypot/:rw,delegated
+    command: /lib/systemd/systemd
+    networks:
+      honeypot:
+        ipv4_address: 192.168.22.33
+
+networks:
+
+  honeypot:
+    driver: bridge
+    driver_opts:
+      ip: 192.168.22.1
+    ipam:
+      config:
+        - subnet: "192.168.22.0/16"

+ 20 - 2
sites/all/modules/contrib/form/honeypot/honeypot.admin.inc

@@ -22,7 +22,9 @@ function honeypot_admin_form($form, &$form_state) {
     '#description' => t('Enable Honeypot protection for ALL forms on this site (it is best to only enable Honeypot for the forms you need below).'),
     '#default_value' => variable_get('honeypot_protect_all_forms', 0),
   );
-  $form['configuration']['honeypot_protect_all_forms']['#description'] .= '<br />' . t('<strong>Page caching will be disabled on any page where a form is present if the Honeypot time limit is not set to 0.</strong>');
+  if (!variable_get('honeypot_use_js_for_cached_pages', FALSE)) {
+    $form['configuration']['honeypot_protect_all_forms']['#description'] .= '<br />' . t('<strong>Page caching will be disabled on any page where a form is present if the Honeypot time limit is not set to 0.</strong>');
+  }
   $form['configuration']['honeypot_log'] = array(
     '#type' => 'checkbox',
     '#title' => t('Log blocked form submissions'),
@@ -46,7 +48,23 @@ function honeypot_admin_form($form, &$form_state) {
     '#size' => 5,
     '#field_suffix' => t('seconds'),
   );
-  $form['configuration']['honeypot_time_limit']['#description'] .= '<br />' . t('<strong>Page caching will be disabled if there is a form protected by time limit on the page.</strong>');
+  if (!variable_get('honeypot_use_js_for_cached_pages', FALSE)) {
+    $form['configuration']['honeypot_time_limit']['#description'] .= '<br />' . t('<strong>Page caching will be disabled if there is a form protected by time limit on the page.</strong>');
+  }
+
+  $form['configuration']['honeypot_use_js_for_cached_pages'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Use Javascript protection for cacheable pages. (experimental)'),
+    '#description' => t('Uses Javascript to preserve Page caching.'),
+    '#default_value' => variable_get('honeypot_use_js_for_cached_pages', FALSE),
+    '#states' => array(
+      // Hide this when time limit is disabled.
+      'invisible' => array(
+        'input[name="honeypot_time_limit"]' => array('value' => 0),
+      ),
+    ),
+  );
+  $form['configuration']['honeypot_use_js_for_cached_pages']['#description'] .= '<br />' . t('<strong>Warning: Users who have javascript disabled will need to confirm their form submission on the next page (if the Honeypot-enabled form is on a cacheable page).</strong>');
 
   // Honeypot Enabled forms.
   $form['enabled_forms'] = array(

+ 3 - 4
sites/all/modules/contrib/form/honeypot/honeypot.info

@@ -6,9 +6,8 @@ package = "Spam control"
 
 files[] = honeypot.test
 
-; Information added by Drupal.org packaging script on 2016-03-11
-version = "7.x-1.22"
+; Information added by Drupal.org packaging script on 2018-08-09
+version = "7.x-1.25"
 core = "7.x"
 project = "honeypot"
-datestamp = "1457672041"
-
+datestamp = "1533849190"

+ 2 - 2
sites/all/modules/contrib/form/honeypot/honeypot.install

@@ -68,8 +68,8 @@ function honeypot_uninstall() {
     }
   }
 
-  // Delete 'honeypot' directory from public file directory.
-  file_unmanaged_delete_recursive('public://honeypot');
+  // Delete 'honeypot' directory from files directory.
+  file_unmanaged_delete_recursive(honeypot_file_default_scheme() . '://honeypot');
 }
 
 /**

+ 125 - 8
sites/all/modules/contrib/form/honeypot/honeypot.module

@@ -78,7 +78,7 @@ function honeypot_form_alter(&$form, &$form_state, $form_id) {
   if (variable_get('honeypot_protect_all_forms', 0) && !in_array($form_id, $unprotected_forms)) {
     // Don't protect system forms - only admins should have access, and system
     // forms may be programmatically submitted by drush and other modules.
-    if (strpos($form_id, 'system_') === FALSE && strpos($form_id, 'search_') === FALSE && strpos($form_id, 'views_exposed_form_') === FALSE) {
+    if (preg_match('/[^a-zA-Z]system_/', $form_id) === 0 && preg_match('/[^a-zA-Z]search_/', $form_id) === 0 && preg_match('/[^a-zA-Z]views_exposed_form_/', $form_id) === 0) {
       honeypot_add_form_protection($form, $form_state, array('honeypot', 'time_restriction'));
     }
   }
@@ -135,6 +135,36 @@ function honeypot_rules_event_info() {
   );
 }
 
+/**
+ * Implements hook_library().
+ */
+function honeypot_library() {
+  $info = system_get_info('module', 'honeypot');
+  $version = $info['version'];
+
+  // Library for Honeypot JS.
+  $libraries['timestamp.js'] = array(
+    'title' => 'Javascript to support timelimit on cached pages.',
+    'version' => $version,
+    'js' => array(
+      array(
+        'type' => 'setting',
+        'data' => array(
+          'honeypot' => array(
+            'jsToken' => honeypot_get_signed_timestamp('js_token:' . mt_rand(0, 2147483647)),
+           ),
+         ),
+      ),
+      drupal_get_path('module', 'honeypot') . '/js/honeypot.js' => array(
+        'group' => JS_LIBRARY,
+        'weight' => 3,
+      ),
+    ),
+  );
+
+  return $libraries;
+}
+
 /**
  * Build an array of all the protected forms on the site, by form_id.
  *
@@ -233,8 +263,16 @@ function honeypot_add_form_protection(&$form, &$form_state, $options = array())
     );
 
     // Disable page caching to make sure timestamp isn't cached.
-    if (user_is_anonymous()) {
-      drupal_page_is_cacheable(FALSE);
+    if (user_is_anonymous() && drupal_page_is_cacheable()) {
+      // Use javascript implementation if this page should be cached.
+      if (variable_get('honeypot_use_js_for_cached_pages', FALSE)) {
+        $form['honeypot_time']['#default_value'] = 'no_js_available';
+        $form['honeypot_time']['#attached']['library'][] = array('honeypot', 'timestamp.js');
+        $form['#attributes']['class'][] = 'honeypot-timestamp-js';
+      }
+      else {
+        drupal_page_is_cacheable(FALSE);
+      }
     }
   }
 
@@ -261,7 +299,7 @@ function _honeypot_honeypot_validate($element, &$form_state) {
 /**
  * Validate honeypot's time restriction field.
  */
-function _honeypot_time_restriction_validate($element, &$form_state) {
+function _honeypot_time_restriction_validate(&$element, &$form_state) {
   if (!empty($form_state['programmed'])) {
     // Don't do anything if the form was submitted programmatically.
     return;
@@ -272,8 +310,43 @@ function _honeypot_time_restriction_validate($element, &$form_state) {
     return;
   }
 
-  // Get the time value.
-  $honeypot_time = honeypot_get_time_from_signed_timestamp($form_state['values']['honeypot_time']);
+  if ($form_state['values']['honeypot_time'] == 'no_js_available') {
+    // Set an error, but do not penalize the user as it might be a legitimate
+    // attempt.
+    form_set_error('', t('You seem to have javascript disabled. Please confirm your form submission.'));
+
+    if (variable_get('honeypot_log', 0)) {
+      $variables = array(
+        '%form'  => $form_state['values']['form_id'],
+      );
+      watchdog('honeypot', 'User tried to submit form %form without javascript enabled.', $variables);
+    }
+
+    // Update the value in $form_state and $element.
+    $form_state['values']['honeypot_time'] = honeypot_get_signed_timestamp(REQUEST_TIME);
+    $element['#value'] = $form_state['values']['honeypot_time'];
+    return;
+  }
+
+  $honeypot_time = FALSE;
+
+  // Update the honeypot_time for JS requests and get the $honeypot_time value.
+  if (strpos($form_state['values']['honeypot_time'], 'js_token:') === 0) {
+    $interval = _honeypot_get_interval_from_signed_js_value($form_state['values']['honeypot_time']);
+    if ($interval) {
+      // Set correct value for timestamp validation.
+      $honeypot_time = REQUEST_TIME - $interval;
+
+      // Update form_state and element values so they're correct.
+      $form_state['values']['honeypot_time'] = honeypot_get_signed_timestamp($honeypot_time);
+      $element['#value'] = $form_state['values']['honeypot_time'];
+    }
+  }
+  // Otherwise just get the $honeypot_time value.
+  else {
+    // Get the time value.
+    $honeypot_time = honeypot_get_time_from_signed_timestamp($form_state['values']['honeypot_time']);
+  }
 
   // Get the honeypot_time_limit.
   $time_limit = honeypot_get_time_limit($form_state['values']);
@@ -284,11 +357,43 @@ function _honeypot_time_restriction_validate($element, &$form_state) {
     _honeypot_log($form_state['values']['form_id'], 'honeypot_time');
     // Get the time limit again, since it increases after first failure.
     $time_limit = honeypot_get_time_limit($form_state['values']);
+    // Update the honeypot_time value in the form state and element.
     $form_state['values']['honeypot_time'] = honeypot_get_signed_timestamp(REQUEST_TIME);
+    $element['#value'] = $form_state['values']['honeypot_time'];
     form_set_error('', t('There was a problem with your form submission. Please wait @limit seconds and try again.', array('@limit' => $time_limit)));
   }
 }
 
+/**
+ * Returns an interval if the given javascript submitted value is valid.
+ *
+ * @param string $honeypot_time
+ *   The signed interval as submitted via javascript.
+ *
+ * @return int|FALSE
+ *   The interval in seconds if the token is valid, FALSE otherwise.
+ */
+function _honeypot_get_interval_from_signed_js_value($honeypot_time) {
+  $t = explode('|', $honeypot_time);
+
+  if (count($t) != 3) {
+    return FALSE;
+  }
+
+  $js_token = $t[0] . '|' . $t[1];
+  $token_check = honeypot_get_time_from_signed_timestamp($js_token);
+  if (!$token_check) {
+    return FALSE;
+  }
+
+  $interval = (int) $t[2];
+  if ($interval == 0) {
+    return FALSE;
+  }
+
+  return $interval;
+}
+
 /**
  * Log blocked form submissions.
  *
@@ -398,7 +503,7 @@ function honeypot_log_failure($form_id, $type) {
  *   The path to the honeypot.css file.
  */
 function honeypot_get_css_file_path() {
-  return variable_get('file_public_path', conf_path() . '/files') . '/honeypot/honeypot.css';
+  return honeypot_file_default_scheme() . '://honeypot/honeypot.css';
 }
 
 /**
@@ -408,7 +513,7 @@ function honeypot_get_css_file_path() {
  *   The honeypot element class name (e.g. 'url').
  */
 function honeypot_create_css($element_name) {
-  $path = 'public://honeypot';
+  $path = honeypot_file_default_scheme() . '://honeypot';
 
   if (!file_prepare_directory($path, FILE_CREATE_DIRECTORY)) {
     drupal_set_message(t('Unable to create Honeypot CSS directory, %path. Check the permissions on your files directory.', array('%path' => file_uri_target($path))), 'error');
@@ -487,3 +592,15 @@ function honeypot_get_time_from_signed_timestamp($signed_timestamp) {
 
   return $honeypot_time;
 }
+
+/**
+ * Gets the default file stream for honeypot.
+ *
+ * @return
+ *   'public', 'private' or any other file scheme defined as the default.
+ *
+ * @see file_default_scheme()
+ */
+function honeypot_file_default_scheme() {
+  return variable_get('honeypot_file_default_scheme', file_default_scheme());
+}

+ 55 - 0
sites/all/modules/contrib/form/honeypot/honeypot.test

@@ -366,6 +366,60 @@ class HoneypotCssTestCase extends DrupalWebTestCase {
     // Revert the honeypot element name back to the original.
     variable_set('honeypot_element_name', $original_element_name);
   }
+
+  /**
+   * Test CSS works when default file scheme is not public://
+   */
+  public function testHoneypotCssNonpublicFileSystem() {
+    variable_set('file_default_scheme', 'private');
+
+    $honeypot_css = honeypot_get_css_file_path();
+
+    // Delete the Honeypot CSS file (if it exists).
+    file_unmanaged_delete($honeypot_css);
+
+    // Make sure the Honeypot CSS file doesn't exist.
+    $this->assertFalse(file_exists($honeypot_css));
+
+    // Run cron.
+    honeypot_cron();
+
+    // Make sure the Honeypot CSS file exists.
+    $this->assertTrue(file_exists($honeypot_css));
+  }
+
+  /**
+   * Test CSS file availability.
+   */
+  public function testHoneypotCssAvailability() {
+    // Public CSS file can be consumed.
+    variable_set('file_default_scheme', 'public');
+    if ($wrapper = file_stream_wrapper_get_instance_by_uri(honeypot_get_css_file_path())) {
+      $url = $wrapper->getExternalUrl();
+    }
+    $this->drupalGet($url);
+    $this->assertResponse(200);
+
+
+    // Private CSS file can not be consumed.
+    variable_set('file_default_scheme', 'private');
+    honeypot_cron();
+    if ($wrapper = file_stream_wrapper_get_instance_by_uri(honeypot_get_css_file_path())) {
+      $url = $wrapper->getExternalUrl();
+    }
+    $this->drupalGet($url);
+    $this->assertNoResponse(200);
+
+    // Site default is private, but override honeypot's to public to consume.
+    variable_set('honeypot_file_default_scheme', 'public');
+    honeypot_cron();
+    if ($wrapper = file_stream_wrapper_get_instance_by_uri(honeypot_get_css_file_path())) {
+      $url = $wrapper->getExternalUrl();
+    }
+    $this->drupalGet($url);
+    $this->assertResponse(200);
+  }
+
 }
 
 /**
@@ -423,4 +477,5 @@ class HoneypotTriggerTestCase extends DrupalWebTestCase {
     $this->drupalGet('node');
     $this->assertText(t('has been banned'), 'User banned successfully.');
   }
+
 }

+ 37 - 0
sites/all/modules/contrib/form/honeypot/js/honeypot.js

@@ -0,0 +1,37 @@
+(function ($) {
+
+  Drupal.honeypot = {};
+  Drupal.honeypot.timestampJS = new Date();
+
+  Drupal.behaviors.honeypotJS = {
+    attach: function (context, settings) {
+      $('form.honeypot-timestamp-js').once('honeypot-timestamp').bind('submit', function() {
+        var $honeypotTime = $(this).find('input[name="honeypot_time"]');
+        $honeypotTime.attr('value', Drupal.behaviors.honeypotJS.getIntervalTimestamp());
+      });
+    },
+    getIntervalTimestamp: function() {
+      var now = new Date();
+      var interval = Math.floor((now - Drupal.honeypot.timestampJS) / 1000);
+      return Drupal.settings.honeypot.jsToken + '|' + interval;
+    }
+  };
+
+  if (Drupal.ajax && Drupal.ajax.prototype && Drupal.ajax.prototype.beforeSubmit) {
+    Drupal.ajax.prototype.honeypotOriginalBeforeSubmit = Drupal.ajax.prototype.beforeSubmit;
+    Drupal.ajax.prototype.beforeSubmit = function (form_values, element, options) {
+      if (this.form && $(this.form).hasClass('honeypot-timestamp-js')) {
+        for (key in form_values) {
+          // Inject the right interval timestamp.
+          if (form_values[key].name == 'honeypot_time' && form_values[key].value == 'no_js_available') {
+            form_values[key].value = Drupal.behaviors.honeypotJS.getIntervalTimestamp();
+          }
+        }
+      }
+
+      // Call the original function in case someone else has overridden it.
+      return Drupal.ajax.prototype.honeypotOriginalBeforeSubmit(form_values, element, options);
+    }
+  }
+
+}(jQuery));

+ 3 - 4
sites/all/modules/contrib/form/honeypot/tests/honeypot_test.info

@@ -4,9 +4,8 @@ core = 7.x
 package = Testing
 hidden = true
 
-; Information added by Drupal.org packaging script on 2016-03-11
-version = "7.x-1.22"
+; Information added by Drupal.org packaging script on 2018-08-09
+version = "7.x-1.25"
 core = "7.x"
 project = "honeypot"
-datestamp = "1457672041"
-
+datestamp = "1533849190"

+ 1 - 0
sites/all/modules/contrib/mail/mailgun/README.txt

@@ -0,0 +1 @@
+Mailgun module documentation is here: https://www.drupal.org/node/2547591.

+ 12 - 0
sites/all/modules/contrib/mail/mailgun/composer.json

@@ -0,0 +1,12 @@
+{
+  "name": "drupal/mailgun",
+  "description": "Provides integration with the Mailgun PHP library.",
+  "type": "drupal-module",
+  "homepage": "https://www.drupal.org/project/mailgun",
+  "license": "GPL-2.0+",
+  "require": {
+    "guzzlehttp/psr7": "~1.3",
+    "php-http/guzzle6-adapter": "^1.0",
+    "mailgun/mailgun-php": "~2.0"
+  }
+}

+ 112 - 59
sites/all/modules/contrib/mail/mailgun/mailgun.admin.inc

@@ -16,11 +16,12 @@
  * @return array
  *   An array containing form items to place on the module settings page.
  */
-function mailgun_admin_settings($form, &$form_state) {
-  $library = libraries_detect('mailgun');
-
-  if (!$library['installed']) {
-    drupal_set_message(t('The Mailgun PHP library is not installed. Please see <a href="@url">documentation</a> for more information.', array('@url' => url('https://www.drupal.org/node/2547591'))), 'error');
+function mailgun_admin_settings(array $form, array &$form_state) {
+  // Check if the Mailgun PHP library is installed.
+  if (!mailgun_check_library()) {
+    drupal_set_message(t('The Mailgun PHP library is not installed. Please see Installation section in the !link.', array(
+      '!link' => l(t('documentation'), MAILGUN_DOCUMENTATION_LINK),
+    )), 'error');
   }
 
   $key = variable_get('mailgun_api_key', '');
@@ -28,7 +29,9 @@ function mailgun_admin_settings($form, &$form_state) {
   $form['mailgun_api_key'] = array(
     '#title' => t('Mailgun API key'),
     '#type' => 'textfield',
-    '#description' => t('Get your Secret API key from the <a href="@url">Mailgun dashboard</a>.', array('@url' => url('https://mailgun.com/app/dashboard'))),
+    '#description' => t('Get your Secret API key from the !link.', array(
+      '!link' => l(t('Mailgun dashboard'), MAILGUN_DASHBOARD_LINK),
+    )),
     '#default_value' => $key,
     '#required' => TRUE,
   );
@@ -37,23 +40,25 @@ function mailgun_admin_settings($form, &$form_state) {
   if (!empty($key)) {
     try {
       $client = mailgun_get_client($key);
-    } catch (Exception $e) {
-      watchdog('mailgun', 'An exception occurred. @code: @message', array('@code' => $e->getCode(), '@message' => $e->getMessage()), WATCHDOG_WARNING, 'admin/config/system/mailgun');
+    }
+    catch (Exception $e) {
+      watchdog('mailgun', 'An exception occurred. @code: @message', array(
+        '@code' => $e->getCode(),
+        '@message' => $e->getMessage(),
+      ), WATCHDOG_WARNING, MAILGUN_ADMIN_PAGE);
       drupal_set_message(t('Mailgun: %message', array('%message' => $e->getMessage())), 'error');
     }
   }
 
-  // Display settings only when a valid API key is present and client is active
+  // Display settings only when a valid API key is present and client is active.
   if ($client) {
     $domain_options = array(
       '_sender' => t('Get domain from sender address'),
     );
-    $domains = array();
-    $result = $client->get('domains');
-    if ($result && $result->http_response_code == 200) {
-      foreach ($result->http_response_body->items as $domain) {
-        $domains[$domain->name] = $domain;
-        $domain_options[$domain->name] = $domain->name;
+    $result = $client->domains()->index();
+    if (!empty($result)) {
+      foreach ($result->getDomains() as $domain) {
+        $domain_options[$domain->getName()] = $domain->getName();
       }
     }
 
@@ -61,7 +66,7 @@ function mailgun_admin_settings($form, &$form_state) {
       '#title' => t('Domain'),
       '#type' => 'select',
       '#options' => $domain_options,
-      '#description' => t('Mails will be sent using this domain'),
+      '#description' => t('Mails will be sent using this domain.'),
       '#default_value' => variable_get('mailgun_domain', '_sender'),
     );
 
@@ -69,69 +74,96 @@ function mailgun_admin_settings($form, &$form_state) {
       '#title' => t('Test mode'),
       '#type' => 'checkbox',
       '#default_value' => variable_get('mailgun_test', FALSE),
-      '#description' => t('Enables sending in test mode'),
-    );
-
-    $form['mailgun_queue'] = array(
-      '#title' => t('Queue mails'),
-      '#type' => 'checkbox',
-      '#description' => t('Mails will be queued and sent during cron runs. Useful for sending a large number of emails.'),
-      '#default_value' => variable_get('mailgun_queue', FALSE),
+      '#description' => t('Mailgun will accept the message but will not send it. This is useful for testing purposes.'),
     );
 
     $form['mailgun_log'] = array(
       '#title' => t('Log mails'),
       '#type' => 'checkbox',
-      '#description' => t('Log mails sent through Mailgun. Should not be enabled on production sites. Messages fail to send will be logged regardless of this setting.'),
+      '#description' => t('Log all mails sent through Mailgun. Messages fail to send will be logged regardless of this setting.'),
       '#default_value' => variable_get('mailgun_log', FALSE),
     );
 
-    $formats = array('_none' => t('- None -'));
-    foreach (filter_formats() as $format) {
-      if ($format->format == 'php_code') {
-        continue;
-      }
-      $formats[$format->format] = t($format->name);
-    }
-    $form['mailgun_format'] = array(
-      '#title' => t('Text format'),
-      '#type' => 'select',
-      '#description' => t('Specify an additional text format to filter the message through before sending the email.'),
-      '#options' => $formats,
-      '#default_value' => variable_get('mailgun_format', '_none'),
+    $form['extra'] = array(
+      '#type' => 'fieldset',
+      '#title' => t('Additional settings'),
+      '#description' => t('These default settings apply to messages sent using Mailgun and may be overriden on a per-message basis.'),
+      '#collapsible' => TRUE,
+      '#collapsed' => TRUE,
     );
 
-    $form['defaults'] = array(
+    // We have the same options for all settings.
+    $options = array(
+      'default' => t('Use default setting'),
+      'enabled' => t('Enabled'),
+      'disabled' => t('Disabled'),
+    );
+
+    $form['extra']['tracking'] = array(
       '#type' => 'fieldset',
-      '#title' => t('Default settings'),
-      '#description' => t('These default settings apply to messages sent using Mailgun and may be overriden on a per-message basis.'),
-      '#collapsible' => FALSE,
-      '#collapsed' => FALSE,
+      '#title' => t('Tracking'),
     );
 
-    $form['defaults']['mailgun_tracking'] = array(
+    $form['extra']['tracking']['mailgun_tracking'] = array(
       '#title' => t('Enable tracking'),
       '#type' => 'select',
-      '#options' => array('default' => t('Use default setting'), 'enabled' => t('Enabled'), 'disabled' => t('Disabled')),
-      '#description' => t('Whether to enable event tracking by default or not. See <a href="@url">Tracking Messages</a> for details.', array('@url' => url('https://documentation.mailgun.com/user_manual.html#tracking-messages'))),
+      '#options' => $options,
+      '#description' => t('Whether to enable event tracking by default or not. See !link for details.', array(
+        '!link' => l(t('Tracking Messages'), 'https://documentation.mailgun.com/user_manual.html#tracking-messages'),
+      )),
       '#default_value' => variable_get('mailgun_tracking', 'default'),
     );
 
-    $form['defaults']['mailgun_tracking_clicks'] = array(
+    $form['extra']['tracking']['mailgun_tracking_clicks'] = array(
       '#title' => t('Enable click tracking'),
       '#type' => 'select',
-      '#options' => array('default' => t('Use default setting'), 'enabled' => t('Enabled'), 'disabled' => t('Disabled')),
+      '#options' => $options,
       '#description' => t('Whether to enable click tracking by default or not.'),
       '#default_value' => variable_get('mailgun_tracking_clicks', 'default'),
     );
 
-    $form['defaults']['mailgun_tracking_opens'] = array(
+    $form['extra']['tracking']['mailgun_tracking_opens'] = array(
       '#title' => t('Enable open tracking'),
       '#type' => 'select',
-      '#options' => array('default' => t('Use default setting'), 'enabled' => t('Enabled'), 'disabled' => t('Disabled')),
+      '#options' => $options,
       '#description' => t('Whether to enable open tracking by default or not.'),
       '#default_value' => variable_get('mailgun_tracking_opens', 'default'),
     );
+
+    $formats = array('_none' => t('- None -'));
+    foreach (filter_formats() as $format) {
+      if ($format->format === 'php_code') {
+        continue;
+      }
+      $formats[$format->format] = $format->name;
+    }
+    $form['extra']['mailgun_format'] = array(
+      '#title' => t('Text format'),
+      '#type' => 'select',
+      '#description' => t('Specify an additional text format to filter the message through before sending the email.'),
+      '#options' => $formats,
+      '#default_value' => variable_get('mailgun_format', '_none'),
+    );
+
+    $form['extra']['mailgun_queue'] = array(
+      '#title' => t('Queue mails'),
+      '#type' => 'checkbox',
+      '#description' => t('Mails will be queued and sent during cron runs. Useful for sending a large number of emails.'),
+      '#default_value' => variable_get('mailgun_queue', FALSE),
+    );
+
+    $form['extra']['mailgun_tagging_mailkey'] = array(
+      '#type' => 'checkbox',
+      '#title' => t('Enable tags by mail key'),
+      '#description' => t('See !url for details.', array(
+        '!url' => l(t('Tagging'), 'https://documentation.mailgun.com/user_manual.html#tagging', array(
+          'attributes' => array(
+            'onclick' => "target='_blank'",
+          ),
+        )),
+      )),
+      '#default_value' => variable_get('mailgun_tagging_mailkey', TRUE),
+    );
   }
 
   $form = system_settings_form($form);
@@ -150,11 +182,21 @@ function mailgun_admin_settings_validate($form, &$form_state) {
     // The API key has changed. Perform validation.
     $form_state['values']['mailgun_api_key'] = trim($form_state['values']['mailgun_api_key']);
     $client = mailgun_get_client($form_state['values']['mailgun_api_key']);
+
+    if ($client === FALSE) {
+      drupal_set_message(t('Could not connect to Mailgun API. Please check your settings'), 'warning');
+      return;
+    }
+
     try {
-      $result = $client->get('domains');
+      $client->domains()->index();
       drupal_set_message(t('Your API key has been successfully validated.'));
-    } catch (Exception $e) {
-      form_set_error('mailgun_api_key', t('An exception occurred. @code: @message', array('@code' => $e->getCode(), '@message' => $e->getMessage())));
+    }
+    catch (Exception $e) {
+      form_set_error('mailgun_api_key', t('An exception occurred. @code: @message', array(
+        '@code' => $e->getCode(),
+        '@message' => $e->getMessage(),
+      )));
     }
   }
 }
@@ -173,9 +215,11 @@ function mailgun_test_form($form, &$form_state) {
     '#required' => TRUE,
   );
 
-  $message = "Howdy!\n\nIf this e-mail is displayed correctly and delivered sound and safe, congrats! You have successfully configured Mailgun.";
-  $message .= ' Visit the <a href="@project">project page</a> to contribute or read <a href="@documentation">documentation</a> to learn more.';
-  $message = t($message, array('@project' => url('https://www.drupal.org/project/mailgun'), '@documentation' => url('https://www.drupal.org/node/2547591')));
+  $message = "Howdy!\n\nIf this e-mail is displayed correctly and delivered sound and safe, congrats! You have successfully configured Mailgun. ";
+  $message .= t('Visit the !project to contribute or read !documentation to learn more.', array(
+    '!project' => l(t('project page'), 'https://www.drupal.org/project/mailgun'),
+    '!documentation' => l(t('documentation'), MAILGUN_DOCUMENTATION_LINK),
+  ));
   $form['message'] = array(
     '#type' => 'textarea',
     '#title' => t('Message'),
@@ -196,7 +240,7 @@ function mailgun_test_form($form, &$form_state) {
   );
   $form['cancel'] = array(
     '#type' => 'link',
-    '#href' => 'admin/config/system/mailgun',
+    '#href' => MAILGUN_ADMIN_PAGE,
     '#title' => t('Cancel'),
   );
 
@@ -205,17 +249,26 @@ function mailgun_test_form($form, &$form_state) {
 
 /**
  * Form submission handler for mailgun_test_form().
+ *
  * Send the test e-mail.
  */
 function mailgun_test_form_submit($form, &$form_state) {
   $to = $form_state['values']['to'];
+  $body = explode('\n', $form_state['values']['message']);
+
   $params = array(
-    'message' => $form_state['values']['message'],
+    'message' => $body,
     'attachment' => $form_state['values']['attachment'],
   );
   $site_name = variable_get('site_name', '');
   $default_from = variable_get('site_mail', ini_get('sendmail_from'));
   $from = (!empty($site_name)) ? $site_name . ' <' . $default_from . '>' : $default_from;
   $result = drupal_mail('mailgun', 'test', $to, $GLOBALS['language'], $params, $from);
-  drupal_set_message(t('Test email sent from %from to %to. If you have the "Log mails" setting enabled, check the <a href="@url">database log</a> for details.', array('%from' => $result['from'], '%to' => $result['to'], '@url' => url('admin/reports/dblog'))), 'status');
+
+  drupal_set_message(t('Test email sent from %from to %to. If you have the "Log mails" setting enabled, check the <a href="@url">database log</a> for details.',
+    array(
+      '%from' => $result['from'],
+      '%to' => $result['to'],
+      '@url' => url('admin/reports/dblog'),
+    )), 'status');
 }

+ 8 - 7
sites/all/modules/contrib/mail/mailgun/mailgun.info

@@ -1,16 +1,17 @@
+package = Mail
 name = Mailgun
 description = "Provides integration with Mailgun's email sending API."
 core = 7.x
-package = Mailgun
-
-dependencies[] = libraries
-dependencies[] = mailsystem
+php = 5.5
+configure = admin/config/system/mailgun
 
 files[] = mailgun.mail.inc
 
-; Information added by Drupal.org packaging script on 2016-06-26
-version = "7.x-1.6+0-dev"
+dependencies[] = mailsystem (>=2.x)
+
+; Information added by Drupal.org packaging script on 2017-12-06
+version = "7.x-1.6"
 core = "7.x"
 project = "mailgun"
-datestamp = "1466914444"
+datestamp = "1512586085"
 

+ 38 - 2
sites/all/modules/contrib/mail/mailgun/mailgun.install

@@ -1,6 +1,5 @@
 <?php
 
-
 /**
  * @file
  * Install, update and uninstall functions for the Mailgun module.
@@ -11,7 +10,19 @@
  */
 function mailgun_uninstall() {
   // Delete variables.
-  $variables = array('mailgun_api_key', 'mailgun_from_action', 'mailgun_from_name', 'mailgun_from_mail', 'mailgun_tracking', 'mailgun_tracking_clicks', 'mailgun_tracking_opens', 'mailgun_queue', 'mailgun_log', 'mailgun_format');
+  $variables = array(
+    'mailgun_api_key',
+    'mailgun_from_action',
+    'mailgun_from_name',
+    'mailgun_from_mail',
+    'mailgun_tracking',
+    'mailgun_tracking_clicks',
+    'mailgun_tracking_opens',
+    'mailgun_queue',
+    'mailgun_log',
+    'mailgun_format',
+    'mailgun_tagging_mailkey',
+  );
   foreach ($variables as $variable) {
     variable_del($variable);
   }
@@ -32,3 +43,28 @@ function mailgun_disable() {
   mailsystem_clear(array('mailgun_test' => 'MailgunMailSystem'));
   watchdog('mailgun', 'Mailgun has been disabled.');
 }
+
+/**
+ * Implements hook_requirements().
+ */
+function mailgun_requirements($phase) {
+  // Ensure translations don't break during installation.
+  $t = get_t();
+
+  $requirements = array();
+
+  if ($phase === 'runtime') {
+    $requirements['mailgun']['title'] = $t('Mailgun');
+
+    if (mailgun_check_library()) {
+      $requirements['mailgun']['value'] = $t('The Mailgun library is installed correctly.');
+      $requirements['mailgun']['severity'] = REQUIREMENT_OK;
+    }
+    else {
+      $requirements['mailgun']['value'] = $t('The Mailgun library has not been installed correctly.');
+      $requirements['mailgun']['severity'] = REQUIREMENT_ERROR;
+    }
+  }
+
+  return $requirements;
+}

+ 77 - 23
sites/all/modules/contrib/mail/mailgun/mailgun.mail.inc

@@ -2,14 +2,14 @@
 
 /**
  * @file
- * Implements Mailgun as a Drupal MailSystemInterface
+ * Implements Mailgun as a Drupal MailSystemInterface.
  */
 
 /**
  * Modify the Drupal mail system to use Mailgun when sending e-mails.
  */
 class MailgunMailSystem implements MailSystemInterface {
-  
+
   /**
    * Concatenate and wrap the e-mail body for either plain-text or HTML e-mails.
    *
@@ -25,26 +25,34 @@ class MailgunMailSystem implements MailSystemInterface {
       $message['body'] = implode("\n\n", $message['body']);
     }
 
-    // If a text format is specified in Mailgun settings, run the message through it.
+    // Run the message through text format if specified in Mailgun settings.
     $format = variable_get('mailgun_format', '_none');
-    if ($format != '_none') {
+    if ($format !== '_none') {
       $message['body'] = check_markup($message['body'], $format);
     }
 
+    // Wrap body with theme function.
+    $message['body'] = theme('mailgun_message', array(
+      'subject' => $message['subject'],
+      'body' => $message['body'],
+      'message' => $message,
+    ));
+
     return $message;
   }
 
   /**
    * Send the e-mail message.
    *
-   * @see drupal_mail()
-   * @see https://documentation.mailgun.com/api-sending.html#sending
-   *
    * @param array $message
-   *   A message array, as described in hook_mail_alter(). $message['params'] may contain additional parameters. See mailgun_send().
+   *   A message array, as described in hook_mail_alter(). $message['params']
+   *   may contain additional parameters. See mailgun_send().
    *
    * @return bool
    *   TRUE if the mail was successfully accepted or queued, FALSE otherwise.
+   *
+   * @see drupal_mail()
+   * @see https://documentation.mailgun.com/api-sending.html#sending
    */
   public function mail(array $message) {
     // Build the Mailgun message array.
@@ -52,7 +60,7 @@ class MailgunMailSystem implements MailSystemInterface {
       'from' => $message['from'],
       'to' => $message['to'],
       'subject' => $message['subject'],
-      'text' => check_plain($message['body']),
+      'text' => drupal_html_to_text($message['body']),
       'html' => $message['body'],
     );
 
@@ -72,23 +80,32 @@ class MailgunMailSystem implements MailSystemInterface {
     $params = array();
 
     // Populate default settings.
-    if ($variable = variable_get('mailgun_tracking', 'default') != 'default') {
-      $params['o:tracking'] = $variable;
+    if (($variable = variable_get('mailgun_tracking', 'default')) !== 'default') {
+      $params['o:tracking'] = ($variable === 'enabled');
     }
-    if ($variable = variable_get('mailgun_tracking_clicks', 'default') != 'default') {
-      $params['o:tracking-clicks'] = $variable;
+    if (($variable = variable_get('mailgun_tracking_clicks', 'default')) !== 'default') {
+      $params['o:tracking-clicks'] = ($variable === 'enabled');
     }
-    if ($variable = variable_get('mailgun_tracking_opens', 'default') != 'default') {
-      $params['o:tracking-opens'] = $variable;
+    if (($variable = variable_get('mailgun_tracking_opens', 'default')) !== 'default') {
+      $params['o:tracking-opens'] = ($variable === 'enabled');
     }
 
-    // For a full list of allowed parameters, see: https://documentation.mailgun.com/api-sending.html#sending.
-    $allowed_params = array('o:tag', 'o:campaign', 'o:deliverytime', 'o:dkim', 'o:testmode', 'o:tracking', 'o:tracking-clicks', 'o:tracking-opens');
+    // For a full list of allowed parameters,
+    // see: https://documentation.mailgun.com/api-sending.html#sending.
+    $allowed_params = array(
+      'o:tag',
+      'o:campaign',
+      'o:deliverytime',
+      'o:dkim',
+      'o:testmode',
+      'o:tracking',
+      'o:tracking-clicks',
+      'o:tracking-opens',
+    );
     foreach ($message['params'] as $key => $value) {
       // Check if it's one of the known parameters.
-      $allowed = (in_array($key, $allowed_params)) ? TRUE : FALSE;
-      // If more options become available but are not yet supported by the module, uncomment the following line.
-      //$allowed = (substr($key, 0, 2) == 'o:') ? TRUE : FALSE;
+      $allowed = in_array($key, $allowed_params) ? TRUE : FALSE;
+
       if ($allowed) {
         $params[$key] = $value;
       }
@@ -96,14 +113,51 @@ class MailgunMailSystem implements MailSystemInterface {
       if (substr($key, 0, 2) == 'h:' || substr($key, 0, 2) == 'v:') {
         $params[$key] = $value;
       }
+
+      // Add additional HIME headers, like Reply-to if it exists.
+      if ($key === 'headers' && is_array($value)) {
+        foreach ($value as $headers_key => $headers_value) {
+          $params['h:' . $headers_key] = $headers_value;
+        }
+      }
+    }
+
+    // Add default tags by mail key if enabled.
+    if (variable_get('mailgun_tagging_mailkey', TRUE)) {
+      $params['o:tag'][] = $message['id'];
     }
 
     // Make sure the files provided in the attachments array exist.
     if (!empty($message['params']['attachments'])) {
-      $params['attachments'] = array();
+      $params['attachment'] = array();
       foreach ($message['params']['attachments'] as $attachment) {
-        if (file_exists($attachment)) {
-          $params['attachments'][] = $attachment;
+        if (is_array($attachment)) {
+          // `filecontent` attachment key can be used by MimeMail as data
+          // of the related file.
+          if (array_key_exists('filecontent', $attachment)) {
+            $temp_file = tmpfile();
+            fwrite($temp_file, $attachment['filecontent']);
+            $temp_f_meta_data = stream_get_meta_data($temp_file);
+            $_attachment = array(
+              'filePath' => $temp_f_meta_data['uri'],
+            );
+            if (array_key_exists('filename', $attachment)) {
+              $_attachment['remoteName'] = $attachment['filename'];
+            }
+            $params['attachment'][] = $_attachment;
+          }
+          elseif (array_key_exists('filepath', $attachment) && file_exists($attachment['filepath'])) {
+            $_attachment = array(
+              'filePath' => $attachment['filepath'],
+            );
+            if (array_key_exists('filename', $attachment)) {
+              $_attachment['remoteName'] = $attachment['filename'];
+            }
+            $params['attachment'][] = $_attachment;
+          }
+        }
+        elseif (file_exists($attachment)) {
+          $params['attachment'][] = $attachment;
         }
       }
     }

+ 188 - 64
sites/all/modules/contrib/mail/mailgun/mailgun.module

@@ -5,13 +5,19 @@
  * Provides integration with Mailgun's email sending API.
  */
 
+use Mailgun\Mailgun;
+
+define('MAILGUN_DOCUMENTATION_LINK', 'https://www.drupal.org/node/2547591');
+define('MAILGUN_DASHBOARD_LINK', 'https://mailgun.com/app/dashboard');
+define('MAILGUN_ADMIN_PAGE', 'admin/config/services/mailgun');
+
 /**
  * Implements hook_menu().
  */
 function mailgun_menu() {
   $items = array();
 
-  $items['admin/config/system/mailgun'] = array(
+  $items[MAILGUN_ADMIN_PAGE] = array(
     'title' => 'Mailgun',
     'description' => 'Configure Mailgun settings.',
     'page callback' => 'drupal_get_form',
@@ -19,12 +25,12 @@ function mailgun_menu() {
     'access arguments' => array('administer mailgun'),
     'file' => 'mailgun.admin.inc',
   );
-  $items['admin/config/system/mailgun/settings'] = array(
+  $items[MAILGUN_ADMIN_PAGE . '/settings'] = array(
     'title' => 'Settings',
     'type' => MENU_DEFAULT_LOCAL_TASK,
     'weight' => 0,
   );
-  $items['admin/config/system/mailgun/test'] = array(
+  $items[MAILGUN_ADMIN_PAGE . '/test'] = array(
     'title' => 'Send test email',
     'page callback' => 'drupal_get_form',
     'page arguments' => array('mailgun_test_form'),
@@ -46,7 +52,25 @@ function mailgun_permission() {
     'administer mailgun' => array(
       'title' => t('Administer Mailgun'),
       'description' => t('Perform administration tasks for the Mailgun e-mail sending service.'),
-      "restrict access" => TRUE,
+      'restrict access' => TRUE,
+    ),
+  );
+}
+
+/**
+ * Implements hook_theme().
+ */
+function mailgun_theme($existing, $type, $theme, $path) {
+  return array(
+    'mailgun_message' => array(
+      'variables' => array(
+        'subject' => NULL,
+        'body' => NULL,
+        'message' => array(),
+      ),
+      'template' => 'mailgun-message',
+      'path' => drupal_get_path('module', 'mailgun') . '/templates',
+      'mail theme' => TRUE,
     ),
   );
 }
@@ -56,12 +80,13 @@ function mailgun_permission() {
  */
 function mailgun_help($path, $arg) {
   switch ($path) {
-    case 'admin/config/system/mailgun':
-      return '<p>' . t('See <a href="@url">documentation</a> for instructions on installing and configuring Mailgun.', array('@url' => url('https://www.drupal.org/node/2547591'))) . '</p>';
-      break;
-    case 'admin/config/system/mailgun/test':
+    case MAILGUN_ADMIN_PAGE:
+      return '<p>' . t('See !link for instructions on installing and configuring Mailgun.', array(
+        '!link' => l(t('documentation'), MAILGUN_DOCUMENTATION_LINK),
+      )) . '</p>';
+
+    case MAILGUN_ADMIN_PAGE . '/test':
       return '<p>' . t('Use this form to send a test e-mail to ensure you have correctly configured Mailgun.') . '</p>';
-      break;
   }
 }
 
@@ -98,14 +123,17 @@ function mailgun_mail($key, &$message, $params) {
 function mailgun_libraries_info() {
   $libraries['mailgun'] = array(
     'name' => 'Mailgun PHP library',
-    'vendor url' => 'https://documentation.mailgun.com/wrappers.html#php',
-    'download url' => 'https://github.com/mailgun/mailgun-php/archive/v1.7.2.zip',
-    'path' => 'vendor',
+
+    'vendor url' => 'https://documentation.mailgun.com/en/latest/libraries.html#php',
+    'download url' => 'https://github.com/mailgun/mailgun-php',
+
     'version arguments' => array(
-      'file' => 'src/Mailgun/Constants/Constants.php',
-      // const SDK_VERSION = "1.7";
-      'pattern' => '/const SDK_VERSION = \"((\d+)\.(\d+))\";/',
+      'file' => 'vendor/mailgun/mailgun-php/CHANGELOG.md',
+      'pattern' => '/##\W+((\d+)\.(\d+))/',
     ),
+
+    // Path to the 'autoload.php' created by Composer.
+    'path' => 'vendor',
     'files' => array(
       'php' => array('autoload.php'),
     ),
@@ -114,28 +142,77 @@ function mailgun_libraries_info() {
   return $libraries;
 }
 
+/**
+ * Implements hook_form_FORM_ID_alter().
+ */
+function mailgun_form_libraries_admin_library_status_form_alter(&$form, &$form_state, $form_id) {
+  $library = drupal_array_get_nested_value($form_state, array(
+    'build_info', 'args', 0,
+  ));
+  if (empty($library['machine name']) || $library['machine name'] !== 'mailgun') {
+    return;
+  }
+  // Libraries module provides own instruction "How to install the library".
+  // We override it because this instruction is not correct and may confuse.
+  $form['instructions'] = array(
+    '#markup' => t('The Mailgun PHP library is not installed. Please see Installation section in the !link.', array(
+      '!link' => l(t('documentation'), MAILGUN_DOCUMENTATION_LINK),
+    )),
+  );
+}
+
 /**
  * Get the Mailgun client to access Mailgun's endpoints.
  *
  * @param string $key
  *   The Mailgun API key. Leave empty to use the API key saved in database.
+ *
+ * @return \Mailgun\Mailgun
+ *   Mailgun object.
  */
 function mailgun_get_client($key = '') {
   // Check if the Mailgun PHP library is installed.
-  $library = libraries_load('mailgun');
-  if (!$library['installed']) {
-    watchdog('mailgun', 'Mailgun client initialization failed: Unable to load the Mailgun PHP library.', NULL, WATCHDOG_ERROR);
+  if (!mailgun_check_library()) {
+    watchdog('mailgun', 'Mailgun client initialization failed: Unable to load the Mailgun PHP library.', array(), WATCHDOG_ERROR);
     return FALSE;
   }
 
   $key = (empty($key)) ? variable_get('mailgun_api_key', '') : $key;
   if (empty($key)) {
-    watchdog('mailgun', 'Mailgun client initialization failed: Missing API key.', NULL, WATCHDOG_ERROR);
+    watchdog('mailgun', 'Mailgun client initialization failed: Missing API key.', array(), WATCHDOG_ERROR);
     return FALSE;
   }
 
-  $client = new \Mailgun\Mailgun($key);
-  return $client;
+  return Mailgun::create($key);
+}
+
+/**
+ * Detect if Mailgun library is installed.
+ *
+ * @return bool
+ *   TRUE if library is installed, FALSE otherwise.
+ */
+function mailgun_check_library() {
+  if (module_exists('libraries')) {
+    libraries_load('mailgun');
+  }
+  if (method_exists('\Mailgun\Mailgun', 'create')) {
+    return TRUE;
+  }
+  return FALSE;
+}
+
+/**
+ * Prepares variables for mailgun-message.tpl.php.
+ *
+ * Adds id/module/key-specific hook suggestions.
+ *
+ * @see templates/mailgun-message.tpl.php
+ */
+function template_preprocess_mailgun_message(&$variables) {
+  $variables['theme_hook_suggestions'][] = 'mailgun_message__' . $variables['message']['id'];
+  $variables['theme_hook_suggestions'][] = 'mailgun_message__' . $variables['message']['module'];
+  $variables['theme_hook_suggestions'][] = 'mailgun_message__' . $variables['message']['key'];
 }
 
 /**
@@ -146,31 +223,50 @@ function mailgun_get_client($key = '') {
  *   - from: The e-mail addressthe message will be sent from.
  *   - to: The e-mail addressthe message will be sent to.
  *   - subject: The subject of the message.
- *   - text: The plain-text version of the message. Processed using check_plain().
+ *   - text: The plain-text version of the message. Processed using
+ *    drupal_html_to_text().
  *   - html: The original message content. May contain HTML tags.
- *   - cc: One or more carbon copy recipients. If multiple, separate with commas.
- *   - bcc: One or more blind carbon copy recipients. If multiple, separate with commas.
- *   - o:tag: An array containing the tags to add to the message. See: https://documentation.mailgun.com/user_manual.html#tagging.
- *   - o:campaign: The campaign ID this message belongs to. See: https://documentation.mailgun.com/user_manual.html#um-campaign-analytics.
- *   - o:deliverytime: Desired time of delivery. Messages can be scheduled for a maximum of 3 days in the future. See: https://documentation.mailgun.com/api-intro.html#date-format.
- *   - o:dkim: Boolean indicating whether or not to enable DKIM signatures on per-message basis.
- *   - o:testmode: Boolean indicating whether or not to enable test mode. See: https://documentation.mailgun.com/user_manual.html#manual-testmode.
- *   - o:tracking: Boolean indicating whether or not to toggle tracking on a per-message basis. See: https://documentation.mailgun.com/user_manual.html#tracking-messages.
- *   - o:tracking-clicks: Boolean or string "htmlonly" indicating whether or not to toggle clicks tracking on a per-message basis. Has higher priority than domain-level setting.
- *   - o:tracking-opens: Boolean indicating whether or not to toggle clicks tracking on a per-message basis. Has higher priority than domain-level setting.
- *   - h:X-My-Header: h: prefix followed by an arbitrary value allows to append a custom MIME header to the message (X-My-Header in this case). For example, h:Reply-To to specify Reply-To address.
- *   - v:my-var: v: prefix followed by an arbitrary name allows to attach a custom JSON data to the message. See: https://documentation.mailgun.com/user_manual.html#manual-customdata.
+ *   - cc: One or more carbon copy recipients. If multiple, separate with
+ *    commas.
+ *   - bcc: One or more blind carbon copy recipients. If multiple, separate
+ *    with commas.
+ *   - o:tag: An array containing the tags to add to the message.
+ *    See: https://documentation.mailgun.com/user_manual.html#tagging.
+ *   - o:campaign: The campaign ID this message belongs to.
+ *    https://documentation.mailgun.com/user_manual.html#um-campaign-analytics
+ *   - o:deliverytime: Desired time of delivery. Messages can be scheduled for
+ *    a maximum of 3 days in the future.
+ *    See: https://documentation.mailgun.com/api-intro.html#date-format.
+ *   - o:dkim: Boolean indicating whether or not to enable DKIM signatures on
+ *    per-message basis.
+ *   - o:testmode: Boolean indicating whether or not to enable test mode.
+ *    See: https://documentation.mailgun.com/user_manual.html#manual-testmode.
+ *   - o:tracking: Boolean indicating whether or not to toggle tracking on a
+ *    per-message basis.
+ *    See: https://documentation.mailgun.com/user_manual.html#tracking-messages.
+ *   - o:tracking-clicks: Boolean or string "htmlonly" indicating whether or
+ *    not to toggle clicks tracking on a per-message basis. Has higher
+ *    priority than domain-level setting.
+ *   - o:tracking-opens: Boolean indicating whether or not to toggle clicks
+ *    tracking on a per-message basis. Has higher priority than domain-level
+ *    setting.
+ *   - h:X-My-Header: h: prefix followed by an arbitrary value allows to append
+ *    a custom MIME header to the message (X-My-Header in this case).
+ *    For example, h:Reply-To to specify Reply-To address.
+ *   - v:my-var: v: prefix followed by an arbitrary name allows to attach a
+ *    custom JSON data to the message.
+ *    See: https://documentation.mailgun.com/user_manual.html#manual-customdata.
  *
  * @return bool
  *   TRUE if the mail was successfully accepted, FALSE otherwise.
  */
-function mailgun_send($mailgun_message) {
+function mailgun_send(array $mailgun_message) {
   $client = mailgun_get_client();
   if (!$client) {
     return FALSE;
   }
 
-  // Test mode
+  // Test mode. Mailgun will accept the message but will not send it.
   if (variable_get('mailgun_test', FALSE)) {
     $mailgun_message['o:testmode'] = 'yes';
   }
@@ -179,35 +275,43 @@ function mailgun_send($mailgun_message) {
   $mailgun_message += $mailgun_message['params'];
   unset($mailgun_message['params']);
 
-  if (variable_get('mailgun_domain', '_sender') == '_sender') {
-    // Extract the domain from the sender's email address. Use regular expression to check since it could be either a plain email address or in the form "Name <example@example.com>".
+  if (variable_get('mailgun_domain', '_sender') === '_sender') {
+    // Extract the domain from the sender's email address.
+    // Use regular expression to check since it could be either a plain email
+    // address or in the form "Name <example@example.com>".
     $tokens = (preg_match('/^\s*(.+?)\s*<\s*([^>]+)\s*>$/', $mailgun_message['from'], $matches) === 1) ? explode('@', $matches[2]) : explode('@', $mailgun_message['from']);
     $mail_domain = array_pop($tokens);
 
     // Retrieve a list of available domains first.
     $domains = array();
     try {
-      $result = $client->get('domains');
-      if ($result->http_response_code == 200) {
-        foreach ($result->http_response_body->items as $item) {
-          $domains[$item->name] = $item->name;
+      $result = $client->domains()->index();
+      if (!empty($result)) {
+        foreach ($result->getDomains() as $domain) {
+          $domains[$domain->getName()] = $domain->getName();
         }
       }
       else {
-        watchdog('mailgun', 'Mailgun server returned a %code error. Could not retrieve domain list.', array('%code' => $result->http_response_code), WATCHDOG_ERROR);
+        watchdog('mailgun', 'Could not retrieve domain list.', array(), WATCHDOG_ERROR);
       }
-    } catch (Exception $e) {
-      watchdog('mailgun', 'An exception occurred while retrieving domains. @code: @message', array('@code' => $e->getCode(), '@message' => $e->getMessage()), WATCHDOG_ERROR);
+    }
+    catch (Exception $e) {
+      watchdog('mailgun', 'An exception occurred while retrieving domains. @code: @message', array(
+        '@code' => $e->getCode(),
+        '@message' => $e->getMessage(),
+      ), WATCHDOG_ERROR);
     }
 
     if (empty($domains)) {
-      // No domain available. Although this shouldn't happen, doesn't hurt to check.
+      // No domain available.
+      // Although this shouldn't happen, doesn't hurt to check.
       return FALSE;
     }
 
-    // Now, we need to get the working domain. This is generally the domain the From address is on or the root domain of it.
+    // Now, we need to get the working domain. This is generally the domain the
+    // From address is on or the root domain of it.
     $working_domain = '';
-    if ($key = array_search($mail_domain, $domains) !== FALSE) {
+    if (in_array($mail_domain, $domains, TRUE)) {
       // Great. Found it.
       $working_domain = $mail_domain;
     }
@@ -222,10 +326,13 @@ function mailgun_send($mailgun_message) {
       }
     }
 
-    // There is a chance that the user is attempting to send from an email address that's on a domain not yet added to the Mailgun account.
+    // There is a chance that the user is attempting to send from an email
+    // address that's on a domain not yet added to the Mailgun account.
     // In that case, abort sending and report error.
     if (empty($working_domain)) {
-      watchdog('mailgun', 'Unable to locate a working domain for From address %mail. Aborting sending.', array('%mail' => $mailgun_message['from']), WATCHDOG_ERROR);
+      watchdog('mailgun', 'Unable to locate a working domain for From address %mail. Aborting sending.', array(
+        '%mail' => $mailgun_message['from'],
+      ), WATCHDOG_ERROR);
       return FALSE;
     }
   }
@@ -233,29 +340,46 @@ function mailgun_send($mailgun_message) {
     $working_domain = variable_get('mailgun_domain', '');
   }
 
-  // Attachments
-  $post_data = array();
-  if (!empty($mailgun_message['attachments'])) {
-    // Send message with attachments.
-    $post_data['attachment'] = $mailgun_message['attachments'];
-    unset($mailgun_message['attachments']);
+  // Send message with attachments.
+  if (!empty($mailgun_message['attachment'])) {
+    foreach ($mailgun_message['attachment'] as &$attachment) {
+      // Ignore array constructions. Not sure what values can be here.
+      if (is_array($attachment)) {
+        continue;
+      }
+      $attachment = array('filePath' => $attachment);
+    }
   }
-  
+
   try {
-    $result = $client->sendMessage($working_domain, $mailgun_message, $post_data);
+    $result = $client->messages()->send($working_domain, $mailgun_message);
 
-    // For a list of HTTP response codes, see: https://documentation.mailgun.com/api-intro.html#errors.
-    if ($result->http_response_code == 200) {
+    if (!empty($result)) {
       if (variable_get('mailgun_log', FALSE)) {
-        watchdog('mailgun', 'Successfully sent message from %from to %to. %code: %message.', array('%from' => $mailgun_message['from'], '%to' => $mailgun_message['to'], '%code' => $result->http_response_code, '%message' => $result->http_response_body->message));
+        watchdog('mailgun', 'Successfully sent message from %from to %to. %message.', array(
+          '%from' => $mailgun_message['from'],
+          '%to' => $mailgun_message['to'],
+          '%message' => $result->getMessage(),
+        ));
       }
       return TRUE;
     }
     else {
-      watchdog('mailgun', 'Failed to send message from %from to %to. %code: %message.', array('%from' => $mailgun_message['from'], '%to' => $mailgun_message['to'], '%code' => $result->http_response_code, '%message' => $result->http_response_body->message), WATCHDOG_ERROR);
+      watchdog('mailgun', 'Failed to send message from %from to %to. %message.', array(
+        '%from' => $mailgun_message['from'],
+        '%to' => $mailgun_message['to'],
+        '%message' => $result->getMessage(),
+      ), WATCHDOG_ERROR);
       return FALSE;
     }
-  } catch (Exception $e) {
-    watchdog('mailgun', 'Exception occurred while trying to send test email from %from to %to. @code: @message.', array('%from' => $mailgun_message['from'], '%to' => $mailgun_message['to'], '@code' => $e->getCode(), '@message' => $e->getMessage()));
+  }
+  catch (Exception $e) {
+    watchdog('mailgun', 'Exception occurred while trying to send test email from %from to %to. @code: @message.', array(
+      '%from' => $mailgun_message['from'],
+      '%to' => $mailgun_message['to'],
+      '@code' => $e->getCode(),
+      '@message' => $e->getMessage(),
+    ));
+    return FALSE;
   }
 }

+ 28 - 0
sites/all/modules/contrib/mail/mailgun/templates/mailgun-message.tpl.php

@@ -0,0 +1,28 @@
+<?php
+
+/**
+ * @file
+ * Default theme implementation of Mailgun mail.
+ *
+ * Available variables:
+ * - $subject: The message subject.
+ * - $body: The message body.
+ *
+ * @see template_preprocess_mailgun_message()
+ */
+?>
+<html>
+  <head>
+    <style type="text/css">
+      body {
+        font-family: Verdana, Arial, sans-serif;
+        font-size: 12px;
+      }
+    </style>
+  </head>
+  <body>
+    <div>
+      <?php print $body; ?>
+    </div>
+  </body>
+</html>

+ 5 - 4
sites/all/modules/contrib/mail/mailsystem/html_to_text.inc

@@ -407,7 +407,8 @@ function _mailsystem_html_to_text(DOMNode $node, array $allowed_tags, array &$no
   // Copy each child node to output.
   if ($node->hasChildNodes()) {
     foreach ($node->childNodes as $child) {
-      $child_text .= _mailsystem_html_to_text($child, $allowed_tags, $notes, $line_length - drupal_strlen($indent), $parents, $child_count);    }
+      $child_text .= _mailsystem_html_to_text($child, $allowed_tags, $notes, $line_length - drupal_strlen($indent), $parents, $child_count);
+    }
   }
   // We only add prefix and suffix if the child nodes were non-empty.
   if ($child_text > '') {
@@ -416,7 +417,7 @@ function _mailsystem_html_to_text(DOMNode $node, array $allowed_tags, array &$no
       $child_text = drupal_strtoupper($child_text);
     }
     // Don't add a newline to an existing newline.
-    if ($suffix === $eol && drupal_substr($child_text, - drupal_strlen($eol)) === $eol) {
+    if ($suffix === $eol && drupal_substr($child_text, -drupal_strlen($eol)) === $eol) {
       $suffix = '';
     }
     // Trim spaces around newlines except with <pre> or inline tags.
@@ -496,7 +497,7 @@ function _mailsystem_html_to_text_table(DOMNode $node, $allowed_tags = NULL, arr
               $footer[] = $current;
               break;
 
-            default: // Either 'tbody' or 'table'
+            default: // Either 'tbody' or 'table'.
               $body[] = $current;
               break;
           }
@@ -647,7 +648,7 @@ function _mailsystem_html_to_text_table(DOMNode $node, $allowed_tags = NULL, arr
       }
       // Generate the row separator line.
       $separator = '+';
-      for($i = 0; $i < $num_cols; $i++) {
+      for ($i = 0; $i < $num_cols; $i++) {
         $separator .= str_repeat('-', $widths[$i]) . '+';
       }
       $separator .= $eol;

+ 9 - 8
sites/all/modules/contrib/mail/mailsystem/mailsystem.admin.inc

@@ -4,6 +4,7 @@
  * @file
  * Administrative form for setting the mail_system variable.
  */
+
 function mailsystem_admin_settings() {
   $args = array(
     '!interface' => url('http://api.drupal.org/api/drupal/includes--mail.inc/interface/MailSystemInterface/7'),
@@ -49,13 +50,13 @@ function mailsystem_admin_settings() {
     '#default_value' => $mail_system[mailsystem_default_id()],
   );
   $mailsystem_classes = array(
-    mailsystem_default_id() => t('Remove this setting.')
+    mailsystem_default_id() => t('Remove this setting.'),
   ) + $mailsystem_classes;
   foreach (array_diff_key($mail_system, $mail_defaults) as $id => $class) {
     // Separate $id into $module and $key.
     $module = $id;
     while ($module && empty($descriptions[$module])) {
-      // Remove a key from the end
+      // Remove a key from the end.
       $module = implode('_', explode('_', $module, -1));
     }
     // If an array key of the $mail_system variable is neither "default-system"
@@ -91,11 +92,11 @@ function mailsystem_admin_settings() {
     }
   }
   $form['mailsystem']['mailsystem_theme'] = array(
-      '#type' => 'select',
-      '#title' => t('Theme to render the emails'),
-      '#description' => t('Select the theme that will be used to render the emails. This can be either the current theme, the default theme, the domain theme or any active theme.'),
-      '#options' => $theme_options,
-      '#default_value' => variable_get('mailsystem_theme', 'current'),
+    '#type' => 'select',
+    '#title' => t('Theme to render the emails'),
+    '#description' => t('Select the theme that will be used to render the emails. This can be either the current theme, the default theme, the domain theme or any active theme.'),
+    '#options' => $theme_options,
+    '#default_value' => variable_get('mailsystem_theme', 'current'),
   );
   $form['class'] = array(
     '#type' => 'fieldset',
@@ -168,7 +169,7 @@ function mailsystem_admin_settings_submit($form, &$form_state) {
       empty($form_state['values'][$default_id])
       ? mailsystem_default_value()
       : $form_state['values'][$default_id]
-    )
+    ),
   );
   foreach (element_children($form_state['values']['mailsystem']) as $module) {
     $class = $form_state['values']['mailsystem'][$module];

+ 3 - 5
sites/all/modules/contrib/mail/mailsystem/mailsystem.info

@@ -1,14 +1,12 @@
 package = Mail
 name = Mail System
 description = Provides a user interface for per-module and site-wide mail_system selection.
-php = 5.0
 core = 7.x
 configure = admin/config/system/mailsystem
 dependencies[] = filter
 
-; Information added by drupal.org packaging script on 2012-04-10
-version = "7.x-2.34"
+; Information added by Drupal.org packaging script on 2018-07-12
+version = "7.x-2.35"
 core = "7.x"
 project = "mailsystem"
-datestamp = "1334082653"
-
+datestamp = "1531385928"

+ 35 - 15
sites/all/modules/contrib/mail/mailsystem/mailsystem.module

@@ -132,15 +132,23 @@ function mailsystem_create_class($classes) {
   }
   $class_name = implode('__', $classes);
   // Ensure that the mailsystem directory exists.
-  $class_dir = file_build_uri('mailsystem');
-  if (!file_prepare_directory($class_dir, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS)) {
+  // First we try the private filesystem.
+  $private_files = variable_get('file_private_path', '');
+  $private_files_full = $private_files . '/mailsystem';
+  $public_files = variable_get('file_public_path', conf_path() . '/files');
+  $public_files_full = $public_files . '/mailsystem';
+  if ($private_files && file_prepare_directory($private_files_full, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS)) {
+    $class_dir = $private_files . '/mailsystem';
+  }
+  // If private filesystem is not defined or writable, we use the public filesystem.
+  elseif (file_prepare_directory($public_files_full, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS)) {
+    $class_dir = $public_files . '/mailsystem';
+  }
+  else {
     return FALSE;
   }
   // Build the class filename.
-  $class_file = drupal_realpath($class_dir) . DIRECTORY_SEPARATOR . "$class_name.mail.inc";
-  // Strip DRUPAL_ROOT.
-  $drupal_root = drupal_realpath(DRUPAL_ROOT) . DIRECTORY_SEPARATOR;
-  $class_file = preg_replace('#^' . preg_quote($drupal_root, '#') . '#', '', $class_file);
+  $class_file = $class_dir . DIRECTORY_SEPARATOR . "$class_name.mail.inc";
   // Build the class implementation as a string.
   $class_contents = '<?php
 class ' . $class_name . ' implements MailSystemInterface {';
@@ -181,17 +189,19 @@ class ' . $class_name . ' implements MailSystemInterface {';
     $file_condition = db_and()
       ->condition('filename', $class_file);
     db_delete('registry_file')
-      ->condition($file_condition);
+      ->condition($file_condition)
+      ->execute();
     db_delete('registry')->condition(
       db_or()->condition($class_condition)
       ->condition($file_condition)
-    );
+    )->execute();
     // Make sure that registry functions are available.
     require_once 'includes/registry.inc';
     // Parse the newly-created class file and add it to the registry.
     _registry_parse_file($class_file, $class_contents, 'mailsystem');
-    // Clear the mailsystem cache so that it will pick up the new class.
+    // Clear the mailsystem caches so that it will pick up the new class.
     drupal_static_reset('mailsystem_get_classes');
+    cache_clear_all('mailsystem_get_classes', 'cache');
     drupal_set_message(
       t('Class <code>%class</code> written to <code>%file</code>.',
         array('%class' => $class_name, '%file' => $class_file)
@@ -257,7 +267,13 @@ function mailsystem_clear(array $setting) {
  * Returns a list of classes which implement MailSystemInterface.
  */
 function &mailsystem_get_classes() {
+  // Load static cache.
   $mailsystem_classes = &drupal_static(__FUNCTION__);
+  // Check persistent cache if necessary.
+  if (!isset($mailsystem_classes) && $cache = cache_get('mailsystem_get_classes')) {
+    $mailsystem_classes = $cache->data;
+  }
+  // Load from db if no cache was hit.
   if (!isset($mailsystem_classes)) {
     $mailsystem_classes = array();
     // @todo Is there a better way to find all mail-related classes?
@@ -282,14 +298,14 @@ function &mailsystem_get_classes() {
       ->execute()
       ->fetchAllKeyed();
     foreach ($mail_classes as $classname => $classfile) {
-      if ( file_exists($classfile)
+      if (file_exists($classfile)
         && drupal_autoload_class($classname)
       ) {
         $all_classes[$classname] = 1;
       }
     }
     foreach ($all_classes as $classname => $autoload) {
-      if ( ($autoload || preg_match('/MailSystem/', $classname))
+      if (($autoload || preg_match('/MailSystem/', $classname))
         && ($object = new $classname)
         && ($object instanceof MailSystemInterface)
       ) {
@@ -311,6 +327,8 @@ function &mailsystem_get_classes() {
       }
     }
     ksort($mailsystem_classes);
+    // Store in persistent cache.
+    cache_set('mailsystem_get_classes', $mailsystem_classes, 'cache', CACHE_TEMPORARY);
   }
   return $mailsystem_classes;
 }
@@ -324,10 +342,10 @@ function mailsystem_theme_registry_alter(&$theme_registry) {
 }
 
 /**
-* Retrieves the key of the theme used to render the emails.
-*
-* @todo Add some kind of hook to let other modules alter this behavior.
-*/
+ * Retrieves the key of the theme used to render the emails.
+ *
+ * @todo Add some kind of hook to let other modules alter this behavior.
+ */
 function mailsystem_get_mail_theme() {
   global $theme_key;
   $theme = variable_get('mailsystem_theme', 'current');
@@ -335,9 +353,11 @@ function mailsystem_get_mail_theme() {
     case 'default':
       $theme = variable_get('theme_default', NULL);
       break;
+
     case 'current':
       $theme = $theme_key;
       break;
+
     case 'domain':
       // Fetch the theme for the current domain.
       if (module_exists('domain_theme')) {

+ 49 - 7
sites/all/modules/contrib/mail/mailsystem/mailsystem.theme.inc

@@ -1,13 +1,13 @@
 <?php
 
 /**
-* @file
-* The theme system, which controls the output of email messages.
-*/
+ * @file
+ * The theme system, which controls the output of email messages.
+ */
 
 /**
-* Implements hook_theme_registry_alter().
-*/
+ * Implements hook_theme_registry_alter().
+ */
 function mailsystem_theme_theme_registry_alter(&$theme_registry) {
   global $theme_key;
   static $recursion_prevention = FALSE;
@@ -26,6 +26,10 @@ function mailsystem_theme_theme_registry_alter(&$theme_registry) {
     if (isset($themes[$mailsystem_theme])) {
       $theme = clone $themes[$mailsystem_theme];
       if (isset($theme)) {
+        // Swap to the mailsystem theme.
+        $old_theme = $theme_key;
+        mailsystem_theme_swap_theme($mailsystem_theme);
+
         // Establish variables for further processing.
         $base_theme = array();
         if (isset($theme->base_themes)) {
@@ -42,10 +46,10 @@ function mailsystem_theme_theme_registry_alter(&$theme_registry) {
 
         // Include template files to let _theme_load_registry add preprocess
         // functions.
-        include_once(drupal_get_path('theme', $theme->name) . '/template.php');
         foreach ($base_theme as $base) {
-          include_once(drupal_get_path('theme', $base->name) . '/template.php');
+          include_once drupal_get_path('theme', $base->name) . '/template.php';
         }
+        include_once drupal_get_path('theme', $theme->name) . '/template.php';
 
         // Get the theme_registry cache.
         $cache = _theme_load_registry($theme, $base_theme, $theme_engine);
@@ -75,8 +79,46 @@ function mailsystem_theme_theme_registry_alter(&$theme_registry) {
             }
           }
         }
+
+        // Swap back to the original theme.
+        mailsystem_theme_swap_theme($old_theme);
       }
     }
   }
   $recursion_prevention = FALSE;
 }
+
+/**
+ * Helper to swap themes safely for use by mailsystem_theme_theme_registry_alter().
+ */
+function mailsystem_theme_swap_theme($new_theme) {
+  // Make sure the theme exists.
+  $themes = list_themes();
+  if (empty($themes[$new_theme])) {
+    return;
+  }
+
+  // Both theme/theme_key are set to the new theme.
+  global $theme, $theme_key;
+  $theme = $theme_key = $new_theme;
+
+  // Create the base_theme_info.
+  global $base_theme_info;
+  $base_theme = array();
+  $ancestor = $theme;
+  while ($ancestor && isset($themes[$ancestor]->base_theme)) {
+    $ancestor = $themes[$ancestor]->base_theme;
+    $base_theme[] = $themes[$ancestor];
+  }
+  $base_theme_info = array_reverse($base_theme);
+
+  // Some other theme related globals.
+  global $theme_engine, $theme_info, $theme_path;
+  $theme_engine = $themes[$theme]->engine;
+  $theme_info = $themes[$theme];
+  $theme_path = dirname($themes[$theme]->filename);
+
+  // We need to reset the drupal_alter and module_implements statics.
+  drupal_static_reset('drupal_alter');
+  drupal_static_reset('module_implements');
+}

Some files were not shown because too many files changed in this diff