From e08a2639c64ae7d203c58837895e1b300bfc8216 Mon Sep 17 00:00:00 2001 From: Bachir Soussi Chiadmi Date: Mon, 13 May 2019 17:55:28 +0200 Subject: [PATCH] updated mailgun, mailsystem, honeypot, googleanalitycs, features, content_taxonomy --- .../contrib/admin/features/features.admin.inc | 6 +- .../contrib/admin/features/features.drush.inc | 2 +- .../admin/features/features.export.inc | 25 +- .../contrib/admin/features/features.info | 7 +- .../contrib/admin/features/features.module | 1 + .../features/includes/features.field.inc | 8 +- .../features/includes/features.image.inc | 10 +- .../admin/features/includes/features.menu.inc | 12 +- .../admin/features/tests/features.test | 3 + .../tests/features_test/features_test.info | 7 +- .../contrib/admin/google_analytics/README.txt | 14 +- .../googleanalytics.admin.inc | 47 +- .../google_analytics/googleanalytics.debug.js | 6 +- .../google_analytics/googleanalytics.info | 7 +- .../google_analytics/googleanalytics.install | 58 +-- .../admin/google_analytics/googleanalytics.js | 6 +- .../google_analytics/googleanalytics.module | 45 +- .../google_analytics/googleanalytics.test | 74 +++- .../google_analytics/googleanalytics.test.js | 14 +- .../googleanalytics.variable.inc | 2 +- .../fields/content_taxonomy/CHANGELOG.txt | 42 ++ .../content_taxonomy/content_taxonomy.api.php | 42 ++ .../content_taxonomy/content_taxonomy.info | 6 +- .../content_taxonomy/content_taxonomy.install | 94 ++++ .../content_taxonomy/content_taxonomy.module | 56 ++- .../content_taxonomy_autocomplete.info | 6 +- .../content_taxonomy_autocomplete.module | 47 +- .../content_taxonomy/content_taxonomy_et.info | 15 + .../content_taxonomy_et.module | 404 ++++++++++++++++++ .../content_taxonomy_migrate.info | 10 +- .../content_taxonomy_migrate.module | 87 +++- .../modules/contrib/form/honeypot/README.md | 58 +++ .../modules/contrib/form/honeypot/README.txt | 43 -- .../contrib/form/honeypot/docker-compose.yml | 32 ++ .../contrib/form/honeypot/honeypot.admin.inc | 22 +- .../contrib/form/honeypot/honeypot.info | 7 +- .../contrib/form/honeypot/honeypot.install | 4 +- .../contrib/form/honeypot/honeypot.module | 133 +++++- .../contrib/form/honeypot/honeypot.test | 55 +++ .../contrib/form/honeypot/js/honeypot.js | 37 ++ .../form/honeypot/tests/honeypot_test.info | 7 +- .../modules/contrib/mail/mailgun/README.txt | 1 + .../contrib/mail/mailgun/composer.json | 12 + .../contrib/mail/mailgun/mailgun.admin.inc | 183 +++++--- .../modules/contrib/mail/mailgun/mailgun.info | 15 +- .../contrib/mail/mailgun/mailgun.install | 40 +- .../contrib/mail/mailgun/mailgun.mail.inc | 100 ++++- .../contrib/mail/mailgun/mailgun.module | 254 ++++++++--- .../mailgun/templates/mailgun-message.tpl.php | 28 ++ .../contrib/mail/mailsystem/html_to_text.inc | 9 +- .../mail/mailsystem/mailsystem.admin.inc | 17 +- .../contrib/mail/mailsystem/mailsystem.info | 8 +- .../contrib/mail/mailsystem/mailsystem.module | 50 ++- .../mail/mailsystem/mailsystem.theme.inc | 56 ++- 54 files changed, 1911 insertions(+), 423 deletions(-) create mode 100644 sites/all/modules/contrib/fields/content_taxonomy/CHANGELOG.txt create mode 100644 sites/all/modules/contrib/fields/content_taxonomy/content_taxonomy.api.php create mode 100644 sites/all/modules/contrib/fields/content_taxonomy/content_taxonomy.install create mode 100644 sites/all/modules/contrib/fields/content_taxonomy/content_taxonomy_et.info create mode 100644 sites/all/modules/contrib/fields/content_taxonomy/content_taxonomy_et.module create mode 100644 sites/all/modules/contrib/form/honeypot/README.md delete mode 100644 sites/all/modules/contrib/form/honeypot/README.txt create mode 100644 sites/all/modules/contrib/form/honeypot/docker-compose.yml create mode 100644 sites/all/modules/contrib/form/honeypot/js/honeypot.js create mode 100644 sites/all/modules/contrib/mail/mailgun/README.txt create mode 100644 sites/all/modules/contrib/mail/mailgun/composer.json create mode 100644 sites/all/modules/contrib/mail/mailgun/templates/mailgun-message.tpl.php diff --git a/sites/all/modules/contrib/admin/features/features.admin.inc b/sites/all/modules/contrib/admin/features/features.admin.inc index 46752c89..2dea423e 100644 --- a/sites/all/modules/contrib/admin/features/features.admin.inc +++ b/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); diff --git a/sites/all/modules/contrib/admin/features/features.drush.inc b/sites/all/modules/contrib/admin/features/features.drush.inc index 696825d6..4be5d98c 100644 --- a/sites/all/modules/contrib/admin/features/features.drush.inc +++ b/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); } } diff --git a/sites/all/modules/contrib/admin/features/features.export.inc b/sites/all/modules/contrib/admin/features/features.export.inc index 5045b131..340818b3 100644 --- a/sites/all/modules/contrib/admin/features/features.export.inc +++ b/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] = "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; } } diff --git a/sites/all/modules/contrib/admin/features/includes/features.field.inc b/sites/all/modules/contrib/admin/features/includes/features.field.inc index 849081dd..ca305ae1 100644 --- a/sites/all/modules/contrib/admin/features/includes/features.field.inc +++ b/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); } } diff --git a/sites/all/modules/contrib/admin/features/includes/features.image.inc b/sites/all/modules/contrib/admin/features/includes/features.image.inc index b2058b7c..bf89191d 100644 --- a/sites/all/modules/contrib/admin/features/includes/features.image.inc +++ b/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); + } + } } } } diff --git a/sites/all/modules/contrib/admin/features/includes/features.menu.inc b/sites/all/modules/contrib/admin/features/includes/features.menu.inc index edd47514..84af6231 100644 --- a/sites/all/modules/contrib/admin/features/includes/features.menu.inc +++ b/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] ? '-' : ''; } diff --git a/sites/all/modules/contrib/admin/features/tests/features.test b/sites/all/modules/contrib/admin/features/tests/features.test index 025ef23c..743cc1bf 100644 --- a/sites/all/modules/contrib/admin/features/tests/features.test +++ b/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'), ); } diff --git a/sites/all/modules/contrib/admin/features/tests/features_test/features_test.info b/sites/all/modules/contrib/admin/features/tests/features_test/features_test.info index 04f01d33..cd414ec6 100644 --- a/sites/all/modules/contrib/admin/features/tests/features_test/features_test.info +++ b/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" diff --git a/sites/all/modules/contrib/admin/google_analytics/README.txt b/sites/all/modules/contrib/admin/google_analytics/README.txt index 25fd22aa..8ef9818f 100644 --- a/sites/all/modules/contrib/admin/google_analytics/README.txt +++ b/sites/all/modules/contrib/admin/google_analytics/README.txt @@ -1,6 +1,6 @@ Module: Google Analytics -Author: Alexander Hass +Author: Alexander Hass 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:
  • Mailto
  • Download file
  • Open colorbox
  • -
  • External link
  • +
  • External link
  • Go link
  • diff --git a/sites/all/modules/contrib/admin/google_analytics/googleanalytics.admin.inc b/sites/all/modules/contrib/admin/google_analytics/googleanalytics.admin.inc index d32939a3..2d8afa35 100644 --- a/sites/all/modules/contrib/admin/google_analytics/googleanalytics.admin.inc +++ b/sites/all/modules/contrib/admin/google_analytics/googleanalytics.admin.inc @@ -18,10 +18,17 @@ function googleanalytics_admin_settings_form($form_state) { '#title' => t('Web Property ID'), '#type' => 'textfield', '#default_value' => variable_get('googleanalytics_account', 'UA-'), - '#size' => 15, + '#size' => 20, '#maxlength' => 20, '#required' => TRUE, - '#description' => t('This ID is unique to each site you want to track separately, and is in the form of UA-xxxxxxx-yy. To get a Web Property ID, register your site with Google Analytics, or if you already have registered your site, go to your Google Analytics Settings page to see the ID next to every site profile. Find more information in the documentation.', array('@analytics' => 'http://www.google.com/analytics/', '@webpropertyid' => url('https://developers.google.com/analytics/resources/concepts/gaConceptsAccounts', array('fragment' => 'webProperty')))), + '#description' => t('This ID is unique to each site you want to track separately, and is in the form of UA-xxxxxxx-yy. To get a Web Property ID, register your site with Google Analytics, or if you already have registered your site, go to your Google Analytics Settings page to see the ID next to every site profile. Find more information in the documentation.', array('@analytics' => 'https://marketingplatform.google.com/about/analytics/', '@webpropertyid' => url('https://developers.google.com/analytics/resources/concepts/gaConceptsAccounts', array('fragment' => 'webProperty')))), + ); + + $form['account']['googleanalytics_premium'] = array( + '#type' => 'checkbox', + '#title' => t('Premium account'), + '#description' => t('If you are a Google Analytics Premium customer, you can use up to 200 instead of 20 custom dimensions and metrics.'), + '#default_value' => variable_get('googleanalytics_premium', FALSE), ); // Visibility settings. @@ -169,7 +176,7 @@ function googleanalytics_admin_settings_form($form_state) { '#type' => 'fieldset', '#title' => t('Users'), ); - $t_permission = array('%permission' => t('opt-in or out of tracking')); + $t_permission = array('%permission' => t('Opt-in or out of tracking')); $form['tracking']['user_vis_settings']['googleanalytics_custom'] = array( '#type' => 'radios', '#title' => t('Allow users to customize tracking on their account page'), @@ -309,7 +316,7 @@ function googleanalytics_admin_settings_form($form_state) { $form['tracking']['privacy']['googleanalytics_privacy_donottrack'] = array( '#type' => 'checkbox', '#title' => t('Universal web tracking opt-out'), - '#description' => t('If enabled and your server receives the Do-Not-Track header from the client browser, the Google Analytics module will not embed any tracking code into your site. Compliance with Do Not Track could be purely voluntary, enforced by industry self-regulation, or mandated by state or federal law. Please accept your visitors privacy. If they have opt-out from tracking and advertising, you should accept their personal decision. This feature is currently limited to logged in users and disabled page caching.', array('@donottrack' => 'http://donottrack.us/')), + '#description' => t('If enabled and your server receives the Do-Not-Track header from the client browser, the Google Analytics module will not embed any tracking code into your site. Compliance with Do Not Track could be purely voluntary, enforced by industry self-regulation, or mandated by state or federal law. Please accept your visitors privacy. If they have opt-out from tracking and advertising, you should accept their personal decision. This feature is currently limited to logged in users and disabled page caching.', array('@donottrack' => 'https://www.eff.org/issues/do-not-track')), '#default_value' => variable_get('googleanalytics_privacy_donottrack', 1), ); @@ -326,8 +333,10 @@ function googleanalytics_admin_settings_form($form_state) { $googleanalytics_custom_dimension = variable_get('googleanalytics_custom_dimension', array()); - // Google Analytics supports up to 20 custom dimensions. - for ($i = 1; $i <= 20; $i++) { + // Standard Google Analytics accounts support up to 20 custom dimensions, + // premium accounts support up to 200 custom dimensions. + $limit = (variable_get('googleanalytics_premium', FALSE)) ? 200 : 20; + for ($i = 1; $i <= $limit; $i++) { $form['googleanalytics_custom_dimension']['indexes'][$i]['index'] = array( '#default_value' => $i, '#description' => t('Index number'), @@ -354,7 +363,7 @@ function googleanalytics_admin_settings_form($form_state) { $form['googleanalytics_custom_dimension']['googleanalytics_description'] = array( '#type' => 'item', - '#description' => t('You can supplement Google Analytics\' basic IP address tracking of visitors by segmenting users based on custom dimensions. Section 7 of the Google Analytics terms of service requires that You will not (and will not allow any third party to) use the Service to track, collect or upload any data that personally identifies an individual (such as a name, userid, email address or billing information), or other data which can be reasonably linked to such information by Google. You will have and abide by an appropriate Privacy Policy and will comply with all applicable laws and regulations relating to the collection of information from Visitors. You must post a Privacy Policy and that Privacy Policy must provide notice of Your use of cookies that are used to collect traffic data, and You must not circumvent any privacy features (e.g., an opt-out) that are part of the Service.', array('@ga_tos' => 'http://www.google.com/analytics/terms/gb.html')), + '#description' => t('You can supplement Google Analytics\' basic IP address tracking of visitors by segmenting users based on custom dimensions. Section 7 of the Google Analytics terms of service requires that You will not (and will not allow any third party to) use the Service to track, collect or upload any data that personally identifies an individual (such as a name, userid, email address or billing information), or other data which can be reasonably linked to such information by Google. You will have and abide by an appropriate Privacy Policy and will comply with all applicable laws and regulations relating to the collection of information from Visitors. You must post a Privacy Policy and that Privacy Policy must provide notice of Your use of cookies that are used to collect traffic data, and You must not circumvent any privacy features (e.g., an opt-out) that are part of the Service.', array('@ga_tos' => 'https://www.google.com/analytics/terms/gb.html')), ); if (module_exists('token')) { $form['googleanalytics_custom_dimension']['googleanalytics_token_tree'] = array( @@ -377,8 +386,9 @@ function googleanalytics_admin_settings_form($form_state) { $googleanalytics_custom_metric = variable_get('googleanalytics_custom_metric', array()); - // Google Analytics supports up to 20 custom metrics. - for ($i = 1; $i <= 20; $i++) { + // Standard Google Analytics accounts support up to 20 custom metrics, + // premium accounts support up to 200 custom metrics. + for ($i = 1; $i <= $limit; $i++) { $form['googleanalytics_custom_metric']['indexes'][$i]['index'] = array( '#default_value' => $i, '#description' => t('Index number'), @@ -405,7 +415,7 @@ function googleanalytics_admin_settings_form($form_state) { $form['googleanalytics_custom_metric']['googleanalytics_description'] = array( '#type' => 'item', - '#description' => t('You can supplement Google Analytics\' basic IP address tracking of visitors by segmenting users based on custom metrics. Section 7 of the Google Analytics terms of service requires that You will not (and will not allow any third party to) use the Service to track, collect or upload any data that personally identifies an individual (such as a name, userid, email address or billing information), or other data which can be reasonably linked to such information by Google. You will have and abide by an appropriate Privacy Policy and will comply with all applicable laws and regulations relating to the collection of information from Visitors. You must post a Privacy Policy and that Privacy Policy must provide notice of Your use of cookies that are used to collect traffic data, and You must not circumvent any privacy features (e.g., an opt-out) that are part of the Service.', array('@ga_tos' => 'http://www.google.com/analytics/terms/gb.html')), + '#description' => t('You can supplement Google Analytics\' basic IP address tracking of visitors by segmenting users based on custom metrics. Section 7 of the Google Analytics terms of service requires that You will not (and will not allow any third party to) use the Service to track, collect or upload any data that personally identifies an individual (such as a name, userid, email address or billing information), or other data which can be reasonably linked to such information by Google. You will have and abide by an appropriate Privacy Policy and will comply with all applicable laws and regulations relating to the collection of information from Visitors. You must post a Privacy Policy and that Privacy Policy must provide notice of Your use of cookies that are used to collect traffic data, and You must not circumvent any privacy features (e.g., an opt-out) that are part of the Service.', array('@ga_tos' => 'https://www.google.com/analytics/terms/gb.html')), ); if (module_exists('token')) { $form['googleanalytics_custom_metric']['googleanalytics_token_tree'] = array( @@ -447,7 +457,7 @@ function googleanalytics_admin_settings_form($form_state) { '#title' => t('Custom JavaScript code'), '#collapsible' => TRUE, '#collapsed' => TRUE, - '#description' => t('You can add custom Google Analytics code snippets here. These will be added every time tracking is in effect. Before you add your custom code, you should read the Google Analytics Tracking Code - Functional Overview and the Google Analytics Tracking API documentation. Do not include the <script> tags, and always end your code with a semicolon (;).', array('@snippets' => 'http://drupal.org/node/248699', '@ga_concepts_overview' => 'https://developers.google.com/analytics/resources/concepts/gaConceptsTrackingOverview', '@ga_js_api' => 'https://developers.google.com/analytics/devguides/collection/analyticsjs/method-reference')), + '#description' => t('You can add custom Google Analytics code snippets here. These will be added every time tracking is in effect. Before you add your custom code, you should read the Google Analytics Tracking Code - Functional Overview and the Google Analytics Tracking API documentation. Do not include the <script> tags, and always end your code with a semicolon (;).', array('@snippets' => 'https://drupal.org/node/248699', '@ga_concepts_overview' => 'https://developers.google.com/analytics/resources/concepts/gaConceptsTrackingOverview', '@ga_js_api' => 'https://developers.google.com/analytics/devguides/collection/analyticsjs/method-reference')), ); $form['advanced']['codesnippet']['googleanalytics_codesnippet_create'] = array( '#type' => 'textarea', @@ -785,16 +795,18 @@ function _googleanalytics_validate_create_field_name($name) { // List of supported field names: // https://developers.google.com/analytics/devguides/collection/analyticsjs/field-reference#create $create_only_fields = array( - 'clientId', - 'userId', - 'sampleRate', - 'siteSpeedSampleRate', - 'alwaysSendReferrer', 'allowAnchor', + 'alwaysSendReferrer', + 'clientId', 'cookieName', 'cookieDomain', 'cookieExpires', 'legacyCookieDomain', + 'legacyHistoryImport', + 'sampleRate', + 'siteSpeedSampleRate', + 'storage', + 'useAmpClientId', ); if ($name == 'name') { @@ -803,6 +815,9 @@ function _googleanalytics_validate_create_field_name($name) { if ($name == 'allowLinker') { return t('Create only field name %name is a disallowed field name. Please select Multiple top-level domains under What are you tracking to enable cross domain tracking.', array('%name' => $name)); } + if ($name == 'userId') { + return t('Create only field name %name is a disallowed field name. Please enable Track User ID under Tracking scope > Users.', array('%name' => $name)); + } if (!in_array($name, $create_only_fields)) { return t('Create only field name %name is an unknown field name. Field names are case sensitive. Please see create only fields documentation for supported field names.', array('%name' => $name, '@url' => 'https://developers.google.com/analytics/devguides/collection/analyticsjs/field-reference#create')); } diff --git a/sites/all/modules/contrib/admin/google_analytics/googleanalytics.debug.js b/sites/all/modules/contrib/admin/google_analytics/googleanalytics.debug.js index a0be81ae..71862304 100644 --- a/sites/all/modules/contrib/admin/google_analytics/googleanalytics.debug.js +++ b/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. diff --git a/sites/all/modules/contrib/admin/google_analytics/googleanalytics.info b/sites/all/modules/contrib/admin/google_analytics/googleanalytics.info index 2b814605..591816a3 100644 --- a/sites/all/modules/contrib/admin/google_analytics/googleanalytics.info +++ b/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" diff --git a/sites/all/modules/contrib/admin/google_analytics/googleanalytics.install b/sites/all/modules/contrib/admin/google_analytics/googleanalytics.install index a4f4e83a..08b4e05c 100644 --- a/sites/all/modules/contrib/admin/google_analytics/googleanalytics.install +++ b/sites/all/modules/contrib/admin/google_analytics/googleanalytics.install @@ -10,6 +10,7 @@ */ function googleanalytics_uninstall() { variable_del('googleanalytics_account'); + variable_del('googleanalytics_premium'); variable_del('googleanalytics_cache'); variable_del('googleanalytics_codesnippet_create'); variable_del('googleanalytics_codesnippet_before'); @@ -41,7 +42,7 @@ function googleanalytics_uninstall() { variable_del('googleanalytics_visibility_roles'); variable_del('googleanalytics_privacy_donottrack'); - // Remove backup variables if exist. Remove this code in D8. + // Remove backup variables if they exist. Remove this code in D8. variable_del('googleanalytics_codesnippet_after_backup_7200'); variable_del('googleanalytics_codesnippet_before_backup_7200'); } @@ -88,7 +89,8 @@ function googleanalytics_requirements($phase) { } /** - * Upgrade old extension variable to new and use old name as enabled/disabled flag. + * Upgrade old extension variable to new and use old name as enabled/disabled + * flag. */ function googleanalytics_update_6000() { variable_set('googleanalytics_trackfiles_extensions', variable_get('googleanalytics_trackfiles', '7z|aac|avi|csv|doc|exe|flv|gif|gz|jpe?g|js|mp(3|4|e?g)|mov|pdf|phps|png|ppt|rar|sit|tar|torrent|txt|wma|wmv|xls|xml|zip')); @@ -101,7 +103,8 @@ function googleanalytics_update_6000() { function googleanalytics_update_6001() { variable_set('googleanalytics_visibility', 0); - // Remove tracking from all administrative pages, see http://drupal.org/node/34970. + // Remove tracking from all administrative pages, see: + // https://drupal.org/node/34970. $pages = array( 'admin*', 'user*', @@ -114,11 +117,12 @@ function googleanalytics_update_6001() { } /** - * Upgrade role settings and per user tracking settings - * of "User 1" and remove outdated tracking variables. + * Upgrade role settings and per user tracking settings of "User 1" and remove + * outdated tracking variables. */ function googleanalytics_update_6002() { - // Upgrade enabled/disabled roles to new logic (correct for upgrades from 5.x-1.4 and 6.x-1.0). + // Upgrade enabled/disabled roles to new logic (correct for upgrades from + // 5.x-1.4 and 6.x-1.0). $roles = array(); $messages = array(); foreach (user_roles() as $rid => $name) { @@ -137,7 +141,7 @@ function googleanalytics_update_6002() { if (!$track_user1 = variable_get('googleanalytics_track__user1', 1)) { variable_set('googleanalytics_custom', 1); - // Load user 1 object, set appropriate value and save new user settings back. + // Load user 1 object, set appropriate value and save new user settings. $account = user_load(1); $account = user_save($account, array('data' => array('googleanalytics' => array('custom' => 0))), 'account'); $messages[] = t('Disabled user specific page tracking for site administrator.'); @@ -152,11 +156,11 @@ function googleanalytics_update_6002() { } /** - * #262468: Clear menu cache to solve stale menu data in 5.x-1.5 and 6.x-1.1 + * #262468: Clear menu cache to solve stale menu data in 5.x-1.5 and 6.x-1.1. */ function googleanalytics_update_6003() { menu_rebuild(); - return t('Menu has been rebuild.'); + return t('Menu has been rebuilt.'); } /** @@ -231,8 +235,9 @@ function googleanalytics_update_6006() { /** * Remove "User identifier" and "User name" from segmentation fields. * - * This is a data protection and privacy law change. For more information see Google Analytics - * terms of use section 8.1 (http://www.google.com/analytics/en-GB/tos.html). + * This is a data protection and privacy law change. For more information see + * Google Analytics terms of use section 8.1: + * https://www.google.com/analytics/en-GB/tos.html */ function googleanalytics_update_6007() { $profile_fields = variable_get('googleanalytics_segmentation', array()); @@ -293,15 +298,15 @@ function googleanalytics_update_6300() { $messages[] = t('Upgraded custom "before" code snippet.'); } - // Upgrade of AFTER code snippet. - // We cannot update this code snippet automatically. Show message that the upgrade has been skipped. + // Upgrade of AFTER code snippet. We cannot update this code snippet + // automatically. Show message that the upgrade has been skipped. $code_after = variable_get('googleanalytics_codesnippet_after', ''); if (!empty($code_after)) { drupal_set_message(Database::getConnection()->prefixTables("Automatic upgrade of Google Analytics custom 'after' code snippet has been skipped. Backup of previous code snippet has been saved in database table '{variable}' as 'googleanalytics_codesnippet_after_backup_6300'. You need to manually upgrade the custom 'after' code snippet."), 'error'); $messages[] = t('Skipped custom "after" code snippet.'); } - return empty($messages) ? t('No custom code snipped found. Nothing to do.') : implode(' ', $messages); + return empty($messages) ? t('No custom code snippet found. Nothing to do.') : implode(' ', $messages); } /** @@ -356,18 +361,20 @@ function googleanalytics_update_7002() { $googleanalytics_custom_vars['slots'][1]['slot'] = 1; $googleanalytics_custom_vars['slots'][1]['name'] = 'User roles'; $googleanalytics_custom_vars['slots'][1]['value'] = '[current-user:role-names]'; - $googleanalytics_custom_vars['slots'][1]['scope'] = 1; // Sets the scope to visitor-level. + // Sets the scope to visitor-level. + $googleanalytics_custom_vars['slots'][1]['scope'] = 1; variable_set('googleanalytics_custom_var', $googleanalytics_custom_vars); - return t('The deprecated profile segmentation setting for "User roles" has been added to custom variables. You need to deselect all selected profile fields in Google Analytics settings and upgrade other profile fields manually or you may loose tracking data in future! See Google Analytics Custom Variables for more information.', array('@customvar' => 'https://developers.google.com/analytics/devguides/collection/gajs/gaTrackingCustomVariables', '@admin' => url('admin/config/system/googleanalytics'))); + return t('The deprecated profile segmentation setting for "User roles" has been added to custom variables. You need to deselect all selected profile fields in Google Analytics settings and upgrade other profile fields manually or you may lose tracking data in future! See Google Analytics Custom Variables for more information.', array('@customvar' => 'https://developers.google.com/analytics/devguides/collection/gajs/gaTrackingCustomVariables', '@admin' => url('admin/config/system/googleanalytics'))); } else { - return t('You need to deselect all selected profile fields in Google Analytics settings and upgrade other profile fields manually or you may loose tracking data in future! See Google Analytics Custom Variables for more information.', array('@customvar' => 'https://developers.google.com/analytics/devguides/collection/gajs/gaTrackingCustomVariables', '@admin' => url('admin/config/system/googleanalytics'))); + return t('You need to deselect all selected profile fields in Google Analytics settings and upgrade other profile fields manually or you may lose tracking data in future! See Google Analytics Custom Variables for more information.', array('@customvar' => 'https://developers.google.com/analytics/devguides/collection/gajs/gaTrackingCustomVariables', '@admin' => url('admin/config/system/googleanalytics'))); } } /** - * Rename googleanalytics_trackoutgoing variable to googleanalytics_trackoutbound. + * Rename googleanalytics_trackoutgoing variable to + * googleanalytics_trackoutbound. */ function googleanalytics_update_7003() { variable_set('googleanalytics_trackoutbound', variable_get('googleanalytics_trackoutgoing', 1)); @@ -377,7 +384,8 @@ function googleanalytics_update_7003() { } /** - * Rename googleanalytics_visibility variable to googleanalytics_visibility_pages for consistency. + * Rename googleanalytics_visibility variable to + * googleanalytics_visibility_pages for consistency. */ function googleanalytics_update_7004() { variable_set('googleanalytics_visibility_pages', variable_get('googleanalytics_visibility', 1)); @@ -401,7 +409,7 @@ function googleanalytics_update_7005() { $diff = array_diff($pages, preg_split('/(\r\n?|\n)/', variable_get('googleanalytics_pages', implode("\n", $pages)))); if (empty($diff)) { - // No diff to previous settings found. Update with new settings. + // No difference to previous settings found. Update with new settings. $pages = array( 'admin', 'admin/*', @@ -437,7 +445,8 @@ function googleanalytics_update_7007() { } /** - * Delete custom ga.js code snipptes to prevent malfunctions in new Universal Analytics tracker. A backup of your snippets will be created. + * Delete custom ga.js code snippets to prevent malfunctions in new Universal + * Analytics tracker. A backup of your snippets will be created. */ function googleanalytics_update_7200() { $messages = array(); @@ -459,11 +468,12 @@ function googleanalytics_update_7200() { $messages[] = t('Manual upgrade of custom "after" code snippet from ja.js to analytics.js API is required.'); } - return empty($messages) ? t('No custom code snipped found. Nothing to do.') : implode(' ', $messages); + return empty($messages) ? t('No custom code snippet found. Nothing to do.') : implode(' ', $messages); } /** - * Delete obsolete custom variables. Custom variables are now custom dimensions and metrics. + * Delete obsolete custom variables. Custom variables are now custom dimensions + * and metrics. */ function googleanalytics_update_7201() { variable_del('googleanalytics_custom_var'); @@ -475,7 +485,7 @@ function googleanalytics_update_7201() { * Delete obsolete JavaScript scope variable. */ function googleanalytics_update_7202() { - // Remove obsolete scope variable + // Remove obsolete scope variable. variable_del('googleanalytics_js_scope'); return t('Removed obsolete JavaScript scope variable.'); diff --git a/sites/all/modules/contrib/admin/google_analytics/googleanalytics.js b/sites/all/modules/contrib/admin/google_analytics/googleanalytics.js index 8d5bde1b..5ba42ca9 100644 --- a/sites/all/modules/contrib/admin/google_analytics/googleanalytics.js +++ b/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. diff --git a/sites/all/modules/contrib/admin/google_analytics/googleanalytics.module b/sites/all/modules/contrib/admin/google_analytics/googleanalytics.module index b45ee103..0026703a 100644 --- a/sites/all/modules/contrib/admin/google_analytics/googleanalytics.module +++ b/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 + * @author: Alexander Hass */ /** @@ -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('Google Analytics is a free (registration required) website traffic and marketing effectiveness service.', array('@ga_url' => 'http://www.google.com/analytics/')); + return t('Google Analytics 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. diff --git a/sites/all/modules/contrib/admin/google_analytics/googleanalytics.test b/sites/all/modules/contrib/admin/google_analytics/googleanalytics.test index 745047a8..52c31489 100644 --- a/sites/all/modules/contrib/admin/google_analytics/googleanalytics.test +++ b/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 message with html tags and link.', 'error'); + //drupal_set_message('Example error message with html tags and link.', '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', ''); $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', ''); $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', ''); diff --git a/sites/all/modules/contrib/admin/google_analytics/googleanalytics.test.js b/sites/all/modules/contrib/admin/google_analytics/googleanalytics.test.js index 05c720fb..f22d6f1a 100644 --- a/sites/all/modules/contrib/admin/google_analytics/googleanalytics.test.js +++ b/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.'); diff --git a/sites/all/modules/contrib/admin/google_analytics/googleanalytics.variable.inc b/sites/all/modules/contrib/admin/google_analytics/googleanalytics.variable.inc index 9d142a27..5b88b1ff 100644 --- a/sites/all/modules/contrib/admin/google_analytics/googleanalytics.variable.inc +++ b/sites/all/modules/contrib/admin/google_analytics/googleanalytics.variable.inc @@ -13,7 +13,7 @@ function googleanalytics_variable_info($options) { 'type' => 'string', 'title' => t('Web Property ID', array(), $options), 'default' => 'UA-', - 'description' => t('This ID is unique to each site you want to track separately, and is in the form of UA-xxxxxxx-yy. To get a Web Property ID, register your site with Google Analytics, or if you already have registered your site, go to your Google Analytics Settings page to see the ID next to every site profile. Find more information in the documentation.', array('@analytics' => 'http://www.google.com/analytics/', '@webpropertyid' => url('https://developers.google.com/analytics/resources/concepts/gaConceptsAccounts', array('fragment' => 'webProperty'))), $options), + 'description' => t('This ID is unique to each site you want to track separately, and is in the form of UA-xxxxxxx-yy. To get a Web Property ID, register your site with Google Analytics, or if you already have registered your site, go to your Google Analytics Settings page to see the ID next to every site profile. Find more information in the documentation.', array('@analytics' => 'https://marketingplatform.google.com/about/analytics/', '@webpropertyid' => url('https://developers.google.com/analytics/resources/concepts/gaConceptsAccounts', array('fragment' => 'webProperty'))), $options), 'required' => TRUE, 'group' => 'googleanalytics', 'localize' => TRUE, diff --git a/sites/all/modules/contrib/fields/content_taxonomy/CHANGELOG.txt b/sites/all/modules/contrib/fields/content_taxonomy/CHANGELOG.txt new file mode 100644 index 00000000..58774a65 --- /dev/null +++ b/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. diff --git a/sites/all/modules/contrib/fields/content_taxonomy/content_taxonomy.api.php b/sites/all/modules/contrib/fields/content_taxonomy/content_taxonomy.api.php new file mode 100644 index 00000000..47939cdc --- /dev/null +++ b/sites/all/modules/contrib/fields/content_taxonomy/content_taxonomy.api.php @@ -0,0 +1,42 @@ +machine_name == 'my_voc') { + $tree_callback = 'my_tree_callback'; + } +} + +/** + * @} End of "addtogroup hooks". + */ diff --git a/sites/all/modules/contrib/fields/content_taxonomy/content_taxonomy.info b/sites/all/modules/contrib/fields/content_taxonomy/content_taxonomy.info index aa7b456e..693cb096 100644 --- a/sites/all/modules/contrib/fields/content_taxonomy/content_taxonomy.info +++ b/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" diff --git a/sites/all/modules/contrib/fields/content_taxonomy/content_taxonomy.install b/sites/all/modules/contrib/fields/content_taxonomy/content_taxonomy.install new file mode 100644 index 00000000..debeb8f7 --- /dev/null +++ b/sites/all/modules/contrib/fields/content_taxonomy/content_taxonomy.install @@ -0,0 +1,94 @@ + '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']))); + } + } + } +} diff --git a/sites/all/modules/contrib/fields/content_taxonomy/content_taxonomy.module b/sites/all/modules/contrib/fields/content_taxonomy/content_taxonomy.module index 2f571acd..d3596464 100644 --- a/sites/all/modules/contrib/fields/content_taxonomy/content_taxonomy.module +++ b/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) { diff --git a/sites/all/modules/contrib/fields/content_taxonomy/content_taxonomy_autocomplete.info b/sites/all/modules/contrib/fields/content_taxonomy/content_taxonomy_autocomplete.info index 9468f913..99692385 100644 --- a/sites/all/modules/contrib/fields/content_taxonomy/content_taxonomy_autocomplete.info +++ b/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" diff --git a/sites/all/modules/contrib/fields/content_taxonomy/content_taxonomy_autocomplete.module b/sites/all/modules/contrib/fields/content_taxonomy/content_taxonomy_autocomplete.module index 845ae7b0..eaa9c5de 100644 --- a/sites/all/modules/contrib/fields/content_taxonomy/content_taxonomy_autocomplete.module +++ b/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']))); } } } diff --git a/sites/all/modules/contrib/fields/content_taxonomy/content_taxonomy_et.info b/sites/all/modules/contrib/fields/content_taxonomy/content_taxonomy_et.info new file mode 100644 index 00000000..f8424c2b --- /dev/null +++ b/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" + diff --git a/sites/all/modules/contrib/fields/content_taxonomy/content_taxonomy_et.module b/sites/all/modules/contrib/fields/content_taxonomy/content_taxonomy_et.module new file mode 100644 index 00000000..69399e8a --- /dev/null +++ b/sites/all/modules/contrib/fields/content_taxonomy/content_taxonomy_et.module @@ -0,0 +1,404 @@ +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; +} diff --git a/sites/all/modules/contrib/fields/content_taxonomy/content_taxonomy_migrate.info b/sites/all/modules/contrib/fields/content_taxonomy/content_taxonomy_migrate.info index 0f4d6fc9..a5f55639 100644 --- a/sites/all/modules/contrib/fields/content_taxonomy/content_taxonomy_migrate.info +++ b/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" diff --git a/sites/all/modules/contrib/fields/content_taxonomy/content_taxonomy_migrate.module b/sites/all/modules/contrib/fields/content_taxonomy/content_taxonomy_migrate.module index ee591879..a2b32ea6 100644 --- a/sites/all/modules/contrib/fields/content_taxonomy/content_taxonomy_migrate.module +++ b/sites/all/modules/contrib/fields/content_taxonomy/content_taxonomy_migrate.module @@ -1,11 +1,18 @@ $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 } } } -} \ No newline at end of file +} diff --git a/sites/all/modules/contrib/form/honeypot/README.md b/sites/all/modules/contrib/form/honeypot/README.md new file mode 100644 index 00000000..91f9a4e8 --- /dev/null +++ b/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). diff --git a/sites/all/modules/contrib/form/honeypot/README.txt b/sites/all/modules/contrib/form/honeypot/README.txt deleted file mode 100644 index bc5fb1bb..00000000 --- a/sites/all/modules/contrib/form/honeypot/README.txt +++ /dev/null @@ -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). \ No newline at end of file diff --git a/sites/all/modules/contrib/form/honeypot/docker-compose.yml b/sites/all/modules/contrib/form/honeypot/docker-compose.yml new file mode 100644 index 00000000..1c8b6bb8 --- /dev/null +++ b/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" diff --git a/sites/all/modules/contrib/form/honeypot/honeypot.admin.inc b/sites/all/modules/contrib/form/honeypot/honeypot.admin.inc index 64e3ae42..84bb7e2c 100644 --- a/sites/all/modules/contrib/form/honeypot/honeypot.admin.inc +++ b/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'] .= '
    ' . t('Page caching will be disabled on any page where a form is present if the Honeypot time limit is not set to 0.'); + if (!variable_get('honeypot_use_js_for_cached_pages', FALSE)) { + $form['configuration']['honeypot_protect_all_forms']['#description'] .= '
    ' . t('Page caching will be disabled on any page where a form is present if the Honeypot time limit is not set to 0.'); + } $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'] .= '
    ' . t('Page caching will be disabled if there is a form protected by time limit on the page.'); + if (!variable_get('honeypot_use_js_for_cached_pages', FALSE)) { + $form['configuration']['honeypot_time_limit']['#description'] .= '
    ' . t('Page caching will be disabled if there is a form protected by time limit on the page.'); + } + + $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'] .= '
    ' . t('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).'); // Honeypot Enabled forms. $form['enabled_forms'] = array( diff --git a/sites/all/modules/contrib/form/honeypot/honeypot.info b/sites/all/modules/contrib/form/honeypot/honeypot.info index 857d242e..6ce86d53 100644 --- a/sites/all/modules/contrib/form/honeypot/honeypot.info +++ b/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" diff --git a/sites/all/modules/contrib/form/honeypot/honeypot.install b/sites/all/modules/contrib/form/honeypot/honeypot.install index 5a560525..c17b4cb4 100644 --- a/sites/all/modules/contrib/form/honeypot/honeypot.install +++ b/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'); } /** diff --git a/sites/all/modules/contrib/form/honeypot/honeypot.module b/sites/all/modules/contrib/form/honeypot/honeypot.module index 46f83ae6..e8137966 100644 --- a/sites/all/modules/contrib/form/honeypot/honeypot.module +++ b/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()); +} diff --git a/sites/all/modules/contrib/form/honeypot/honeypot.test b/sites/all/modules/contrib/form/honeypot/honeypot.test index dd136b93..aab7f6e0 100644 --- a/sites/all/modules/contrib/form/honeypot/honeypot.test +++ b/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.'); } + } diff --git a/sites/all/modules/contrib/form/honeypot/js/honeypot.js b/sites/all/modules/contrib/form/honeypot/js/honeypot.js new file mode 100644 index 00000000..63acffd7 --- /dev/null +++ b/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)); diff --git a/sites/all/modules/contrib/form/honeypot/tests/honeypot_test.info b/sites/all/modules/contrib/form/honeypot/tests/honeypot_test.info index 7b0226ad..89aa78bc 100644 --- a/sites/all/modules/contrib/form/honeypot/tests/honeypot_test.info +++ b/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" diff --git a/sites/all/modules/contrib/mail/mailgun/README.txt b/sites/all/modules/contrib/mail/mailgun/README.txt new file mode 100644 index 00000000..db4ea809 --- /dev/null +++ b/sites/all/modules/contrib/mail/mailgun/README.txt @@ -0,0 +1 @@ +Mailgun module documentation is here: https://www.drupal.org/node/2547591. diff --git a/sites/all/modules/contrib/mail/mailgun/composer.json b/sites/all/modules/contrib/mail/mailgun/composer.json new file mode 100644 index 00000000..ef41f065 --- /dev/null +++ b/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" + } +} diff --git a/sites/all/modules/contrib/mail/mailgun/mailgun.admin.inc b/sites/all/modules/contrib/mail/mailgun/mailgun.admin.inc index b5f99f1f..61958092 100644 --- a/sites/all/modules/contrib/mail/mailgun/mailgun.admin.inc +++ b/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 documentation 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 Mailgun dashboard.', 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,31 +74,70 @@ 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), ); + $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, + ); + + // 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('Tracking'), + ); + + $form['extra']['tracking']['mailgun_tracking'] = array( + '#title' => t('Enable tracking'), + '#type' => 'select', + '#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['extra']['tracking']['mailgun_tracking_clicks'] = array( + '#title' => t('Enable click tracking'), + '#type' => 'select', + '#options' => $options, + '#description' => t('Whether to enable click tracking by default or not.'), + '#default_value' => variable_get('mailgun_tracking_clicks', 'default'), + ); + + $form['extra']['tracking']['mailgun_tracking_opens'] = array( + '#title' => t('Enable open tracking'), + '#type' => 'select', + '#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') { + if ($format->format === 'php_code') { continue; } - $formats[$format->format] = t($format->name); + $formats[$format->format] = $format->name; } - $form['mailgun_format'] = array( + $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.'), @@ -101,36 +145,24 @@ function mailgun_admin_settings($form, &$form_state) { '#default_value' => variable_get('mailgun_format', '_none'), ); - $form['defaults'] = 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, + $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['defaults']['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 Tracking Messages for details.', array('@url' => url('https://documentation.mailgun.com/user_manual.html#tracking-messages'))), - '#default_value' => variable_get('mailgun_tracking', 'default'), - ); - - $form['defaults']['mailgun_tracking_clicks'] = array( - '#title' => t('Enable click tracking'), - '#type' => 'select', - '#options' => array('default' => t('Use default setting'), 'enabled' => t('Enabled'), 'disabled' => t('Disabled')), - '#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( - '#title' => t('Enable open tracking'), - '#type' => 'select', - '#options' => array('default' => t('Use default setting'), 'enabled' => t('Enabled'), 'disabled' => t('Disabled')), - '#description' => t('Whether to enable open tracking by default or not.'), - '#default_value' => variable_get('mailgun_tracking_opens', 'default'), + $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), ); } @@ -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 project page to contribute or read documentation 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 database log 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 database log for details.', + array( + '%from' => $result['from'], + '%to' => $result['to'], + '@url' => url('admin/reports/dblog'), + )), 'status'); } diff --git a/sites/all/modules/contrib/mail/mailgun/mailgun.info b/sites/all/modules/contrib/mail/mailgun/mailgun.info index f9822c4a..b72016f7 100644 --- a/sites/all/modules/contrib/mail/mailgun/mailgun.info +++ b/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" diff --git a/sites/all/modules/contrib/mail/mailgun/mailgun.install b/sites/all/modules/contrib/mail/mailgun/mailgun.install index 52c72700..b1e6573c 100644 --- a/sites/all/modules/contrib/mail/mailgun/mailgun.install +++ b/sites/all/modules/contrib/mail/mailgun/mailgun.install @@ -1,6 +1,5 @@ '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; +} diff --git a/sites/all/modules/contrib/mail/mailgun/mailgun.mail.inc b/sites/all/modules/contrib/mail/mailgun/mailgun.mail.inc index 6dbd22f0..6c752fb8 100644 --- a/sites/all/modules/contrib/mail/mailgun/mailgun.mail.inc +++ b/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; } } } diff --git a/sites/all/modules/contrib/mail/mailgun/mailgun.module b/sites/all/modules/contrib/mail/mailgun/mailgun.module index a5bb037b..193de9b7 100644 --- a/sites/all/modules/contrib/mail/mailgun/mailgun.module +++ b/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 '

    ' . t('See documentation for instructions on installing and configuring Mailgun.', array('@url' => url('https://www.drupal.org/node/2547591'))) . '

    '; - break; - case 'admin/config/system/mailgun/test': + case MAILGUN_ADMIN_PAGE: + return '

    ' . t('See !link for instructions on installing and configuring Mailgun.', array( + '!link' => l(t('documentation'), MAILGUN_DOCUMENTATION_LINK), + )) . '

    '; + + case MAILGUN_ADMIN_PAGE . '/test': return '

    ' . t('Use this form to send a test e-mail to ensure you have correctly configured Mailgun.') . '

    '; - 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 ". + 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 ". $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); - // For a list of HTTP response codes, see: https://documentation.mailgun.com/api-intro.html#errors. - if ($result->http_response_code == 200) { + try { + $result = $client->messages()->send($working_domain, $mailgun_message); + + 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; } } diff --git a/sites/all/modules/contrib/mail/mailgun/templates/mailgun-message.tpl.php b/sites/all/modules/contrib/mail/mailgun/templates/mailgun-message.tpl.php new file mode 100644 index 00000000..d721b182 --- /dev/null +++ b/sites/all/modules/contrib/mail/mailgun/templates/mailgun-message.tpl.php @@ -0,0 +1,28 @@ + + + + + + +
    + +
    + + diff --git a/sites/all/modules/contrib/mail/mailsystem/html_to_text.inc b/sites/all/modules/contrib/mail/mailsystem/html_to_text.inc index 08576220..39f06eb4 100644 --- a/sites/all/modules/contrib/mail/mailsystem/html_to_text.inc +++ b/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
     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;
    diff --git a/sites/all/modules/contrib/mail/mailsystem/mailsystem.admin.inc b/sites/all/modules/contrib/mail/mailsystem/mailsystem.admin.inc
    index 2dca212c..b6361d05 100644
    --- a/sites/all/modules/contrib/mail/mailsystem/mailsystem.admin.inc
    +++ b/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];
    diff --git a/sites/all/modules/contrib/mail/mailsystem/mailsystem.info b/sites/all/modules/contrib/mail/mailsystem/mailsystem.info
    index 7d4956fe..250c1e1f 100644
    --- a/sites/all/modules/contrib/mail/mailsystem/mailsystem.info
    +++ b/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"
    diff --git a/sites/all/modules/contrib/mail/mailsystem/mailsystem.module b/sites/all/modules/contrib/mail/mailsystem/mailsystem.module
    index e414280b..b1c54a95 100644
    --- a/sites/all/modules/contrib/mail/mailsystem/mailsystem.module
    +++ b/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 = '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 %class written to %file.',
             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')) {
    diff --git a/sites/all/modules/contrib/mail/mailsystem/mailsystem.theme.inc b/sites/all/modules/contrib/mail/mailsystem/mailsystem.theme.inc
    index 8a22b912..ca6cda1d 100644
    --- a/sites/all/modules/contrib/mail/mailsystem/mailsystem.theme.inc
    +++ b/sites/all/modules/contrib/mail/mailsystem/mailsystem.theme.inc
    @@ -1,13 +1,13 @@
     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');
    +}