From f6f7fd575f45e6b95eae1180539c38bb8f2e017e Mon Sep 17 00:00:00 2001
From: Bachir Soussi Chiadmi
Date: Tue, 25 Oct 2016 16:23:00 +0200
Subject: [PATCH] security updates of unpatched modules
---
.../contrib/admin/google_analytics/README.txt | 59 +-
.../googleanalytics.admin.inc | 46 +-
.../google_analytics/googleanalytics.admin.js | 9 +
.../google_analytics/googleanalytics.debug.js | 64 +-
.../google_analytics/googleanalytics.info | 6 +-
.../google_analytics/googleanalytics.install | 9 +-
.../admin/google_analytics/googleanalytics.js | 58 +-
.../google_analytics/googleanalytics.module | 36 +-
.../google_analytics/googleanalytics.test | 62 +-
.../googleanalytics.variable.inc | 4 +
.../modules/contrib/dev/elysia_cron/API.txt | 15 +-
.../contrib/dev/elysia_cron/INSTALL.txt | 17 +-
.../contrib/dev/elysia_cron/README.txt | 121 +-
.../modules/contrib/dev/elysia_cron/cron.php | 14 +-
...on-multilingual-image-path-2096571-1.patch | 13 -
.../dev/elysia_cron/elysia_cron.admin.inc | 524 +++--
.../dev/elysia_cron/elysia_cron.api.php | 134 ++
.../dev/elysia_cron/elysia_cron.ctools.inc | 229 ++-
.../dev/elysia_cron/elysia_cron.drush.inc | 170 +-
.../contrib/dev/elysia_cron/elysia_cron.info | 10 +-
.../dev/elysia_cron/elysia_cron.install | 117 +-
.../dev/elysia_cron/elysia_cron.module | 1770 ++++++++++-------
.../dev/elysia_cron/elysia_cron_scheduler.inc | 372 +++-
.../elysia_cron/elysia_cron_scheduler_old.inc | 230 ---
.../dev/elysia_cron/elysia_cron_update.php | 327 ---
.../dev/elysia_cron/elysia_drupalconv.php | 86 -
.../modules/contrib/flag/flag/flag.api.php | 58 +-
.../modules/contrib/flag/flag/flag.flag.inc | 2 +-
sites/all/modules/contrib/flag/flag/flag.info | 6 +-
.../modules/contrib/flag/flag/flag.install | 46 +-
.../all/modules/contrib/flag/flag/flag.module | 162 +-
.../modules/contrib/flag/flag/flag.tokens.inc | 9 +-
.../contrib/flag/flag/flag_actions.info | 8 +-
.../contrib/flag/flag/flag_actions.module | 28 +-
.../flag/flag_bookmark/flag_bookmark.info | 7 +-
.../includes/flag_bookmark.views.inc | 21 +
.../includes/flag_bookmark.views_default.inc | 7 +-
.../flag_bookmark_plugin_validate_user.inc | 89 +
.../flag/flag/includes/flag.actions.inc | 2 +-
.../contrib/flag/flag/includes/flag.admin.inc | 17 +-
.../flag/flag/includes/flag.export.inc | 6 +-
.../flag/flag/includes/flag.features.inc | 2 +-
.../contrib/flag/flag/includes/flag.pages.inc | 6 +-
.../flag/flag/includes/flag/flag_comment.inc | 80 +
.../flag/flag/includes/flag/flag_flag.inc | 104 +-
.../flag/flag/includes/flag/flag_node.inc | 4 +-
.../flag/flag/includes/views/flag.views.inc | 21 +
.../views/flag_handler_argument_entity_id.inc | 2 +-
.../modules/contrib/flag/flag/tests/flag.test | 130 +-
.../flag_comment_flag_test.info | 13 +
.../flag_comment_flag_test.module | 41 +
.../flag_fields_test/flag_fields_test.info | 6 +-
.../tests/flag_hook_test/flag_hook_test.info | 6 +-
.../flag_hook_test/flag_hook_test.module | 4 +-
.../tests/flagaccesstest/flagaccesstest.info | 6 +-
.../webform/includes/webform.submissions.inc | 41 +-
.../modules/contrib/form/webform/webform.info | 6 +-
.../contrib/form/webform/webform.module | 16 +-
.../modules/contrib/panels/panels/README.txt | 7 +-
.../modules/contrib/panels/panels/UPGRADE.txt | 12 +-
.../contrib/panels/panels/css/panels.css | 5 -
.../contrib/panels/panels/css/panels_dnd.css | 16 +-
.../panels/panels/help/plugins-layout.html | 2 +-
.../panels/i18n_panels/i18n_panels.info | 6 +-
.../panels/panels/includes/add-content.inc | 2 +
.../contrib/panels/panels/includes/common.inc | 40 +-
.../panels/panels/includes/display-edit.inc | 4 +-
.../panels/panels/includes/display-layout.inc | 2 +
.../panels/panels/includes/plugins.inc | 27 +-
.../contrib/panels/panels/js/panels-base.js | 39 +
.../modules/contrib/panels/panels/panels.info | 17 +-
.../contrib/panels/panels/panels.install | 199 +-
.../contrib/panels/panels/panels.module | 122 +-
.../panels/panels/panels_ipe/js/panels_ipe.js | 24 +-
.../panels/panels_ipe/panels_ipe.api.php | 31 +
.../panels/panels/panels_ipe/panels_ipe.info | 7 +-
.../panels/panels_ipe/panels_ipe.module | 7 +-
.../panels_renderer_ipe.class.php | 73 +-
.../panels/panels_mini/panels_mini.info | 7 +-
.../panels/panels_mini/panels_mini.install | 60 +-
.../panels/panels_mini/panels_mini.module | 91 +-
.../plugins/content_types/panels_mini.inc | 11 +-
.../plugins/panels_storage/panels_mini.inc | 22 +
.../panels/panels_node/panels_node.info | 7 +-
.../panels/panels_node/panels_node.install | 96 +
.../panels/panels_node/panels_node.module | 76 +-
.../plugins/panels_storage/panels_node.inc | 25 +
.../panels_renderer_editor.class.php | 50 +-
.../panels_renderer_standard.class.php | 46 +-
.../plugins/page_wizards/landing_page.inc | 5 +
.../plugins/panels_storage/page_manager.inc | 19 +
.../panels/panels/plugins/styles/stylizer.inc | 8 +-
.../plugins/task_handlers/panel_context.inc | 57 +-
.../panels-add-content-modal.tpl.php | 6 +
.../tests/PanelsEntityViewWebTestCase.test | 103 +
.../tests/PanelsNodeViewWebTestCase.test | 31 +
.../tests/PanelsTermViewWebTestCase.test | 31 +
.../tests/PanelsUserViewWebTestCase.test | 31 +
.../contrib/search/search_api/CHANGELOG.txt | 51 +-
.../plugins/facetapi/adapter.inc | 13 +-
.../plugins/facetapi/query_type_date.inc | 72 +-
.../plugins/facetapi/query_type_term.inc | 27 +-
.../search_api_facetapi.info | 6 +-
.../search_api_facetapi.install | 32 +-
.../search_api_facetapi.module | 79 +-
.../includes/display_facet_block.inc | 25 +
.../includes/handler_filter_entity.inc | 11 -
.../includes/handler_filter_taxonomy_term.inc | 46 +-
.../search_api_views/includes/query.inc | 12 +-
.../search_api_views/search_api_views.info | 6 +-
.../includes/callback_bundle_filter.inc | 7 +-
.../search_api/includes/datasource_entity.inc | 11 +-
.../search_api/includes/index_entity.inc | 4 +-
.../includes/processor_highlight.inc | 16 +-
.../includes/processor_html_filter.inc | 4 +-
.../search/search_api/includes/query.inc | 11 +-
.../search/search_api/search_api.admin.inc | 7 -
.../contrib/search/search_api/search_api.info | 8 +-
.../search/search_api/search_api.module | 118 +-
.../contrib/search/search_api/search_api.test | 4 +-
.../search_api/tests/search_api_test.info | 6 +-
.../views/views_data_export/.gitignore | 1 -
...iews_data_export_plugin_display_export.inc | 73 +-
.../views_data_export_plugin_style_export.inc | 15 +-
...ws_data_export_plugin_style_export_csv.inc | 16 +
.../views/views_data_export/tests/access.test | 208 ++
.../views/views_data_export/tests/base.test | 6 +-
.../views_data_export/tests/csv_export.test | 74 +
.../tests/garbagecollection.test | 187 ++
.../theme/views_data_export.theme.inc | 42 +-
.../views_data_export.drush.inc | 18 +-
.../views_data_export/views_data_export.info | 8 +-
.../views_data_export.module | 66 +-
133 files changed, 5598 insertions(+), 2574 deletions(-)
delete mode 100644 sites/all/modules/contrib/dev/elysia_cron/elysia_cron-multilingual-image-path-2096571-1.patch
create mode 100644 sites/all/modules/contrib/dev/elysia_cron/elysia_cron.api.php
delete mode 100644 sites/all/modules/contrib/dev/elysia_cron/elysia_cron_scheduler_old.inc
delete mode 100644 sites/all/modules/contrib/dev/elysia_cron/elysia_cron_update.php
delete mode 100644 sites/all/modules/contrib/dev/elysia_cron/elysia_drupalconv.php
create mode 100644 sites/all/modules/contrib/flag/flag/flag_bookmark/includes/flag_bookmark.views.inc
create mode 100644 sites/all/modules/contrib/flag/flag/flag_bookmark/plugins/flag_bookmark_plugin_validate_user.inc
create mode 100644 sites/all/modules/contrib/flag/flag/tests/flag_comment_flag_test/flag_comment_flag_test.info
create mode 100644 sites/all/modules/contrib/flag/flag/tests/flag_comment_flag_test/flag_comment_flag_test.module
create mode 100644 sites/all/modules/contrib/panels/panels/panels_ipe/panels_ipe.api.php
create mode 100644 sites/all/modules/contrib/panels/panels/panels_mini/plugins/panels_storage/panels_mini.inc
create mode 100644 sites/all/modules/contrib/panels/panels/panels_node/plugins/panels_storage/panels_node.inc
create mode 100644 sites/all/modules/contrib/panels/panels/plugins/panels_storage/page_manager.inc
create mode 100644 sites/all/modules/contrib/panels/panels/tests/PanelsEntityViewWebTestCase.test
create mode 100644 sites/all/modules/contrib/panels/panels/tests/PanelsNodeViewWebTestCase.test
create mode 100644 sites/all/modules/contrib/panels/panels/tests/PanelsTermViewWebTestCase.test
create mode 100644 sites/all/modules/contrib/panels/panels/tests/PanelsUserViewWebTestCase.test
delete mode 100644 sites/all/modules/contrib/views/views_data_export/.gitignore
create mode 100644 sites/all/modules/contrib/views/views_data_export/tests/access.test
create mode 100644 sites/all/modules/contrib/views/views_data_export/tests/garbagecollection.test
diff --git a/sites/all/modules/contrib/admin/google_analytics/README.txt b/sites/all/modules/contrib/admin/google_analytics/README.txt
index fab2f2ec..25fd22aa 100644
--- a/sites/all/modules/contrib/admin/google_analytics/README.txt
+++ b/sites/all/modules/contrib/admin/google_analytics/README.txt
@@ -12,12 +12,20 @@ Requirements
* Google Analytics user account
-
Installation
============
Copy the 'googleanalytics' module directory in to your Drupal
sites/all/modules directory as usual.
+Upgrading from 6.x-3.x and 7.x-1.x
+==================================
+If you upgrade from 6.x-3.x and 7.x-1.x (ga.js) to 7.x-2.x (analytics.js) you
+should verify if you used custom variables. Write down your settings or make a
+screenshot. You need to re-configure the settings to use custom dimensions or
+metrics. There is no automatic upgrade path for custom variables feature. All
+other module settings are upgraded automatically.
+
+See https://support.google.com/analytics/answer/2795983?hl=en for more details.
Usage
=====
@@ -27,12 +35,8 @@ All pages will now have the required JavaScript added to the
HTML footer can confirm this by viewing the page source from
your browser.
-New approach to page tracking in 5.x-1.5 and 6.x-1.1
-====================================================
-With 5.x-1.5 and 6.x-1.1 there are new settings on the settings page at
-admin/config/system/googleanalytics. The "Page specific tracking" area now
-comes with an interface that copies Drupal's block visibility settings.
-
+Page specific tracking
+======================
The default is set to "Add to every page except the listed pages". By
default the following pages are listed for exclusion:
@@ -44,23 +48,24 @@ node/*/*
user/*/*
These defaults are changeable by the website administrator or any other
-user with 'administer google analytics' permission.
+user with 'Administer Google Analytics' permission.
-Like the blocks visibility settings in Drupal core, there is now 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.
+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.
Custom dimensions and metrics
=============================
One example for custom dimensions tracking is the "User roles" tracking.
-1. In the Google Analytics Management Interface 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 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.
-2. Enter the below configuration data into the custom dimensions settings form
- under admin/config/system/googleanalytics. You can also choose another index,
- but keep it always in sync with the index used in step #1.
+2. Enter the below configuration data into the Drupal custom dimensions settings
+ form under admin/config/system/googleanalytics. You can also choose another
+ index, but keep it always in sync with the index used in step #1.
Index: 1
Value: [current-user:role-names]
@@ -77,3 +82,23 @@ provided for any customisations you include.
To speed up page loading you may also cache the Google Analytics "analytics.js"
file locally.
+
+Manual JS debugging
+===================
+For manual debugging of the JS code you are able to create a test node. This
+is the example HTML code for this test node. You need to enable debugging mode
+in your Drupal configuration of Google Analytics settings to see verbose
+messages in your browsers JS console.
+
+Title: Google Analytics test page
+
+Body:
+
+
+Text format: Full HTML
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 4449077d..d32939a3 100644
--- a/sites/all/modules/contrib/admin/google_analytics/googleanalytics.admin.inc
+++ b/sites/all/modules/contrib/admin/google_analytics/googleanalytics.admin.inc
@@ -224,6 +224,18 @@ function googleanalytics_admin_settings_form($form_state) {
),
),
);
+
+ $colorbox_dependencies = '';
+ $colorbox_dependencies .= t('Requires: !module-list', array('!module-list' => (module_exists('colorbox') ? t('@module (enabled)', array('@module' => 'Colorbox')) : t('@module (disabled)', array('@module' => 'Colorbox')))));
+ $colorbox_dependencies .= '
';
+
+ $form['tracking']['linktracking']['googleanalytics_trackcolorbox'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Track content in colorbox modal dialogs'),
+ '#default_value' => variable_get('googleanalytics_trackcolorbox', 1),
+ '#description' => t('Enable to track the content shown in colorbox modal windows.') . $colorbox_dependencies,
+ '#disabled' => (module_exists('colorbox') ? FALSE : TRUE),
+ );
$form['tracking']['linktracking']['googleanalytics_tracklinkid'] = array(
'#type' => 'checkbox',
'#title' => t('Track enhanced link attribution'),
@@ -246,7 +258,7 @@ function googleanalytics_admin_settings_form($form_state) {
'#type' => 'checkboxes',
'#title' => t('Track messages of type'),
'#default_value' => variable_get('googleanalytics_trackmessages', array()),
- '#description' => t('This will track the selected message types shown to users. Tracking of form validation errors may help you identifying usability issues in your site. For each visit (user session), a maximum of approximately 500 combined GATC requests (both events and page views) can be tracked. Every message is tracked as one individual event. Note that - as the number of events in a session approaches the limit - additional events might not be tracked. Messages from excluded pages cannot tracked.'),
+ '#description' => t('This will track the selected message types shown to users. Tracking of form validation errors may help you identifying usability issues in your site. For each visit (user session), a maximum of approximately 500 combined GATC requests (both events and page views) can be tracked. Every message is tracked as one individual event. Note that - as the number of events in a session approaches the limit - additional events might not be tracked. Messages from excluded pages cannot be tracked.'),
'#options' => array(
'status' => t('Status message'),
'warning' => t('Warning message'),
@@ -305,7 +317,7 @@ function googleanalytics_admin_settings_form($form_state) {
$form['googleanalytics_custom_dimension'] = array(
'#collapsed' => TRUE,
'#collapsible' => TRUE,
- '#description' => t('You can set values for Google Analytics Custom Dimensions here. You must have already configured your custom dimensions in the Google Analytics Management Interface. You may use tokens. Global and user tokens are always available; on node pages, node tokens are also available. A dimension value is allowed to have a maximum lenght of 150 bytes. Expect longer values to get trimmed.', array('@custom_var_documentation' => 'https://developers.google.com/analytics/devguides/collection/analyticsjs/custom-dims-mets', '@setup_documentation' => 'https://support.google.com/analytics/answer/2709829')),
+ '#description' => t('You can set values for Google Analytics Custom Dimensions here. You must have already configured your custom dimensions in the Google Analytics Management Interface. You may use tokens. Global and user tokens are always available; on node pages, node tokens are also available. A dimension value is allowed to have a maximum length of 150 bytes. Expect longer values to get trimmed.', array('@custom_var_documentation' => 'https://developers.google.com/analytics/devguides/collection/analyticsjs/custom-dims-mets', '@setup_documentation' => 'https://support.google.com/analytics/answer/2709829')),
'#theme' => 'googleanalytics_admin_custom_var_table',
'#title' => t('Custom dimensions'),
'#tree' => TRUE,
@@ -428,6 +440,8 @@ function googleanalytics_admin_settings_form($form_state) {
);
}
+ $user_access_add_js_snippets = !user_access('add JS snippets for google analytics');
+ $user_access_add_js_snippets_permission_warning = $user_access_add_js_snippets ? ' ' . t('This field has been disabled because you do not have sufficient permissions to edit it.') . '' : '';
$form['advanced']['codesnippet'] = array(
'#type' => 'fieldset',
'#title' => t('Custom JavaScript code'),
@@ -447,15 +461,17 @@ function googleanalytics_admin_settings_form($form_state) {
'#type' => 'textarea',
'#title' => t('Code snippet (before)'),
'#default_value' => variable_get('googleanalytics_codesnippet_before', ''),
+ '#disabled' => $user_access_add_js_snippets,
'#rows' => 5,
- '#description' => t('Code in this textarea will be added before ga("send", "pageview");
.'),
+ '#description' => t('Code in this textarea will be added before ga("send", "pageview");
.') . $user_access_add_js_snippets_permission_warning,
);
$form['advanced']['codesnippet']['googleanalytics_codesnippet_after'] = array(
'#type' => 'textarea',
'#title' => t('Code snippet (after)'),
'#default_value' => variable_get('googleanalytics_codesnippet_after', ''),
+ '#disabled' => $user_access_add_js_snippets,
'#rows' => 5,
- '#description' => t('Code in this textarea will be added after ga("send", "pageview");
. This is useful if you\'d like to track a site in two accounts.'),
+ '#description' => t('Code in this textarea will be added after ga("send", "pageview");
. This is useful if you\'d like to track a site in two accounts.') . $user_access_add_js_snippets_permission_warning,
);
$form['advanced']['googleanalytics_debug'] = array(
@@ -541,6 +557,12 @@ function googleanalytics_admin_settings_form_validate($form, &$form_state) {
/**
* Layout for the custom variables table in the admin settings form.
+ *
+ * @param array $variables
+ * An array contains the form elements.
+ *
+ * @return string
+ * The rendered output.
*/
function theme_googleanalytics_admin_custom_var_table($variables) {
$form = $variables['form'];
@@ -551,7 +573,7 @@ function theme_googleanalytics_admin_custom_var_table($variables) {
);
$rows = array();
- foreach (element_children($form['indexes']) as $key => $id) {
+ foreach (element_children($form['indexes']) as $id) {
$rows[] = array(
'data' => array(
drupal_render($form['indexes'][$id]['index']),
@@ -600,11 +622,18 @@ function googleanalytics_token_element_validate(&$element, &$form_state) {
return $element;
}
+/**
+ * @param array $value
+ * An array of token values.
+ *
+ * @return array
+ * A unique array of invalid tokens.
+ */
function _googleanalytics_get_forbidden_tokens($value) {
$invalid_tokens = array();
$value_tokens = is_string($value) ? token_scan($value) : $value;
- foreach ($value_tokens as $type => $tokens) {
+ foreach ($value_tokens as $tokens) {
if (array_filter($tokens, '_googleanalytics_contains_forbidden_token')) {
$invalid_tokens = array_merge($invalid_tokens, array_values($tokens));
}
@@ -617,8 +646,9 @@ function _googleanalytics_get_forbidden_tokens($value) {
/**
* Validate if a string contains forbidden tokens not allowed by privacy rules.
*
- * @param $token_string
+ * @param string $token_string
* A string with one or more tokens to be validated.
+ *
* @return boolean
* TRUE if blacklisted token has been found, otherwise FALSE.
*/
@@ -724,7 +754,7 @@ function _googleanalytics_extract_create_field_values($string) {
$list = array_map('trim', $list);
$list = array_filter($list, 'strlen');
- foreach ($list as $position => $text) {
+ foreach ($list as $text) {
// Check for an explicit key.
$matches = array();
if (preg_match('/(.*)\|(.*)/', $text, $matches)) {
diff --git a/sites/all/modules/contrib/admin/google_analytics/googleanalytics.admin.js b/sites/all/modules/contrib/admin/google_analytics/googleanalytics.admin.js
index 083eeaa3..c81470d7 100644
--- a/sites/all/modules/contrib/admin/google_analytics/googleanalytics.admin.js
+++ b/sites/all/modules/contrib/admin/google_analytics/googleanalytics.admin.js
@@ -65,6 +65,15 @@ Drupal.behaviors.trackingSettingsSummary = {
if ($('input#edit-googleanalytics-trackfiles', context).is(':checked')) {
vals.push(Drupal.t('Downloads'));
}
+ if ($('input#edit-googleanalytics-trackcolorbox', context).is(':checked')) {
+ vals.push(Drupal.t('Colorbox'));
+ }
+ if ($('input#edit-googleanalytics-tracklinkid', context).is(':checked')) {
+ vals.push(Drupal.t('Link attribution'));
+ }
+ if ($('input#edit-googleanalytics-trackurlfragments', context).is(':checked')) {
+ vals.push(Drupal.t('URL fragments'));
+ }
if (!vals.length) {
return Drupal.t('Not tracked');
}
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 861f7fdb..a0be81ae 100644
--- a/sites/all/modules/contrib/admin/google_analytics/googleanalytics.debug.js
+++ b/sites/all/modules/contrib/admin/google_analytics/googleanalytics.debug.js
@@ -8,16 +8,16 @@ $(document).ready(function() {
// clicks on all elements.
$(document.body).bind("mousedown keyup touchstart", function(event) {
console.group("Running Google Analytics for Drupal.");
- console.info(event);
+ console.info("Event '%s' has been detected.", event.type);
// Catch the closest surrounding link of a clicked element.
$(event.target).closest("a,area").each(function() {
- console.info("Element '%o' has been detected. Link '%s' found.", this, this.href);
+ console.info("Closest element '%o' has been found. URL '%s' extracted.", this, this.href);
// Is the clicked URL internal?
if (Drupal.googleanalytics.isInternal(this.href)) {
// Skip 'click' tracking, if custom tracking events are bound.
- if ($(this).is('.colorbox')) {
+ if ($(this).is('.colorbox') && (Drupal.settings.googleanalytics.trackColorbox)) {
// Do nothing here. The custom event will handle all tracking.
console.info("Click on .colorbox item has been detected.");
}
@@ -25,12 +25,22 @@ $(document).ready(function() {
else if (Drupal.settings.googleanalytics.trackDownload && Drupal.googleanalytics.isDownload(this.href)) {
// Download link clicked.
console.info("Download url '%s' has been found. Tracked download as extension '%s'.", Drupal.googleanalytics.getPageUrl(this.href), Drupal.googleanalytics.getDownloadExtension(this.href).toUpperCase());
- ga("send", "event", "Downloads", Drupal.googleanalytics.getDownloadExtension(this.href).toUpperCase(), Drupal.googleanalytics.getPageUrl(this.href));
+ ga("send", {
+ "hitType": "event",
+ "eventCategory": "Downloads",
+ "eventAction": Drupal.googleanalytics.getDownloadExtension(this.href).toUpperCase(),
+ "eventLabel": Drupal.googleanalytics.getPageUrl(this.href),
+ "transport": "beacon"
+ });
}
else if (Drupal.googleanalytics.isInternalSpecial(this.href)) {
// Keep the internal URL for Google Analytics website overlay intact.
console.info("Click on internal special link '%s' has been tracked.", Drupal.googleanalytics.getPageUrl(this.href));
- ga("send", "pageview", { "page": Drupal.googleanalytics.getPageUrl(this.href) });
+ ga("send", {
+ "hitType": "pageview",
+ "page": Drupal.googleanalytics.getPageUrl(this.href),
+ "transport": "beacon"
+ });
}
else {
// e.g. anchor in same page or other internal page link
@@ -41,13 +51,25 @@ $(document).ready(function() {
if (Drupal.settings.googleanalytics.trackMailto && $(this).is("a[href^='mailto:'],area[href^='mailto:']")) {
// Mailto link clicked.
console.info("Click on e-mail '%s' has been tracked.", this.href.substring(7));
- ga("send", "event", "Mails", "Click", this.href.substring(7));
+ ga("send", {
+ "hitType": "event",
+ "eventCategory": "Mails",
+ "eventAction": "Click",
+ "eventLabel": this.href.substring(7),
+ "transport": "beacon"
+ });
}
else if (Drupal.settings.googleanalytics.trackOutbound && this.href.match(/^\w+:\/\//i)) {
- if (Drupal.settings.googleanalytics.trackDomainMode != 2 || (Drupal.settings.googleanalytics.trackDomainMode == 2 && !Drupal.googleanalytics.isCrossDomain(this.hostname, Drupal.settings.googleanalytics.trackCrossDomains))) {
+ if (Drupal.settings.googleanalytics.trackDomainMode !== 2 || (Drupal.settings.googleanalytics.trackDomainMode === 2 && !Drupal.googleanalytics.isCrossDomain(this.hostname, Drupal.settings.googleanalytics.trackCrossDomains))) {
// External link clicked / No top-level cross domain clicked.
console.info("Outbound link '%s' has been tracked.", this.href);
- ga("send", "event", "Outbound links", "Click", this.href);
+ ga("send", {
+ "hitType": "event",
+ "eventCategory": "Outbound links",
+ "eventAction": "Click",
+ "eventLabel": this.href,
+ "transport": "beacon"
+ });
}
else {
console.info("Internal link '%s' clicked, not tracked.", this.href);
@@ -63,19 +85,27 @@ $(document).ready(function() {
if (Drupal.settings.googleanalytics.trackUrlFragments) {
window.onhashchange = function() {
console.info("Track URL '%s' as pageview. Hash '%s' has changed.", location.pathname + location.search + location.hash, location.hash);
- ga('send', 'pageview', location.pathname + location.search + location.hash);
- }
+ ga("send", {
+ "hitType": "pageview",
+ "page": location.pathname + location.search + location.hash
+ });
+ };
}
// Colorbox: This event triggers when the transition has completed and the
// newly loaded content has been revealed.
- $(document).bind("cbox_complete", function () {
- var href = $.colorbox.element().attr("href");
- if (href) {
- console.info("Colorbox transition to url '%s' has been tracked.", Drupal.googleanalytics.getPageUrl(href));
- ga("send", "pageview", { "page": Drupal.googleanalytics.getPageUrl(href) });
- }
- });
+ if (Drupal.settings.googleanalytics.trackColorbox) {
+ $(document).bind("cbox_complete", function () {
+ var href = $.colorbox.element().attr("href");
+ if (href) {
+ console.info("Colorbox transition to url '%s' has been tracked.", Drupal.googleanalytics.getPageUrl(href));
+ ga("send", {
+ "hitType": "pageview",
+ "page": Drupal.googleanalytics.getPageUrl(href)
+ });
+ }
+ });
+ }
});
diff --git a/sites/all/modules/contrib/admin/google_analytics/googleanalytics.info b/sites/all/modules/contrib/admin/google_analytics/googleanalytics.info
index adbf134a..2b814605 100644
--- a/sites/all/modules/contrib/admin/google_analytics/googleanalytics.info
+++ b/sites/all/modules/contrib/admin/google_analytics/googleanalytics.info
@@ -5,9 +5,9 @@ package = Statistics
configure = admin/config/system/googleanalytics
files[] = googleanalytics.test
test_dependencies[] = token
-; Information added by Drupal.org packaging script on 2014-11-29
-version = "7.x-2.1"
+; Information added by Drupal.org packaging script on 2016-08-09
+version = "7.x-2.3"
core = "7.x"
project = "google_analytics"
-datestamp = "1417276982"
+datestamp = "1470779953"
diff --git a/sites/all/modules/contrib/admin/google_analytics/googleanalytics.install b/sites/all/modules/contrib/admin/google_analytics/googleanalytics.install
index d7b2edfb..a4f4e83a 100644
--- a/sites/all/modules/contrib/admin/google_analytics/googleanalytics.install
+++ b/sites/all/modules/contrib/admin/google_analytics/googleanalytics.install
@@ -25,6 +25,7 @@ function googleanalytics_uninstall() {
variable_del('googleanalytics_roles');
variable_del('googleanalytics_site_search');
variable_del('googleanalytics_trackadsense');
+ variable_del('googleanalytics_trackcolorbox');
variable_del('googleanalytics_trackdoubleclick');
variable_del('googleanalytics_tracker_anonymizeip');
variable_del('googleanalytics_trackfiles');
@@ -446,16 +447,16 @@ function googleanalytics_update_7200() {
if (!empty($googleanalytics_codesnippet_before) && stristr($googleanalytics_codesnippet_before, '_gaq.push(')) {
variable_set('googleanalytics_codesnippet_before_backup_7200', $googleanalytics_codesnippet_before);
variable_del('googleanalytics_codesnippet_before');
- drupal_set_message(Database::getConnection()->prefixTables("A backup of your previous Google Analytics code snippet has been saved in database table '{variable}' as 'googleanalytics_codesnippet_before_backup_7200'. You need to manually upgrade the custom 'before' code snippet."), 'warning');
- $messages[] = t('Manual upgrade of custom "before" code snippet is required.');
+ drupal_set_message(Database::getConnection()->prefixTables("A backup of your previous Google Analytics code snippet (ga.js) has been saved in database table '{variable}' as 'googleanalytics_codesnippet_before_backup_7200'. You need to manually upgrade the custom 'before' code snippet to analytics.js API."), 'warning');
+ $messages[] = t('Manual upgrade of custom "before" code snippet from ja.js to analytics.js API is required.');
}
$googleanalytics_codesnippet_after = variable_get('googleanalytics_codesnippet_after', '');
if (!empty($googleanalytics_codesnippet_after) && stristr($googleanalytics_codesnippet_after, '_gaq.push(')) {
variable_set('googleanalytics_codesnippet_after_backup_7200', $googleanalytics_codesnippet_after);
variable_del('googleanalytics_codesnippet_after');
- drupal_set_message(Database::getConnection()->prefixTables("A backup of your previous Google Analytics code snippet has been saved in database table '{variable}' as 'googleanalytics_codesnippet_before_backup_7200'. You need to manually upgrade the custom 'before' code snippet."), 'warning');
- $messages[] = t('Manual upgrade of custom "after" code snippet is required.');
+ drupal_set_message(Database::getConnection()->prefixTables("A backup of your previous Google Analytics code snippet (ga.js) has been saved in database table '{variable}' as 'googleanalytics_codesnippet_after_backup_7200'. You need to manually upgrade the custom 'after' code snippet to analytics.js API."), 'warning');
+ $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);
diff --git a/sites/all/modules/contrib/admin/google_analytics/googleanalytics.js b/sites/all/modules/contrib/admin/google_analytics/googleanalytics.js
index 1eed3c32..8d5bde1b 100644
--- a/sites/all/modules/contrib/admin/google_analytics/googleanalytics.js
+++ b/sites/all/modules/contrib/admin/google_analytics/googleanalytics.js
@@ -14,29 +14,51 @@ $(document).ready(function() {
// Is the clicked URL internal?
if (Drupal.googleanalytics.isInternal(this.href)) {
// Skip 'click' tracking, if custom tracking events are bound.
- if ($(this).is('.colorbox')) {
+ if ($(this).is('.colorbox') && (Drupal.settings.googleanalytics.trackColorbox)) {
// Do nothing here. The custom event will handle all tracking.
//console.info("Click on .colorbox item has been detected.");
}
// Is download tracking activated and the file extension configured for download tracking?
else if (Drupal.settings.googleanalytics.trackDownload && Drupal.googleanalytics.isDownload(this.href)) {
// Download link clicked.
- ga("send", "event", "Downloads", Drupal.googleanalytics.getDownloadExtension(this.href).toUpperCase(), Drupal.googleanalytics.getPageUrl(this.href));
+ ga("send", {
+ "hitType": "event",
+ "eventCategory": "Downloads",
+ "eventAction": Drupal.googleanalytics.getDownloadExtension(this.href).toUpperCase(),
+ "eventLabel": Drupal.googleanalytics.getPageUrl(this.href),
+ "transport": "beacon"
+ });
}
else if (Drupal.googleanalytics.isInternalSpecial(this.href)) {
// Keep the internal URL for Google Analytics website overlay intact.
- ga("send", "pageview", { "page": Drupal.googleanalytics.getPageUrl(this.href) });
+ ga("send", {
+ "hitType": "pageview",
+ "page": Drupal.googleanalytics.getPageUrl(this.href),
+ "transport": "beacon"
+ });
}
}
else {
if (Drupal.settings.googleanalytics.trackMailto && $(this).is("a[href^='mailto:'],area[href^='mailto:']")) {
// Mailto link clicked.
- ga("send", "event", "Mails", "Click", this.href.substring(7));
+ ga("send", {
+ "hitType": "event",
+ "eventCategory": "Mails",
+ "eventAction": "Click",
+ "eventLabel": this.href.substring(7),
+ "transport": "beacon"
+ });
}
else if (Drupal.settings.googleanalytics.trackOutbound && this.href.match(/^\w+:\/\//i)) {
- if (Drupal.settings.googleanalytics.trackDomainMode != 2 || (Drupal.settings.googleanalytics.trackDomainMode == 2 && !Drupal.googleanalytics.isCrossDomain(this.hostname, Drupal.settings.googleanalytics.trackCrossDomains))) {
+ if (Drupal.settings.googleanalytics.trackDomainMode !== 2 || (Drupal.settings.googleanalytics.trackDomainMode === 2 && !Drupal.googleanalytics.isCrossDomain(this.hostname, Drupal.settings.googleanalytics.trackCrossDomains))) {
// External link clicked / No top-level cross domain clicked.
- ga("send", "event", "Outbound links", "Click", this.href);
+ ga("send", {
+ "hitType": "event",
+ "eventCategory": "Outbound links",
+ "eventAction": "Click",
+ "eventLabel": this.href,
+ "transport": "beacon"
+ });
}
}
}
@@ -46,18 +68,26 @@ $(document).ready(function() {
// Track hash changes as unique pageviews, if this option has been enabled.
if (Drupal.settings.googleanalytics.trackUrlFragments) {
window.onhashchange = function() {
- ga('send', 'pageview', location.pathname + location.search + location.hash);
- }
+ ga("send", {
+ "hitType": "pageview",
+ "page": location.pathname + location.search + location.hash
+ });
+ };
}
// Colorbox: This event triggers when the transition has completed and the
// newly loaded content has been revealed.
- $(document).bind("cbox_complete", function () {
- var href = $.colorbox.element().attr("href");
- if (href) {
- ga("send", "pageview", { "page": Drupal.googleanalytics.getPageUrl(href) });
- }
- });
+ if (Drupal.settings.googleanalytics.trackColorbox) {
+ $(document).bind("cbox_complete", function () {
+ var href = $.colorbox.element().attr("href");
+ if (href) {
+ ga("send", {
+ "hitType": "pageview",
+ "page": Drupal.googleanalytics.getPageUrl(href)
+ });
+ }
+ });
+ }
});
diff --git a/sites/all/modules/contrib/admin/google_analytics/googleanalytics.module b/sites/all/modules/contrib/admin/google_analytics/googleanalytics.module
index 4051b1b9..b45ee103 100644
--- a/sites/all/modules/contrib/admin/google_analytics/googleanalytics.module
+++ b/sites/all/modules/contrib/admin/google_analytics/googleanalytics.module
@@ -69,6 +69,11 @@ function googleanalytics_permission() {
'description' => t('Enter PHP code in the field for tracking visibility settings.'),
'restrict access' => TRUE,
),
+ 'add JS snippets for google analytics' => array(
+ 'title' => t('Add JavaScript snippets'),
+ 'description' => 'Enter JavaScript code snippets for advanced Google Analytics functionality.',
+ 'restrict access' => TRUE,
+ ),
);
}
@@ -125,6 +130,9 @@ function googleanalytics_page_alter(&$page) {
$link_settings['trackDownload'] = $track_download;
$link_settings['trackDownloadExtensions'] = $trackfiles_extensions;
}
+ if (module_exists('colorbox') && ($track_colorbox = variable_get('googleanalytics_trackcolorbox', 1))) {
+ $link_settings['trackColorbox'] = $track_colorbox;
+ }
if ($track_domain_mode = variable_get('googleanalytics_domain_mode', 0)) {
$link_settings['trackDomainMode'] = $track_domain_mode;
}
@@ -294,10 +302,7 @@ function googleanalytics_page_alter(&$page) {
// Track logged in users across all devices.
if (variable_get('googleanalytics_trackuserid', 0) && user_is_logged_in()) {
- // The USER_ID value should be a unique, persistent, and non-personally
- // identifiable string identifier that represents a user or signed-in
- // account across devices.
- $create_only_fields['userId'] = drupal_hmac_base64($user->uid, drupal_get_private_key() . drupal_get_hash_salt());
+ $create_only_fields['userId'] = google_analytics_user_id_hash($user->uid);
}
// Create a tracker.
@@ -352,13 +357,30 @@ function googleanalytics_page_alter(&$page) {
// Custom tracking. Prepend before all other JavaScript.
// @TODO: https://support.google.com/adsense/answer/98142
// sounds like it could be appended to $script.
- drupal_add_js($googleanalytics_adsense_script, array('type' => 'inline', 'group' => JS_LIBRARY-1));
+ drupal_add_js($googleanalytics_adsense_script, array('type' => 'inline', 'group' => JS_LIBRARY-1, 'requires_jquery' => FALSE));
}
- drupal_add_js($script, array('scope' => 'header', 'type' => 'inline'));
+ drupal_add_js($script, array('scope' => 'header', 'type' => 'inline', 'requires_jquery' => FALSE));
}
}
+/**
+ * Generate user id hash to implement USER_ID.
+ *
+ * The USER_ID value should be a unique, persistent, and non-personally
+ * identifiable string identifier that represents a user or signed-in
+ * account across devices.
+ *
+ * @param int $uid
+ * User id.
+ *
+ * @return string
+ * User id hash.
+ */
+function google_analytics_user_id_hash($uid) {
+ return drupal_hmac_base64($uid, drupal_get_private_key() . drupal_get_hash_salt());
+}
+
/**
* Implements hook_field_extra_fields().
*/
@@ -456,7 +478,7 @@ function googleanalytics_preprocess_search_results(&$variables) {
// found. But the pager item mumber can tell the number of search results.
global $pager_total_items;
- drupal_add_js('window.googleanalytics_search_results = ' . intval($pager_total_items[0]) . ';', array('type' => 'inline', 'group' => JS_LIBRARY-1));
+ drupal_add_js('window.googleanalytics_search_results = ' . intval($pager_total_items[0]) . ';', array('type' => 'inline', 'group' => JS_LIBRARY-1, 'requires_jquery' => FALSE));
}
}
diff --git a/sites/all/modules/contrib/admin/google_analytics/googleanalytics.test b/sites/all/modules/contrib/admin/google_analytics/googleanalytics.test
index 0b64bb82..745047a8 100644
--- a/sites/all/modules/contrib/admin/google_analytics/googleanalytics.test
+++ b/sites/all/modules/contrib/admin/google_analytics/googleanalytics.test
@@ -6,6 +6,13 @@
*/
class GoogleAnalyticsBasicTest extends DrupalWebTestCase {
+ /**
+ * User without permissions to edit snippets.
+ *
+ * @var \StdClass
+ */
+ protected $noSnippetUser;
+
public static function getInfo() {
return array(
'name' => 'Google Analytics basic tests',
@@ -25,6 +32,8 @@ class GoogleAnalyticsBasicTest extends DrupalWebTestCase {
);
// User to set up google_analytics.
+ $this->noSnippetUser = $this->drupalCreateUser($permissions);
+ $permissions[] = 'add JS snippets for google analytics';
$this->admin_user = $this->drupalCreateUser($permissions);
$this->drupalLogin($this->admin_user);
}
@@ -48,6 +57,26 @@ class GoogleAnalyticsBasicTest extends DrupalWebTestCase {
$edit['googleanalytics_account'] = $this->randomName(2);
$this->drupalPost('admin/config/system/googleanalytics', $edit, t('Save configuration'));
$this->assertRaw(t('A valid Google Analytics Web Property ID is case sensitive and formatted like UA-xxxxxxx-yy.'), '[testGoogleAnalyticsConfiguration]: Invalid Web Property ID number validated.');
+
+ // User should have access to code snippets.
+ $this->assertFieldByName('googleanalytics_codesnippet_create');
+ $this->assertFieldByName('googleanalytics_codesnippet_before');
+ $this->assertFieldByName('googleanalytics_codesnippet_after');
+ $this->assertNoFieldByXPath("//textarea[@name='googleanalytics_codesnippet_create' and @disabled='disabled']", NULL, '"Create only fields" is enabled.');
+ $this->assertNoFieldByXPath("//textarea[@name='googleanalytics_codesnippet_before' and @disabled='disabled']", NULL, '"Code snippet (before)" is enabled.');
+ $this->assertNoFieldByXPath("//textarea[@name='googleanalytics_codesnippet_after' and @disabled='disabled']", NULL, '"Code snippet (after)" is enabled.');
+
+ // Login as user without JS permissions.
+ $this->drupalLogin($this->noSnippetUser);
+ $this->drupalGet('admin/config/system/googleanalytics');
+
+ // User should *not* have access to snippets, but create fields.
+ $this->assertFieldByName('googleanalytics_codesnippet_create');
+ $this->assertFieldByName('googleanalytics_codesnippet_before');
+ $this->assertFieldByName('googleanalytics_codesnippet_after');
+ $this->assertNoFieldByXPath("//textarea[@name='googleanalytics_codesnippet_create' and @disabled='disabled']", NULL, '"Create only fields" is enabled.');
+ $this->assertFieldByXPath("//textarea[@name='googleanalytics_codesnippet_before' and @disabled='disabled']", NULL, '"Code snippet (before)" is disabled.');
+ $this->assertFieldByXPath("//textarea[@name='googleanalytics_codesnippet_after' and @disabled='disabled']", NULL, '"Code snippet (after)" is disabled.');
}
function testGoogleAnalyticsPageVisibility() {
@@ -284,6 +313,7 @@ class GoogleAnalyticsCustomDimensionsAndMetricsTest extends DrupalWebTestCase {
// User to set up google_analytics.
$this->admin_user = $this->drupalCreateUser($permissions);
+ $this->drupalLogin($this->admin_user);
}
function testGoogleAnalyticsCustomDimensions() {
@@ -362,34 +392,30 @@ class GoogleAnalyticsCustomDimensionsAndMetricsTest extends DrupalWebTestCase {
1 => array(
'index' => 1,
'value' => '6',
- 'value_expected' => 6,
),
2 => array(
'index' => 2,
'value' => '8000',
- 'value_expected' => 8000,
),
3 => array(
'index' => 3,
'value' => '7.8654',
- 'value_expected' => 7.8654,
),
4 => array(
'index' => 4,
'value' => '1123.4',
- 'value_expected' => 1123.4,
),
5 => array(
'index' => 5,
'value' => '5,67',
- 'value_expected' => 5,
),
);
+
variable_set('googleanalytics_custom_metric', $googleanalytics_custom_metric);
$this->drupalGet('');
foreach ($googleanalytics_custom_metric as $metric) {
- $this->assertRaw('ga("set", ' . drupal_json_encode('metric' . $metric['index']) . ', ' . drupal_json_encode($metric['value_expected']) . ');', '[testGoogleAnalyticsCustomDimensionsAndMetrics]: Metric #' . $metric['index'] . ' is shown.');
+ $this->assertRaw('ga("set", ' . drupal_json_encode('metric' . $metric['index']) . ', ' . drupal_json_encode((float) $metric['value']) . ');', '[testGoogleAnalyticsCustomDimensionsAndMetrics]: Metric #' . $metric['index'] . ' is shown.');
}
// Test whether tokens are replaced in custom metric values.
@@ -421,6 +447,30 @@ class GoogleAnalyticsCustomDimensionsAndMetricsTest extends DrupalWebTestCase {
$this->assertNoRaw('ga("set", ' . drupal_json_encode('metric3') . ', ' . drupal_json_encode('') . ');', '[testGoogleAnalyticsCustomDimensionsAndMetrics]: Empty value is not shown.');
$this->assertRaw('ga("set", ' . drupal_json_encode('metric4') . ', ' . drupal_json_encode(0) . ');', '[testGoogleAnalyticsCustomDimensionsAndMetrics]: Value 0 is shown.');
}
+
+ /**
+ * Tests if Custom Dimensions token form validation works.
+ */
+ public function testGoogleAnalyticsCustomDimensionsTokenFormValidation() {
+ $ua_code = 'UA-123456-1';
+
+ // Check form validation.
+ $edit['googleanalytics_account'] = $ua_code;
+ $edit['googleanalytics_custom_dimension[indexes][1][value]'] = '[current-user:name]';
+ $edit['googleanalytics_custom_dimension[indexes][2][value]'] = '[current-user:edit-url]';
+ $edit['googleanalytics_custom_dimension[indexes][3][value]'] = '[user:name]';
+ $edit['googleanalytics_custom_dimension[indexes][4][value]'] = '[term:name]';
+ $edit['googleanalytics_custom_dimension[indexes][5][value]'] = '[term:tid]';
+
+ $this->drupalPost('admin/config/system/googleanalytics', $edit, t('Save configuration'));
+
+ $this->assertRaw(t('The %element-title is using the following forbidden tokens with personal identifying information: @invalid-tokens.', array('%element-title' => t('Custom dimension value #@index', array('@index' => 1)), '@invalid-tokens' => implode(', ', array('[current-user:name]')))));
+ $this->assertRaw(t('The %element-title is using the following forbidden tokens with personal identifying information: @invalid-tokens.', array('%element-title' => t('Custom dimension value #@index', array('@index' => 2)), '@invalid-tokens' => implode(', ', array('[current-user:edit-url]')))));
+ $this->assertRaw(t('The %element-title is using the following forbidden tokens with personal identifying information: @invalid-tokens.', array('%element-title' => t('Custom dimension value #@index', array('@index' => 3)), '@invalid-tokens' => implode(', ', array('[user:name]')))));
+ // BUG #2037595
+ //$this->assertNoRaw(t('The %element-title is using the following forbidden tokens with personal identifying information: @invalid-tokens.', array('%element-title' => t('Custom dimension value #@index', array('@index' => 4)), '@invalid-tokens' => implode(', ', array('[term:name]')))));
+ //$this->assertNoRaw(t('The %element-title is using the following forbidden tokens with personal identifying information: @invalid-tokens.', array('%element-title' => t('Custom dimension value #@index', array('@index' => 5)), '@invalid-tokens' => implode(', ', array('[term:tid]')))));
+ }
}
class GoogleAnalyticsStatusMessagesTest extends DrupalWebTestCase {
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 6a657350..9d142a27 100644
--- a/sites/all/modules/contrib/admin/google_analytics/googleanalytics.variable.inc
+++ b/sites/all/modules/contrib/admin/google_analytics/googleanalytics.variable.inc
@@ -40,6 +40,10 @@ function googleanalytics_variable_group_info() {
/**
* Validate Web Property ID variable.
+ *
+ * @param array $variable
+ *
+ * @return string
*/
function googleanalytics_validate_googleanalytics_account($variable) {
// Replace all type of dashes (n-dash, m-dash, minus) with the normal dashes.
diff --git a/sites/all/modules/contrib/dev/elysia_cron/API.txt b/sites/all/modules/contrib/dev/elysia_cron/API.txt
index 8eebf380..727b970a 100644
--- a/sites/all/modules/contrib/dev/elysia_cron/API.txt
+++ b/sites/all/modules/contrib/dev/elysia_cron/API.txt
@@ -126,7 +126,7 @@ You can use the "hook_cron_alter" function to edit cronapi data of other
modules.
Example:
-function module_cron_alter($data) {
+function module_cron_alter(&$data) {
$data['key']['rule'] = '0 */6 * * *';
}
@@ -182,6 +182,19 @@ function phptemplate_elysia_cron_description($job) {
Note: module default theme_elysia_cron_description($job) already contains
some common tasks descriptions.
+-----------------------------------------------------------------------------
+DISABLE CRON JOBS VIA settings.php FILE
+-----------------------------------------------------------------------------
+If you have some instances for the project you can want to disable some cron
+jobs on different instances. For example you don't want to execute PROD cron
+jobs on DEV instance. There is no need to make it via interface or via SQL
+query. You can define variable for each cron job to manage it state. For more
+information please look at `_elysia_cron_is_job_disabled` and `_ec_get_name`
+functions.
+
+For example, if you have cron job with name googleanalytics_cron, you can
+add this string to your settings.php file:
+$conf['ec_googleanalytics_cron_d'] = TRUE;
-----------------------------------------------------------------------------
OLD 1.x MODULE API (OBSOLETE)
diff --git a/sites/all/modules/contrib/dev/elysia_cron/INSTALL.txt b/sites/all/modules/contrib/dev/elysia_cron/INSTALL.txt
index dac08617..dd92d9be 100644
--- a/sites/all/modules/contrib/dev/elysia_cron/INSTALL.txt
+++ b/sites/all/modules/contrib/dev/elysia_cron/INSTALL.txt
@@ -16,8 +16,7 @@ up and running.
You can stop here if you don't need a great precision over task execution and
you don't have to execute a task more often than once an hour.
For example, if you need only the "Once a day", "Once a week" or "Once a month"
-schedule rules the basic install is fine. (For D6 users that want to stop here:
-you should have installed Drupal crontab as described in Drupal INSTALL guide).
+schedule rules the basic install is fine.
Instead, if you need:
- to run some tasks more often than once an hour (eg: you have a function that
@@ -41,8 +40,8 @@ The only difference is that you should use the "* * * * *" rule part instead of
"0 * * * *" or "45 * * * *" as described in the guide.
While you're editing the system crontab, it's also recommended to replace the
-"/cron.php" part with "/sites/modules/elysia_cron/cron.php" (if you have
-installed elysia_cron in "sites/modules" directory).
+"/cron.php" part with "/sites/all/modules/elysia_cron/cron.php" (if you have
+installed elysia_cron in "sites/all/modules" directory).
This is an optional step (you can leave "/cron.php" if you want), doing it will
result in a better performance in bigger sites (elysia_cron's cron.php handles
cache in a better way).
@@ -78,6 +77,10 @@ red; font-weight: bold; }
PERMISSIONS
------------
-You can also give 'administer elysia_cron' permission to all user roles that
-needs to administer cron jobs. You can do this with standard drupal users
-administration.
+There are three permission provided by module:
+ * Administer elysia cron - Perform changes to cron jobs timings, disable cron
+ or single jobs and access cron execution statistics;
+ * Execute elysia cron jobs - Allow users to view statistics, execution status
+ and do manually execute cron jobs;
+ * View elysia cron stats - Allows users to view statistics and execution status
+ of cron jobs;
diff --git a/sites/all/modules/contrib/dev/elysia_cron/README.txt b/sites/all/modules/contrib/dev/elysia_cron/README.txt
index 30e7878d..e5f5d56c 100644
--- a/sites/all/modules/contrib/dev/elysia_cron/README.txt
+++ b/sites/all/modules/contrib/dev/elysia_cron/README.txt
@@ -9,27 +9,27 @@ For module developers API documetation read API.TXT
FEATURES
-----------------------------------------------------------------------------
-Elysia Cron extends Drupal standard cron, allowing a fine grain control over
+Elysia Cron extends Drupal standard cron, allowing a fine grain control over
each task and several ways to add custom cron jobs to your site.
- Set the timings and frequencies of each cron task (you can run some jobs every
day at a specified hour, other only monthly and so on...).
- For each task you can simply choose between some frequently used options
- ("once a day", "once a month" ...), or use a powerful "linux crontab"-like
- syntax to set the accurate timings. You can even define your frequently used
+ For each task you can simply choose between some frequently used options
+ ("once a day", "once a month" ...), or use a powerful "linux crontab"-like
+ syntax to set the accurate timings. You can even define your frequently used
options to speed up site configuration.
-- Parallel execution of cron task: you can group jobs in channels and execute
+- Parallel execution of cron task: you can group jobs in channels and execute
then simultaneously: so a task that takes a lot of time to execute won't block
other tasks that need to be executed every 5 minutes...
- You can disable all tasks, an entire channel or a single task.
- Change the priority/order of task execution.
- Manual force the execution of a cron tasks.
-- Detailed overview of cron status with time statistics for single tasks and
+- Detailed overview of cron status with time statistics for single tasks and
channels.
-- Powerful API for module developers: you can define extra cron tasks for your
- modules, each one with own default timings (site administrators can override
- them by configuration, other modules via hook_alter). Elysia Cron 2.0 gives a
+- Powerful API for module developers: you can define extra cron tasks for your
+ modules, each one with own default timings (site administrators can override
+ them by configuration, other modules via hook_alter). Elysia Cron 2.0 gives a
brand new API support (compatible with 1.0 version) with a lot of features.
- Administrators can define custom jobs (call to functions with parameters), via
the "script" option.
@@ -43,7 +43,7 @@ It also can be used in a Drupal install profile.
3rd party integration:
- Ping feature, for external tracking services like host-tracker to tell whether
cron is functioning properly on your site.
-- Drush support: you can call "drush elysia-cron" to manually run extended cron.
+- Drush support: you can call "drush elysia-cron run" to manually run extended cron.
- CTools support for exports/backup of task settings.
- Features support.
@@ -53,35 +53,35 @@ USAGE EXAMPLES
Elysia cron is usually used in large sites that needs performance optimization.
-- Avoid drupal peak loads by distributing heavy load tasks during quiet periods
- of the day: for example you may want to rebuild the XML Sitemap of your site
- at 2:00AM in the morning, where usually only a few people are visiting your
- site. You can even move some tasks to be executed only once a month (log
+- Avoid drupal peak loads by distributing heavy load tasks during quiet periods
+ of the day: for example you may want to rebuild the XML Sitemap of your site
+ at 2:00AM in the morning, where usually only a few people are visiting your
+ site. You can even move some tasks to be executed only once a month (log
rotation, old records expiry...).
-- If you have tasks that should be executed very often, but don't want to
- execute ALL drupal cron tasks that often! For example, you may want to check
- for emails that needs to be sent to your users every 2 minutes. Standard cron
- is managed in a "monolithic" way, so even if you find out how to execute it
+- If you have tasks that should be executed very often, but don't want to
+ execute ALL drupal cron tasks that often! For example, you may want to check
+ for emails that needs to be sent to your users every 2 minutes. Standard cron
+ is managed in a "monolithic" way, so even if you find out how to execute it
every 2 minutes, you will end in having all cron tasks executed so often, with
a lot of performance problems.
-- Fine tune cron cache management : drupal cron will invalidate variable cache
- every cron run, and this is a great performance problem if you have a
- frequently called task. Elysia cron optimize cache management, and doesn't
+- Fine tune cron cache management : drupal cron will invalidate variable cache
+ every cron run, and this is a great performance problem if you have a
+ frequently called task. Elysia cron optimize cache management, and doesn't
need to invalidate cache.
-- Setup tasks that should be run at a precise time: for example if you want to
+- Setup tasks that should be run at a precise time: for example if you want to
send a SimpleNews newsletter every monday at 9:00AM, you can do it.
-- Parallel execution: if you have a task that takes a lot of time to execute,
- you can setup a different channel for it so it won't block other tasks that
+- Parallel execution: if you have a task that takes a lot of time to execute,
+ you can setup a different channel for it so it won't block other tasks that
need to be executed every 5 minutes.
- Turn off (disable) a cron task/feature you don't need.
- Debug system cron problems. If your cron does not terminate correctly you can
- use extended logging, see at each cron timings and disable task to track down
+ use extended logging, see at each cron timings and disable task to track down
the problem.
-----------------------------------------------------------------------------
@@ -93,11 +93,11 @@ Channels are groups of tasks. Each channel is a "parallel line" of execution
Tasks inside a channel will be executed sequentially (if they should).
WARNING: It's not recommended to create more than 2 or 3 channels.
-Every channel will increase the delay between each cron check (of the same
+Every channel will increase the delay between each cron check (of the same
channel), because each cron call will cycle between all channels.
So, for example:
If you have 1 channel it will be checked once a minute.
-If you have 2 channel each one will be checked every 2 minutes (almost usually,
+If you have 2 channel each one will be checked every 2 minutes (almost usually,
when the other one is running it will be checked once a minute).
It you have 10 channels there will be a check every 10 minutes... if you have
a job that should be executed every 5 minutes it won't do so!
@@ -106,10 +106,10 @@ a job that should be executed every 5 minutes it won't do so!
EXPORT VIA CTOOLS/FEATURES MODULE
-----------------------------------------------------------------------------
-With 2.0 version of Elysia Cron you can use "Bulk Export" functionality of
+With 2.0 version of Elysia Cron you can use "Bulk Export" functionality of
"Chaos Tools Suite" to export cron settings.
To use it simply enable all modules, go to Structure > Bulk Exporter and
-select the tasks you want to export settings. You can also select all
+select the tasks you want to export settings. You can also select all
"elysia_cron" prefixed variables to export global options.
Than generate the module.
@@ -117,7 +117,7 @@ The generated code will set the new defaults of elysia cron settings. This way
you can simply enable it to use them, but you are free to override them in
the future using the normal settings page.
Note that if you want to delete all overrides, and return to exported settings,
-you should do a "reset to defaults" from elysia cron settings page.
+you should do a "reset to defaults" from elysia cron settings page.
You can also use "Features" module to create a Feature module will the settings
you need.
@@ -133,7 +133,32 @@ DRUSH SUPPORT
Elysia Cron 2.0 adds a simple support for Drush module.
-You can execute the "elysia-cron" command to run cron.
+Run all cron tasks in all active modules for specified site using elysia cron
+system. This replaces the standard "core-cron" drush handler.
+
+Examples:
+ elysia-cron run Run all cron tasks in all active
+ modules (as the standard "core-cron")
+ elysia-cron run --verbose Run all cron tasks in verbose mode
+ elysia-cron run @channel Run all cron tasks in specified
+ channel
+ elysia-cron run search_cron --ignore-time Run only search index
+ build task (force execution)
+ elysia-cron list --elysia-cron-verbose List all channels/tasks
+ in verbose mode
+ elysia-cron disable search_cron Disable search index build task
+
+Options:
+ --elysia-cron-verbose enable extended output (the same as
+ --verbose, but without enabling drush
+ verbose mode)
+ --ignore-disable run channels/tasks even if disabled
+ --ignore-running run channels/tasks even
+ if already running
+ --ignore-time run channels/tasks even if not ready
+ for execution
+ --quiet suppress all output
+ --verbose enable extended output
-----------------------------------------------------------------------------
RULES AND SCRIPT SYNTAX
@@ -150,17 +175,17 @@ RULES AND SCRIPT SYNTAX
| | | | |
* * * * *
-Each of the patterns from the first five fields may be either * (an asterisk),
-which matches all legal values, or a list of elements separated by commas
+Each of the patterns from the first five fields may be either * (an asterisk),
+which matches all legal values, or a list of elements separated by commas
(see below).
-For "day of the week" (field 5), 0 is considered Sunday, 6 is Saturday (7 is
+For "day of the week" (field 5), 0 is considered Sunday, 6 is Saturday (7 is
an illegal value)
-A job is executed when the time/date specification fields all match the current
-time and date. There is one exception: if both "day of month" and "day of week"
-are restricted (not "*"), then either the "day of month" field (3) or the "day
-of week" field (5) must match the current day (even though the other of the two
+A job is executed when the time/date specification fields all match the current
+time and date. There is one exception: if both "day of month" and "day of week"
+are restricted (not "*"), then either the "day of month" field (3) or the "day
+of week" field (5) must match the current day (even though the other of the two
fields need not match the current day).
2. FIELDS OPERATOR
@@ -169,13 +194,13 @@ fields need not match the current day).
There are several ways of specifying multiple date/time values in a field:
* The comma (',') operator specifies a list of values, for example: "1,3,4,7,8"
-* The dash ('-') operator specifies a range of values, for example: "1-6", which
+* The dash ('-') operator specifies a range of values, for example: "1-6", which
is equivalent to "1,2,3,4,5,6"
-* The asterisk ('*') operator specifies all possible values for a field. For
- example, an asterisk in the hour time field would be equivalent to 'every hour'
+* The asterisk ('*') operator specifies all possible values for a field. For
+ example, an asterisk in the hour time field would be equivalent to 'every hour'
(subject to matching other specified fields).
-* The slash ('/') operator (called "step") can be used to skip a given number of
- values. For example, "*/3" in the hour time field is equivalent to
+* The slash ('/') operator (called "step") can be used to skip a given number of
+ values. For example, "*/3" in the hour time field is equivalent to
"0,3,6,9,12,15,18,21".
3. EXAMPLES
@@ -190,10 +215,10 @@ There are several ways of specifying multiple date/time values in a field:
4. SCRIPTS
---------------------------------
-You can use the script section to easily create new jobs (by calling a php
+You can use the script section to easily create new jobs (by calling a php
function) or to change the scheduling of an existing job.
-Every line of the script can be a comment (if it starts with #) or a job
+Every line of the script can be a comment (if it starts with #) or a job
definition.
The syntax of a job definition is:
@@ -207,10 +232,10 @@ The syntax of a job definition is:
* [job]: could be the name of a supported job (for example: 'search_cron') or a
function call, ending with ; (for example: 'process_queue();').
-A comment on the line just preceding a job definition is considered the job
+A comment on the line just preceding a job definition is considered the job
description.
-Remember that script OVERRIDES all settings on single jobs sections or channel
+Remember that script OVERRIDES all settings on single jobs sections or channel
sections of the configuration
5. EXAMPLE OF SCRIPT
@@ -219,7 +244,7 @@ sections of the configuration
# Search indexing every 2 hours (i'm setting this as the job description)
0 */2 * * * search_cron
-# I'll check for module status only on sunday nights
+# I'll check for module status only on sunday nights
# (and this is will not be the job description, see the empty line below)
0 2 * * 0 update_status_cron
diff --git a/sites/all/modules/contrib/dev/elysia_cron/cron.php b/sites/all/modules/contrib/dev/elysia_cron/cron.php
index 3ba20503..294a1c4d 100644
--- a/sites/all/modules/contrib/dev/elysia_cron/cron.php
+++ b/sites/all/modules/contrib/dev/elysia_cron/cron.php
@@ -8,9 +8,11 @@
if (!file_exists('includes/bootstrap.inc')) {
if (!empty($_SERVER['DOCUMENT_ROOT']) && file_exists($_SERVER['DOCUMENT_ROOT'] . '/includes/bootstrap.inc')) {
chdir($_SERVER['DOCUMENT_ROOT']);
- } elseif (preg_match('@^(.*)[\\\\/]sites[\\\\/][^\\\\/]+[\\\\/]modules[\\\\/]([^\\\\/]+[\\\\/])?elysia(_cron)?$@', getcwd(), $r) && file_exists($r[1] . '/includes/bootstrap.inc')) {
+ }
+ elseif (preg_match('@^(.*)[\\\\/]sites[\\\\/][^\\\\/]+[\\\\/]modules[\\\\/]([^\\\\/]+[\\\\/])?elysia(_cron)?$@', getcwd(), $r) && file_exists($r[1] . '/includes/bootstrap.inc')) {
chdir($r[1]);
- } else {
+ }
+ else {
die("Cron Fatal Error: Can't locate bootstrap.inc. Check cron.php position.");
}
}
@@ -21,16 +23,14 @@ if (!file_exists('includes/bootstrap.inc')) {
define('DRUPAL_ROOT', getcwd());
include_once DRUPAL_ROOT . '/includes/bootstrap.inc';
-drupal_override_server_variables(array(
- 'SCRIPT_NAME' => '/cron.php',
-));
+drupal_override_server_variables(array('SCRIPT_NAME' => '/cron.php'));
drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);
-if (!isset($_GET['cron_key']) || variable_get('cron_key', 'drupal') != $_GET['cron_key']) {
+if ((variable_get('cron_key') && empty($_GET['cron_key'])) || !empty($_GET['cron_key']) && variable_get('cron_key') != $_GET['cron_key']) {
watchdog('cron', 'Cron could not run because an invalid key was used.', array(), WATCHDOG_NOTICE);
drupal_access_denied();
}
-elseif (variable_get('maintenance_mode', 0)) {
+elseif (variable_get('maintenance_mode', 0) && !variable_get('elysia_cron_run_maintenance', FALSE)) {
watchdog('cron', 'Cron could not run because the site is in maintenance mode.', array(), WATCHDOG_NOTICE);
drupal_access_denied();
}
diff --git a/sites/all/modules/contrib/dev/elysia_cron/elysia_cron-multilingual-image-path-2096571-1.patch b/sites/all/modules/contrib/dev/elysia_cron/elysia_cron-multilingual-image-path-2096571-1.patch
deleted file mode 100644
index ddd57ea1..00000000
--- a/sites/all/modules/contrib/dev/elysia_cron/elysia_cron-multilingual-image-path-2096571-1.patch
+++ /dev/null
@@ -1,13 +0,0 @@
-diff --git a/elysia_cron.admin.inc b/elysia_cron.admin.inc
-index fa5c6de..4011d91 100644
---- a/elysia_cron.admin.inc
-+++ b/elysia_cron.admin.inc
-@@ -42,7 +42,7 @@ function elysia_cron_admin_page() {
-
- $rows = array();
-
-- $ipath = url(drupal_get_path('module', 'elysia_cron') . '/images/icon_');
-+ $ipath = file_create_url(drupal_get_path('module', 'elysia_cron') . '/images/icon_');
-
- foreach ($elysia_cron_settings_by_channel as $channel => $data) {
- $running = elysia_cron_is_channel_running($channel);
diff --git a/sites/all/modules/contrib/dev/elysia_cron/elysia_cron.admin.inc b/sites/all/modules/contrib/dev/elysia_cron/elysia_cron.admin.inc
index 4011d913..ba6251d8 100644
--- a/sites/all/modules/contrib/dev/elysia_cron/elysia_cron.admin.inc
+++ b/sites/all/modules/contrib/dev/elysia_cron/elysia_cron.admin.inc
@@ -1,35 +1,34 @@
' . t('Global disable') . ': ' . ($v ? '' . t('YES') . '' : 'no') . '
';
- $output .= '' . t('Last channel executed') . ': ' . (($c = elysia_cron_last_channel()) ? $c : t('n/a')) . '
';
-
- if (EC_DRUPAL_VERSION < 7) {
- if (_ec_variable_get('elysia_cron_semaphore', 0)) {
- $output .= '' . t('Global semaphore active since !date', array('!date' => elysia_cron_date(_ec_variable_get('elysia_cron_semaphore', 0)))) . '
';
- }
+ if (elysia_cron_access('execute elysia_cron')) {
+ $aoutput[] = drupal_get_form('elysia_cron_run_form');
}
+ $output = '';
+ elysia_cron_initialize();
+ global $_elysia_cron_settings_by_channel;
+
+ $global_disabled = variable_get('elysia_cron_disabled', FALSE);
+ $last_channel = elysia_cron_last_channel();
+ $output .= '' . t('Global disable') . ': ' . ($global_disabled ? '' . t('YES') . '' : t('no')) . '
';
+ $output .= '' . t('Last channel executed') . ': ' . ($last_channel ? $last_channel : t('n/a')) . '
';
+
$running = '';
- foreach ($elysia_cron_settings_by_channel as $channel => $data) {
+ foreach ($_elysia_cron_settings_by_channel as $channel => $data) {
if (elysia_cron_is_channel_running($channel)) {
$running .= $channel . ' ';
}
@@ -39,19 +38,33 @@ function elysia_cron_admin_page() {
}
$output .= '' . t('Last run') . ': ' . elysia_cron_date(_ec_variable_get('elysia_cron_last_run', 0)) . '
';
-
$rows = array();
-
- $ipath = file_create_url(drupal_get_path('module', 'elysia_cron') . '/images/icon_');
-
- foreach ($elysia_cron_settings_by_channel as $channel => $data) {
+ $ipath = drupal_get_path('module', 'elysia_cron') . '/images/icon_';
+
+ foreach ($_elysia_cron_settings_by_channel as $channel => $data) {
$running = elysia_cron_is_channel_running($channel);
$rows[] = array(
- array('data' => '' . t('Channel') . ': ' . $channel . ($data['#data']['disabled'] ? ' (' . t('DISABLED') . ')' : '') . '
', 'colspan' => 2, 'header' => 'true'),
- array('data' => elysia_cron_date($data['#data']['last_run']), 'header' => 'true'),
- array('data' => $data['#data']['last_execution_time'] . 's ' . t('(Shutdown: !shutdown)', array('!shutdown' => $data['#data']['last_shutdown_time'] . 's')), 'header' => 'true'),
- array('data' => $data['#data']['execution_count'], 'header' => 'true'),
- array('data' => $data['#data']['avg_execution_time'] . 's / ' . $data['#data']['max_execution_time'] . 's', 'header' => 'true'),
+ array(
+ 'data' => '' . t('Channel') . ': ' . $channel . ($data['#data']['disabled'] ? ' (' . t('DISABLED') . ')' : '') . '
',
+ 'colspan' => 2,
+ 'header' => TRUE,
+ ),
+ array(
+ 'data' => elysia_cron_date($data['#data']['last_run']),
+ 'header' => TRUE,
+ ),
+ array(
+ 'data' => $data['#data']['last_execution_time'] . 's ' . t('(Shutdown: !shutdown)', array('!shutdown' => $data['#data']['last_shutdown_time'] . 's')),
+ 'header' => TRUE,
+ ),
+ array(
+ 'data' => $data['#data']['execution_count'],
+ 'header' => TRUE,
+ ),
+ array(
+ 'data' => $data['#data']['avg_execution_time'] . 's / ' . $data['#data']['max_execution_time'] . 's',
+ 'header' => TRUE,
+ ),
);
$messages = '';
if ($running) {
@@ -68,10 +81,14 @@ function elysia_cron_admin_page() {
$messages .= implode(', ', $msg) . '
';
}
if ($messages) {
- $rows[] = array( '', '', array('data' => $messages, 'colspan' => 4, 'header' => true) );
- $rows[] = array( array('data' => '', 'colspan' => 6) );
+ $rows[] = array(
+ '',
+ '',
+ array('data' => $messages, 'colspan' => 4, 'header' => TRUE),
+ );
+ $rows[] = array(array('data' => '', 'colspan' => 6));
}
-
+
foreach ($data as $job => $conf) {
$icon = 'idle';
$caption = '' . $job . '';
@@ -90,16 +107,25 @@ function elysia_cron_admin_page() {
$icon = 'waiting';
$tip = t('Waiting for execution');
}
-
+
if ($job != '#data') {
+ $force_run = elysia_cron_access('execute elysia_cron')
+ ? l(t('Force run'), 'admin/config/system/cron/execute/' . $job, array('attributes' => array('onclick' => 'return confirm("' . t('Force execution of !job?', array('!job' => $job)) . '");')))
+ : '';
+
+ $icon_image = theme('image', array(
+ 'path' => $ipath . $icon . '.png',
+ 'alt' => $tip,
+ 'title' => $tip,
+ ));
$rows[] = array(
- array( 'data' => '
', 'align' => 'right' ),
- array( 'data' => $caption . ': ' . elysia_cron_description($job) . ' ', 'colspan' => 4 ),
- array( 'data' => _dco_l(t('Force run'), _dcf_internal_path('admin/config/system/cron/execute/') . $job, array('attributes' => array('onclick' => 'return confirm("' . t('Force execution of !job?', array('!job' => $job)) . '");'))), 'align' => 'right'),
+ array('data' => $icon_image, 'align' => 'right'),
+ array('data' => $caption . ': ' . elysia_cron_description($job) . ' ', 'colspan' => 4),
+ array('data' => $force_run, 'align' => 'right'),
);
$rows[] = array(
'',
- $conf['rule'] . (!empty($conf['weight']) ? ' (' . t('Weight') . ': ' . $conf['weight'] . ')' : ''),
+ check_plain($conf['rule']) . (!empty($conf['weight']) ? ' (' . t('Weight') . ': ' . $conf['weight'] . ')' : ''),
elysia_cron_date($conf['last_run']),
$conf['last_execution_time'] . 's',
$conf['execution_count'],
@@ -107,21 +133,38 @@ function elysia_cron_admin_page() {
);
}
}
- $rows[] = array(' ','','','','','');
+
+ $rows[] = array(' ', '', '', '', '', '');
}
-
- $output .= _dco_theme('table', array('header' => array('', t('Job / Rule'), t('Last run'), t('Last exec time'), t('Exec count'), t('Avg/Max Exec time')), 'rows' => $rows));
+
+ $output .= theme('table', array(
+ 'header' => array(
+ '',
+ t('Job / Rule'),
+ t('Last run'),
+ t('Last exec time'),
+ t('Exec count'),
+ t('Avg/Max Exec time'),
+ ),
+ 'rows' => $rows,
+ ));
$output .= '
';
-
- $output .= _dco_theme('table', array(
+
+ $legend_icons = array(
+ 'idle' => theme('image', array('path' => $ipath . 'idle.png')),
+ 'waiting' => theme('image', array('path' => $ipath . 'waiting.png')),
+ 'running' => theme('image', array('path' => $ipath . 'running.png')),
+ 'disabled' => theme('image', array('path' => $ipath . 'disabled.png')),
+ );
+ $output .= theme('table', array(
'header' => array(t('Legend')),
'rows' => array(
- array('
' . t('Job shouldn\'t do anything right now')),
- array('
' . t('Job is ready to be executed, and is waiting for system cron call')),
- array('
' . t('Job is running right now')),
- array('
' . t('Job is disabled by settings, and won\'t run until enabled again')),
- array(t('Notes: job times don\'t include shutdown times (only shown on channel times).')),
- array(t('If an abort occours usually the job is not properly terminated, and so job timings can be inaccurate or wrong.')),
+ array($legend_icons['idle'] . ' - ' . t("Job shouldn't do anything right now")),
+ array($legend_icons['waiting'] . ' - ' . t('Job is ready to be executed, and is waiting for system cron call')),
+ array($legend_icons['running'] . ' - ' . t('Job is running right now')),
+ array($legend_icons['disabled'] . ' - ' . t("Job is disabled by settings, and won't run until enabled again")),
+ array(t("Notes: job times don't include shutdown times (only shown on channel times).")),
+ array(t('If an abort occurs usually the job is not properly terminated, and so job timings can be inaccurate or wrong.')),
),
));
@@ -130,11 +173,17 @@ function elysia_cron_admin_page() {
'#markup' => $output,
);
- return _dcr_render_array($aoutput);
+ return $aoutput;
}
+/**
+ * Form builder for general settings form.
+ *
+ * @return array
+ * From API array.
+ */
function elysia_cron_settings_form() {
- global $elysia_cron_settings, $elysia_cron_settings_by_channel;
+ global $_elysia_cron_settings_by_channel;
elysia_cron_initialize();
$form = array();
@@ -142,8 +191,8 @@ function elysia_cron_settings_form() {
$form['prefix_1'] = array(
'#type' => 'fieldset',
'#title' => t('Click for help and cron rules and script syntax'),
- '#collapsible' => true,
- '#collapsed' => true,
+ '#collapsible' => TRUE,
+ '#collapsed' => TRUE,
'#description' => t(<<Fields order
@@ -190,7 +239,7 @@ or to change the scheduling of an existing job.
<-> [rule] <ch:CHANNEL> [job]
-(Tokens betweens [] are mandatory)
+(Tokens between [] are mandatory)
- <->: a line starting with "-" means that the job is DISABLED.
- [rule]: a crontab schedule rule. See above.
@@ -216,13 +265,13 @@ or to change the scheduling of an existing job.
# Disable node_cron (i must set the cron rule even if disabled)
- */15 * * * * node_cron
-# Launch function send_summary_mail('test@test.com', false); every night
+# Launch function send_summary_mail('test@test.com', FALSE); every night
# And set its description to "Send daily summary"
# Send daily summary
-0 1 * * * send_summary_mail('test@test.com', false);
+0 1 * * * send_summary_mail('test@test.com', FALSE);
EOT
-),
+ ),
);
$form['prefix_2'] = array(
@@ -232,54 +281,71 @@ EOT
$form['main'] = array(
'#title' => t('Main'),
'#type' => 'fieldset',
- '#collapsible' => false,
- '#collapsed' => false,
+ '#collapsible' => FALSE,
+ '#collapsed' => FALSE,
);
$form['main']['elysia_cron_disabled'] = array(
'#title' => t('Global disable'),
'#type' => 'checkbox',
- '#default_value' => variable_get('elysia_cron_disabled', false),
+ '#default_value' => variable_get('elysia_cron_disabled', FALSE),
+ );
+ $form['main']['elysia_cron_run_maintenance'] = array(
+ '#title' => t('Run in maintenance'),
+ '#description' => t('Allow to run cron in maintenance mode.'),
+ '#type' => 'checkbox',
+ '#default_value' => variable_get('elysia_cron_run_maintenance', FALSE),
);
$form['installation'] = array(
'#title' => t('Installation settings'),
'#type' => 'fieldset',
- '#collapsible' => true,
- '#collapsed' => true,
+ '#collapsible' => TRUE,
+ '#collapsed' => TRUE,
);
- if (EC_DRUPAL_VERSION >= 7) {
- $form['installation']['cron_safe_threshold'] = array(
- '#type' => 'select',
- '#title' => t('Run cron on visitor\'s requests, every'),
- '#default_value' => variable_get('cron_safe_threshold', DRUPAL_CRON_DEFAULT_THRESHOLD),
- '#description' => t('Setting a time here will enable the "poormanscron" method, which runs the Drupal cron operation using normal browser/page requests instead of having to set up a crontab to request the cron.php script. This approach requires that your site gets regular traffic/visitors in order to trigger the cron request.') . '
' .
- t('This way is fine if you don\'t need a great control over job starting times and execution frequency.') . '
' .
- t('If you need a fine grained control over cron timings use the crontab metod, as described in Drupal installation guide.', array('!cron_url' => url('http://drupal.org/cron'))) . '
' .
- t('If you have a very large site, or you need to execute some jobs very often (more than once an hour) refer to Elysia cron\'s INSTALL.TXT to improve main cron setup.'),
- '#options' => array(0 => t('Never / Use external crontab')) + drupal_map_assoc(array(3600, 10800, 21600, 43200, 86400, 604800), 'format_interval'),
- );
- }
+ $options[0] = t('Never / Use external crontab');
+ $options += drupal_map_assoc(array(3600, 10800, 21600, 43200, 86400, 604800), 'format_interval');
+ $form['installation']['cron_safe_threshold'] = array(
+ '#type' => 'select',
+ '#title' => t("Run cron on visitor's requests, every"),
+ '#default_value' => variable_get('cron_safe_threshold', DRUPAL_CRON_DEFAULT_THRESHOLD),
+ '#description' => t('Setting a time here will enable the "poormanscron" method, which runs the Drupal cron operation using normal browser/page requests instead of having to set up a crontab to request the cron.php script. This approach requires that your site gets regular traffic/visitors in order to trigger the cron request.')
+ . '
'
+ . t("This way is fine if you don't need a great control over job starting times and execution frequency.")
+ . '
'
+ . t('If you need fine-grained control over cron timings use the crontab method, as described in Drupal installation guide.', array('!cron_url' => url('http://drupal.org/cron')))
+ . '
'
+ . t("If you have a very large site, or you need to execute some jobs very often (more than once an hour) refer to Elysia cron's INSTALL.TXT to improve main cron setup."),
+ '#options' => $options,
+ );
+
+ $form['installation']['elysia_cron_queue_show_count'] = array(
+ '#title' => t('Show the number of items in queues'),
+ '#description' => t('Some queue backends may have performance issue related with counting items in queue. If you faced with it, just disable this option.'),
+ '#type' => 'checkbox',
+ '#default_value' => variable_get('elysia_cron_queue_show_count', TRUE),
+ );
$form['installation']['cron_key'] = array(
'#title' => t('Cron key'),
'#type' => 'textfield',
- '#default_value' => variable_get('cron_key', ''),
- '#description' => t('This is used to avoid external cron calling. If you set this cron will by accessible only by calling http://site/cron.php?cron_key=XXX, so you\'ll need to modify system crontab to support this (Logged user with [administer elysia_cron] permission avoid this check).'),
+ '#default_value' => variable_get('cron_key'),
+ '#description' => t("This is used to avoid external cron calling. If you set this cron will by accessible only by calling http://site/cron.php?cron_key=XXX, so you'll need to modify system crontab to support this (Logged users with execute elysia_cron permission avoid this check).
+
If you left this field empty, you can run cron without cron_key parameter, like this http://site/cron.php, but it HIGHLY NOT RECOMMENDED."),
);
$form['installation']['elysia_cron_allowed_hosts'] = array(
'#title' => t('Allowed hosts'),
'#type' => 'textfield',
'#default_value' => variable_get('elysia_cron_allowed_hosts', ''),
- '#description' => t('Insert a list of ip addresses separated by , that can run cron.php (Logged user with [administer elysia_cron] permission avoid this check).'),
+ '#description' => t('Insert a list of ip addresses separated by , that can run cron.php (Logged user with [execute elysia_cron] permission avoid this check).'),
);
$form['installation']['elysia_cron_default_rule'] = array(
'#title' => t('Default schedule rule'),
'#type' => 'textfield',
- '#default_value' => variable_get('elysia_cron_default_rule', false),
- '#description' => t('If you don\'t specify a rule for a process, and if it has not a module specified one, this rule will apply'),
+ '#default_value' => variable_get('elysia_cron_default_rule', FALSE),
+ '#description' => t("If you don't specify a rule for a process, and if it has not a module specified one, this rule will apply"),
);
if (!ini_get('safe_mode')) {
@@ -309,25 +375,25 @@ EOT
'#description' => t('Enable extended logging (in watchdog)'),
);
- $default_ruless = '';
- $default_rules = variable_get('elysia_cron_default_rules', $GLOBALS['elysia_cron_default_rules']);
+ $default_rules_human = '';
+ $default_rules = variable_get('elysia_cron_default_rules', _elysia_cron_default_rules());
foreach ($default_rules as $dk => $dr) {
- $default_ruless .= $dr . ' = ' . $dk . "\n";
+ $default_rules_human .= $dr . ' = ' . $dk . PHP_EOL;
}
$form['installation']['elysia_cron_default_rules'] = array(
'#title' => t('Predefined rules'),
'#type' => 'textarea',
'#rows' => 5,
- '#default_value' => $default_ruless,
+ '#default_value' => $default_rules_human,
'#description' => t('You can put here standard rules used in your system, each one with its own caption. Put each rule in a separate line, in the form "caption = rule". For example: "every 15 minutes = */15 * * * *".'),
);
$form['installation']['elysia_cron_alert_fieldset'] = array(
'#title' => t('External cron tracking'),
'#type' => 'fieldset',
- '#collapsible' => true,
- '#collapsed' => true,
+ '#collapsible' => TRUE,
+ '#collapsed' => TRUE,
'#description' => t('This lets you use an external tracking system like Host Tracker to be used to monitor the health of cron on your site. Point the tracking service to !cron-ping-url. If Elysia cron has been called within the time interval specified below, the ping page will return HTTP 200. If not, the ping page will throw a 404 (page not found).', array('!cron-ping-url' => url('admin/build/cron/ping'))),
);
$form['installation']['elysia_cron_alert_fieldset']['elysia_cron_alert_interval'] = array(
@@ -341,49 +407,46 @@ EOT
$form['elysia_cron_script_fieldset'] = array(
'#title' => t('Script'),
'#type' => 'fieldset',
- '#collapsible' => true,
+ '#collapsible' => TRUE,
'#collapsed' => !variable_get('elysia_cron_script', ''),
);
$form['elysia_cron_script_fieldset']['elysia_cron_script'] = array(
'#type' => 'textarea',
'#rows' => 20,
'#default_value' => variable_get('elysia_cron_script', ''),
- '#description' => t('You can specify new cron jobs or modify existing schedules by adding lines to the script.
' .
- 'Warning All rules specified in the script will OVERRIDE single job settings and channel settings (sections below).'),
+ '#description' => t('You can specify new cron jobs or modify existing schedules by adding lines to the script.')
+ . '
'
+ . t('Warning All rules specified in the script will OVERRIDE single job settings and channel settings (sections below).'),
);
-
+
$form['single_job'] = array(
'#title' => t('Single job settings'),
- '#description' =>
- ''.t('Disabled').': '.t('Flag this to disable job execution').'
'.
- ''.t('Schedule rule').': '.t('Timing rule for the job. Leave empty to use default rule (shown after the field in parenthesis)').'
'.
- ''.t('Weight').': '.t('Use this to specify execution order: low weights are executed before high weights. Default value shown in parenthesis').'
'.
- ''.t('Channel').': '.t('Specify a channel for the job (create the channel if not exists)').'
',
+ '#description' => '' . t('Disabled') . ': ' . t('Flag this to disable job execution') . '
'
+ . '' . t('Schedule rule') . ': ' . t('Timing rule for the job. Leave empty to use default rule (shown after the field in parenthesis)') . '
'
+ . '' . t('Weight') . ': ' . t('Use this to specify execution order: low weights are executed before high weights. Default value shown in parenthesis') . '
'
+ . '' . t('Channel') . ': ' . t('Specify a channel for the job (create the channel if not exists)') . '
',
'#type' => 'fieldset',
- '#collapsible' => true,
- //'#collapsed' => true,
+ '#collapsible' => TRUE,
);
-
+
$jobchannels = array(
'#title' => t('Job channel associations'),
'#description' => t('Leave empty for default channel'),
'#type' => 'fieldset',
- '#collapsible' => true,
- '#collapsed' => true,
+ '#collapsible' => TRUE,
+ '#collapsed' => TRUE,
);
- foreach ($elysia_cron_settings_by_channel as $channel => $cconf) {
+ foreach ($_elysia_cron_settings_by_channel as $channel => $cconf) {
foreach ($cconf as $job => $conf) {
if ($job != '#data' && empty($conf['expression'])) {
$form['single_job']['elysia_cron_' . $job] = array(
- '#title' => $job, // t('Job !job', array('!job' => $job)),
+ '#title' => $job,
'#description' => elysia_cron_description($job),
'#type' => 'fieldset',
- '#collapsible' => true,
+ '#collapsible' => TRUE,
'#collapsed' => !elysia_cron_get_job_rule($job) && !elysia_cron_get_job_weight($job) && !elysia_cron_is_job_disabled($job) && !elysia_cron_get_job_channel($job),
);
- //if (!$form['single_job']['elysia_cron_'.$job]['#collapsed'])
- // $form['single_job']['#collapsed'] = false;
$rule = elysia_cron_get_job_rule($job);
$options = array_merge(array('default' => t('Default') . ' (' . (!empty($default_rules[$conf['default_rule']]) ? $default_rules[$conf['default_rule']] : $conf['default_rule']) . ')'), $default_rules);
@@ -414,23 +477,18 @@ EOT
'#description' => '(' . $conf['default_weight'] . ')',
);
- //$form['single_job']['elysia_cron_'.$job]['elysia_cron_'.$job.'_disabled'] = array(
$form['single_job']['elysia_cron_' . $job]['_elysia_cron_job_disabled_' . $job] = array(
'#title' => t('Disabled'),
'#type' => 'checkbox',
- '#default_value' => elysia_cron_is_job_disabled($job, false),
+ '#default_value' => elysia_cron_is_job_disabled($job, FALSE),
);
- //$jobchannels['elysia_cron_'.$job.'_channel'] = array(
$form['single_job']['elysia_cron_' . $job]['_elysia_cron_job_channel_' . $job] = array(
- '#title' => t('Channel'), // t('Channel for !job', array('!job' => $job)),
+ '#title' => t('Channel'),
'#type' => 'textfield',
'#size' => 20,
'#default_value' => elysia_cron_get_job_channel($job),
);
-
- //if (elysia_cron_get_job_channel($job))
- // $jobchannels['#collapsed'] = false;
}
}
}
@@ -438,13 +496,12 @@ EOT
$form['channels'] = array(
'#title' => t('Channels settings'),
'#type' => 'fieldset',
- '#collapsible' => true,
- //'#collapsed' => $jobchannels['#collapsed'],
+ '#collapsible' => TRUE,
);
- foreach ($elysia_cron_settings_by_channel as $channel => $conf) {
+ foreach ($_elysia_cron_settings_by_channel as $channel => $conf) {
$form['channels']['elysia_cron_ch_' . $channel] = array(
- '#title' => $channel, // t('Channel !channel', array('!channel' => $channel)),
+ '#title' => $channel,
'#type' => 'fieldset',
);
$form['channels']['elysia_cron_ch_' . $channel]['_elysia_cron_ch_disabled_' . $channel] = array(
@@ -458,12 +515,8 @@ EOT
'#size' => 20,
'#default_value' => elysia_cron_get_channel_rule($channel),
);
- //if (elysia_cron_is_channel_disabled($channel))
- // $form['channels']['#collapsed'] = false;
}
- //$form['channels']['jobchannels'] = $jobchannels;
-
$form['buttons'] = array('#type' => 'actions');
$form['buttons']['submit'] = array(
'#type' => 'submit',
@@ -478,17 +531,22 @@ EOT
elysia_cron_error('The settings have not been saved because of the errors.');
}
- return _dcr_form($form);
+ return $form;
}
-function theme_elysia_cron_settings_form($_dco_variables) {
- extract(_dcf_theme_form($_dco_variables));
+/**
+ * Theme function for general settings form.
+ *
+ * @param array $variables
+ * Theme vars.
+ *
+ * @return string
+ * Ready for print HTML.
+ */
+function theme_elysia_cron_settings_form(array &$variables) {
$form = &$variables['form'];
$output = '
+
$column): ?>
diff --git a/sites/all/modules/contrib/panels/panels/tests/PanelsEntityViewWebTestCase.test b/sites/all/modules/contrib/panels/panels/tests/PanelsEntityViewWebTestCase.test
new file mode 100644
index 00000000..03116d74
--- /dev/null
+++ b/sites/all/modules/contrib/panels/panels/tests/PanelsEntityViewWebTestCase.test
@@ -0,0 +1,103 @@
+checkPermissions(array(), TRUE);
+ cache_clear_all();
+
+ // Create the admin user.
+ $this->adminUser = $this->drupalCreateUser($permissions);
+ }
+
+ /**
+ * Ensure that the {ENTITY}_view toggle works correctly.
+ */
+ function testToggle() {
+ // Log in as the admin user.
+ $this->drupalLogin($this->adminUser);
+
+ // Load the Panels dashboard.
+ $this->drupalGet('admin/structure/panels');
+ $this->assertResponse(200, 'Loaded the main Panels admin page.');
+
+ // Confirm that the Node View task handler is disabled.
+ $this->assertText(t($this->view_label));
+ $this->assertLinkByHref(url('admin/structure/pages/edit/' . $this->view_name, array('absolute' => FALSE)));
+ $xpath = $this->xpath("//tr[contains(@class,:tr)]/td/div/div/ul/li[contains(@class,:li)]/a", array(':tr' => 'page-task-' . $this->view_name, ':li' => 'first'));
+ $this->assertEqual($xpath[0][0], t('Enable'));
+
+ // Set the Node View handler to "off".
+ variable_set('page_manager_' . $this->view_name . '_disabled', TRUE);
+
+ // Load the Panels dashboard again.
+ $this->drupalGet('admin/structure/panels');
+ $this->assertResponse(200, 'Loaded the main Panels admin page.');
+
+ // Confirm that the Node View task handler is still disabled.
+ $this->assertText(t($this->view_label));
+ $this->assertNoLinkByHref(url('admin/structure/pages/nojs/disable/' . $this->view_name, array('absolute' => FALSE)));
+ $xpath = $this->xpath("//tr[contains(@class,:tr)]/td/div/div/ul/li[contains(@class,:li)]/a", array(':tr' => 'page-task-' . $this->view_name, ':li' => 'first'));
+ $this->assertEqual($xpath[0][0], t('Enable'));
+
+ // Set the Node View handler to "on".
+ variable_set('page_manager_' . $this->view_name . '_disabled', FALSE);
+
+ // Load the Panels dashboard again.
+ $this->drupalGet('admin/structure/panels');
+ $this->assertResponse(200, 'Loaded the main Panels admin page.');
+
+ // Confirm that the Node View task handler is now enabled.
+ $this->assertLinkByHref(url('admin/structure/pages/nojs/disable/' . $this->view_name, array('absolute' => FALSE)));
+ $xpath = $this->xpath("//tr[contains(@class,:tr)]/td/div/div/ul/li[contains(@class,:li)]/a", array(':tr' => 'page-task-' . $this->view_name, ':li' => 'last'));
+ $this->assertEqual($xpath[0][0], t('Disable'));
+ }
+
+}
diff --git a/sites/all/modules/contrib/panels/panels/tests/PanelsNodeViewWebTestCase.test b/sites/all/modules/contrib/panels/panels/tests/PanelsNodeViewWebTestCase.test
new file mode 100644
index 00000000..5bf8f4b9
--- /dev/null
+++ b/sites/all/modules/contrib/panels/panels/tests/PanelsNodeViewWebTestCase.test
@@ -0,0 +1,31 @@
+ 'Panels node_view tests',
+ 'description' => 'Test the standard node_view task handler.',
+ 'group' => 'Panels',
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected $view_name = 'node_view';
+
+ /**
+ * {@inheritdoc}
+ */
+ protected $view_label = 'Node template';
+
+}
diff --git a/sites/all/modules/contrib/panels/panels/tests/PanelsTermViewWebTestCase.test b/sites/all/modules/contrib/panels/panels/tests/PanelsTermViewWebTestCase.test
new file mode 100644
index 00000000..2af6a6e5
--- /dev/null
+++ b/sites/all/modules/contrib/panels/panels/tests/PanelsTermViewWebTestCase.test
@@ -0,0 +1,31 @@
+ 'Panels term_view tests',
+ 'description' => 'Test the standard term_view task handler.',
+ 'group' => 'Panels',
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected $view_name = 'term_view';
+
+ /**
+ * {@inheritdoc}
+ */
+ protected $view_label = 'Taxonomy term template';
+
+}
diff --git a/sites/all/modules/contrib/panels/panels/tests/PanelsUserViewWebTestCase.test b/sites/all/modules/contrib/panels/panels/tests/PanelsUserViewWebTestCase.test
new file mode 100644
index 00000000..98afd7a0
--- /dev/null
+++ b/sites/all/modules/contrib/panels/panels/tests/PanelsUserViewWebTestCase.test
@@ -0,0 +1,31 @@
+ 'Panels user_view tests',
+ 'description' => 'Test the standard user_view task handler.',
+ 'group' => 'Panels',
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected $view_name = 'user_view';
+
+ /**
+ * {@inheritdoc}
+ */
+ protected $view_label = 'Users';
+
+}
diff --git a/sites/all/modules/contrib/search/search_api/CHANGELOG.txt b/sites/all/modules/contrib/search/search_api/CHANGELOG.txt
index 04ad2fe0..5e5ec734 100644
--- a/sites/all/modules/contrib/search/search_api/CHANGELOG.txt
+++ b/sites/all/modules/contrib/search/search_api/CHANGELOG.txt
@@ -1,5 +1,52 @@
-Search API 1.x, dev (xxxx-xx-xx):
----------------------------------
+Search API 1.20 (2016-07-21):
+-----------------------------
+- #2731103 by drunken monkey: Fixed the default value for the taxonomy term
+ filter "multiple" setting.
+- #1818572 by morningtime, drunken monkey, lodey, guillaumev: Added pretty
+ paths support to the Views facets block.
+- #2753441 by Johnny vd Laar: Fixed translated field names in
+ language-independent cache.
+
+Search API 1.19 (2016-07-05):
+-----------------------------
+- #2724687 by StefanPr, drunken monkey: Fixed failed sanitization of NULL field
+ values.
+- #2744189 by nikolabintev, drunken monkey: Fixed highlighting for single-word
+ fields.
+- #2744995 by John Cook, drunken monkey: Fixed search views without pager.
+- #2742053 by tunic: Fixed change notification on node access records change.
+- #2733447 by jsacksick: Fixed translatability of our Views taxonomy term
+ filter.
+- #2720465 by drunken monkey: Fixed bundle filter's handling of entity types
+ with no bundles on multi-type indexes.
+- #2710893 by alan-ps, drunken monkey: Fixed creation of comment indexes when
+ no nodes exist.
+- #2707039 by alan-ps: Fixed indexes of flag entities with "bundles" setting.
+- #2700879 by drunken monkey: Fixed breadcrumbs on index tabs.
+- #1889940 by cspurk, Yaron Tal: Fixed "HTML filter" processor to recognize all
+ valid HTML tags.
+- #2700011 by drunken monkey: Fixed compatibility issues of facets from
+ different indexes.
+- #2665970 by andrei.colesnic, drunken monkey: Added "Limit list to selected
+ items" exposed option support for Views taxonomy term filters.
+- #2703675 by drunken monkey, heykarthikwithu: Fixed accidental assumption that
+ all facets are taxonomy terms.
+- #2419853 by drunken monkey: Fixed HTML filter leaves escaped entities in
+ field values sometimes.
+
+Search API 1.18 (2016-04-20):
+-----------------------------
+- Various security fixes – see https://www.drupal.org/node/2710063.
+- #2693425 by jojyja: Fixed a typo in search_api.info.
+
+Search API 1.17 (2016-03-14):
+-----------------------------
+- #2665586 by recrit, drunken monkey: Fixed parsing of invalid date facet
+ filters.
+- #2677900 by stefan.r, drunken monkey: Added the possibility to change date
+ facet formats.
+- #2678856 by stefan.r, drunken monkey: Fixed date facets showing wrong month
+ on certain days.
- #2667872 by Les Lim: Added "0" to field boost options.
- #2654328 by drunken monkey, donquixote: Fixed use of "<" and ">" for open
facet ranges.
diff --git a/sites/all/modules/contrib/search/search_api/contrib/search_api_facetapi/plugins/facetapi/adapter.inc b/sites/all/modules/contrib/search/search_api/contrib/search_api_facetapi/plugins/facetapi/adapter.inc
index a10db9c0..6251e32b 100644
--- a/sites/all/modules/contrib/search/search_api/contrib/search_api_facetapi/plugins/facetapi/adapter.inc
+++ b/sites/all/modules/contrib/search/search_api/contrib/search_api_facetapi/plugins/facetapi/adapter.inc
@@ -61,6 +61,10 @@ class SearchApiFacetapiAdapter extends FacetapiAdapter {
public function initActiveFilters($query) {
$search_id = $query->getOption('search id');
$index_id = $this->info['instance'];
+ // Only act on queries from the right index.
+ if ($index_id != $query->getIndex()->machine_name) {
+ return;
+ }
$facets = facetapi_get_enabled_facets($this->info['name']);
$this->fields = array();
@@ -83,13 +87,16 @@ class SearchApiFacetapiAdapter extends FacetapiAdapter {
if (array_search($search_id, $facet_search_ids) === FALSE) {
if (!$default_true) {
- continue; // We are only to show facets for explicitly named search ids.
+ // We are only to show facets for explicitly named search ids.
+ continue;
}
}
elseif ($default_true) {
- continue; // The 'facet_search_ids' in the settings are to be excluded.
+ // The 'facet_search_ids' in the settings are to be excluded.
+ continue;
}
- $active[$facet['name']] = $search_id;
+ $facet_key = $facet['name'] . '@' . $this->getSearcher();
+ $active[$facet_key] = $search_id;
$this->fields[$facet['name']] = array(
'field' => $facet['field'],
'limit' => $options['hard_limit'],
diff --git a/sites/all/modules/contrib/search/search_api/contrib/search_api_facetapi/plugins/facetapi/query_type_date.inc b/sites/all/modules/contrib/search/search_api/contrib/search_api_facetapi/plugins/facetapi/query_type_date.inc
index 8a565302..6741cd81 100644
--- a/sites/all/modules/contrib/search/search_api/contrib/search_api_facetapi/plugins/facetapi/query_type_date.inc
+++ b/sites/all/modules/contrib/search/search_api/contrib/search_api_facetapi/plugins/facetapi/query_type_date.inc
@@ -58,27 +58,36 @@ class SearchApiFacetapiDate extends SearchApiFacetapiTerm implements FacetapiQue
$item = end($active);
$field = $this->facet['field'];
$filter = $this->createRangeFilter($item['value']);
- $this->addFacetFilter($query, $field, $filter);
+ if ($filter) {
+ $this->addFacetFilter($query, $field, $filter);
+ }
}
}
/**
* Rewrites the handler-specific date range syntax to the normal facet syntax.
*
- * @param $value
+ * @param string $value
* The user-facing facet value.
*
- * @return string
+ * @return string|null
* A facet to add as a filter, in the format used internally in this module.
+ * Or NULL if the raw facet in $value is not valid.
*/
protected function createRangeFilter($value) {
- // Gets the granularity. Ignore any filters passed directly from the server
- // (range or missing). We always create filters starting with a year.
- if (!$value || !ctype_digit($value[0])) {
- return $value;
+ // Ignore any filters passed directly from the server (range or missing).
+ if (!$value || $value == '!' || (!ctype_digit($value[0]) && preg_match('/^[\[(][^ ]+ [^ ]+[])]$/', $value))) {
+ return $value ? $value : NULL;
+ }
+
+ // Parse into date parts.
+ $parts = $this->parseRangeFilter($value);
+
+ // Return NULL if the date parts are invalid or none were found.
+ if (empty($parts)) {
+ return NULL;
}
- $parts = explode('-', $value);
$date = new DateTime();
switch (count($parts)) {
case 1:
@@ -140,6 +149,48 @@ class SearchApiFacetapiDate extends SearchApiFacetapiTerm implements FacetapiQue
return "[$lower TO $upper]";
}
+ /**
+ * Parses the date range filter value into parts.
+ *
+ * @param string $value
+ * The user-facing facet value.
+ *
+ * @return int[]|null
+ * An array of date parts, or NULL if an invalid value was provided.
+ */
+ protected static function parseRangeFilter($value) {
+ $parts = explode('-', $value);
+
+ foreach ($parts as $i => $part) {
+ // Invalidate if part is not an integer.
+ if ($part === '' || !is_numeric($part) || intval($part) != $part) {
+ return NULL;
+ }
+ $parts[$i] = (int) $part;
+ // Depending on the position, negative numbers or 0 are invalid.
+ switch ($i) {
+ case 0:
+ // Years can contain anything – negative values are unlikely, but
+ // technically possible.
+ break;
+ case 1:
+ case 2:
+ // Days and months have to be positive.
+ if ($part <= 0) {
+ return NULL;
+ }
+ break;
+ default:
+ // All others can be 0, but not negative.
+ if ($part < 0) {
+ return NULL;
+ }
+ }
+ }
+
+ return $parts;
+ }
+
/**
* Replacement callback for replacing ISO dates with timestamps.
*
@@ -159,10 +210,11 @@ class SearchApiFacetapiDate extends SearchApiFacetapiTerm implements FacetapiQue
public function build() {
$facet = $this->adapter->getFacet($this->facet);
$search_ids = drupal_static('search_api_facetapi_active_facets', array());
- if (empty($search_ids[$facet['name']]) || !search_api_current_search($search_ids[$facet['name']])) {
+ $facet_key = $facet['name'] . '@' . $this->adapter->getSearcher();
+ if (empty($search_ids[$facet_key]) || !search_api_current_search($search_ids[$facet_key])) {
return array();
}
- $search_id = $search_ids[$facet['name']];
+ $search_id = $search_ids[$facet_key];
$build = array();
$search = search_api_current_search($search_id);
$results = $search[1];
diff --git a/sites/all/modules/contrib/search/search_api/contrib/search_api_facetapi/plugins/facetapi/query_type_term.inc b/sites/all/modules/contrib/search/search_api/contrib/search_api_facetapi/plugins/facetapi/query_type_term.inc
index d17721f4..64d797a7 100644
--- a/sites/all/modules/contrib/search/search_api/contrib/search_api_facetapi/plugins/facetapi/query_type_term.inc
+++ b/sites/all/modules/contrib/search/search_api/contrib/search_api_facetapi/plugins/facetapi/query_type_term.inc
@@ -57,21 +57,27 @@ class SearchApiFacetapiTerm extends FacetapiQueryType implements FacetapiQueryTy
// When the operator is OR, remove parent terms from the active ones if
// children are active. If we don't do this, sending a term and its
// parent will produce the same results as just sending the parent.
- if ($settings['flatten'] == '0') {
+ if (is_callable($this->facet['hierarchy callback']) && !$settings['flatten']) {
// Check the filters in reverse order, to avoid checking parents that
// will afterwards be removed anyways.
- foreach (array_reverse(array_keys($active)) as $filter) {
+ $values = array_keys($active);
+ $parents = call_user_func($this->facet['hierarchy callback'], $values);
+ foreach (array_reverse($values) as $filter) {
// Skip this filter if it was already removed, or if it is the
// "missing value" filter ("!").
if (!isset($active[$filter]) || !is_numeric($filter)) {
continue;
}
- $parents = taxonomy_get_parents_all($filter);
- // The return value of taxonomy_get_parents_all() includes the term
- // itself at index 0. Remove that to only get the term's ancestors.
- unset($parents[0]);
- foreach ($parents as $parent) {
- unset($active[$parent->tid]);
+ // Go through the entire hierarchy of the value and remove all its
+ // ancestors.
+ while (!empty($parents[$filter])) {
+ $ancestor = array_shift($parents[$filter]);
+ if (isset($active[$ancestor])) {
+ unset($active[$ancestor]);
+ if (!empty($parents[$ancestor])) {
+ $parents[$filter] = array_merge($parents[$filter], $parents[$ancestor]);
+ }
+ }
}
}
}
@@ -165,10 +171,11 @@ class SearchApiFacetapiTerm extends FacetapiQueryType implements FacetapiQueryTy
// initActiveFilters) so that we can retrieve it here and get the correct
// current search for this facet.
$search_ids = drupal_static('search_api_facetapi_active_facets', array());
- if (empty($search_ids[$facet['name']]) || !search_api_current_search($search_ids[$facet['name']])) {
+ $facet_key = $facet['name'] . '@' . $this->adapter->getSearcher();
+ if (empty($search_ids[$facet_key]) || !search_api_current_search($search_ids[$facet_key])) {
return array();
}
- $search_id = $search_ids[$facet['name']];
+ $search_id = $search_ids[$facet_key];
list(, $results) = search_api_current_search($search_id);
$build = array();
diff --git a/sites/all/modules/contrib/search/search_api/contrib/search_api_facetapi/search_api_facetapi.info b/sites/all/modules/contrib/search/search_api/contrib/search_api_facetapi/search_api_facetapi.info
index 63c473bf..0c24621f 100644
--- a/sites/all/modules/contrib/search/search_api/contrib/search_api_facetapi/search_api_facetapi.info
+++ b/sites/all/modules/contrib/search/search_api/contrib/search_api_facetapi/search_api_facetapi.info
@@ -9,9 +9,9 @@ files[] = plugins/facetapi/adapter.inc
files[] = plugins/facetapi/query_type_term.inc
files[] = plugins/facetapi/query_type_date.inc
-; Information added by Drupal.org packaging script on 2016-02-26
-version = "7.x-1.16+29-dev"
+; Information added by Drupal.org packaging script on 2016-07-21
+version = "7.x-1.20"
core = "7.x"
project = "search_api"
-datestamp = "1456500713"
+datestamp = "1469117342"
diff --git a/sites/all/modules/contrib/search/search_api/contrib/search_api_facetapi/search_api_facetapi.install b/sites/all/modules/contrib/search/search_api/contrib/search_api_facetapi/search_api_facetapi.install
index a626a0cd..5743e080 100644
--- a/sites/all/modules/contrib/search/search_api/contrib/search_api_facetapi/search_api_facetapi.install
+++ b/sites/all/modules/contrib/search/search_api/contrib/search_api_facetapi/search_api_facetapi.install
@@ -5,9 +5,39 @@
* Install, update and uninstall functions for the Search facets module.
*/
+/**
+ * Implements hook_install().
+ */
+function search_api_facetapi_install() {
+ variable_set('date_format_search_api_facetapi_' . FACETAPI_DATE_YEAR, 'Y');
+ variable_set('date_format_search_api_facetapi_' . FACETAPI_DATE_MONTH, 'F Y');
+ variable_set('date_format_search_api_facetapi_' . FACETAPI_DATE_DAY, 'F j, Y');
+ variable_set('date_format_search_api_facetapi_' . FACETAPI_DATE_HOUR, 'H:__');
+ variable_set('date_format_search_api_facetapi_' . FACETAPI_DATE_MINUTE, 'H:i');
+ variable_set('date_format_search_api_facetapi_' . FACETAPI_DATE_SECOND, 'H:i:S');
+}
+
/**
* Implements hook_uninstall().
*/
function search_api_facetapi_uninstall() {
variable_del('search_api_facets_search_ids');
-}
\ No newline at end of file
+ variable_del('date_format_search_api_facetapi_' . FACETAPI_DATE_YEAR);
+ variable_del('date_format_search_api_facetapi_' . FACETAPI_DATE_MONTH);
+ variable_del('date_format_search_api_facetapi_' . FACETAPI_DATE_DAY);
+ variable_del('date_format_search_api_facetapi_' . FACETAPI_DATE_HOUR);
+ variable_del('date_format_search_api_facetapi_' . FACETAPI_DATE_MINUTE);
+ variable_del('date_format_search_api_facetapi_' . FACETAPI_DATE_SECOND);
+}
+
+/**
+ * Set up date formats.
+ */
+function search_api_facetapi_update_7101() {
+ variable_set('date_format_search_api_facetapi_' . FACETAPI_DATE_YEAR, 'Y');
+ variable_set('date_format_search_api_facetapi_' . FACETAPI_DATE_MONTH, 'F Y');
+ variable_set('date_format_search_api_facetapi_' . FACETAPI_DATE_DAY, 'F j, Y');
+ variable_set('date_format_search_api_facetapi_' . FACETAPI_DATE_HOUR, 'H:__');
+ variable_set('date_format_search_api_facetapi_' . FACETAPI_DATE_MINUTE, 'H:i');
+ variable_set('date_format_search_api_facetapi_' . FACETAPI_DATE_SECOND, 'H:i:S');
+}
diff --git a/sites/all/modules/contrib/search/search_api/contrib/search_api_facetapi/search_api_facetapi.module b/sites/all/modules/contrib/search/search_api/contrib/search_api_facetapi/search_api_facetapi.module
index bb3c8b24..3696eae5 100644
--- a/sites/all/modules/contrib/search/search_api/contrib/search_api_facetapi/search_api_facetapi.module
+++ b/sites/all/modules/contrib/search/search_api/contrib/search_api_facetapi/search_api_facetapi.module
@@ -211,6 +211,58 @@ function search_api_facetapi_search_api_query_alter($query) {
}
}
+/**
+ * Implements hook_date_formats().
+ */
+function search_api_facetapi_date_formats() {
+ return array(
+ array(
+ 'type' => 'search_api_facetapi_' . FACETAPI_DATE_YEAR,
+ 'format' => 'Y',
+ 'locales' => array(),
+ ),
+ array(
+ 'type' => 'search_api_facetapi_' . FACETAPI_DATE_MONTH,
+ 'format' => 'F Y',
+ 'locales' => array(),
+ ),
+ array(
+ 'type' => 'search_api_facetapi_' . FACETAPI_DATE_DAY,
+ 'format' => 'F j, Y',
+ 'locales' => array(),
+ ),
+ array(
+ 'type' => 'search_api_facetapi_' . FACETAPI_DATE_HOUR,
+ 'format' => 'H:__',
+ 'locales' => array(),
+ ),
+ array(
+ 'type' => 'search_api_facetapi_' . FACETAPI_DATE_MINUTE,
+ 'format' => 'H:i',
+ 'locales' => array(),
+ ),
+ array(
+ 'type' => 'search_api_facetapi_' . FACETAPI_DATE_SECOND,
+ 'format' => 'H:i:s',
+ 'locales' => array(),
+ ),
+ );
+}
+
+/**
+ * Implements hook_date_format_types().
+ */
+function search_api_facetapi_date_format_types() {
+ return array(
+ 'search_api_facetapi_' . FACETAPI_DATE_YEAR => t('Search facets - Years'),
+ 'search_api_facetapi_' . FACETAPI_DATE_MONTH => t('Search facets - Months'),
+ 'search_api_facetapi_' . FACETAPI_DATE_DAY => t('Search facets - Days'),
+ 'search_api_facetapi_' . FACETAPI_DATE_HOUR => t('Search facets - Hours'),
+ 'search_api_facetapi_' . FACETAPI_DATE_MINUTE => t('Search facets - Minutes'),
+ 'search_api_facetapi_' . FACETAPI_DATE_SECOND => t('Search facets - Seconds'),
+ );
+}
+
/**
* Menu callback for the facet settings page.
*/
@@ -532,16 +584,12 @@ function search_api_facetapi_map_date(array $values, array $options = array()) {
continue;
}
- // For years, the URL value is already the label.
- if ($granularity == FACETAPI_DATE_YEAR) {
- $map[$value] = $value;
- continue;
- }
-
// Otherwise, parse the timestamp from the known format and format it as a
// label.
$format = search_api_facetapi_date_get_granularity_format($granularity);
- $date = DateTime::createFromFormat($format, $value);
+ // Use the "!" modifier to make the date parsing independent of the current
+ // date/time. (See #2678856.)
+ $date = DateTime::createFromFormat('!' . $format, $value);
if (!$date) {
continue;
}
@@ -568,17 +616,16 @@ function search_api_facetapi_map_date(array $values, array $options = array()) {
*/
function search_api_facetapi_format_timestamp($timestamp, $precision = FACETAPI_DATE_YEAR) {
$formats = array(
- FACETAPI_DATE_YEAR => 'Y',
- FACETAPI_DATE_MONTH => 'F Y',
- FACETAPI_DATE_DAY => 'F j, Y',
- FACETAPI_DATE_HOUR => 'H:__',
- FACETAPI_DATE_MINUTE => 'H:i',
- FACETAPI_DATE_SECOND => 'H:i:s',
+ FACETAPI_DATE_YEAR,
+ FACETAPI_DATE_MONTH,
+ FACETAPI_DATE_DAY,
+ FACETAPI_DATE_HOUR,
+ FACETAPI_DATE_MINUTE,
+ FACETAPI_DATE_SECOND,
);
- if (!isset($formats[$precision])) {
+ if (!in_array($precision, $formats)) {
$precision = FACETAPI_DATE_YEAR;
}
-
- return format_date($timestamp, 'custom', $formats[$precision]);
+ return format_date($timestamp, 'search_api_facetapi_' . $precision);
}
diff --git a/sites/all/modules/contrib/search/search_api/contrib/search_api_views/includes/display_facet_block.inc b/sites/all/modules/contrib/search/search_api/contrib/search_api_views/includes/display_facet_block.inc
index 35ad14f5..e3e29191 100644
--- a/sites/all/modules/contrib/search/search_api/contrib/search_api_views/includes/display_facet_block.inc
+++ b/sites/all/modules/contrib/search/search_api/contrib/search_api_views/includes/display_facet_block.inc
@@ -247,6 +247,31 @@ class SearchApiViewsFacetsBlockDisplay extends views_plugin_display_block {
),
);
+ // Override the $variables['#path'] if facetapi_pretty_paths is enabled.
+ if (module_exists('facetapi_pretty_paths')) {
+ // Get the appropriate facet adapter.
+ $adapter = facetapi_adapter_load('search_api@' . $index->machine_name);
+
+ // Get the URL processor and check if it uses pretty paths.
+ $urlProcessor = $adapter->getUrlProcessor();
+ if ($urlProcessor instanceof FacetapiUrlProcessorPrettyPaths) {
+ // Retrieve the pretty path alias from the URL processor.
+ $facet = facetapi_facet_load($facet_field, 'search_api@' . $index->machine_name);
+ $values = array(trim($term['filter'], '"'));
+
+ // Get the pretty path for the facet and remove the current search's
+ // base path from it.
+ $base_path_current = $urlProcessor->getBasePath();
+ $pretty_path = $urlProcessor->getFacetPath($facet, $values, FALSE);
+ $pretty_path = str_replace($base_path_current, '', $pretty_path);
+
+ // Set the new, pretty path for the facet and remove the "f" query
+ // parameter.
+ $variables['path'] = $variables['path'] . $pretty_path;
+ unset($variables['options']['query']['f']);
+ }
+ }
+
// Themes the link, adds row to facets.
$facets[] = array(
'class' => array('leaf'),
diff --git a/sites/all/modules/contrib/search/search_api/contrib/search_api_views/includes/handler_filter_entity.inc b/sites/all/modules/contrib/search/search_api/contrib/search_api_views/includes/handler_filter_entity.inc
index ffae8ad4..ce5c753d 100644
--- a/sites/all/modules/contrib/search/search_api/contrib/search_api_views/includes/handler_filter_entity.inc
+++ b/sites/all/modules/contrib/search/search_api/contrib/search_api_views/includes/handler_filter_entity.inc
@@ -67,17 +67,6 @@ abstract class SearchApiViewsHandlerFilterEntity extends SearchApiViewsHandlerFi
return $operators;
}
- /**
- * {@inheritdoc}
- */
- public function option_definition() {
- $options = parent::option_definition();
-
- $options['expose']['multiple']['default'] = TRUE;
-
- return $options;
- }
-
/**
* {@inheritdoc}
*/
diff --git a/sites/all/modules/contrib/search/search_api/contrib/search_api_views/includes/handler_filter_taxonomy_term.inc b/sites/all/modules/contrib/search/search_api/contrib/search_api_views/includes/handler_filter_taxonomy_term.inc
index efa685af..b6db4f69 100644
--- a/sites/all/modules/contrib/search/search_api/contrib/search_api_views/includes/handler_filter_taxonomy_term.inc
+++ b/sites/all/modules/contrib/search/search_api/contrib/search_api_views/includes/handler_filter_taxonomy_term.inc
@@ -27,6 +27,7 @@ class SearchApiViewsHandlerFilterTaxonomyTerm extends SearchApiViewsHandlerFilte
$options['type'] = array('default' => !empty($this->definition['vocabulary']) ? 'textfield' : 'select');
$options['hierarchy'] = array('default' => 0);
+ $options['expose']['contains']['reduce'] = array('default' => FALSE);
$options['error_message'] = array('default' => TRUE, 'bool' => TRUE);
return $options;
@@ -72,13 +73,13 @@ class SearchApiViewsHandlerFilterTaxonomyTerm extends SearchApiViewsHandlerFilte
}
else {
if ($vocabulary && !empty($this->options['hierarchy'])) {
- $tree = taxonomy_get_tree($vocabulary->vid);
+ $tree = taxonomy_get_tree($vocabulary->vid, 0, NULL, TRUE);
$options = array();
if ($tree) {
foreach ($tree as $term) {
$choice = new stdClass();
- $choice->option = array($term->tid => str_repeat('-', $term->depth) . $term->name);
+ $choice->option = array($term->tid => str_repeat('-', $term->depth) . check_plain(entity_label('taxonomy_term', $term)));
$options[] = $choice;
}
}
@@ -97,8 +98,15 @@ class SearchApiViewsHandlerFilterTaxonomyTerm extends SearchApiViewsHandlerFilte
$query->condition('tv.machine_name', $vocabulary->machine_name);
}
$result = $query->execute();
+ $tids = array();
+
foreach ($result as $term) {
- $options[$term->tid] = $term->name;
+ $tids[] = $term->tid;
+ }
+ $terms = taxonomy_term_load_multiple($tids);
+
+ foreach ($terms as $term) {
+ $options[$term->tid] = check_plain(entity_label('taxonomy_term', $term));
}
}
@@ -229,6 +237,14 @@ class SearchApiViewsHandlerFilterTaxonomyTerm extends SearchApiViewsHandlerFilte
parent::exposed_validate($form, $form_state);
}
+ /**
+ * {@inheritdoc}
+ */
+ public function expose_options() {
+ parent::expose_options();
+ $this->options['expose']['reduce'] = FALSE;
+ }
+
/**
* {@inheritdoc}
*/
@@ -282,15 +298,23 @@ class SearchApiViewsHandlerFilterTaxonomyTerm extends SearchApiViewsHandlerFilte
*/
public function expose_form(&$form, &$form_state) {
parent::expose_form($form, $form_state);
- if ($this->options['type'] != 'select') {
- unset($form['expose']['reduce']);
+
+ if ($this->options['type'] == 'select') {
+ $form['expose']['reduce'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Limit list to selected items'),
+ '#description' => t('If checked, the only items presented to the user will be the ones selected here.'),
+ '#default_value' => $this->options['expose']['reduce'],
+ );
+ }
+ else {
+ $form['error_message'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Display error message'),
+ '#description' => t('Display an error message if one of the entered terms could not be found.'),
+ '#default_value' => $this->options['error_message'],
+ );
}
- $form['error_message'] = array(
- '#type' => 'checkbox',
- '#title' => t('Display error message'),
- '#description' => t('Display an error message if one of the entered terms could not be found.'),
- '#default_value' => !empty($this->options['error_message']),
- );
}
/**
diff --git a/sites/all/modules/contrib/search/search_api/contrib/search_api_views/includes/query.inc b/sites/all/modules/contrib/search/search_api/contrib/search_api_views/includes/query.inc
index 958ee7a3..bc9af4de 100644
--- a/sites/all/modules/contrib/search/search_api/contrib/search_api_views/includes/query.inc
+++ b/sites/all/modules/contrib/search/search_api/contrib/search_api_views/includes/query.inc
@@ -344,13 +344,15 @@ class SearchApiViewsQuery extends views_plugin_query {
// FALSE.
$skip_result_count = $this->query->getOption('skip result count', TRUE);
if ($skip_result_count) {
- $skip_result_count = !$this->pager->use_count_query() && empty($view->get_total_rows);
+ $skip_result_count = !$this->pager || (!$this->pager->use_count_query() && empty($view->get_total_rows));
$this->query->setOption('skip result count', $skip_result_count);
}
try {
// Trigger pager pre_execute().
- $this->pager->pre_execute($this->query);
+ if ($this->pager) {
+ $this->pager->pre_execute($this->query);
+ }
// Views passes sometimes NULL and sometimes the integer 0 for "All" in a
// pager. If set to 0 items, a string "0" is passed. Therefore, we unset
@@ -385,7 +387,9 @@ class SearchApiViewsQuery extends views_plugin_query {
$view->execute_time = microtime(TRUE) - $start;
// Trigger pager post_execute().
- $this->pager->post_execute($view->result);
+ if ($this->pager) {
+ $this->pager->post_execute($view->result);
+ }
}
catch (Exception $e) {
$this->errors[] = $e->getMessage();
@@ -444,7 +448,7 @@ class SearchApiViewsQuery extends views_plugin_query {
// Gather any fields from the search results.
if (!empty($result['fields'])) {
- $row['_entity_properties'] += $result['fields'];
+ $row['_entity_properties'] += search_api_get_sanitized_field_values($result['fields']);
}
// Check whether we need to extract any properties from the result item.
diff --git a/sites/all/modules/contrib/search/search_api/contrib/search_api_views/search_api_views.info b/sites/all/modules/contrib/search/search_api/contrib/search_api_views/search_api_views.info
index 9aa7129f..f6f15245 100644
--- a/sites/all/modules/contrib/search/search_api/contrib/search_api_views/search_api_views.info
+++ b/sites/all/modules/contrib/search/search_api/contrib/search_api_views/search_api_views.info
@@ -27,9 +27,9 @@ files[] = includes/handler_sort.inc
files[] = includes/plugin_cache.inc
files[] = includes/query.inc
-; Information added by Drupal.org packaging script on 2016-02-26
-version = "7.x-1.16+29-dev"
+; Information added by Drupal.org packaging script on 2016-07-21
+version = "7.x-1.20"
core = "7.x"
project = "search_api"
-datestamp = "1456500713"
+datestamp = "1469117342"
diff --git a/sites/all/modules/contrib/search/search_api/includes/callback_bundle_filter.inc b/sites/all/modules/contrib/search/search_api/includes/callback_bundle_filter.inc
index 50a9dc9f..ab743d6c 100644
--- a/sites/all/modules/contrib/search/search_api/includes/callback_bundle_filter.inc
+++ b/sites/all/modules/contrib/search/search_api/includes/callback_bundle_filter.inc
@@ -34,7 +34,8 @@ class SearchApiAlterBundleFilter extends SearchApiAbstractAlterCallback {
return;
}
- if ($this->isMultiEntityIndex()) {
+ $multi_entity = $this->isMultiEntityIndex();
+ if ($multi_entity) {
$bundle_prop = 'item_bundle';
}
else {
@@ -46,6 +47,10 @@ class SearchApiAlterBundleFilter extends SearchApiAbstractAlterCallback {
$default = (bool) $this->options['default'];
foreach ($items as $id => $item) {
+ // Ignore types that have no bundles.
+ if ($multi_entity && !self::hasBundles(entity_get_info($item->item_type))) {
+ continue;
+ }
if (isset($bundles[$item->$bundle_prop]) == $default) {
unset($items[$id]);
}
diff --git a/sites/all/modules/contrib/search/search_api/includes/datasource_entity.inc b/sites/all/modules/contrib/search/search_api/includes/datasource_entity.inc
index 034bb525..3ebbab24 100644
--- a/sites/all/modules/contrib/search/search_api/includes/datasource_entity.inc
+++ b/sites/all/modules/contrib/search/search_api/includes/datasource_entity.inc
@@ -166,6 +166,10 @@ class SearchApiEntityDataSourceController extends SearchApiAbstractDataSourceCon
$bundle_column = 'vid';
$bundles = db_query('SELECT vid FROM {taxonomy_vocabulary} WHERE machine_name IN (:bundles)', array(':bundles' => $bundles))->fetchCol();
}
+ elseif ($this->entityType == 'flagging') {
+ $bundle_column = 'fid';
+ $bundles = db_query('SELECT fid FROM {flag} WHERE name IN (:bundles)', array(':bundles' => $bundles))->fetchCol();
+ }
elseif ($this->entityType == 'comment') {
// Comments are significantly more complicated, since they don't
// store their bundle explicitly in their database table. Instead,
@@ -182,14 +186,17 @@ class SearchApiEntityDataSourceController extends SearchApiAbstractDataSourceCon
$bundles = db_query('SELECT nid FROM {node} WHERE type IN (:bundles)', array(':bundles' => $node_types))->fetchCol();
}
else {
- return;
+ continue;
}
}
else {
$this->startTrackingFallback(array($index->machine_name => $index));
+ continue;
}
}
- $query->condition($bundle_column, $bundles);
+ if ($bundles) {
+ $query->condition($bundle_column, $bundles);
+ }
}
// INSERT ... SELECT ...
diff --git a/sites/all/modules/contrib/search/search_api/includes/index_entity.inc b/sites/all/modules/contrib/search/search_api/includes/index_entity.inc
index 7a4234a5..95848f12 100644
--- a/sites/all/modules/contrib/search/search_api/includes/index_entity.inc
+++ b/sites/all/modules/contrib/search/search_api/includes/index_entity.inc
@@ -764,12 +764,14 @@ class SearchApiIndex extends Entity {
* "additional fields" key.
*/
public function getFields($only_indexed = TRUE, $get_additional = FALSE) {
+ global $language;
+
$only_indexed = $only_indexed ? 1 : 0;
$get_additional = $get_additional ? 1 : 0;
// First, try the static cache and the persistent cache bin.
if (empty($this->fields[$only_indexed][$get_additional])) {
- $cid = $this->getCacheId() . "-$only_indexed-$get_additional";
+ $cid = $this->getCacheId() . "-$only_indexed-$get_additional-{$language->language}";
$cache = cache_get($cid);
if ($cache) {
$this->fields[$only_indexed][$get_additional] = $cache->data;
diff --git a/sites/all/modules/contrib/search/search_api/includes/processor_highlight.inc b/sites/all/modules/contrib/search/search_api/includes/processor_highlight.inc
index 566599af..8accf4de 100644
--- a/sites/all/modules/contrib/search/search_api/includes/processor_highlight.inc
+++ b/sites/all/modules/contrib/search/search_api/includes/processor_highlight.inc
@@ -158,13 +158,14 @@ class SearchApiHighlight extends SearchApiAbstractProcessor {
if ($this->options['highlight'] != 'never') {
$fields = $this->getFulltextFields($response['results'], $id, $fulltext_fields, $this->options['highlight'] == 'always');
foreach ($fields as $field => $data) {
+ $result['fields'][$field] = array('#sanitize_callback' => FALSE);
if (is_array($data)) {
foreach ($data as $i => $text) {
- $result['fields'][$field][$i] = $this->highlightField($text, $keys);
+ $result['fields'][$field]['#value'][$i] = $this->highlightField($text, $keys);
}
}
else {
- $result['fields'][$field] = $this->highlightField($data, $keys);
+ $result['fields'][$field]['#value'] = $this->highlightField($data, $keys);
}
}
}
@@ -200,9 +201,10 @@ class SearchApiHighlight extends SearchApiAbstractProcessor {
// We only need detailed fields data if $load is TRUE.
$fields = $load ? $this->index->getFields() : array();
$needs_extraction = array();
+ $returned_fields = search_api_get_sanitized_field_values(array_intersect_key($result['fields'], array_flip($fulltext_fields)));
foreach ($fulltext_fields as $field) {
- if (array_key_exists($field, $result['fields'])) {
- $data[$field] = $result['fields'][$field];
+ if (array_key_exists($field, $returned_fields)) {
+ $data[$field] = $returned_fields[$field];
}
elseif ($load) {
$needs_extraction[$field] = $fields[$field];
@@ -225,7 +227,7 @@ class SearchApiHighlight extends SearchApiAbstractProcessor {
}
$wrapper = $this->index->entityWrapper($result['entity'], FALSE);
$wrapper->language($language->language);
- $extracted = search_api_extract_fields($wrapper, $needs_extraction);
+ $extracted = search_api_extract_fields($wrapper, $needs_extraction, array('sanitize' => TRUE));
foreach ($extracted as $field => $info) {
if (isset($info['value'])) {
@@ -448,12 +450,12 @@ class SearchApiHighlight extends SearchApiAbstractProcessor {
* @param array $array
* The array to flatten.
* @param string $glue
- * The separator to insert between individual array items.
+ * (optional) The separator to insert between individual array items.
*
* @return string
* The glued string.
*/
- protected function flattenArrayValues(array $array, $glue = "\n\n") {
+ protected function flattenArrayValues(array $array, $glue = " \n\n ") {
$ret = array();
foreach ($array as $item) {
if (is_array($item)) {
diff --git a/sites/all/modules/contrib/search/search_api/includes/processor_html_filter.inc b/sites/all/modules/contrib/search/search_api/includes/processor_html_filter.inc
index 180c9c18..0cc4800d 100644
--- a/sites/all/modules/contrib/search/search_api/includes/processor_html_filter.inc
+++ b/sites/all/modules/contrib/search/search_api/includes/processor_html_filter.inc
@@ -101,7 +101,7 @@ class SearchApiHtmlFilter extends SearchApiAbstractProcessor {
$value = $this->parseText($text);
}
else {
- $value = strip_tags($text);
+ $value = html_entity_decode(strip_tags($text));
// Remove any multiple or leading/trailing spaces we might have introduced.
$value = preg_replace('/\s\s+/', ' ', trim($value));
}
@@ -120,7 +120,7 @@ class SearchApiHtmlFilter extends SearchApiAbstractProcessor {
);
}
$text = substr($text, $pos + 1);
- if (!preg_match('#^(/?)([-:_a-zA-Z]+)#', $text, $m)) {
+ if (!preg_match('#^(/?)([:_a-zA-Z][-:_a-zA-Z0-9.]*)#', $text, $m)) {
continue;
}
$text = substr($text, strpos($text, '>') + 1);
diff --git a/sites/all/modules/contrib/search/search_api/includes/query.inc b/sites/all/modules/contrib/search/search_api/includes/query.inc
index 30cd6e9a..debed66a 100644
--- a/sites/all/modules/contrib/search/search_api/includes/query.inc
+++ b/sites/all/modules/contrib/search/search_api/includes/query.inc
@@ -201,7 +201,16 @@ interface SearchApiQueryInterface {
* already ready-to-use. This allows search engines (or postprocessors)
* to store extracted fields so other modules don't have to extract them
* again. This fields should always be checked by modules that want to
- * use field contents of the result items.
+ * use field contents of the result items. The format of the array is
+ * field IDs (as used by the Search API internally) mapped to either the
+ * raw value of the field (scalar or array value), or an associative
+ * array with the following keys:
+ * - #value: The raw field value.
+ * - #sanitize_callback: The callback to use for sanitizing the field
+ * value for HTML output, or FALSE to state that the field value is
+ * already sanitized.
+ * In the simple form, it's assumed the field value should be sanitized
+ * with check_plain().
* - entity: (optional) If set, the fully loaded result item. This field
* should always be used by modules using search results, to avoid
* duplicate item loads.
diff --git a/sites/all/modules/contrib/search/search_api/search_api.admin.inc b/sites/all/modules/contrib/search/search_api/search_api.admin.inc
index 2167e787..3afbaa78 100644
--- a/sites/all/modules/contrib/search/search_api/search_api.admin.inc
+++ b/sites/all/modules/contrib/search/search_api/search_api.admin.inc
@@ -337,13 +337,6 @@ function search_api_admin_add_server_submit(array $form, array &$form_state) {
drupal_set_message(t('The server was successfully created.'));
}
-/**
- * Title callback for viewing or editing a server or index.
- */
-function search_api_admin_item_title($object) {
- return $object->name;
-}
-
/**
* Page callback: Displays information about a server.
*
diff --git a/sites/all/modules/contrib/search/search_api/search_api.info b/sites/all/modules/contrib/search/search_api/search_api.info
index 369aa579..dd9a210b 100644
--- a/sites/all/modules/contrib/search/search_api/search_api.info
+++ b/sites/all/modules/contrib/search/search_api/search_api.info
@@ -1,5 +1,5 @@
name = Search API
-description = "Provides a generic API for modules offering search capabilites."
+description = "Provides a generic API for modules offering search capabilities."
dependencies[] = entity
core = 7.x
package = Search
@@ -36,9 +36,9 @@ files[] = includes/service.inc
configure = admin/config/search/search_api
-; Information added by Drupal.org packaging script on 2016-02-26
-version = "7.x-1.16+29-dev"
+; Information added by Drupal.org packaging script on 2016-07-21
+version = "7.x-1.20"
core = "7.x"
project = "search_api"
-datestamp = "1456500713"
+datestamp = "1469117342"
diff --git a/sites/all/modules/contrib/search/search_api/search_api.module b/sites/all/modules/contrib/search/search_api/search_api.module
index 1e4fb6a1..0890fca4 100644
--- a/sites/all/modules/contrib/search/search_api/search_api.module
+++ b/sites/all/modules/contrib/search/search_api/search_api.module
@@ -216,10 +216,13 @@ function search_api_hook_info() {
'search_api_data_type_info' => $hook_info,
'search_api_data_type_info_alter' => $hook_info,
'search_api_alter_callback_info' => $hook_info,
+ 'search_api_alter_callback_info_alter' => $hook_info,
'search_api_processor_info' => $hook_info,
+ 'search_api_processor_info_alter' => $hook_info,
'search_api_index_items_alter' => $hook_info,
'search_api_items_indexed' => $hook_info,
'search_api_query_alter' => $hook_info,
+ 'search_api_results_alter' => $hook_info,
'search_api_server_load' => $hook_info,
'search_api_server_insert' => $hook_info,
'search_api_server_update' => $hook_info,
@@ -920,6 +923,38 @@ function search_api_entity_delete($entity, $type) {
}
}
+/**
+ * Implements hook_node_access_records_alter().
+ *
+ * Marks the node as "changed" in indexes that use the "Node access" data
+ * alteration. Also marks the node's comments as changed in indexes that use the
+ * "Comment access" data alteration.
+ */
+function search_api_node_access_records_alter(&$grants, $node) {
+ foreach (search_api_index_load_multiple(FALSE) as $index) {
+ $item_ids = array();
+ if (!empty($index->options['data_alter_callbacks']['search_api_alter_node_access']['status'])) {
+ $item_id = $index->datasource()->getItemId($node);
+ if ($item_id !== NULL) {
+ $item_ids = array($item_id);
+ }
+ }
+ elseif (!empty($index->options['data_alter_callbacks']['search_api_alter_comment_access']['status'])) {
+ if (!isset($comments)) {
+ $comments = comment_load_multiple(FALSE, array('nid' => $node->nid));
+ }
+ foreach ($comments as $comment) {
+ $item_ids[] = $index->datasource()->getItemId($comment);
+ }
+ }
+
+ if ($item_ids) {
+ $indexes = array($index->machine_name => $index);
+ search_api_track_item_change_for_indexes($index->item_type, $item_ids, $indexes);
+ }
+ }
+}
+
/**
* Implements hook_field_attach_rename_bundle().
*
@@ -1186,6 +1221,20 @@ function search_api_track_item_change($type, array $item_ids) {
if (!$indexes) {
return;
}
+ search_api_track_item_change_for_indexes($type, $item_ids, $indexes);
+}
+
+/**
+ * Marks the items with the specified IDs as "dirty" for the given indexes.
+ *
+ * @param string $type
+ * The item type of the items.
+ * @param array $item_ids
+ * The item IDs.
+ * @param SearchApiIndex[] $indexes
+ * The indexes for which to mark the items as "dirty".
+ */
+function search_api_track_item_change_for_indexes($type, array $item_ids, $indexes) {
try {
$returned_indexes = search_api_get_datasource_controller($type)->trackItemChange($item_ids, $indexes);
if (isset($returned_indexes)) {
@@ -1207,7 +1256,6 @@ function search_api_track_item_change($type, array $item_ids) {
catch (SearchApiException $e) {
$vars['%item_type'] = $type;
watchdog_exception('search_api', $e, '%type while updating items of type %item_type: !message in %function (line %line of %file).', $vars);
- return;
}
}
@@ -2058,7 +2106,7 @@ function _search_api_query_add_node_access($account, SearchApiQueryInterface $qu
}
// If the user cannot access content/comments at all, return no results.
- if (!user_access('access content', $account) || ($is_comment && !user_access('access content', $account))) {
+ if (!user_access('access content', $account) || ($is_comment && !user_access('access comments', $account))) {
// Simple hack for returning no results.
$query->condition('status', 0);
$query->condition('status', 1);
@@ -2419,6 +2467,13 @@ function search_api_server_url(SearchApiServer $server) {
);
}
+/**
+ * Title callback for viewing or editing a server or index.
+ */
+function search_api_admin_item_title($object) {
+ return $object->name;
+}
+
/**
* Title callback for determining which title should be displayed for the
* "delete" local task.
@@ -2829,6 +2884,65 @@ function search_api_index_delete($id) {
return TRUE;
}
+/**
+ * Sanitizes field values returned from the server.
+ *
+ * @param array $values
+ * The field values, as returned from the server. See
+ * SearchApiQueryInterface::execute() for documentation on the structure.
+ *
+ * @return array
+ * An associative array of field IDs mapped to their sanitized values (scalar
+ * or array-valued).
+ */
+function search_api_get_sanitized_field_values(array $values) {
+ // Sanitize the field values returned from the server. Usually we use
+ // check_plain(), but this can be overridden by setting the field value to
+ // an array with "#value" and "#sanitize_callback" keys.
+ foreach ($values as $field_id => $field_value) {
+ if (is_array($field_value)
+ && isset($field_value['#sanitize_callback'])
+ && ($field_value['#sanitize_callback'] === FALSE || is_callable($field_value['#sanitize_callback']))
+ && array_key_exists('#value', $field_value)
+ ) {
+ $sanitize_callback = $field_value['#sanitize_callback'];
+ $field_value = $field_value['#value'];
+ }
+ else {
+ $sanitize_callback = 'check_plain';
+ }
+ if ($sanitize_callback !== FALSE) {
+ $field_value = search_api_sanitize_field_value($field_value, $sanitize_callback);
+ }
+ $values[$field_id] = $field_value;
+ }
+ return $values;
+}
+
+/**
+ * Sanitizes the given field value(s).
+ *
+ * @param mixed $field_value
+ * A scalar field value, or an array of field values.
+ * @param callable $sanitize_callback
+ * (optional) The callback to use for sanitizing a scalar value.
+ *
+ * @return mixed
+ * The sanitized field value(s).
+ */
+function search_api_sanitize_field_value($field_value, $sanitize_callback = 'check_plain') {
+ if ($field_value === NULL) {
+ return $field_value;
+ }
+ if (is_scalar($field_value)) {
+ return call_user_func($sanitize_callback, $field_value);
+ }
+ foreach ($field_value as &$nested_value) {
+ $nested_value = search_api_sanitize_field_value($nested_value, $sanitize_callback);
+ }
+ return $field_value;
+}
+
/**
* Options list callback for search indexes.
*
diff --git a/sites/all/modules/contrib/search/search_api/search_api.test b/sites/all/modules/contrib/search/search_api/search_api.test
index 89883028..23f36bcf 100644
--- a/sites/all/modules/contrib/search/search_api/search_api.test
+++ b/sites/all/modules/contrib/search/search_api/search_api.test
@@ -1062,7 +1062,7 @@ class SearchApiUnitTest extends DrupalWebTestCase {
protected function checkHtmlFilter() {
$orig = <<
a test.
+"something">a test.Header
How to write links to other sites: <a href="URL" title="MOUSEOVER TEXT">TEXT</a>.
< signs can be escaped with "<".
@@ -1071,6 +1071,7 @@ END;
$tags = << 'This', 'score' => 1),
@@ -1078,6 +1079,7 @@ END;
array('value' => 'something', 'score' => 1.5),
array('value' => 'a', 'score' => 1.5),
array('value' => 'test', 'score' => 1.5),
+ array('value' => 'Header', 'score' => 3),
array('value' => 'How', 'score' => 1),
array('value' => 'to', 'score' => 1),
array('value' => 'write', 'score' => 1),
diff --git a/sites/all/modules/contrib/search/search_api/tests/search_api_test.info b/sites/all/modules/contrib/search/search_api/tests/search_api_test.info
index 8c7ab142..d974c14d 100644
--- a/sites/all/modules/contrib/search/search_api/tests/search_api_test.info
+++ b/sites/all/modules/contrib/search/search_api/tests/search_api_test.info
@@ -10,9 +10,9 @@ files[] = search_api_test.module
hidden = TRUE
-; Information added by Drupal.org packaging script on 2016-02-26
-version = "7.x-1.16+29-dev"
+; Information added by Drupal.org packaging script on 2016-07-21
+version = "7.x-1.20"
core = "7.x"
project = "search_api"
-datestamp = "1456500713"
+datestamp = "1469117342"
diff --git a/sites/all/modules/contrib/views/views_data_export/.gitignore b/sites/all/modules/contrib/views/views_data_export/.gitignore
deleted file mode 100644
index e43b0f98..00000000
--- a/sites/all/modules/contrib/views/views_data_export/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-.DS_Store
diff --git a/sites/all/modules/contrib/views/views_data_export/plugins/views_data_export_plugin_display_export.inc b/sites/all/modules/contrib/views/views_data_export/plugins/views_data_export_plugin_display_export.inc
index a461566a..9ce5721e 100644
--- a/sites/all/modules/contrib/views/views_data_export/plugins/views_data_export_plugin_display_export.inc
+++ b/sites/all/modules/contrib/views/views_data_export/plugins/views_data_export_plugin_display_export.inc
@@ -238,8 +238,17 @@ class views_data_export_plugin_display_export extends views_plugin_display_feed
}
// Try and get a batch context if possible.
- $eid = !empty($_GET['eid']) ? $_GET['eid'] :
- (!empty($this->batched_execution_state->eid) ? $this->batched_execution_state->eid : FALSE);
+
+ if (!empty($_GET['eid']) && !empty($_GET['token']) && drupal_valid_token($_GET['token'], 'views_data_export/' . $_GET['eid'])) {
+ $eid = $_GET['eid'];
+ }
+ elseif (!empty($this->batched_execution_state->eid)) {
+ $eid = $this->batched_execution_state->eid;
+ }
+ else {
+ $eid = FALSE;
+ }
+
if ($eid) {
$this->batched_execution_state = views_data_export_get($eid);
}
@@ -293,19 +302,29 @@ class views_data_export_plugin_display_export extends views_plugin_display_feed
$this->batched_execution_state = views_data_export_new($this->view->name, $this->view->current_display, $this->outputfile_create());
views_data_export_view_store($this->batched_execution_state->eid, $this->view);
+ // Record a usage of our file, so we can identify our exports later.
+ file_usage_add(file_load($this->batched_execution_state->fid), 'views_data_export', 'eid', $this->batched_execution_state->eid);
+
// We need to build the index right now, before we lose $_GET etc.
$this->initialize_index();
//$this->batched_execution_state->fid = $this->outputfile_create();
- // Initialize the progress counter
- $this->batched_execution_state->sandbox['max'] = db_query('SELECT COUNT(*) FROM {' . $this->index_tablename() . '}')->fetchField();
+ // Initialize the progress counter.
+ if (db_table_exists($this->index_tablename())) {
+ $this->batched_execution_state->sandbox['max'] = db_query('SELECT COUNT(*) FROM {' . $this->index_tablename() . '}')->fetchField();
+ }
+
// Record the time we started.
list($usec, $sec) = explode(' ', microtime());
$this->batched_execution_state->sandbox['started'] = (float) $usec + (float) $sec;
+ // Pop something into the session to ensure it stays aorund.
+ $_SESSION['views_data_export'][$this->batched_execution_state->eid] = TRUE;
+
// Build up our querystring for the final page callback.
$querystring = array(
'eid' => $this->batched_execution_state->eid,
+ 'token' => drupal_get_token('views_data_export/' . $this->batched_execution_state->eid),
'return-url' => NULL,
);
@@ -401,6 +420,9 @@ class views_data_export_plugin_display_export extends views_plugin_display_feed
break;
case VIEWS_DATA_EXPORT_FOOTER:
+ // Update the temporary file size, otherwise we would get a problematic
+ // "Content-Length: 0" HTTP header, that may break the export download.
+ $this->outputfile_update_size();
$sandbox['finished'] = 1;
$state->batch_state = VIEWS_DATA_EXPORT_FINISHED;
break;
@@ -418,6 +440,13 @@ class views_data_export_plugin_display_export extends views_plugin_display_feed
function execute_final() {
// Should we download the file.
if (!empty($_GET['download'])) {
+ // Clean up our session, if we need to.
+ if (isset($_SESSION)) {
+ unset($_SESSION['views_data_export'][$this->batched_execution_state->eid]);
+ if (empty($_SESSION['views_data_export'])) {
+ unset($_SESSION['views_data_export']);
+ }
+ }
// This next method will exit.
$this->transfer_file();
}
@@ -526,6 +555,7 @@ class views_data_export_plugin_display_export extends views_plugin_display_feed
$query = array(
'download' => 1,
'eid' => $this->batched_execution_state->eid,
+ 'token' => drupal_get_token('views_data_export/' . $this->batched_execution_state->eid),
);
return theme('views_data_export_complete_page', array(
@@ -573,7 +603,10 @@ class views_data_export_plugin_display_export extends views_plugin_display_feed
}
// Set the headers.
$this->add_http_headers();
- file_transfer($this->outputfile_path(), array());
+ $headers = array(
+ 'Content-Length' => $this->outputfile_entity()->filesize,
+ );
+ file_transfer($this->outputfile_path(), $headers);
}
/**
@@ -666,9 +699,9 @@ class views_data_export_plugin_display_export extends views_plugin_display_feed
}
/**
- * Get the output file path.
+ * Get the output file entity.
*/
- function outputfile_path() {
+ public function outputfile_entity() {
if (empty($this->_output_file)) {
if (!empty($this->batched_execution_state->fid)) {
// Return the filename associated with this file.
@@ -678,7 +711,16 @@ class views_data_export_plugin_display_export extends views_plugin_display_feed
return NULL;
}
}
- return $this->_output_file->uri;
+ return $this->_output_file;
+ }
+
+ /**
+ * Get the output file path.
+ */
+ public function outputfile_path() {
+ if ($file = $this->outputfile_entity()) {
+ return $file->uri;
+ }
}
/**
@@ -700,6 +742,11 @@ class views_data_export_plugin_display_export extends views_plugin_display_feed
// Save the file into the DB.
$file = $this->file_save_file($path);
+ // Make sure the file is marked as temporary.
+ // There is no FILE_STATUS_TEMPORARY constant.
+ $file->status = 0;
+ file_save($file);
+
return $file->fid;
}
@@ -713,6 +760,16 @@ class views_data_export_plugin_display_export extends views_plugin_display_feed
}
}
+ /**
+ * Updates the file size in the file entity.
+ */
+ protected function outputfile_update_size() {
+ if ($file = $this->outputfile_entity()) {
+ $file->filesize = filesize($file->uri);
+ file_save($file);
+ }
+ }
+
function abort_export($errors) {
// Just cause the next batch to do the clean-up
if (!is_array($errors)) {
diff --git a/sites/all/modules/contrib/views/views_data_export/plugins/views_data_export_plugin_style_export.inc b/sites/all/modules/contrib/views/views_data_export/plugins/views_data_export_plugin_style_export.inc
index 493796f6..8d7eed66 100644
--- a/sites/all/modules/contrib/views/views_data_export/plugins/views_data_export_plugin_style_export.inc
+++ b/sites/all/modules/contrib/views/views_data_export/plugins/views_data_export_plugin_style_export.inc
@@ -343,9 +343,20 @@ class views_data_export_plugin_style_export extends views_plugin_style {
if (!empty($view->exposed_input[$handler->options['expose']['identifier']])) {
$identifier = $handler->options['expose']['identifier'];
$option = $view->exposed_input[$identifier];
- // The option may be a string or an array, depending on whether the
- // widget is a text box/area or a select box.
+ // The option may be a string or an array or even an array of arrays,
+ // depending on whether the widget is a text box/area, a select box
+ // or a date range.
if (is_array($option)) {
+ // loop over each option
+ foreach ($option as $key => $item) {
+ if (!is_array($item)) {
+ continue;
+ }
+ // if its an array implode it first
+ else {
+ $option[$key] = implode('--', $item);
+ }
+ }
$option = implode('--', $option);
}
$exposed[] = check_plain($identifier) . '_' . check_plain($option);
diff --git a/sites/all/modules/contrib/views/views_data_export/plugins/views_data_export_plugin_style_export_csv.inc b/sites/all/modules/contrib/views/views_data_export/plugins/views_data_export_plugin_style_export_csv.inc
index daebeafd..40b9054b 100644
--- a/sites/all/modules/contrib/views/views_data_export/plugins/views_data_export_plugin_style_export_csv.inc
+++ b/sites/all/modules/contrib/views/views_data_export/plugins/views_data_export_plugin_style_export_csv.inc
@@ -40,6 +40,10 @@ class views_data_export_plugin_style_export_csv extends views_data_export_plugin
'default' => ', ',
'translatable' => FALSE,
);
+ $options['newline_token'] = array(
+ 'default' => 1,
+ 'translatable' => FALSE,
+ );
$options['header'] = array(
'default' => TRUE,
'translatable' => FALSE,
@@ -99,6 +103,18 @@ class views_data_export_plugin_style_export_csv extends views_data_export_plugin
'#process' => array('ctools_dependent_process'),
'#dependency' => array('edit-style-options-replace-newlines' => array(TRUE)),
);
+ $form['newline_token'] = array(
+ '#prefix' => '',
+ '#type' => 'radios',
+ '#default_value' => !empty($this->options['newline_token']) ? $this->options['newline_token'] : '0',
+ '#title' => t('What to replace?'),
+ '#options' => array(
+ t('Replace only Linefeed ("\n")'),
+ t('Replace Carriage Return plus Linefeed ("\r\n") and single Linefeed ("\n") as well')
+ ),
+ '#dependency' => array('edit-style-options-replace-newlines' => array(TRUE)),
+ );
$form['header'] = array(
'#type' => 'checkbox',
'#title' => t('Make first row a list of column headers.'),
diff --git a/sites/all/modules/contrib/views/views_data_export/tests/access.test b/sites/all/modules/contrib/views/views_data_export/tests/access.test
new file mode 100644
index 00000000..f826d751
--- /dev/null
+++ b/sites/all/modules/contrib/views/views_data_export/tests/access.test
@@ -0,0 +1,208 @@
+ 'Access to temp files',
+ 'description' => 'Check access to created export files.',
+ 'group' => 'Views Data Export',
+ );
+ }
+
+ /**
+ * Test that VDE export can only be downloaded by the user that created them.
+ */
+ public function testExportedTempFileAccess() {
+ $this->admin_user1 = $this->drupalCreateUser();
+ $this->admin_user2 = $this->drupalCreateUser();
+
+ // Run a batched export.
+ $path = 'vde_test/' . $this->randomName();
+ list($view, $expected) = $this->getExportView($path);
+ $display = &$view->display['vde_test']->handler;
+ // Set this view to be batched.
+ $display->override_option('use_batch', 'batch');
+ // Save this view so we can hit the path.
+ $view->save();
+ // Ensure that the menu router system is rebuilt on the next page load.
+ variable_set('menu_rebuild_needed', TRUE);
+
+ $this->drupalLogin($this->admin_user1);
+ // Catpure the session_id as the redirects in the request ditch it.
+ $session_id = $this->session_id;
+ $this->assertBatchedExportEqual($path, $expected, 'Batched access export matched expected output.');
+
+ // Remove all the test data, so future exports will be different.
+ db_truncate('views_test')->execute();
+ $this->resetAll();
+
+ // Assert that we can re-download directly when supplying the token.
+ // We rely on this being the first export in this test class.
+ // Restore the session_id from above so we can use drupalGetToken.
+ $this->session_id = $session_id;
+ $token = $this->drupalGetToken('views_data_export/1');
+ $this->drupalGet($path, array('query' => array('eid' => 1, 'download' => 1, 'token' => $token)));
+ $output = $this->drupalGetContent();
+ $this->assertEqual($this->normaliseString($output), $expected, 'Re-download of export file by original user is possible with session token.');
+
+ // Assert that we cannot re-download directly without supplying the token.
+ // We rely on this being the first export in this test class.
+ $this->drupalGet($path, array('query' => array('eid' => 1, 'download' => 1)));
+ $output = $this->drupalGetContent();
+ $this->assertEqual($this->normaliseString($output), '', 'Re-download of export file by original user is not possible.');
+
+ // Assert that someone else can't download our file.
+ // We rely on this being the first export in this test class.
+ $this->drupalLogin($this->admin_user2);
+ $this->drupalGet($path, array('query' => array('eid' => 1, 'download' => 1, 'token' => $token)));
+ $output = $this->drupalGetContent();
+ $this->assertEqual($this->normaliseString($output), '', 'Re-download of export file by different user is not possible.');
+ }
+
+ /**
+ * Overrides DrupalWebTestCase::drupalGetToken() to support the hash salt.
+ *
+ * @todo Remove when http://drupal.org/node/1555862 is fixed in core.
+ */
+ protected function drupalGetToken($value = '') {
+ $private_key = drupal_get_private_key();
+ return drupal_hmac_base64($value, $this->session_id . $private_key . drupal_get_hash_salt());
+ }
+
+ /**
+ * Build and return a basic view of the views_test table.
+ *
+ * @return view
+ */
+ protected function getBasicExportView() {
+ views_include('view');
+
+ // Create the basic view.
+ $view = new view();
+ $view->vid = 'new';
+ $view->base_table = 'views_test';
+
+ // Set up the fields we need.
+ $display = $view->new_display('default', 'Master', 'default');
+
+ $display->override_option('fields', array(
+ 'id' => array(
+ 'id' => 'id',
+ 'table' => 'views_test',
+ 'field' => 'id',
+ 'relationship' => 'none',
+ ),
+ 'name' => array(
+ 'id' => 'name',
+ 'table' => 'views_test',
+ 'field' => 'name',
+ 'relationship' => 'none',
+ ),
+ 'age' => array(
+ 'id' => 'age',
+ 'table' => 'views_test',
+ 'field' => 'age',
+ 'relationship' => 'none',
+ ),
+ ));
+
+ // Set up the sort order.
+ $display->override_option('sorts', array(
+ 'id' => array(
+ 'order' => 'ASC',
+ 'id' => 'id',
+ 'table' => 'views_test',
+ 'field' => 'id',
+ 'relationship' => 'none',
+ ),
+ ));
+
+ // Set up the pager.
+ $display->override_option('pager', array(
+ 'type' => 'none',
+ 'options' => array('offset' => 0),
+ ));
+
+ return $view;
+ }
+
+ protected function getStylePluginName() {
+ return 'views_data_export_txt';
+ }
+
+ protected function getExportView($path = 'vde_test') {
+ // Create the basic view.
+ $view = $this->getBasicExportView();
+
+ $display = $view->new_display('views_data_export', 'Data export', 'vde_test');
+ $display->override_option('style_plugin', $this->getStylePluginName());
+ $display->override_option('path', $path);
+
+ $expected = '[ID]
+
+1
+[Name]
+
+John
+[Age]
+
+25
+----------------------------------------
+
+[ID]
+
+2
+[Name]
+
+George
+[Age]
+
+27
+----------------------------------------
+
+[ID]
+
+3
+[Name]
+
+Ringo
+[Age]
+
+28
+----------------------------------------
+
+[ID]
+
+4
+[Name]
+
+Paul
+[Age]
+
+26
+----------------------------------------
+
+[ID]
+
+5
+[Name]
+
+Meredith
+[Age]
+
+30
+----------------------------------------';
+
+ return array(&$view, $expected);
+ }
+}
\ No newline at end of file
diff --git a/sites/all/modules/contrib/views/views_data_export/tests/base.test b/sites/all/modules/contrib/views/views_data_export/tests/base.test
index 455f5d16..ffb720f2 100644
--- a/sites/all/modules/contrib/views/views_data_export/tests/base.test
+++ b/sites/all/modules/contrib/views/views_data_export/tests/base.test
@@ -212,13 +212,13 @@ abstract class ViewsDataExportBaseTest extends ViewsTestCase {
array(
'name' => 'John',
'age' => 25,
- 'job' => 'Singer',
+ 'job' => "Singer\r\nSongwriter\r\nPianist",
'created' => gmmktime(0, 0, 0, 1, 1, 2000),
),
array(
'name' => 'George',
'age' => 27,
- 'job' => 'Singer',
+ 'job' => "Singer\nGuitar player\nSitar player",
'created' => gmmktime(0, 0, 0, 1, 2, 2000),
),
array(
@@ -230,7 +230,7 @@ abstract class ViewsDataExportBaseTest extends ViewsTestCase {
array(
'name' => 'Paul',
'age' => 26,
- 'job' => 'Songwriter',
+ 'job' => "Songwriter\rBass guitarist",
'created' => gmmktime(6, 0, 0, 1, 1, 2000),
),
array(
diff --git a/sites/all/modules/contrib/views/views_data_export/tests/csv_export.test b/sites/all/modules/contrib/views/views_data_export/tests/csv_export.test
index 9852b778..148ef8f2 100644
--- a/sites/all/modules/contrib/views/views_data_export/tests/csv_export.test
+++ b/sites/all/modules/contrib/views/views_data_export/tests/csv_export.test
@@ -208,4 +208,78 @@ class CSVExportViewsDataExportTests extends ViewsDataExportSimpleExportTest {
}
+ /**
+ * Test to ensure that all new line characters are replaced in CSV files when requested.
+ */
+ protected function testReplaceNewLines() {
+ $view = $this->getBasicExportView();
+
+ $display = $view->display['default']->handler;
+
+ $display->override_option('fields', array(
+ 'id' => array(
+ 'id' => 'id',
+ 'table' => 'views_test',
+ 'field' => 'id',
+ 'relationship' => 'none',
+ 'label' => 'ID',
+ ),
+ 'name' => array(
+ 'id' => 'name',
+ 'table' => 'views_test',
+ 'field' => 'name',
+ 'relationship' => 'none',
+ ),
+ 'age' => array(
+ 'id' => 'age',
+ 'table' => 'views_test',
+ 'field' => 'age',
+ 'relationship' => 'none',
+ ),
+ 'job' => array(
+ 'id' => 'job',
+ 'table' => 'views_test',
+ 'field' => 'job',
+ 'relationship' => 'none',
+ ),
+ ));
+
+ $style_options = array(
+ 'replace_newlines' => TRUE,
+ 'newline_replacement' => ';',
+ 'newline_token' => 0,
+ );
+
+ $expected = <<executeAndCompareGivenView($view, $expected, $message, $style_options);
+
+ // And now replace all kind of newlines.
+ $style_options = array(
+ 'replace_newlines' => TRUE,
+ 'newline_replacement' => ';',
+ 'newline_token' => 1,
+ );
+
+ $expected = '"ID","Name","Age","Job"
+"1","John","25","Singer;Songwriter;Pianist"
+"2","George","27","Singer;Guitar player;Sitar player"
+"3","Ringo","28","Drummer"
+"4","Paul","26","Songwriter;Bass guitarist"
+"5","Meredith","30","Speaker"';
+
+ $message = 'All newline characters are replaced.';
+
+ $this->executeAndCompareGivenView($view, $expected, $message, $style_options);
+ }
+
}
diff --git a/sites/all/modules/contrib/views/views_data_export/tests/garbagecollection.test b/sites/all/modules/contrib/views/views_data_export/tests/garbagecollection.test
new file mode 100644
index 00000000..f29dc3c9
--- /dev/null
+++ b/sites/all/modules/contrib/views/views_data_export/tests/garbagecollection.test
@@ -0,0 +1,187 @@
+ 'Garbage collection',
+ 'description' => 'Checks garbage collection of batched exports',
+ 'group' => 'Views Data Export',
+ );
+ }
+
+ /**
+ * Test that VDE export can only be downloaded by the user that created them.
+ */
+ public function testExportedGarbageCollection() {
+ // Run a batched export.
+ $path = 'vde_test/' . $this->randomName();
+ list($view, $expected) = $this->getExportView($path);
+ $display = &$view->display['vde_test']->handler;
+ // Set this view to be batched.
+ $display->override_option('use_batch', 'batch');
+ // Save this view so we can hit the path.
+ $view->save();
+ // Ensure that the menu router system is rebuilt on the next page load.
+ variable_set('menu_rebuild_needed', TRUE);
+ $exports = $this->getNumberOfStoredExports();
+ $files = $this->getNumberOfFiles();
+ $this->assertBatchedExportEqual($path, $expected, 'Batched access export matched expected output.');
+ // We should have created a new export and file.
+ $this->assertEqual($this->getNumberOfStoredExports(), $exports + 1, 'A single new batched export was created');
+ $this->assertEqual($this->getNumberOfFiles(), $files + 1, 'A single new temporary file was created');
+
+ $middle_timestamp = time();
+ sleep(1);
+ $this->assertBatchedExportEqual($path, $expected, 'Batched access export matched expected output.');
+ // We should have created a new export and file.
+ $this->assertEqual($this->getNumberOfStoredExports(), $exports + 2, 'A single new batched export was created');
+ $this->assertEqual($this->getNumberOfFiles(), $files + 2, 'A single new temporary file was created');
+
+ // Garbage collect the first export only.
+ views_data_export_garbage_collect(REQUEST_TIME - $middle_timestamp);
+ $this->assertEqual($this->getNumberOfStoredExports(), $exports + 1, 'Garbage collection removed 1 old export');
+ $this->assertEqual($this->getNumberOfFiles(), $files + 1, 'Garbage collection removed 1 old temporary file');
+ }
+
+ protected function getNumberOfStoredExports() {
+ return (int) db_select('views_data_export')->countQuery()->execute()->fetchField();
+ }
+
+ protected function getNumberOfFiles() {
+ return (int) db_select('file_managed')->countQuery()->execute()->fetchField();
+ }
+
+
+ /**
+ * Build and return a basic view of the views_test table.
+ *
+ * @return view
+ */
+ protected function getBasicExportView() {
+ views_include('view');
+
+ // Create the basic view.
+ $view = new view();
+ $view->vid = 'new';
+ $view->base_table = 'views_test';
+
+ // Set up the fields we need.
+ $display = $view->new_display('default', 'Master', 'default');
+
+ $display->override_option('fields', array(
+ 'id' => array(
+ 'id' => 'id',
+ 'table' => 'views_test',
+ 'field' => 'id',
+ 'relationship' => 'none',
+ ),
+ 'name' => array(
+ 'id' => 'name',
+ 'table' => 'views_test',
+ 'field' => 'name',
+ 'relationship' => 'none',
+ ),
+ 'age' => array(
+ 'id' => 'age',
+ 'table' => 'views_test',
+ 'field' => 'age',
+ 'relationship' => 'none',
+ ),
+ ));
+
+ // Set up the sort order.
+ $display->override_option('sorts', array(
+ 'id' => array(
+ 'order' => 'ASC',
+ 'id' => 'id',
+ 'table' => 'views_test',
+ 'field' => 'id',
+ 'relationship' => 'none',
+ ),
+ ));
+
+ // Set up the pager.
+ $display->override_option('pager', array(
+ 'type' => 'none',
+ 'options' => array('offset' => 0),
+ ));
+
+ return $view;
+ }
+
+ protected function getStylePluginName() {
+ return 'views_data_export_txt';
+ }
+
+ protected function getExportView($path = 'vde_test') {
+ // Create the basic view.
+ $view = $this->getBasicExportView();
+
+ $display = $view->new_display('views_data_export', 'Data export', 'vde_test');
+ $display->override_option('style_plugin', $this->getStylePluginName());
+ $display->override_option('path', $path);
+
+ $expected = '[ID]
+
+1
+[Name]
+
+John
+[Age]
+
+25
+----------------------------------------
+
+[ID]
+
+2
+[Name]
+
+George
+[Age]
+
+27
+----------------------------------------
+
+[ID]
+
+3
+[Name]
+
+Ringo
+[Age]
+
+28
+----------------------------------------
+
+[ID]
+
+4
+[Name]
+
+Paul
+[Age]
+
+26
+----------------------------------------
+
+[ID]
+
+5
+[Name]
+
+Meredith
+[Age]
+
+30
+----------------------------------------';
+
+ return array(&$view, $expected);
+ }
+}
diff --git a/sites/all/modules/contrib/views/views_data_export/theme/views_data_export.theme.inc b/sites/all/modules/contrib/views/views_data_export/theme/views_data_export.theme.inc
index 8416d96e..4e31fb0a 100644
--- a/sites/all/modules/contrib/views/views_data_export/theme/views_data_export.theme.inc
+++ b/sites/all/modules/contrib/views/views_data_export/theme/views_data_export.theme.inc
@@ -171,7 +171,12 @@ function template_preprocess_views_data_export_csv_body(&$vars) {
}
}
if (!empty($vars['options']['replace_newlines'])) {
- $output = str_replace("\n", $vars['options']['newline_replacement'], $output);
+ if (!empty($vars['options']['newline_token'])) {
+ $output = str_replace( array("\r\n", "\r", "\n"), $vars['options']['newline_replacement'], $output);
+ }
+ else {
+ $output = str_replace("\n", $vars['options']['newline_replacement'], $output);
+ }
}
$vars['themed_rows'][$i][$j] = $wrap . str_replace('"', $replace_value, $output) . $wrap;
}
@@ -254,11 +259,17 @@ function template_preprocess_views_data_export_txt_body(&$vars) {
_views_data_export_body_shared_preprocess($vars);
}
+/**
+ * Implements hook_preprocess_views_data_export_doc_body().
+ */
function template_preprocess_views_data_export_doc_body(&$vars) {
// Pass through the generic MS Office preprocess.
template_preprocess_views_data_export_msoffice_body($vars);
}
+/**
+ * Implements hook_preprocess_views_data_export_xls_body().
+ */
function template_preprocess_views_data_export_xls_body(&$vars) {
// Pass through the generic MS Office preprocess.
template_preprocess_views_data_export_msoffice_body($vars);
@@ -267,6 +278,25 @@ function template_preprocess_views_data_export_xls_body(&$vars) {
function template_preprocess_views_data_export_msoffice_body(&$vars) {
_views_data_export_header_shared_preprocess($vars);
_views_data_export_body_shared_preprocess($vars);
+}
+
+/**
+ * Implements hook_process_views_data_export_doc_body().
+ */
+function template_process_views_data_export_doc_body(&$vars) {
+ // Pass through the generic MS Office process.
+ template_process_views_data_export_msoffice_body($vars);
+}
+
+/**
+ * Implements hook_process_views_data_export_xls_body().
+ */
+function template_process_views_data_export_xls_body(&$vars) {
+ // Pass through the generic MS Office process.
+ template_process_views_data_export_msoffice_body($vars);
+}
+
+function template_process_views_data_export_msoffice_body(&$vars) {
$output = '';
@@ -475,14 +505,18 @@ function _views_data_export_xml_tag_clean($tag) {
function _views_data_export_header_shared_preprocess(&$vars) {
$view = $vars['view'];
$fields = &$view->field;
-
+ $fields_info = $view->display_handler->get_option('fields');
$vars['header'] = array();
foreach ($fields as $key => $field) {
if (empty($field->options['exclude'])) {
- $vars['header'][$key] = check_plain($field->label());
+ if (isset($fields_info) && isset($fields_info[$key]['label'])) {
+ $vars['header'][$key] = check_plain($fields_info[$key]['label']);
+ }
+ else {
+ $vars['header'][$key] = check_plain($field->label());
+ }
}
}
-
}
/**
diff --git a/sites/all/modules/contrib/views/views_data_export/views_data_export.drush.inc b/sites/all/modules/contrib/views/views_data_export/views_data_export.drush.inc
index ce99aa5d..4d5c3cce 100644
--- a/sites/all/modules/contrib/views/views_data_export/views_data_export.drush.inc
+++ b/sites/all/modules/contrib/views/views_data_export/views_data_export.drush.inc
@@ -17,12 +17,12 @@ function views_data_export_drush_command() {
'output_file' => 'The file to write the results to - will be overwritten if it already exists',
),
'options' => array (
- '--arguments' => 'Comma separated list of arguments to be passed to the view.',
- '--format' => 'csv,doc,txt,xls or xml. These options are ignored if the display_id passed is a "views_data_export" display.',
- '--separator' => 'csv only: What character separates the fields (default:,)',
- '--trim-whitespace' => 'csv only: Trim whitespace from either side of fields (default:1)',
- '--header-row' => 'csv only: Make the first row a row of headers (default:1)',
- '--quote-values' => 'csv only: Surround each field in quotes (default:1)',
+ 'arguments' => 'Comma separated list of arguments to be passed to the view.',
+ 'format' => 'csv,doc,txt,xls or xml. These options are ignored if the display_id passed is a "views_data_export" display.',
+ 'separator' => 'csv only: What character separates the fields (default:,)',
+ 'trim-whitespace' => 'csv only: Trim whitespace from either side of fields (default:1)',
+ 'header-row' => 'csv only: Make the first row a row of headers (default:1)',
+ 'quote-values' => 'csv only: Surround each field in quotes (default:1)',
),
'examples' => array (
'drush views-data-export myviewname views_data_export_1 output.csv' => 'Export myviewname:views_data_export_1 and write the output to output.csv in the current directory',
@@ -168,19 +168,19 @@ function drush_views_data_export($view_name, $display_id, $output_file) {
$options['output_file'] = realpath(drush_get_context('DRUSH_OLDCWD', getcwd())) . '/' . $output_file;
}
+ $arguments = drush_get_option('arguments', '');
+ $arguments = explode(',', $arguments);
if ($view->display_handler->is_batched()) {
// This is a batched export, and needs to be handled as such.
_drush_views_data_export_override_batch($view_name, $display_id, $options);
- $arguments = drush_get_option('arguments', '');
- $arguments = explode(',', $arguments);
$view->execute_display($display_id, $arguments);
}
else {
// This export isn't batched.
ob_start();
- $view->execute_display($display_id);
+ $view->execute_display($display_id, $arguments);
// Get the results, and clean the output buffer.
$result = ob_get_contents();
// Clean the buffer.
diff --git a/sites/all/modules/contrib/views/views_data_export/views_data_export.info b/sites/all/modules/contrib/views/views_data_export/views_data_export.info
index 478b4fb8..c7bf2ded 100644
--- a/sites/all/modules/contrib/views/views_data_export/views_data_export.info
+++ b/sites/all/modules/contrib/views/views_data_export/views_data_export.info
@@ -14,15 +14,17 @@ files[] = plugins/views_data_export_plugin_style_export_xml.inc
; Tests
files[] = "tests/base.test"
+files[] = "tests/access.test"
+files[] = "tests/garbagecollection.test"
files[] = "tests/csv_export.test"
files[] = "tests/doc_export.test"
files[] = "tests/txt_export.test"
files[] = "tests/xls_export.test"
files[] = "tests/xml_export.test"
-; Information added by Drupal.org packaging script on 2014-08-27
-version = "7.x-3.0-beta8"
+; Information added by Drupal.org packaging script on 2016-09-20
+version = "7.x-3.1"
core = "7.x"
project = "views_data_export"
-datestamp = "1409118835"
+datestamp = "1474360174"
diff --git a/sites/all/modules/contrib/views/views_data_export/views_data_export.module b/sites/all/modules/contrib/views/views_data_export/views_data_export.module
index 92d9bf2b..a616545a 100644
--- a/sites/all/modules/contrib/views/views_data_export/views_data_export.module
+++ b/sites/all/modules/contrib/views/views_data_export/views_data_export.module
@@ -30,6 +30,25 @@ function views_data_export_views_api() {
);
}
+/**
+/**
+ * Checks whether the passed URI identifies an export file.
+ *
+ * @param string $uri
+ * A file URI.
+ *
+ * @return bool
+ * TRUE if the URI identifies an export file, FALSE otherwise.
+ */
+function views_data_export_is_export_file($uri) {
+ foreach (entity_load('file', FALSE, array('uri' => $uri)) as $file) {
+ // See if this is an export file.
+ $usages = file_usage_list($file);
+ return !empty($usages['views_data_export']['eid']);
+ }
+ return FALSE;
+}
+
/**
* Implementation of hook_theme().
*/
@@ -114,9 +133,10 @@ function views_data_export_garbage_collect($expires = NULL, $chunk = NULL) {
}
// We do two things to exports we want to garbage collect
- // 1. Delete the index table for it, if it is still around
- // 2. Delete the row from the exports table
- // 3. Delete the view from the object_cache
+ // 1. Delete the index table for it, if it is still around.
+ // 2. Delete the files used during the export.
+ // 3. Delete the row from the exports table.
+ // 4. Delete the view from the object_cache.
if (count($eids_to_clear)) {
foreach ($eids_to_clear as $eid) {
// 1. Delete index table, if it is still around for some reason
@@ -124,14 +144,19 @@ function views_data_export_garbage_collect($expires = NULL, $chunk = NULL) {
if (db_table_exists($table)) {
db_drop_table($table);
}
+
+ // 2. Delete the files used during the export.
+ foreach (views_data_export_export_list_files($eid) as $file) {
+ file_delete($file, TRUE);
+ }
}
- // 2. Delete the entries in the exports table.
+ // 3. Delete the entries in the exports table.
db_delete('views_data_export')
->condition('eid', $eids_to_clear, 'IN')
->execute();
- // 3. Clear the cached views
+ // 4. Clear the cached views
views_data_export_view_clear($eids_to_clear);
}
@@ -139,6 +164,25 @@ function views_data_export_garbage_collect($expires = NULL, $chunk = NULL) {
}
}
+/**
+ * Determines where a file is used.
+ *
+ * @param $eid
+ * The ID of a Views Data Export.
+ *
+ * @return array
+ * An array of loaded files objects used by the specified export.
+ */
+function views_data_export_export_list_files($eid) {
+ $result = db_select('file_usage', 'f')
+ ->fields('f', array('fid'))
+ ->condition('id', $eid)
+ ->condition('type', 'eid')
+ ->condition('module', 'views_data_export')
+ ->execute();
+ return file_load_multiple($result->fetchCol());
+}
+
/**
* Batch API callback.
@@ -268,15 +312,3 @@ function views_data_export_view_clear($export_id) {
->condition('eid', $export_id)
->execute();
}
-
-/**
- * Implements hook_file_presave().
- */
-function views_data_export_file_presave($file) {
- // Ensure temporary files really are temporary.
- // @see: https://drupal.org/node/2198399
- if (strpos($file->filename, 'views_data_export') === 0) {
- // There is no FILE_STATUS_TEMPORARY.
- $file->status = 0;
- }
-}