From f7cd9c0858ed8591d7f85ae90e7bdffabd4d6fe9 Mon Sep 17 00:00:00 2001 From: Bachir Soussi Chiadmi Date: Thu, 17 Sep 2015 14:44:25 +0200 Subject: [PATCH] ran security updates on contrib modules ctools, video_embed_field, migrate, views_bulk_operations --- .../dev/ctools/bulk_export/bulk_export.info | 6 +- .../modules/contrib/dev/ctools/ctools.info | 6 +- .../modules/contrib/dev/ctools/ctools.module | 70 +++++- .../ctools_access_ruleset.info | 6 +- .../ctools_ajax_sample.info | 6 +- .../ctools_ajax_sample.module | 2 +- .../ctools_custom_content.info | 6 +- .../ctools_plugin_example.info | 6 +- .../contrib/dev/ctools/includes/content.inc | 11 +- .../dev/ctools/includes/context-admin.inc | 12 + .../contrib/dev/ctools/includes/context.inc | 4 +- .../contrib/dev/ctools/includes/css.inc | 10 +- .../contrib/dev/ctools/includes/jump-menu.inc | 2 +- .../contrib/dev/ctools/includes/math-expr.inc | 2 +- .../contrib/dev/ctools/includes/modal.inc | 1 + .../contrib/dev/ctools/includes/plugins.inc | 8 +- .../contrib/dev/ctools/includes/stylizer.inc | 2 +- .../contrib/dev/ctools/includes/uuid.inc | 3 +- .../modules/contrib/dev/ctools/js/modal.js | 151 ++++++++++++- .../contrib/dev/ctools/js/states-show.js | 43 ++++ .../dev/ctools/page_manager/page_manager.info | 6 +- .../ctools/page_manager/page_manager.module | 13 +- .../page_manager/plugins/tasks/node_edit.inc | 2 +- .../page_manager/plugins/tasks/node_view.inc | 7 +- .../page_manager/plugins/tasks/page.admin.inc | 4 +- .../content_types/comment/comment_created.inc | 74 +++++++ .../entity_context/entity_field.inc | 10 + .../content_types/form/entity_form_field.inc | 52 ++++- .../node_context/node_comment_form.inc | 17 -- .../plugins/content_types/page/page_title.inc | 3 + .../term_context/term_description.inc | 5 +- .../dev/ctools/plugins/contexts/user.inc | 4 +- .../plugins/contexts/user_edit_form.inc | 8 +- .../export_ui/ctools_export_ui.class.php | 8 +- .../relationships/entity_from_field.inc | 2 +- .../contrib/dev/ctools/stylizer/stylizer.info | 6 +- .../dev/ctools/term_depth/term_depth.info | 6 +- .../ctools_export_test.info | 6 +- .../dev/ctools/tests/ctools_plugin_test.info | 6 +- .../cached/ctoolsCachedPluginArray.class.php | 2 +- .../cached/ctoolsCachedPluginArray2.class.php | 2 +- .../ctoolsNotCachedPluginArray.class.php | 2 +- .../ctools/views_content/views_content.info | 6 +- .../export_ui/video_embed_field_export_ui.inc | 2 +- .../video_embed_brightcove.info | 6 +- .../video_embed_brightcove.module | 13 +- .../video_embed_facebook.info | 6 +- .../video_embed_facebook.module | 21 +- .../video_embed_field.admin.inc | 12 + .../video_embed_field.field.inc | 2 +- .../video_embed_field.handlers.inc | 71 ++++-- .../video_embed_field/video_embed_field.info | 8 +- .../video_embed_field.install | 26 +++ .../video_embed_field.module | 30 ++- .../contrib/migrate/migrate/BACKPORT.txt | 5 - .../contrib/migrate/migrate/CHANGELOG.txt | 31 ++- .../contrib/migrate/migrate/includes/base.inc | 53 ++++- .../migrate/migrate/includes/migration.inc | 35 ++- .../migrate/migrate/includes/source.inc | 4 +- .../contrib/migrate/migrate/migrate.drush.inc | 79 ++++--- .../contrib/migrate/migrate/migrate.info | 6 +- .../contrib/migrate/migrate/migrate.module | 13 +- .../migrate/migrate/migrate_example/beer.inc | 6 +- .../migrate_example/migrate_example.info | 6 +- .../migrate_example_oracle.info | 6 +- .../migrate/migrate/migrate_example/wine.inc | 24 +- .../migrate_example_baseball.info | 6 +- .../migrate/migrate_ui/migrate_ui.info | 6 +- .../migrate/migrate_ui/migrate_ui.pages.inc | 208 +++++++++++++----- .../migrate/plugins/destinations/comment.inc | 2 +- .../migrate/plugins/destinations/entity.inc | 30 +++ .../migrate/plugins/destinations/fields.inc | 17 +- .../migrate/plugins/destinations/file.inc | 15 +- .../migrate/plugins/destinations/node.inc | 2 +- .../migrate/plugins/destinations/term.inc | 2 +- .../migrate/plugins/destinations/user.inc | 2 +- .../migrate/migrate/plugins/sources/list.inc | 2 +- .../migrate/plugins/sources/multiitems.inc | 2 +- .../migrate/migrate/plugins/sources/sql.inc | 27 ++- .../migrate/plugins/sources/sqlmap.inc | 45 +++- .../migrate/migrate/tests/import/options.test | 4 - .../actions/archive.action.inc | 1 + .../actions/book.action.inc | 2 + .../actions/delete.action.inc | 1 + .../actions/modify.action.inc | 9 +- .../actions/user_cancel.action.inc | 1 + .../actions/user_roles.action.inc | 29 +-- .../actions_permissions.info | 6 +- .../js/views_bulk_operations.js | 32 +-- .../plugins/operation_types/action.class.php | 2 +- .../operation_types/rules_component.class.php | 4 +- ...lk_operations_handler_field_operations.inc | 22 ++ .../views_bulk_operations.info | 6 +- .../views_bulk_operations.module | 97 ++++---- 94 files changed, 1242 insertions(+), 413 deletions(-) create mode 100644 sites/all/modules/contrib/dev/ctools/js/states-show.js create mode 100644 sites/all/modules/contrib/dev/ctools/plugins/content_types/comment/comment_created.inc delete mode 100644 sites/all/modules/contrib/migrate/migrate/BACKPORT.txt diff --git a/sites/all/modules/contrib/dev/ctools/bulk_export/bulk_export.info b/sites/all/modules/contrib/dev/ctools/bulk_export/bulk_export.info index fd355450..dd9f3e91 100644 --- a/sites/all/modules/contrib/dev/ctools/bulk_export/bulk_export.info +++ b/sites/all/modules/contrib/dev/ctools/bulk_export/bulk_export.info @@ -6,9 +6,9 @@ package = Chaos tool suite version = CTOOLS_MODULE_VERSION -; Information added by Drupal.org packaging script on 2015-03-18 -version = "7.x-1.7" +; Information added by Drupal.org packaging script on 2015-08-19 +version = "7.x-1.9" core = "7.x" project = "ctools" -datestamp = "1426696183" +datestamp = "1440020680" diff --git a/sites/all/modules/contrib/dev/ctools/ctools.info b/sites/all/modules/contrib/dev/ctools/ctools.info index 3283454b..02c86f52 100644 --- a/sites/all/modules/contrib/dev/ctools/ctools.info +++ b/sites/all/modules/contrib/dev/ctools/ctools.info @@ -9,9 +9,9 @@ files[] = includes/math-expr.inc files[] = includes/stylizer.inc files[] = tests/css_cache.test -; Information added by Drupal.org packaging script on 2015-03-18 -version = "7.x-1.7" +; Information added by Drupal.org packaging script on 2015-08-19 +version = "7.x-1.9" core = "7.x" project = "ctools" -datestamp = "1426696183" +datestamp = "1440020680" diff --git a/sites/all/modules/contrib/dev/ctools/ctools.module b/sites/all/modules/contrib/dev/ctools/ctools.module index 3e8cc88f..008214e2 100644 --- a/sites/all/modules/contrib/dev/ctools/ctools.module +++ b/sites/all/modules/contrib/dev/ctools/ctools.module @@ -23,7 +23,7 @@ define('CTOOLS_API_VERSION', '2.0.8'); * ; Requires CTools v7.x-1.4 or newer. * dependencies[] = ctools (>=1.4) */ -define('CTOOLS_MODULE_VERSION', '7.x-1.7'); +define('CTOOLS_MODULE_VERSION', '7.x-1.9'); /** * Test the CTools API version. @@ -617,6 +617,27 @@ function ctools_registry_files_alter(&$files, $indexed_modules) { return _ctools_registry_files_alter($files, $indexed_modules); } +// ----------------------------------------------------------------------- +// FAPI hooks that must be in the .module file. + +/** + * Alter the comment form to get a little more control over it. + */ +function ctools_form_comment_form_alter(&$form, &$form_state) { + if (!empty($form_state['ctools comment alter'])) { + // Force the form to post back to wherever we are. + $form['#action'] = url($_GET['q'], array('fragment' => 'comment-form')); + if (empty($form['#submit'])) { + $form['#submit'] = array('comment_form_submit'); + } + $form['#submit'][] = 'ctools_node_comment_form_submit'; + } +} + +function ctools_node_comment_form_submit(&$form, &$form_state) { + $form_state['redirect'][0] = $_GET['q']; +} + // ----------------------------------------------------------------------- // CTools hook implementations. @@ -1018,3 +1039,50 @@ function ctools_ctools_entity_context_alter(&$plugin, &$entity, $plugin_id) { } } } + +/** + * Implements hook_field_create_field(). + */ +function ctools_field_create_field($field) { + ctools_flush_field_caches(); +} + +/** + * Implements hook_field_create_instance(). + */ +function ctools_field_create_instance($instance) { + ctools_flush_field_caches(); +} +/** + * Implements hook_field_delete_field(). + */ +function ctools_field_delete_field($field) { + ctools_flush_field_caches(); +} +/** + * Implements hook_field_delete_instance(). + */ +function ctools_field_delete_instance($instance) { + ctools_flush_field_caches(); +} +/** + * Implements hook_field_update_field(). + */ +function ctools_field_update_field($field, $prior_field, $has_data) { + ctools_flush_field_caches(); +} + +/** + * Implements hook_field_update_instance(). + */ +function ctools_field_update_instance($instance, $prior_instance) { + ctools_flush_field_caches(); +} + +/** + * Clear field related caches. + */ +function ctools_flush_field_caches() { + // Clear caches of 'Entity field' content type plugin. + cache_clear_all('ctools_entity_field_content_type_content_types', 'cache'); +} diff --git a/sites/all/modules/contrib/dev/ctools/ctools_access_ruleset/ctools_access_ruleset.info b/sites/all/modules/contrib/dev/ctools/ctools_access_ruleset/ctools_access_ruleset.info index d7ec175d..66ca12c0 100644 --- a/sites/all/modules/contrib/dev/ctools/ctools_access_ruleset/ctools_access_ruleset.info +++ b/sites/all/modules/contrib/dev/ctools/ctools_access_ruleset/ctools_access_ruleset.info @@ -5,9 +5,9 @@ package = Chaos tool suite version = CTOOLS_MODULE_VERSION dependencies[] = ctools -; Information added by Drupal.org packaging script on 2015-03-18 -version = "7.x-1.7" +; Information added by Drupal.org packaging script on 2015-08-19 +version = "7.x-1.9" core = "7.x" project = "ctools" -datestamp = "1426696183" +datestamp = "1440020680" diff --git a/sites/all/modules/contrib/dev/ctools/ctools_ajax_sample/ctools_ajax_sample.info b/sites/all/modules/contrib/dev/ctools/ctools_ajax_sample/ctools_ajax_sample.info index bc1a213d..f5d1e745 100644 --- a/sites/all/modules/contrib/dev/ctools/ctools_ajax_sample/ctools_ajax_sample.info +++ b/sites/all/modules/contrib/dev/ctools/ctools_ajax_sample/ctools_ajax_sample.info @@ -5,9 +5,9 @@ version = CTOOLS_MODULE_VERSION dependencies[] = ctools core = 7.x -; Information added by Drupal.org packaging script on 2015-03-18 -version = "7.x-1.7" +; Information added by Drupal.org packaging script on 2015-08-19 +version = "7.x-1.9" core = "7.x" project = "ctools" -datestamp = "1426696183" +datestamp = "1440020680" diff --git a/sites/all/modules/contrib/dev/ctools/ctools_ajax_sample/ctools_ajax_sample.module b/sites/all/modules/contrib/dev/ctools/ctools_ajax_sample/ctools_ajax_sample.module index 2a30c2a5..4638ac34 100644 --- a/sites/all/modules/contrib/dev/ctools/ctools_ajax_sample/ctools_ajax_sample.module +++ b/sites/all/modules/contrib/dev/ctools/ctools_ajax_sample/ctools_ajax_sample.module @@ -524,7 +524,7 @@ function ctools_ajax_sample_wizard_next(&$form_state) { } /** - * Handle the 'finish' click on teh add/edit pane form wizard. + * Handle the 'finish' click on the add/edit pane form wizard. * * All we need to do is set a flag so the return can handle adding * the pane. diff --git a/sites/all/modules/contrib/dev/ctools/ctools_custom_content/ctools_custom_content.info b/sites/all/modules/contrib/dev/ctools/ctools_custom_content/ctools_custom_content.info index 2b2d04bb..185d8b64 100644 --- a/sites/all/modules/contrib/dev/ctools/ctools_custom_content/ctools_custom_content.info +++ b/sites/all/modules/contrib/dev/ctools/ctools_custom_content/ctools_custom_content.info @@ -5,9 +5,9 @@ package = Chaos tool suite version = CTOOLS_MODULE_VERSION dependencies[] = ctools -; Information added by Drupal.org packaging script on 2015-03-18 -version = "7.x-1.7" +; Information added by Drupal.org packaging script on 2015-08-19 +version = "7.x-1.9" core = "7.x" project = "ctools" -datestamp = "1426696183" +datestamp = "1440020680" diff --git a/sites/all/modules/contrib/dev/ctools/ctools_plugin_example/ctools_plugin_example.info b/sites/all/modules/contrib/dev/ctools/ctools_plugin_example/ctools_plugin_example.info index f9158762..d378641e 100644 --- a/sites/all/modules/contrib/dev/ctools/ctools_plugin_example/ctools_plugin_example.info +++ b/sites/all/modules/contrib/dev/ctools/ctools_plugin_example/ctools_plugin_example.info @@ -8,9 +8,9 @@ dependencies[] = page_manager dependencies[] = advanced_help core = 7.x -; Information added by Drupal.org packaging script on 2015-03-18 -version = "7.x-1.7" +; Information added by Drupal.org packaging script on 2015-08-19 +version = "7.x-1.9" core = "7.x" project = "ctools" -datestamp = "1426696183" +datestamp = "1440020680" diff --git a/sites/all/modules/contrib/dev/ctools/includes/content.inc b/sites/all/modules/contrib/dev/ctools/includes/content.inc index b4660b43..ae1c6073 100644 --- a/sites/all/modules/contrib/dev/ctools/includes/content.inc +++ b/sites/all/modules/contrib/dev/ctools/includes/content.inc @@ -341,7 +341,16 @@ function ctools_content_editable($type, $subtype, $conf) { return FALSE; } - if ($function = ctools_plugin_get_function($subtype, 'check editable')) { + $function = FALSE; + + if (!empty($subtype['check editable'])) { + $function = ctools_plugin_get_function($subtype, 'check editable'); + } + elseif (!empty($type['check editable'])) { + $function = ctools_plugin_get_function($type, 'check editable'); + } + + if ($function) { return $function($type, $subtype, $conf); } diff --git a/sites/all/modules/contrib/dev/ctools/includes/context-admin.inc b/sites/all/modules/contrib/dev/ctools/includes/context-admin.inc index 11205474..821a5b32 100644 --- a/sites/all/modules/contrib/dev/ctools/includes/context-admin.inc +++ b/sites/all/modules/contrib/dev/ctools/includes/context-admin.inc @@ -736,6 +736,15 @@ function ctools_edit_context_form_defaults($form, &$form_state) { '#default_value' => $conf['keyword'], ); + if ($type_info['key'] == 'requiredcontexts') { + $form['optional'] = array( + '#type' => 'checkbox', + '#title' => t('Context is optional'), + '#default_value' => !empty($form_state['conf']['optional']), + '#description' => t('This context need not be present for the component to function.'), + ); + } + $form['#submit'][] = 'ctools_edit_context_form_defaults_submit'; return $form; @@ -752,6 +761,9 @@ function ctools_edit_context_form_defaults_submit(&$form, &$form_state) { $form_state['conf']['default'] = $form_state['values']['default']; $form_state['conf']['title'] = $form_state['values']['title']; } + if ($form_state['type info']['key'] == 'requiredcontexts') { + $form_state['conf']['optional'] = $form_state['values']['optional']; + } $form_state['conf']['identifier'] = $form_state['values']['identifier']; $form_state['conf']['keyword'] = $form_state['values']['keyword']; diff --git a/sites/all/modules/contrib/dev/ctools/includes/context.inc b/sites/all/modules/contrib/dev/ctools/includes/context.inc index 6628b5f9..1f9c1e45 100644 --- a/sites/all/modules/contrib/dev/ctools/includes/context.inc +++ b/sites/all/modules/contrib/dev/ctools/includes/context.inc @@ -216,7 +216,7 @@ class ctools_context_optional extends ctools_context_required { $context = new ctools_context('any'); $context->title = t('No context'); $context->identifier = t('No context'); - $contexts = array_merge(array('empty' => $context), $contexts); + $contexts['empty'] = $context; } function filter($contexts) { @@ -1505,7 +1505,7 @@ function ctools_access($settings, $contexts = array()) { return TRUE; } else if (!$pass && $settings['logic'] == 'and') { - // Fail if 'and' and htis rule failed. + // Fail if 'and' and this rule failed. return FALSE; } } diff --git a/sites/all/modules/contrib/dev/ctools/includes/css.inc b/sites/all/modules/contrib/dev/ctools/includes/css.inc index 9813a872..8cf5ed40 100644 --- a/sites/all/modules/contrib/dev/ctools/includes/css.inc +++ b/sites/all/modules/contrib/dev/ctools/includes/css.inc @@ -172,10 +172,12 @@ function ctools_css_cache($css, $filter = TRUE) { // @todo Is this slow? Does it matter if it is? $filename = $path . '/' . md5($css) . '.css'; - // This will do renames if the file already exists, ensuring we don't - // accidentally overwrite other files who share the same md5. Yes this - // is a very miniscule chance but it's safe. - $filename = file_unmanaged_save_data($css, $filename); + // Generally md5 is considered unique enough to sign file downloads. + // So this replaces already existing files based on the assumption that two + // files with the same hash are identical content wise. + // If we rename, the cache folder can potentially fill up with thousands of + // files with the same content. + $filename = file_unmanaged_save_data($css, $filename, FILE_EXISTS_REPLACE); return $filename; } diff --git a/sites/all/modules/contrib/dev/ctools/includes/jump-menu.inc b/sites/all/modules/contrib/dev/ctools/includes/jump-menu.inc index a5f99aa3..51f45982 100644 --- a/sites/all/modules/contrib/dev/ctools/includes/jump-menu.inc +++ b/sites/all/modules/contrib/dev/ctools/includes/jump-menu.inc @@ -51,7 +51,7 @@ function ctools_jump_menu($form, &$form_state, $select, $options = array()) { 'hide' => TRUE, ); - ctools_add_js('jump-menu'); + $form['#attached']['js'][] = ctools_attach_js('jump-menu'); if (!empty($options['choose'])) { $select = array('' => $options['choose']) + $select; diff --git a/sites/all/modules/contrib/dev/ctools/includes/math-expr.inc b/sites/all/modules/contrib/dev/ctools/includes/math-expr.inc index beffb93c..eeb184d8 100644 --- a/sites/all/modules/contrib/dev/ctools/includes/math-expr.inc +++ b/sites/all/modules/contrib/dev/ctools/includes/math-expr.inc @@ -271,7 +271,7 @@ class ctools_math_expr { } elseif (in_array($op, $ops) and !$expecting_op) { return $this->trigger("unexpected operator '$op'"); } else { // I don't even want to know what you did to get here - return $this->trigger("an unexpected error occured"); + return $this->trigger("an unexpected error occurred"); } if ($index == strlen($expr)) { if (in_array($op, $ops)) { // did we end with an operator? bad. diff --git a/sites/all/modules/contrib/dev/ctools/includes/modal.inc b/sites/all/modules/contrib/dev/ctools/includes/modal.inc index ee969aba..fc990159 100644 --- a/sites/all/modules/contrib/dev/ctools/includes/modal.inc +++ b/sites/all/modules/contrib/dev/ctools/includes/modal.inc @@ -66,6 +66,7 @@ function ctools_modal_add_js() { drupal_add_library('system', 'jquery.form'); drupal_add_library('system', 'drupal.progress'); drupal_add_library('system', 'drupal.ajax'); + drupal_add_library('system', 'ui'); ctools_add_js('modal'); ctools_add_css('modal'); diff --git a/sites/all/modules/contrib/dev/ctools/includes/plugins.inc b/sites/all/modules/contrib/dev/ctools/includes/plugins.inc index eadb68f7..79a6087f 100644 --- a/sites/all/modules/contrib/dev/ctools/includes/plugins.inc +++ b/sites/all/modules/contrib/dev/ctools/includes/plugins.inc @@ -79,7 +79,9 @@ function ctools_plugin_api_info($owner, $api, $minimum_version, $current_version } // Only process if version is between minimum and current, inclusive. - if (version_compare($version, $minimum_version, '>=') && version_compare($version, $current_version, '<=')) { + if (($version == $minimum_version) || ($version == $current_version) + || (version_compare($version, $minimum_version, '>=') + && version_compare($version, $current_version, '<='))) { if (!isset($info['path'])) { $info['path'] = drupal_get_path('module', $module); } @@ -110,7 +112,7 @@ function ctools_plugin_api_info($owner, $api, $minimum_version, $current_version } // Allow other modules to hook in. - drupal_alter($hook, $cache[$owner][$api]); + drupal_alter($hook, $cache[$owner][$api], $owner, $api); } return $cache[$owner][$api]; @@ -213,7 +215,7 @@ function ctools_plugin_api_get_hook($owner, $api) { */ function ctools_get_plugins($module, $type, $id = NULL) { // Store local caches of plugins and plugin info so we don't have to do full - // lookups everytime. + // lookups every time. static $drupal_static_fast; if (!isset($drupal_static_fast)) { $drupal_static_fast['plugins'] = &drupal_static('ctools_plugins', array()); diff --git a/sites/all/modules/contrib/dev/ctools/includes/stylizer.inc b/sites/all/modules/contrib/dev/ctools/includes/stylizer.inc index 9fdc81d7..5bc8450c 100644 --- a/sites/all/modules/contrib/dev/ctools/includes/stylizer.inc +++ b/sites/all/modules/contrib/dev/ctools/includes/stylizer.inc @@ -500,7 +500,7 @@ class ctools_stylizer_image_processor { $palette[$luminosity_input]['green'] = $green; $palette[$luminosity_input]['blue'] = $blue; - // Now we complete the palette, first we'll do it tothe black, and then to + // Now we complete the palette, first we'll do it to the black, and then to // the white. // From input to black diff --git a/sites/all/modules/contrib/dev/ctools/includes/uuid.inc b/sites/all/modules/contrib/dev/ctools/includes/uuid.inc index b0567d34..6e4c42c3 100644 --- a/sites/all/modules/contrib/dev/ctools/includes/uuid.inc +++ b/sites/all/modules/contrib/dev/ctools/includes/uuid.inc @@ -25,7 +25,8 @@ function _ctools_uuid_generate_com() { * Generates an universally unique identifier using the PECL extension. */ function _ctools_uuid_generate_pecl() { - return uuid_create(UUID_TYPE_DEFAULT); + $uuid_type = UUID_TYPE_DEFAULT; + return uuid_create($uuid_type); } /** diff --git a/sites/all/modules/contrib/dev/ctools/js/modal.js b/sites/all/modules/contrib/dev/ctools/js/modal.js index 37908cf3..c757ef27 100644 --- a/sites/all/modules/contrib/dev/ctools/js/modal.js +++ b/sites/all/modules/contrib/dev/ctools/js/modal.js @@ -48,7 +48,8 @@ modalOptions: { opacity: .55, background: '#fff' - } + }, + modalClass: 'default' }; var settings = {}; @@ -97,8 +98,8 @@ resize(); $('span.modal-title', Drupal.CTools.Modal.modal).html(Drupal.CTools.Modal.currentSettings.loadingText); - Drupal.CTools.Modal.modalContent(Drupal.CTools.Modal.modal, settings.modalOptions, settings.animation, settings.animationSpeed); - $('#modalContent .modal-content').html(Drupal.theme(settings.throbberTheme)); + Drupal.CTools.Modal.modalContent(Drupal.CTools.Modal.modal, settings.modalOptions, settings.animation, settings.animationSpeed, settings.modalClass); + $('#modalContent .modal-content').html(Drupal.theme(settings.throbberTheme)).addClass('ctools-modal-loading'); // Position autocomplete results based on the scroll position of the modal. $('#modalContent .modal-content').delegate('input.form-autocomplete', 'keyup', function() { @@ -299,6 +300,17 @@ // Attach behaviors within a modal dialog. var settings = response.settings || ajax.settings || Drupal.settings; Drupal.attachBehaviors('#modalContent', settings); + + if ($('#modal-content').hasClass('ctools-modal-loading')) { + $('#modal-content').removeClass('ctools-modal-loading'); + } + else { + // If the modal was already shown, and we are simply replacing its + // content, then focus on the first focusable element in the modal. + // (When first showing the modal, focus will be placed on the close + // button by the show() function called above.) + $('#modal-content :focusable:first').focus(); + } } /** @@ -349,8 +361,9 @@ * @param css obj of css attributes * @param animation (fadeIn, slideDown, show) * @param speed (valid animation speeds slow, medium, fast or # in ms) + * @param modalClass class added to div#modalContent */ - Drupal.CTools.Modal.modalContent = function(content, css, animation, speed) { + Drupal.CTools.Modal.modalContent = function(content, css, animation, speed, modalClass) { // If our animation isn't set, make it just show/pop if (!animation) { animation = 'show'; @@ -402,9 +415,56 @@ if( docHeight < winHeight ) docHeight = winHeight; // Create our divs - $('body').append('
' + $(content).html() + '
'); + $('body').append(''); - // Keyboard and focus event handler ensures focus stays on modal elements only + // Get a list of the tabbable elements in the modal content. + var getTabbableElements = function () { + var tabbableElements = $('#modalContent :tabbable'), + radioButtons = tabbableElements.filter('input[type="radio"]'); + + // The list of tabbable elements from jQuery is *almost* right. The + // exception is with groups of radio buttons. The list from jQuery will + // include all radio buttons, when in fact, only the selected radio button + // is tabbable, and if no radio buttons in a group are selected, then only + // the first is tabbable. + if (radioButtons.length > 0) { + // First, build up an index of which groups have an item selected or not. + var anySelected = {}; + radioButtons.each(function () { + var name = this.name; + + if (typeof anySelected[name] === 'undefined') { + anySelected[name] = radioButtons.filter('input[name="' + name + '"]:checked').length !== 0; + } + }); + + // Next filter out the radio buttons that aren't really tabbable. + var found = {}; + tabbableElements = tabbableElements.filter(function () { + var keep = true; + + if (this.type == 'radio') { + if (anySelected[this.name]) { + // Only keep the selected one. + keep = this.checked; + } + else { + // Only keep the first one. + if (found[this.name]) { + keep = false; + } + found[this.name] = true; + } + } + + return keep; + }); + } + + return tabbableElements.get(); + }; + + // Keyboard and focus event handler ensures only modal elements gain focus. modalEventHandler = function( event ) { target = null; if ( event ) { //Mozilla @@ -428,7 +488,7 @@ return true; } else { - $('#modalContent').focus(); + getTabbableElements()[0].focus(); } event.preventDefault(); @@ -436,6 +496,59 @@ $('body').bind( 'focus', modalEventHandler ); $('body').bind( 'keypress', modalEventHandler ); + // Keypress handler Ensures you can only TAB to elements within the modal. + // Based on the psuedo-code from WAI-ARIA 1.0 Authoring Practices section + // 3.3.1 "Trapping Focus". + modalTabTrapHandler = function (evt) { + // We only care about the TAB key. + if (evt.which != 9) { + return true; + } + + var tabbableElements = getTabbableElements(), + firstTabbableElement = tabbableElements[0], + lastTabbableElement = tabbableElements[tabbableElements.length - 1], + singleTabbableElement = firstTabbableElement == lastTabbableElement, + node = evt.target; + + // If this is the first element and the user wants to go backwards, then + // jump to the last element. + if (node == firstTabbableElement && evt.shiftKey) { + if (!singleTabbableElement) { + lastTabbableElement.focus(); + } + return false; + } + // If this is the last element and the user wants to go forwards, then + // jump to the first element. + else if (node == lastTabbableElement && !evt.shiftKey) { + if (!singleTabbableElement) { + firstTabbableElement.focus(); + } + return false; + } + // If this element isn't in the dialog at all, then jump to the first + // or last element to get the user into the game. + else if ($.inArray(node, tabbableElements) == -1) { + // Make sure the node isn't in another modal (ie. WYSIWYG modal). + var parents = $(node).parents().get(); + for (var i = 0; i < parents.length; ++i) { + var position = $(parents[i]).css('position'); + if (position == 'absolute' || position == 'fixed') { + return true; + } + } + + if (evt.shiftKey) { + lastTabbableElement.focus(); + } + else { + firstTabbableElement.focus(); + } + } + }; + $('body').bind('keydown', modalTabTrapHandler); + // Create our content div, get the dimensions, and hide it var modalContent = $('#modalContent').css('top','-1000px'); var mdcTop = wt + ( winHeight / 2 ) - ( modalContent.outerHeight() / 2); @@ -457,12 +570,19 @@ $(document).bind('keydown', modalEventEscapeCloseHandler); + // Per WAI-ARIA 1.0 Authoring Practices, initial focus should be on the + // close button, but we should save the original focus to restore it after + // the dialog is closed. + var oldFocus = document.activeElement; + $('.close').focus(); + // Close the open modal content and backdrop function close() { // Unbind the events $(window).unbind('resize', modalContentResize); $('body').unbind( 'focus', modalEventHandler); $('body').unbind( 'keypress', modalEventHandler ); + $('body').unbind( 'keydown', modalTabTrapHandler ); $('.close').unbind('click', modalContentClose); $('body').unbind('keypress', modalEventEscapeCloseHandler); $(document).trigger('CToolsDetachBehaviors', $('#modalContent')); @@ -478,12 +598,19 @@ // Remove the content $('#modalContent').remove(); $('#modalBackdrop').remove(); + + // Restore focus to where it was before opening the dialog + $(oldFocus).focus(); }; - // Move and resize the modalBackdrop and modalContent on resize of the window - modalContentResize = function(){ + // Move and resize the modalBackdrop and modalContent on window resize. + modalContentResize = function(){ - // position code lifted from http://www.quirksmode.org/viewport/compatibility.html + // Reset the backdrop height/width to get accurate document size. + $('#modalBackdrop').css('height', '').css('width', ''); + + // Position code lifted from: + // http://www.quirksmode.org/viewport/compatibility.html if (self.pageYOffset) { // all except Explorer var wt = self.pageYOffset; } else if (document.documentElement && document.documentElement.scrollTop) { // Explorer 6 Strict @@ -509,8 +636,6 @@ modalContent.css('top', mdcTop + 'px').css('left', mdcLeft + 'px').show(); }; $(window).bind('resize', modalContentResize); - - $('#modalContent').focus(); }; /** @@ -533,7 +658,9 @@ $(window).unbind('resize', modalContentResize); $('body').unbind('focus', modalEventHandler); $('body').unbind('keypress', modalEventHandler); + $('body').unbind( 'keydown', modalTabTrapHandler ); $('.close').unbind('click', modalContentClose); + $('body').unbind('keypress', modalEventEscapeCloseHandler); $(document).trigger('CToolsDetachBehaviors', $('#modalContent')); // jQuery magic loop through the instances and run the animations or removal. diff --git a/sites/all/modules/contrib/dev/ctools/js/states-show.js b/sites/all/modules/contrib/dev/ctools/js/states-show.js new file mode 100644 index 00000000..88df5841 --- /dev/null +++ b/sites/all/modules/contrib/dev/ctools/js/states-show.js @@ -0,0 +1,43 @@ +/** + * @file + * Custom state for handling visibility + */ + +/** + * Add a new state to Drupal #states. We use this to toggle element-invisible + * to show/hidden #states elements. This allows elements to be visible to + * screen readers. + * + * To use: + * $form['my_form_field'] = array( + * .. + * // Only show this field if 'some_other_field' is checked. + * '#states => array( + * 'show' => array( + * 'some-other-field' => array('checked' => TRUE), + * ), + * ), + * .. + * // Required to load the 'show' state handler. + * '#attached' => array( + * 'js' => array(ctools_attach_js('states-show')), + * ), + * ); + */ + +(function ($) { + 'use strict'; + + Drupal.states.State.aliases.hidden = '!show'; + + // Show/hide form items by toggling the 'element-invisible' class. This is a + // more accessible option than the core 'visible' state. + $(document).bind('state:show', function(e) { + if (e.trigger) { + var element = $(e.target).closest('.form-item, .form-submit, .form-wrapper'); + element.toggle(e.value); + e.value === true ? element.removeClass('element-invisible') : element.addClass('element-invisible'); + } + }); + +})(jQuery); diff --git a/sites/all/modules/contrib/dev/ctools/page_manager/page_manager.info b/sites/all/modules/contrib/dev/ctools/page_manager/page_manager.info index c34099ed..c7f4df3a 100644 --- a/sites/all/modules/contrib/dev/ctools/page_manager/page_manager.info +++ b/sites/all/modules/contrib/dev/ctools/page_manager/page_manager.info @@ -5,9 +5,9 @@ dependencies[] = ctools package = Chaos tool suite version = CTOOLS_MODULE_VERSION -; Information added by Drupal.org packaging script on 2015-03-18 -version = "7.x-1.7" +; Information added by Drupal.org packaging script on 2015-08-19 +version = "7.x-1.9" core = "7.x" project = "ctools" -datestamp = "1426696183" +datestamp = "1440020680" diff --git a/sites/all/modules/contrib/dev/ctools/page_manager/page_manager.module b/sites/all/modules/contrib/dev/ctools/page_manager/page_manager.module index 70237520..f3cb743e 100644 --- a/sites/all/modules/contrib/dev/ctools/page_manager/page_manager.module +++ b/sites/all/modules/contrib/dev/ctools/page_manager/page_manager.module @@ -440,13 +440,18 @@ function page_manager_cache_load($task_name) { */ function page_manager_handler_get_name($task_name, $handlers, $handler) { $base = str_replace('-', '_', $task_name); + $name = ''; + // Optional machine name. if (!empty($handler->conf['name'])) { $name = $base . '__' . $handler->conf['name']; + if (count(ctools_export_load_object('page_manager_handlers', 'names', array($name)))) { + $name = ''; + } } - // If no machine name was provided, generate a unique name. - else { + // If no machine name was provided or the name is in use, generate a unique name. + if (empty($name)) { $base .= '__' . $handler->handler; // Use the ctools uuid generator to generate a unique id. @@ -472,6 +477,10 @@ function page_manager_handler_add_to_page(&$page, &$handler, $title = NULL) { if ($title) { $handler->conf['title'] = $title; + $handler->conf['name'] = trim(preg_replace('/[^a-z0-9_]+/', '-', strtolower($title)), '-'); + } + else { + $handler->conf['name'] = ''; } $name = page_manager_handler_get_name($page->task_name, $page->handlers, $handler); diff --git a/sites/all/modules/contrib/dev/ctools/page_manager/plugins/tasks/node_edit.inc b/sites/all/modules/contrib/dev/ctools/page_manager/plugins/tasks/node_edit.inc index e0585e80..61ef13ac 100644 --- a/sites/all/modules/contrib/dev/ctools/page_manager/plugins/tasks/node_edit.inc +++ b/sites/all/modules/contrib/dev/ctools/page_manager/plugins/tasks/node_edit.inc @@ -108,7 +108,7 @@ function page_manager_node_edit($node) { * Callback to handle the process of adding a node. * * This creates a basic $node and passes that off to page_manager_node_edit(). - * It is modeled after Drupal's node_add() function. + * It is modelled after Drupal's node_add() function. * * Unlike node_add() we do not need to check node_access because that was * already checked by the menu system. diff --git a/sites/all/modules/contrib/dev/ctools/page_manager/plugins/tasks/node_view.inc b/sites/all/modules/contrib/dev/ctools/page_manager/plugins/tasks/node_view.inc index ad754e0e..89a29128 100644 --- a/sites/all/modules/contrib/dev/ctools/page_manager/plugins/tasks/node_view.inc +++ b/sites/all/modules/contrib/dev/ctools/page_manager/plugins/tasks/node_view.inc @@ -78,10 +78,6 @@ function page_manager_node_view_menu_alter(&$items, $task) { * node view, which is node_page_view(). */ function page_manager_node_view_page($node) { - // Prep the node to be displayed so all of the regular hooks are triggered. - // Also save the output for later, in case it is needed. - $default_output = node_page_view($node); - // Load my task plugin $task = page_manager_get_task('node_view'); @@ -107,6 +103,9 @@ function page_manager_node_view_page($node) { } } + // Prepare the node to be displayed so all of the regular hooks are triggered. + $default_output = node_page_view($node); + // Otherwise, fall back to the default output generated by node_page_view(). return $default_output; } diff --git a/sites/all/modules/contrib/dev/ctools/page_manager/plugins/tasks/page.admin.inc b/sites/all/modules/contrib/dev/ctools/page_manager/plugins/tasks/page.admin.inc index 7c560fb8..97bd37bc 100644 --- a/sites/all/modules/contrib/dev/ctools/page_manager/plugins/tasks/page.admin.inc +++ b/sites/all/modules/contrib/dev/ctools/page_manager/plugins/tasks/page.admin.inc @@ -61,7 +61,7 @@ function page_manager_page_menu(&$items, $task) { } $path = array(); - $page_arguments = array($subtask_id); + $page_arguments = array((string) $subtask_id); $access_arguments = array($subtask->access); $load_arguments = array($subtask_id, '%index', '%map'); @@ -566,7 +566,7 @@ function page_manager_page_form_basic_validate(&$form, &$form_state) { if (strpos($path, '%') === FALSE) { $alias = db_query('SELECT alias, source FROM {url_alias} WHERE alias = :path', array(':path' => $path))->fetchObject(); if ($alias) { - form_error($form['path'], t('That path is currently assigned to be an alias for @alias. This system cannot override existing aliases.', array('@alias' => $alias->src))); + form_error($form['path'], t('That path is currently assigned to be an alias for @alias. This system cannot override existing aliases.', array('@alias' => $alias->source))); } } else { diff --git a/sites/all/modules/contrib/dev/ctools/plugins/content_types/comment/comment_created.inc b/sites/all/modules/contrib/dev/ctools/plugins/content_types/comment/comment_created.inc new file mode 100644 index 00000000..62944e71 --- /dev/null +++ b/sites/all/modules/contrib/dev/ctools/plugins/content_types/comment/comment_created.inc @@ -0,0 +1,74 @@ + TRUE, + 'title' => t('Comment created date'), + 'icon' => 'icon_comment.png', + 'description' => t('The date the referenced comment was created.'), + 'required context' => new ctools_context_required(t('Comment'), 'entity:comment'), + 'category' => t('Comment'), + 'defaults' => array( + 'format' => 'small', + ), +); + +/** + * Render the custom content type. + */ +function ctools_comment_created_content_type_render($subtype, $conf, $panel_args, $context) { + if (empty($context) || empty($context->data)) { + return; + } + + // Get a shortcut to the comment. + $comment = $context->data; + + // Build the content type block. + $block = new stdClass(); + $block->module = 'comment_created'; + $block->title = t('Created date'); + $block->content = format_date($comment->created, $conf['format']); + $block->delta = $comment->cid; + + return $block; +} + +/** + * Returns an edit form for custom type settings. + */ +function ctools_comment_created_content_type_edit_form($form, &$form_state) { + $conf = $form_state['conf']; + $date_types = array(); + + foreach (system_get_date_types() as $date_type => $definition) { + $date_types[$date_type] = format_date(REQUEST_TIME, $date_type); + } + $form['format'] = array( + '#title' => t('Date format'), + '#type' => 'select', + '#options' => $date_types, + '#default_value' => $conf['format'], + ); + return $form; +} + +/** + * Submit handler for the custom type settings form. + */ +function ctools_comment_created_content_type_edit_form_submit($form, &$form_state) { + // Copy everything from our defaults. + foreach (array_keys($form_state['plugin']['defaults']) as $key) { + $form_state['conf'][$key] = $form_state['values'][$key]; + } +} + +/** + * Returns the administrative title for a type. + */ +function ctools_comment_created_content_type_admin_title($subtype, $conf, $context) { + return t('"@s" created date', array('@s' => $context->identifier)); +} diff --git a/sites/all/modules/contrib/dev/ctools/plugins/content_types/entity_context/entity_field.inc b/sites/all/modules/contrib/dev/ctools/plugins/content_types/entity_context/entity_field.inc index 5861d1c5..65367a02 100644 --- a/sites/all/modules/contrib/dev/ctools/plugins/content_types/entity_context/entity_field.inc +++ b/sites/all/modules/contrib/dev/ctools/plugins/content_types/entity_context/entity_field.inc @@ -34,6 +34,14 @@ function ctools_entity_field_content_type_content_types() { return $types; } + $cache_key = 'ctools_entity_field_content_type_content_types'; + if ($cache = cache_get($cache_key)) { + $types = $cache->data; + if (!empty($types)) { + return $types; + } + } + // This will hold all the individual field content types. $context_types = array(); $entities = entity_get_info(); @@ -82,6 +90,8 @@ function ctools_entity_field_content_type_content_types() { unset($context_types[$key]['types']); } + cache_set($cache_key, $types); + return $types; } diff --git a/sites/all/modules/contrib/dev/ctools/plugins/content_types/form/entity_form_field.inc b/sites/all/modules/contrib/dev/ctools/plugins/content_types/form/entity_form_field.inc index a030f693..582bd785 100644 --- a/sites/all/modules/contrib/dev/ctools/plugins/content_types/form/entity_form_field.inc +++ b/sites/all/modules/contrib/dev/ctools/plugins/content_types/form/entity_form_field.inc @@ -57,6 +57,26 @@ function ctools_entity_form_field_content_type_content_types() { } } + if (module_exists('field_group')) { + foreach ($entities as $entity_type => $entity) { + foreach ($entity['bundles'] as $type => $bundle) { + if ($group_info = field_group_info_groups($entity_type, $type, "form")) { + foreach ($group_info as $group_name => $group) { + if (!isset($types[$entity_type . ':' . $group_name])) { + $types[$entity_type . ':' . $group_name] = array( + 'category' => t('Form'), + 'icon' => 'icon_field.png', + 'title' => t('Group form: @widget_label', array('@widget_label' => $group->label)), + 'description' => t('Field group on the referenced entity.'), + ); + } + $content_types[$entity_type . ':' . $group_name]['types'][$type] = $bundle['label']; + } + } + } + } + } + // Create the required context for each field related to the bundle types. foreach ($types as $key => $field_content_type) { list($entity_type, $field_name) = explode(':', $key, 2); @@ -85,16 +105,38 @@ function ctools_entity_form_field_content_type_render($subtype, $conf, $panel_ar $ids = entity_extract_ids($entity_type, $entity); $field = field_info_instance($entity_type, $field_name, $ids[2]); - // Do not render if the entity type does not have this field. - if (empty($field)) { + // Check for field groups. + if (empty($field) && module_exists('field_group')) { + $groups = field_group_info_groups($entity_type, $entity->type, "form"); + $group = !empty($groups[$field_name]) ? $groups[$field_name] : NULL; + } + + // Do not render if the entity type does not have this field or group. + if (empty($field) && empty($group)) { return; } - $block = new stdClass(); + $block = new stdClass(); if (isset($context->form)) { $block->content = array(); - $block->content[$field_name] = $context->form[$field_name]; - unset($context->form[$field_name]); + if (!empty($field)) { + $block->content[$field_name] = $context->form[$field_name]; + unset($context->form[$field_name]); + } + else { + // Pre-render the form to populate field groups. + if (isset($context->form['#pre_render'])) { + foreach ($context->form['#pre_render'] as $function) { + if (function_exists($function)) { + $context->form = $function($context->form); + } + } + unset($context->form['#pre_render']); + } + + $block->content[$field_name] = $context->form[$field_name]; + unset($context->form[$field_name]); + } } else { $block->content = t('Entity info.'); diff --git a/sites/all/modules/contrib/dev/ctools/plugins/content_types/node_context/node_comment_form.inc b/sites/all/modules/contrib/dev/ctools/plugins/content_types/node_context/node_comment_form.inc index f77b66e4..c21e7bc7 100644 --- a/sites/all/modules/contrib/dev/ctools/plugins/content_types/node_context/node_comment_form.inc +++ b/sites/all/modules/contrib/dev/ctools/plugins/content_types/node_context/node_comment_form.inc @@ -77,20 +77,3 @@ function ctools_node_comment_form_content_type_edit_form_submit($form, &$form_st } } -/** - * Alter the comment form to get a little more control over it. - */ -function ctools_form_comment_form_alter(&$form, &$form_state) { - if (!empty($form_state['ctools comment alter'])) { - // Force the form to post back to wherever we are. - $form['#action'] = url($_GET['q'], array('fragment' => 'comment-form')); - if (empty($form['#submit'])) { - $form['#submit'] = array('comment_form_submit'); - } - $form['#submit'][] = 'ctools_node_comment_form_submit'; - } -} - -function ctools_node_comment_form_submit(&$form, &$form_state) { - $form_state['redirect'][0] = $_GET['q']; -} diff --git a/sites/all/modules/contrib/dev/ctools/plugins/content_types/page/page_title.inc b/sites/all/modules/contrib/dev/ctools/plugins/content_types/page/page_title.inc index e3032dfb..cc091ab2 100644 --- a/sites/all/modules/contrib/dev/ctools/plugins/content_types/page/page_title.inc +++ b/sites/all/modules/contrib/dev/ctools/plugins/content_types/page/page_title.inc @@ -29,6 +29,9 @@ $plugin = array( * Outputs the page title of the current page. */ function ctools_page_title_content_type_render($subtype, $conf, $panel_args) { + if (!drupal_get_title()) { + return; + } // TODO: This should have a setting or something for the markup. if (empty($conf['markup'])) { $conf['markup'] = 'h1'; diff --git a/sites/all/modules/contrib/dev/ctools/plugins/content_types/term_context/term_description.inc b/sites/all/modules/contrib/dev/ctools/plugins/content_types/term_context/term_description.inc index 2b953ed0..f95c4f54 100644 --- a/sites/all/modules/contrib/dev/ctools/plugins/content_types/term_context/term_description.inc +++ b/sites/all/modules/contrib/dev/ctools/plugins/content_types/term_context/term_description.inc @@ -18,8 +18,8 @@ function ctools_term_description_content_type_render($subtype, $conf, $panel_arg $block = new stdClass(); $block->module = 'node_type'; - $block->title = $term->name; - if ($term) { + if (!empty($term)) { + $block->title = $term->name; $block->content = check_markup($term->description, $term->format, '', TRUE); $block->delta = $term->tid; @@ -33,6 +33,7 @@ function ctools_term_description_content_type_render($subtype, $conf, $panel_arg } } else { + $block->title = ''; $block->content = t('Term description goes here.'); $block->delta = 'unknown'; } diff --git a/sites/all/modules/contrib/dev/ctools/plugins/contexts/user.inc b/sites/all/modules/contrib/dev/ctools/plugins/contexts/user.inc index 02bfe987..18781c76 100644 --- a/sites/all/modules/contrib/dev/ctools/plugins/contexts/user.inc +++ b/sites/all/modules/contrib/dev/ctools/plugins/contexts/user.inc @@ -43,7 +43,9 @@ function ctools_context_create_user($empty, $data = NULL, $conf = FALSE) { if ($data['type'] == 'current') { global $user; $data = user_load($user->uid); - $data->logged_in_user = TRUE; + if (user_is_logged_in()) { + $data->logged_in_user = TRUE; + } } else { $data = user_load($data['uid']); diff --git a/sites/all/modules/contrib/dev/ctools/plugins/contexts/user_edit_form.inc b/sites/all/modules/contrib/dev/ctools/plugins/contexts/user_edit_form.inc index 707632d2..a28c5990 100644 --- a/sites/all/modules/contrib/dev/ctools/plugins/contexts/user_edit_form.inc +++ b/sites/all/modules/contrib/dev/ctools/plugins/contexts/user_edit_form.inc @@ -34,15 +34,15 @@ function ctools_context_create_user_edit_form($empty, $user = NULL, $conf = FALS $category = !empty($conf['category']) ? $conf['category'] : FALSE; unset($conf['category']); + // If no category was specified, use the default 'account'. + if (!$category) { + $category = 'account'; + } // Return previously created contexts, per category. static $created = array(); if (!empty($created[$category])) { return $created[$category]; } - // If no category was specified, use the default 'account'. - if (!$category) { - $category = 'account'; - } $context = new ctools_context(array('form', 'user_edit', 'user_form', 'user_edit_form', 'user', 'entity:user')); // Store this context for later. diff --git a/sites/all/modules/contrib/dev/ctools/plugins/export_ui/ctools_export_ui.class.php b/sites/all/modules/contrib/dev/ctools/plugins/export_ui/ctools_export_ui.class.php index 640c93a9..60c1dc28 100644 --- a/sites/all/modules/contrib/dev/ctools/plugins/export_ui/ctools_export_ui.class.php +++ b/sites/all/modules/contrib/dev/ctools/plugins/export_ui/ctools_export_ui.class.php @@ -724,7 +724,13 @@ class ctools_export_ui { // Export the handler, which is a fantastic way to clean database IDs out of it. $export = ctools_export_crud_export($this->plugin['schema'], $original); $item = ctools_export_crud_import($this->plugin['schema'], $export); - $item->{$this->plugin['export']['key']} = 'clone_of_' . $item->{$this->plugin['export']['key']}; + + if (!empty($input[$this->plugin['export']['key']])) { + $item->{$this->plugin['export']['key']} = $input[$this->plugin['export']['key']]; + } + else { + $item->{$this->plugin['export']['key']} = 'clone_of_' . $item->{$this->plugin['export']['key']}; + } } // Tabs and breadcrumb disappearing, this helps alleviate through cheating. diff --git a/sites/all/modules/contrib/dev/ctools/plugins/relationships/entity_from_field.inc b/sites/all/modules/contrib/dev/ctools/plugins/relationships/entity_from_field.inc index d4880c6f..fdffc41c 100644 --- a/sites/all/modules/contrib/dev/ctools/plugins/relationships/entity_from_field.inc +++ b/sites/all/modules/contrib/dev/ctools/plugins/relationships/entity_from_field.inc @@ -185,7 +185,7 @@ function ctools_entity_from_field_context($context, $conf) { $loaded_to_entity = array_shift($loaded_to_entity); // Pass current user account and entity type to access callback. - if (function_exists($to_entity_info['access callback']) && !call_user_func($to_entity_info['access callback'], 'view', $loaded_to_entity, $account, $to_entity)) { + if (isset($to_entity_info['access callback']) && function_exists($to_entity_info['access callback']) && !call_user_func($to_entity_info['access callback'], 'view', $loaded_to_entity)) { return ctools_context_create_empty('entity:' . $to_entity, NULL); } else { diff --git a/sites/all/modules/contrib/dev/ctools/stylizer/stylizer.info b/sites/all/modules/contrib/dev/ctools/stylizer/stylizer.info index 69fa16cb..9e9890ff 100644 --- a/sites/all/modules/contrib/dev/ctools/stylizer/stylizer.info +++ b/sites/all/modules/contrib/dev/ctools/stylizer/stylizer.info @@ -6,9 +6,9 @@ version = CTOOLS_MODULE_VERSION dependencies[] = ctools dependencies[] = color -; Information added by Drupal.org packaging script on 2015-03-18 -version = "7.x-1.7" +; Information added by Drupal.org packaging script on 2015-08-19 +version = "7.x-1.9" core = "7.x" project = "ctools" -datestamp = "1426696183" +datestamp = "1440020680" diff --git a/sites/all/modules/contrib/dev/ctools/term_depth/term_depth.info b/sites/all/modules/contrib/dev/ctools/term_depth/term_depth.info index 7b6cc38b..d6fa37fb 100644 --- a/sites/all/modules/contrib/dev/ctools/term_depth/term_depth.info +++ b/sites/all/modules/contrib/dev/ctools/term_depth/term_depth.info @@ -5,9 +5,9 @@ dependencies[] = ctools package = Chaos tool suite version = CTOOLS_MODULE_VERSION -; Information added by Drupal.org packaging script on 2015-03-18 -version = "7.x-1.7" +; Information added by Drupal.org packaging script on 2015-08-19 +version = "7.x-1.9" core = "7.x" project = "ctools" -datestamp = "1426696183" +datestamp = "1440020680" diff --git a/sites/all/modules/contrib/dev/ctools/tests/ctools_export_test/ctools_export_test.info b/sites/all/modules/contrib/dev/ctools/tests/ctools_export_test/ctools_export_test.info index bae67668..e016aeb5 100644 --- a/sites/all/modules/contrib/dev/ctools/tests/ctools_export_test/ctools_export_test.info +++ b/sites/all/modules/contrib/dev/ctools/tests/ctools_export_test/ctools_export_test.info @@ -8,9 +8,9 @@ hidden = TRUE files[] = ctools_export.test -; Information added by Drupal.org packaging script on 2015-03-18 -version = "7.x-1.7" +; Information added by Drupal.org packaging script on 2015-08-19 +version = "7.x-1.9" core = "7.x" project = "ctools" -datestamp = "1426696183" +datestamp = "1440020680" diff --git a/sites/all/modules/contrib/dev/ctools/tests/ctools_plugin_test.info b/sites/all/modules/contrib/dev/ctools/tests/ctools_plugin_test.info index 8ee53032..15a510b6 100644 --- a/sites/all/modules/contrib/dev/ctools/tests/ctools_plugin_test.info +++ b/sites/all/modules/contrib/dev/ctools/tests/ctools_plugin_test.info @@ -12,9 +12,9 @@ files[] = math_expression.test files[] = math_expression_stack.test hidden = TRUE -; Information added by Drupal.org packaging script on 2015-03-18 -version = "7.x-1.7" +; Information added by Drupal.org packaging script on 2015-08-19 +version = "7.x-1.9" core = "7.x" project = "ctools" -datestamp = "1426696183" +datestamp = "1440020680" diff --git a/sites/all/modules/contrib/dev/ctools/tests/plugins/cached/ctoolsCachedPluginArray.class.php b/sites/all/modules/contrib/dev/ctools/tests/plugins/cached/ctoolsCachedPluginArray.class.php index 3037daa5..ea087fa6 100644 --- a/sites/all/modules/contrib/dev/ctools/tests/plugins/cached/ctoolsCachedPluginArray.class.php +++ b/sites/all/modules/contrib/dev/ctools/tests/plugins/cached/ctoolsCachedPluginArray.class.php @@ -1,7 +1,7 @@ 'administer video styles', // Define the menu item. 'menu' => array( - 'menu prefix' => 'admin/config/media', + 'menu prefix' => 'admin/config/media/vef', 'menu item' => 'vef_video_styles', 'menu title' => 'Video Embed Styles', 'menu description' => 'Administer Video Embed Field\'s video styles.', diff --git a/sites/all/modules/contrib/fields/video_embed_field/video_embed_brightcove/video_embed_brightcove.info b/sites/all/modules/contrib/fields/video_embed_field/video_embed_brightcove/video_embed_brightcove.info index b9eafdd2..8ca93ff1 100644 --- a/sites/all/modules/contrib/fields/video_embed_field/video_embed_brightcove/video_embed_brightcove.info +++ b/sites/all/modules/contrib/fields/video_embed_field/video_embed_brightcove/video_embed_brightcove.info @@ -6,9 +6,9 @@ configure = admin/config/media/vef_video_styles dependencies[] = "video_embed_field" -; Information added by Drupal.org packaging script on 2015-04-17 -version = "7.x-2.0-beta8+7-dev" +; Information added by Drupal.org packaging script on 2015-09-07 +version = "7.x-2.0-beta11" core = "7.x" project = "video_embed_field" -datestamp = "1429278491" +datestamp = "1441639440" diff --git a/sites/all/modules/contrib/fields/video_embed_field/video_embed_brightcove/video_embed_brightcove.module b/sites/all/modules/contrib/fields/video_embed_field/video_embed_brightcove/video_embed_brightcove.module index 0ba0e0c9..f48f6c19 100644 --- a/sites/all/modules/contrib/fields/video_embed_field/video_embed_brightcove/video_embed_brightcove.module +++ b/sites/all/modules/contrib/fields/video_embed_field/video_embed_brightcove/video_embed_brightcove.module @@ -24,6 +24,7 @@ function video_embed_brightcove_video_embed_handler_info() { 'defaults' => array( 'width' => 640, 'height' => 360, + 'class' => '', ), ); @@ -57,6 +58,13 @@ function video_embed_brightcove_form($defaults) { '#default_value' => $defaults['height'], ); + $form['class'] = array( + '#type' => 'textfield', + '#title' => t('Player CSS class'), + '#description' => t('CSS class to add to the player'), + '#default_value' => $defaults['class'], + ); + return $form; } @@ -83,7 +91,7 @@ function video_embed_brightcove_handle_video($url, $settings) { if (isset($parameters['id']) && isset($parameters['key'])) { // Embed code. - $embed = ' + $embed = ' @@ -100,6 +108,7 @@ function video_embed_brightcove_handle_video($url, $settings) { '!key' => $parameters['key'], '@width' => $settings['width'], '@height' => $settings['height'], + '@class' => $settings['class'], '!videoplayer' => $parameters['player'], )); @@ -152,7 +161,7 @@ function _video_embed_brightcove_get_video_properties($url) { $string = "/(.*){$component['start']}(.*){$component['finish']}/"; preg_match($string, $url, $matches); if ($matches && !empty($matches[2])) { - $return[$key] = $matches[2]; + $return[$key] = check_plain($matches[2]); } } return $return; diff --git a/sites/all/modules/contrib/fields/video_embed_field/video_embed_facebook/video_embed_facebook.info b/sites/all/modules/contrib/fields/video_embed_field/video_embed_facebook/video_embed_facebook.info index b1db153a..618c2d7c 100644 --- a/sites/all/modules/contrib/fields/video_embed_field/video_embed_facebook/video_embed_facebook.info +++ b/sites/all/modules/contrib/fields/video_embed_field/video_embed_facebook/video_embed_facebook.info @@ -5,9 +5,9 @@ package = Media configure = admin/config/media/vef_video_styles dependencies[] = "video_embed_field" -; Information added by Drupal.org packaging script on 2015-04-17 -version = "7.x-2.0-beta8+7-dev" +; Information added by Drupal.org packaging script on 2015-09-07 +version = "7.x-2.0-beta11" core = "7.x" project = "video_embed_field" -datestamp = "1429278491" +datestamp = "1441639440" diff --git a/sites/all/modules/contrib/fields/video_embed_field/video_embed_facebook/video_embed_facebook.module b/sites/all/modules/contrib/fields/video_embed_field/video_embed_facebook/video_embed_facebook.module index 00378861..bca02972 100644 --- a/sites/all/modules/contrib/fields/video_embed_field/video_embed_facebook/video_embed_facebook.module +++ b/sites/all/modules/contrib/fields/video_embed_field/video_embed_facebook/video_embed_facebook.module @@ -25,6 +25,7 @@ function video_embed_facebook_video_embed_handler_info() { 'defaults' => array( 'width' => 640, 'height' => 360, + 'class' => '', ), ); @@ -57,6 +58,13 @@ function video_embed_facebook_form($defaults) { '#default_value' => $defaults['height'], ); + $form['class'] = array( + '#type' => 'textfield', + '#title' => t('Player CSS class'), + '#description' => t('CSS class to add to the player'), + '#default_value' => $defaults['class'], + ); + return $form; } @@ -90,12 +98,13 @@ function video_embed_facebook_handle_video($url, $settings) { if ($id) { // Our embed code. - $embed=' '; + $embed=' '; // Use format_string to replace our placeholders with the settings values. $embed = format_string($embed, array( '!id' => $id, - '!width' => $settings['width'], - '!height' => $settings['height'], + '@width' => $settings['width'], + '@height' => $settings['height'], + '@class' => $settings['class'], )); $video = array( @@ -137,10 +146,10 @@ function video_embed_facebook_handle_thumbnail($url) { function _video_embed_facebook_get_video_id($url) { // Parse_url is an easy way to break a url into its components. $matches = array(); - preg_match('/(.*)?[v|video_id]=([^&#]*)/', $url, $matches); + preg_match('/(?:.*)(?:v=|video_id=|videos\/|videos\/v.\.\d+\/)(\d+).*/', $url, $matches); // If the v or video_id get parameters are set, return it. - if ($matches && !empty($matches[2])) { - return $matches[2]; + if ($matches && !empty($matches[1])) { + return check_plain($matches[1]); } // Otherwise return false. return FALSE; diff --git a/sites/all/modules/contrib/fields/video_embed_field/video_embed_field.admin.inc b/sites/all/modules/contrib/fields/video_embed_field/video_embed_field.admin.inc index 56738e02..861fc64b 100644 --- a/sites/all/modules/contrib/fields/video_embed_field/video_embed_field.admin.inc +++ b/sites/all/modules/contrib/fields/video_embed_field/video_embed_field.admin.inc @@ -32,3 +32,15 @@ function video_embed_field_video_style_form(&$form, &$form_state) { return $form; } + +/** + * VEF settings page form callback. + */ +function video_embed_field_settings_form($form, &$form_state) { + $form['video_embed_field_youtube_v3_api_key'] = array( + '#type' => 'textfield', + '#title' => t('Youtube v3 API key'), + '#default_value' => variable_get('video_embed_field_youtube_v3_api_key', ''), + ); + return system_settings_form($form); +} diff --git a/sites/all/modules/contrib/fields/video_embed_field/video_embed_field.field.inc b/sites/all/modules/contrib/fields/video_embed_field/video_embed_field.field.inc index 9ca982ae..0c23804f 100644 --- a/sites/all/modules/contrib/fields/video_embed_field/video_embed_field.field.inc +++ b/sites/all/modules/contrib/fields/video_embed_field/video_embed_field.field.inc @@ -454,7 +454,7 @@ function video_embed_field_field_formatter_view($entity_type, $entity, $field, $ if (isset($item['description']) && $item['description'] && $settings['description'] && $instance['settings']['description_field']) { $description = array( '#prefix' => '
', - '#markup' => $item['description'], + '#markup' => check_plain($item['description']), '#suffix' => '
', ); $alt = $item['description']; diff --git a/sites/all/modules/contrib/fields/video_embed_field/video_embed_field.handlers.inc b/sites/all/modules/contrib/fields/video_embed_field/video_embed_field.handlers.inc index 06cdfdf7..19620082 100644 --- a/sites/all/modules/contrib/fields/video_embed_field/video_embed_field.handlers.inc +++ b/sites/all/modules/contrib/fields/video_embed_field/video_embed_field.handlers.inc @@ -38,6 +38,7 @@ function video_embed_field_video_embed_handler_info() { 'modestbranding' => 0, 'theme' => 'dark', 'iv_load_policy' => 1, + 'class' => '', ), ); @@ -46,6 +47,7 @@ function video_embed_field_video_embed_handler_info() { 'function' => 'video_embed_field_handle_vimeo', 'thumbnail_function' => 'video_embed_field_handle_vimeo_thumbnail', 'thumbnail_default' => drupal_get_path('module', 'video_embed_field') . '/img/vimeo.jpg', + 'data_function' => '_video_embed_field_get_vimeo_data', 'form' => 'video_embed_field_handler_vimeo_form', 'form_validate' => 'video_embed_field_handler_vimeo_form_validate', 'domains' => array( @@ -61,6 +63,7 @@ function video_embed_field_video_embed_handler_info() { 'autoplay' => 0, 'loop' => 0, 'froogaloop' => 0, + 'class' => '' ), ); @@ -146,7 +149,7 @@ function _video_embed_field_get_youtube_id($url) { $id = substr($url, $pos); } } - return $id; + return check_plain($id); } /** @@ -163,16 +166,21 @@ function _video_embed_field_get_youtube_id($url) { function video_embed_field_handle_youtube($url, $settings) { $output = array(); - // Grab the minutes and seconds, and just convert it down to seconds. - preg_match('/#t=((?P\d+)m)?((?P\d+)s)?/', $url, $matches); - - // Give it some default data in case there is no #t=... - $matches += array( - "min" => 0, - "sec" => 0, - ); - $time = ($matches["min"] * 60) + $matches["sec"]; - $settings['start'] = $time; + if(preg_match('/#t=((?P\d+)m)?((?P\d+)s)?((?P\d+))?/', $url, $matches)){ + if(isset($matches['tinsec'])){ + $settings['start'] = $matches['tinsec']; // url already in form #t=125 for 2 minutes and 5 seconds + } else { + // url in form #t=2m5s or with other useless data, this is why we still keep adding the default data.. + // give it some default data in case there is no #t=... + $matches += array( + "min" => 0, + "sec" => 0, + ); + if ($time = ($matches["min"] * 60) + $matches["sec"]) { + $settings['start'] = $time; + } + } + } $id = _video_embed_field_get_youtube_id($url); if (!$id) { @@ -180,11 +188,16 @@ function video_embed_field_handle_youtube($url, $settings) { $output['#markup'] = l($url, $url); return $output; } + + // Add class to variable to avoid adding it to URL param string. + $class = $settings['class']; + unset($settings['class']); + // Construct the embed code. $settings['wmode'] = 'opaque'; - $settings_str = _video_embed_code_get_settings_str($settings); + $settings_str = urlencode(_video_embed_code_get_settings_str($settings)); - $output['#markup'] = ''; + $output['#markup'] = ''; return $output; } @@ -243,11 +256,17 @@ function video_embed_field_handle_youtube_data($url) { $id = _video_embed_field_get_youtube_id($url); if ($id) { - $response = drupal_http_request('http://gdata.youtube.com/feeds/api/videos/' . $id . '?v=2&alt=json'); + + $options['v'] = 3; + $options['key'] = variable_get('video_embed_field_youtube_v3_api_key', ''); + $options['part'] = 'snippet'; + $options['id'] = $id; + + $response = drupal_http_request(url('https://www.googleapis.com/youtube/v3/videos', array('query' => $options))); + if (!isset($response->error)) { $data = json_decode($response->data); - $data = isset($data->entry) ? (array) $data->entry : (array) $data->feed; - return _video_embed_field_clean_up_youtube_data($data); + return _video_embed_field_clean_up_youtube_data($data->items); } } @@ -401,6 +420,13 @@ function video_embed_field_handler_youtube_form($defaults) { '#default_value' => $defaults['autohide'], ); + $form['class'] = array( + '#type' => 'textfield', + '#title' => t('Player CSS class'), + '#description' => t('CSS class to add to the player'), + '#default_value' => $defaults['class'], + ); + return $form; } @@ -489,10 +515,14 @@ function video_embed_field_handle_vimeo($url, $settings) { } unset($settings['froogaloop']); + // Add class to variable to avoid adding it to URL param string. + $class = $settings['class']; + unset($settings['class']); + $settings_str = _video_embed_code_get_settings_str($settings); return array( - '#markup' => '', ); } @@ -616,6 +646,13 @@ function video_embed_field_handler_vimeo_form($defaults) { '#default_value' => $defaults['loop'], ); + $form['class'] = array( + '#type' => 'textfield', + '#title' => t('Player CSS class'), + '#description' => t('CSS class to add to the player'), + '#default_value' => $defaults['class'], + ); + return $form; } diff --git a/sites/all/modules/contrib/fields/video_embed_field/video_embed_field.info b/sites/all/modules/contrib/fields/video_embed_field/video_embed_field.info index ee487fbb..ffb8d652 100644 --- a/sites/all/modules/contrib/fields/video_embed_field/video_embed_field.info +++ b/sites/all/modules/contrib/fields/video_embed_field/video_embed_field.info @@ -2,7 +2,7 @@ name = "Video Embed Field" description = "Expose a field type for embedding videos from youtube or vimeo." core = 7.x package = Media -configure = admin/config/media/vef_video_styles +configure = admin/config/media/vef files[] = video_embed_field.migrate.inc files[] = views/handlers/views_embed_field_views_handler_field_thumbnail_path.inc @@ -10,9 +10,9 @@ files[] = views/handlers/views_embed_field_views_handler_field_thumbnail_path.in dependencies[] = ctools dependencies[] = image -; Information added by Drupal.org packaging script on 2015-04-17 -version = "7.x-2.0-beta8+7-dev" +; Information added by Drupal.org packaging script on 2015-09-07 +version = "7.x-2.0-beta11" core = "7.x" project = "video_embed_field" -datestamp = "1429278491" +datestamp = "1441639440" diff --git a/sites/all/modules/contrib/fields/video_embed_field/video_embed_field.install b/sites/all/modules/contrib/fields/video_embed_field/video_embed_field.install index 7b0d23da..2009b2a5 100644 --- a/sites/all/modules/contrib/fields/video_embed_field/video_embed_field.install +++ b/sites/all/modules/contrib/fields/video_embed_field/video_embed_field.install @@ -96,6 +96,13 @@ function video_embed_field_schema() { return $schema; } +/** + * Implements hook_uninstall(). + */ +function video_embed_field_uninstall() { + variable_del('video_embed_field_youtube_api_key'); +} + /** * Adds an optional description form. */ @@ -363,3 +370,22 @@ function video_embed_field_update_7009() { return t('Updated default instance settings'); } + +/** + * Update styles with empty class parameter. + */ +function video_embed_field_update_7010() { + drupal_get_schema('vef_video_styles', TRUE); + ctools_include('export'); + $styles = ctools_export_load_object('vef_video_styles'); + foreach ($styles as $style) { + foreach ($style->data as &$provider) { + if (!isset($provider['class'])) { + $provider['class'] = ''; + } + } + ctools_export_crud_save('vef_video_styles', $style); + } + + return 'Parameter class added to existing styles'; +} diff --git a/sites/all/modules/contrib/fields/video_embed_field/video_embed_field.module b/sites/all/modules/contrib/fields/video_embed_field/video_embed_field.module index 70647876..8826eada 100644 --- a/sites/all/modules/contrib/fields/video_embed_field/video_embed_field.module +++ b/sites/all/modules/contrib/fields/video_embed_field/video_embed_field.module @@ -93,6 +93,26 @@ function video_embed_field_menu() { 'type' => MENU_CALLBACK, ); + $items['admin/config/media/vef'] = array( + 'title' => 'Video Embed Field', + 'description' => 'Video Embed Field configuration', + 'page callback' => 'system_admin_menu_block_page', + 'access arguments' => array('administer video styles'), + 'file' => 'system.admin.inc', + 'file path' => drupal_get_path('module', 'system'), + 'type' => MENU_NORMAL_ITEM, + ); + + $items['admin/config/media/vef/settings'] = array( + 'title' => 'Settings', + 'description' => 'Video Embed Field module settings', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('video_embed_field_settings_form'), + 'file' => 'video_embed_field.admin.inc', + 'access arguments' => array('administer video styles'), + 'type' => MENU_NORMAL_ITEM, + ); + return $items; } @@ -577,15 +597,7 @@ function _video_embed_field_get_provider_domains() { * An array containing the allowed video domains. */ function _video_embed_field_get_instance_provider_domains($instance) { - $domains = _video_embed_field_get_provider_domains(); - - foreach ($domains as $domain => $provider) { - if (empty($instance['settings']['allowed_providers'][$provider])) { - unset($domains[$domain]); - } - } - - return $domains; + return array_intersect(_video_embed_field_get_provider_domains(), $instance['settings']['allowed_providers']); } /** diff --git a/sites/all/modules/contrib/migrate/migrate/BACKPORT.txt b/sites/all/modules/contrib/migrate/migrate/BACKPORT.txt deleted file mode 100644 index c23bf27c..00000000 --- a/sites/all/modules/contrib/migrate/migrate/BACKPORT.txt +++ /dev/null @@ -1,5 +0,0 @@ -Conventions to make porting changes between Drupal 6 and Drupal 7 easier: - -Try to always use specific DBTNG functions such as db_select() instead of the -more general db_query(), which needs to be renamed to dbtng_query() under -Drupal 6. diff --git a/sites/all/modules/contrib/migrate/migrate/CHANGELOG.txt b/sites/all/modules/contrib/migrate/migrate/CHANGELOG.txt index dc6ac96d..b0092b88 100644 --- a/sites/all/modules/contrib/migrate/migrate/CHANGELOG.txt +++ b/sites/all/modules/contrib/migrate/migrate/CHANGELOG.txt @@ -1,11 +1,31 @@ -Migrate 2.7 +Migrate 2.8 =========== -Bug fixes -- #2415597 - Make batching of SQL sources optional, and force map_joinable FALSE. +Features and enhancements +- #2379289 - Better handle interaction of --update with highwater marks. +- #2403643 - Support an additional level of subfields. +- #2472045 - Add language subfields only if field is translatable. +- #2474809 - Provide better message for bad dependencies. +- #2397791 - Provide detailed field validation errors. +- #2309563 - Add support for running migrations via wildcard name. +- #2095841 - Abstract mail system disablement for more flexibility. +- #2419373 - Provide ability to cache map lookups. +- #2141687 - Provide detailed message on file copy error. -Migrate 2.7 Release Candidate 1 -=============================== +Bug fixes + Field sanitization added to prevent possibility of XSS - see security advisory + https://security.drupal.org/node/155268. +- #2447115 - Add xpath handling to the field mapping editor. +- #2497015 - Term reference handler would ignore all terms if one was NULL. +- #2488560 - MigrateSourceList/MigrateSourceMultiItems iterators prematurely + return. +- #2446105 - Keep coded DNM source field mappings from overriding UI mappings. +- #2415977 - Use temporary:// instead of /tmp for drush logging. +- #2475473 - Fix handling of --idlist when map not joined. +- #2465387 - Fix handling of --stop option on migrate-import. + +Migrate 2.7 +=========== Features and enhancements - #2296911 - Add a source handler for IBM DB2. @@ -14,6 +34,7 @@ Features and enhancements - #1751438 - Add spreadsheet source plugin. Bug fixes +- #2415597 - Make batching of SQL sources optional, and force map_joinable FALSE. - #2403593 - SQL batching messes up cases with altered queries, such as idlist. - #2298969 - Verify wizard validation function exists. - #2268863 - Fix drush --all option. diff --git a/sites/all/modules/contrib/migrate/migrate/includes/base.inc b/sites/all/modules/contrib/migrate/migrate/includes/base.inc index 2369fbd1..3c89260c 100644 --- a/sites/all/modules/contrib/migrate/migrate/includes/base.inc +++ b/sites/all/modules/contrib/migrate/migrate/includes/base.inc @@ -301,6 +301,11 @@ abstract class MigrationBase { return $this->disableHooks; } + /** + * An array to track 'mail_system' variable if disabled. + */ + protected $mailSystem = array(); + /** * Have we already warned about obsolete constructor argumentss on this request? * @@ -432,16 +437,11 @@ abstract class MigrationBase { // Record the time limit $this->timeLimit = ini_get('max_execution_time'); - // Prevent any emails from being sent out on migration - global $conf; - if (!empty($conf['mail_system'])) { - foreach ($conf['mail_system'] as $system => $class) { - $conf['mail_system'][$system] = 'MigrateMailIgnore'; - } - } - else { - $conf['mail_system']['default-system'] = 'MigrateMailIgnore'; - } + // Save the current mail system, prior to disabling emails. + $this->saveMailSystem(); + + // Prevent emails from being sent out during migrations. + $this->disableMailSystem(); // Make sure we clear our semaphores in case of abrupt exit drupal_register_shutdown_function(array($this, 'endProcess')); @@ -1355,6 +1355,39 @@ abstract class MigrationBase { } return $time; } + + /** + * Saves the current mail system, or set a system default if there is none. + */ + protected function saveMailSystem() { + global $conf; + if (empty($conf['mail_system'])) { + $conf['mail_system']['default-system'] = 'MigrateMailIgnore'; + } + else { + $this->mailSystem = $conf['mail_system']; + } + } + + /** + * Disables mail system to prevent emails from being sent during migrations. + */ + public function disableMailSystem() { + global $conf; + if (!empty($conf['mail_system'])) { + foreach ($conf['mail_system'] as $system => $class) { + $conf['mail_system'][$system] = 'MigrateMailIgnore'; + } + } + } + + /** + * Restores the original saved mail system for migrations that require it. + */ + public function restoreMailSystem() { + global $conf; + $conf['mail_system'] = $this->mailSystem; + } } // Make sure static members (in particular, $displayFunction) get diff --git a/sites/all/modules/contrib/migrate/migrate/includes/migration.inc b/sites/all/modules/contrib/migrate/migrate/includes/migration.inc index b4b7f29a..4be8229b 100644 --- a/sites/all/modules/contrib/migrate/migrate/includes/migration.inc +++ b/sites/all/modules/contrib/migrate/migrate/includes/migration.inc @@ -141,16 +141,26 @@ abstract class Migration extends MigrationBase { // destination field, keep only the last (so the UI can override a source // field DNM that was defined in code). $no_destination = array(); + // But also remove a mapping of a source field to nothing, if there is + // a mapping to something. + $mapped_source_fields = array(); + /** @var MigrateFieldMapping $mapping */ foreach ($this->allFieldMappings as $destination_field => $mapping) { + $source_field = $mapping->getSourceField(); // If the source field is not mapped to a destination field, the // array index is integer. if (is_int($destination_field)) { - $source_field = $mapping->getSourceField(); if (isset($no_destination[$source_field])) { unset($this->allFieldMappings[$no_destination[$source_field]]); unset($no_destination[$source_field]); } $no_destination[$source_field] = $destination_field; + if (isset($mapped_source_fields[$source_field])) { + unset($this->allFieldMappings[$destination_field]); + } + } + else { + $mapped_source_fields[$source_field] = $source_field; } } @@ -1267,8 +1277,11 @@ abstract class Migration extends MigrationBase { // Are we dealing with the primary value of the destination field, or a // subfield? $destination = explode(':', $destination); + // Count how many levels of fields are in the mapping. We'll use the + // last one. + $destination_count = count($destination); $destination_field = $destination[0]; - if (isset($destination[1])) { + if ($destination_count == 2) { $subfield = $destination[1]; // We're processing the subfield before the primary value, initialize it if (!property_exists($this->destinationValues, $destination_field)) { @@ -1282,6 +1295,24 @@ abstract class Migration extends MigrationBase { // Add the subfield value to the arguments array. $this->destinationValues->{$destination_field}['arguments'][$subfield] = $destination_values; } + elseif ($destination_count == 3) { + $subfield2 = $destination[2]; + // We're processing the subfield before the primary value, initialize it + if (!property_exists($this->destinationValues, $destination_field)) { + $this->destinationValues->$destination_field = array(); + } + // We have a value, and need to convert to an array so we can add + // arguments. + elseif (!is_array($this->destinationValues->$destination_field)) { + $this->destinationValues->$destination_field = array($this->destinationValues->$destination_field); + } + if (!is_array($this->destinationValues->{$destination_field}['arguments'][$destination[1]])) { + // Convert first subfield level to an array so we can add to it. + $this->destinationValues->{$destination_field}['arguments'][$destination[1]] = array( $this->destinationValues->{$destination_field}['arguments'][$destination[1]] ); + } + // Add the subfield value to the arguments array. + $this->destinationValues->{$destination_field}['arguments'][$destination[1]]['arguments'][$subfield2] = $destination_values; + } // Just the primary value, the first time through for this field, simply // set it. elseif (!property_exists($this->destinationValues, $destination_field)) { diff --git a/sites/all/modules/contrib/migrate/migrate/includes/source.inc b/sites/all/modules/contrib/migrate/migrate/includes/source.inc index 39396a41..46c132cd 100644 --- a/sites/all/modules/contrib/migrate/migrate/includes/source.inc +++ b/sites/all/modules/contrib/migrate/migrate/includes/source.inc @@ -303,9 +303,11 @@ abstract class MigrateSource implements Iterator { // highwaters and map rows). $prepared = FALSE; if (!empty($this->idList)) { + // Check first source key. if (!in_array(reset($this->currentKey), $this->idList)) { + // If this is a compound source key, check the full key. $compoundKey = implode($this->multikeySeparator, $this->currentKey); - if (count($this->currentKey) > 1 && !in_array($compoundKey, $this->idList)) { + if (count($this->currentKey) == 1 || !in_array($compoundKey, $this->idList)) { // Could not find the key, skip. continue; } diff --git a/sites/all/modules/contrib/migrate/migrate/migrate.drush.inc b/sites/all/modules/contrib/migrate/migrate/migrate.drush.inc index d5fcd07c..4044d26a 100644 --- a/sites/all/modules/contrib/migrate/migrate/migrate.drush.inc +++ b/sites/all/modules/contrib/migrate/migrate/migrate.drush.inc @@ -18,6 +18,7 @@ function migrate_drush_command() { 'force' => 'Force an operation to run, even if all dependencies are not satisfied', 'group' => 'Name of the migration group to run', 'notify' => 'Send email notification upon completion of operation', + 'wildcard' => 'Process migrations that match a certain pattern. For example, Content*.', ); $items['migrate-status'] = array( 'description' => 'List all migrations with current status.', @@ -161,6 +162,7 @@ function migrate_drush_command() { 'migrate-import Article' => 'Import new articles', 'migrate-import Article --update' => 'Import new items, and also update previously-imported items', 'migrate-import Article --idlist=4,9' => 'Import two specific articles. The ids refer to the value of the primary key in base table', + 'migrate-import Article --idlist=450:pasta,451' => 'Import two specific articles. A colon can be used to separate parts of compound keys; otherwise, compound keys match by the first key field.', 'migrate-import Article --limit="60 seconds" --stop --rollback' => 'Import for up to 60 seconds after stopping and rolling back the Article migration.', 'migrate-import Article --limit="100 items"' => @@ -824,15 +826,6 @@ function drush_migrate_rollback($args = NULL) { // Capture non-informational output for mailing ob_start(); ob_implicit_flush(FALSE); - // Save original mail setup, which Migrate will disable, so we can - // restore it later. - global $conf; - if (!empty($conf['mail_system'])) { - $mail_system = $conf['mail_system']; - } - else { - $mail_system = NULL; - } } $migrations = drush_migrate_get_migrations($args); @@ -928,12 +921,6 @@ function drush_migrate_rollback($args = NULL) { // Notify user if (drush_get_option('notify')) { - if (is_null($mail_system)) { - unset($conf['mail_system']); - } - else { - $conf['mail_system'] = $mail_system; - } _drush_migrate_notify(); } } @@ -985,6 +972,14 @@ function drush_migrate_get_migrations($args) { } } } + elseif ($wildcard = drush_get_option('wildcard')) { + foreach ($migration_objects as $name => $migration) { + if (!fnmatch(drupal_strtolower($wildcard), drupal_strtolower($name)) || + !$migration->getEnabled()) { + unset($migration_objects[$name]); + } + } + } else { $named_migrations = array(); foreach (explode(',', $args) as $name) { @@ -1057,18 +1052,23 @@ function drush_migrate_rollback_validate($args = NULL) { function drush_migrate_validate_common($args) { if (drush_get_option('all')) { - if (!empty($args) || drush_get_option('group')) { - return drush_set_error(NULL, dt('You must specify exactly one of a migration name, --all, or --group')); + if (!empty($args) || drush_get_option('group') || drush_get_option('wildcard')) { + return drush_set_error(NULL, dt('You must specify exactly one of a migration name, --all, or --group, or --wildcard')); } } elseif (drush_get_option('group')) { - if (!empty($args) || drush_get_option('all')) { - return drush_set_error(NULL, dt('You must specify exactly one of a migration name, --all, or --group')); + if (!empty($args) || drush_get_option('all') || drush_get_option('wildcard')) { + return drush_set_error(NULL, dt('You must specify exactly one of a migration name, --all, or --group, or --wildcard')); + } + } + elseif (drush_get_option('wildcard')) { + if (!empty($args) || drush_get_option('all') || drush_get_option('group')) { + return drush_set_error(NULL, dt('You must specify exactly one of a migration name, --all, or --group, or --wildcard')); } } else { if (empty($args)) { - return drush_set_error(NULL, dt('You must specify exactly one of a migration name, --all, or --group')); + return drush_set_error(NULL, dt('You must specify exactly one of a migration name, --all, or --group, or --wildcard')); } $machine_names = explode(',', $args); @@ -1107,7 +1107,29 @@ function drush_migrate_validate_common($args) { */ function drush_migrate_pre_migrate_import($args = NULL) { if (drush_get_option('stop')) { - drush_invoke('migrate-stop', $args); + drush_unset_option('stop'); + try { + /** @var Migration[] $migrations */ + $migrations = drush_migrate_get_migrations($args); + foreach ($migrations as $migration) { + $status = $migration->getStatus(); + if ($status == MigrationBase::STATUS_IMPORTING || + $status == MigrationBase::STATUS_ROLLING_BACK) { + drush_log(dt("Stopping '!description' migration", array('!description' => $migration->getMachineName()))); + $migration->stopProcess(); + // Give the process a chance to stop. + $count = 0; + while ($migration->getStatus() != MigrationBase::STATUS_IDLE + && $count++ < 5) { + sleep(1); + } + } + } + } + catch (MigrateException $e) { + drush_print($e->getMessage()); + exit; + } } if (drush_get_option('rollback')) { drush_unset_option('rollback'); @@ -1127,15 +1149,6 @@ function drush_migrate_import($args = NULL) { // Capture non-informational output for mailing ob_start(); ob_implicit_flush(FALSE); - // Save original mail setup, which Migrate will disable, so we can - // restore it later. - global $conf; - if (!empty($conf['mail_system'])) { - $mail_system = $conf['mail_system']; - } - else { - $mail_system = NULL; - } } $migrations = drush_migrate_get_migrations($args); $options = array(); @@ -1294,12 +1307,6 @@ function drush_migrate_import($args = NULL) { // Notify user if (drush_get_option('notify')) { - if (is_null($mail_system)) { - unset($conf['mail_system']); - } - else { - $conf['mail_system'] = $mail_system; - } _drush_migrate_notify(); } } diff --git a/sites/all/modules/contrib/migrate/migrate/migrate.info b/sites/all/modules/contrib/migrate/migrate/migrate.info index 5cab8b60..f54551f2 100644 --- a/sites/all/modules/contrib/migrate/migrate/migrate.info +++ b/sites/all/modules/contrib/migrate/migrate/migrate.info @@ -51,9 +51,9 @@ files[] = tests/plugins/destinations/term.test files[] = tests/plugins/destinations/user.test files[] = tests/plugins/sources/xml.test -; Information added by Drupal.org packaging script on 2015-02-09 -version = "7.x-2.7" +; Information added by Drupal.org packaging script on 2015-07-01 +version = "7.x-2.8" core = "7.x" project = "migrate" -datestamp = "1423521491" +datestamp = "1435760949" diff --git a/sites/all/modules/contrib/migrate/migrate/migrate.module b/sites/all/modules/contrib/migrate/migrate/migrate.module index 43e46d00..7d9bf89e 100644 --- a/sites/all/modules/contrib/migrate/migrate/migrate.module +++ b/sites/all/modules/contrib/migrate/migrate/migrate.module @@ -93,11 +93,18 @@ function migrate_migrations($reset = NULL) { $final_migrations[$name] = array(); } - // Fill in the grouped list + // Fill in the grouped list. foreach ($migrations as $machine_name => $migration) { - $final_migrations[$migration->getGroup()->getName()][$machine_name] = $migration; + if (!method_exists($migration, 'getGroup')) { + MigrationBase::displayMessage(t('Migration !machine_name is not a valid Migration dependency.', array( + '!machine_name' => $machine_name, + ))); + } + else { + $final_migrations[$migration->getGroup()->getName()][$machine_name] = $migration; + } } - // Then flatten the list + // Flatten the grouped list. $migrations = array(); foreach ($final_migrations as $group_name => $group_migrations) { foreach ($group_migrations as $machine_name => $migration) { diff --git a/sites/all/modules/contrib/migrate/migrate/migrate_example/beer.inc b/sites/all/modules/contrib/migrate/migrate/migrate_example/beer.inc index 02e3a244..11e6dcc9 100644 --- a/sites/all/modules/contrib/migrate/migrate/migrate_example/beer.inc +++ b/sites/all/modules/contrib/migrate/migrate/migrate_example/beer.inc @@ -424,15 +424,13 @@ class BeerNodeMigration extends BasicExampleMigration { // subfields of the same field may be grouped on the same line), and indent // subfields to distinguish them from top-level fields. $this->addUnmigratedDestinations(array( - 'body:format', 'body:language', + 'body:format', 'changed', 'comment', 'created', - 'field_migrate_example_country:language', 'field_migrate_example_image:destination_dir', 'field_migrate_example_image:destination_file', 'field_migrate_example_image:file_replace', - 'field_migrate_example_image:language', 'field_migrate_example_image:preserve_files', 'field_migrate_example_image:urlencode', 'is_new', @@ -515,7 +513,7 @@ class BeerCommentMigration extends BasicExampleMigration { // Unmapped destination fields $this->addUnmigratedDestinations(array( 'changed', - 'comment_body:format', 'comment_body:language', + 'comment_body:format', 'created', 'homepage', 'hostname', diff --git a/sites/all/modules/contrib/migrate/migrate/migrate_example/migrate_example.info b/sites/all/modules/contrib/migrate/migrate/migrate_example/migrate_example.info index 5bf403e2..aee3c0bc 100644 --- a/sites/all/modules/contrib/migrate/migrate/migrate_example/migrate_example.info +++ b/sites/all/modules/contrib/migrate/migrate/migrate_example/migrate_example.info @@ -18,9 +18,9 @@ files[] = wine.inc ; For testing table_copy plugin. Since is infrequently used, we comment it out. ; files[] = example.table_copy.inc -; Information added by Drupal.org packaging script on 2015-02-09 -version = "7.x-2.7" +; Information added by Drupal.org packaging script on 2015-07-01 +version = "7.x-2.8" core = "7.x" project = "migrate" -datestamp = "1423521491" +datestamp = "1435760949" diff --git a/sites/all/modules/contrib/migrate/migrate/migrate_example/migrate_example_oracle/migrate_example_oracle.info b/sites/all/modules/contrib/migrate/migrate/migrate_example/migrate_example_oracle/migrate_example_oracle.info index 6c4c0e8a..74c0d081 100644 --- a/sites/all/modules/contrib/migrate/migrate/migrate_example/migrate_example_oracle/migrate_example_oracle.info +++ b/sites/all/modules/contrib/migrate/migrate/migrate_example/migrate_example_oracle/migrate_example_oracle.info @@ -11,9 +11,9 @@ name = "Migrate example - Oracle" package = "Migration" project = "migrate_example_oracle" -; Information added by Drupal.org packaging script on 2015-02-09 -version = "7.x-2.7" +; Information added by Drupal.org packaging script on 2015-07-01 +version = "7.x-2.8" core = "7.x" project = "migrate" -datestamp = "1423521491" +datestamp = "1435760949" diff --git a/sites/all/modules/contrib/migrate/migrate/migrate_example/wine.inc b/sites/all/modules/contrib/migrate/migrate/migrate_example/wine.inc index 9824ce7d..39271de6 100644 --- a/sites/all/modules/contrib/migrate/migrate/migrate_example/wine.inc +++ b/sites/all/modules/contrib/migrate/migrate/migrate_example/wine.inc @@ -449,7 +449,7 @@ class WineProducerMigration extends AdvancedExampleMigration { // Unmapped destination fields $this->addUnmigratedDestinations(array( - 'body:format', 'body:language', + 'body:format', 'changed', 'comment', 'created', @@ -550,7 +550,7 @@ class WineProducerXMLMigration extends XMLMigration { ->xpath('/producer/description'); $this->addUnmigratedDestinations(array( - 'body:summary', 'body:format', 'body:language', + 'body:summary', 'body:format', 'changed', 'comment', 'created', @@ -663,7 +663,7 @@ class WineProducerNamespaceXMLMigration extends XMLMigration { ->xpath('/pr:producer/pr:description'); $this->addUnmigratedDestinations(array( - 'body:summary', 'body:format', 'body:language', + 'body:summary', 'body:format', 'changed', 'comment', 'created', @@ -784,7 +784,7 @@ class WineProducerMultiXMLMigration extends XMLMigration { ->xpath('description'); $this->addUnmigratedDestinations(array( - 'body:summary', 'body:format', 'body:language', + 'body:summary', 'body:format', 'changed', 'comment', 'created', @@ -907,7 +907,7 @@ class WineProducerMultiNamespaceXMLMigration extends XMLMigration { ->xpath('pr:description'); $this->addUnmigratedDestinations(array( - 'body:summary', 'body:format', 'body:language', + 'body:summary', 'body:format', 'changed', 'comment', 'created', @@ -1012,7 +1012,7 @@ class WineProducerXMLPullMigration extends XMLMigration { ->xpath('description'); $this->addUnmigratedDestinations(array( - 'body:summary', 'body:format', 'body:language', + 'body:summary', 'body:format', 'changed', 'comment', 'created', @@ -1119,7 +1119,7 @@ class WineProducerNamespaceXMLPullMigration extends XMLMigration { ->xpath('pr:description'); $this->addUnmigratedDestinations(array( - 'body:summary', 'body:format', 'body:language', + 'body:summary', 'body:format', 'changed', 'comment', 'created', @@ -1290,12 +1290,11 @@ class WineWineMigration extends AdvancedExampleMigration { // Unmapped destination fields $this->addUnmigratedDestinations(array( - 'body:format', 'body:language', + 'body:format', 'comment', 'field_migrate_example_image:destination_dir', 'field_migrate_example_image:destination_file', 'field_migrate_example_image:file_class', - 'field_migrate_example_image:language', 'field_migrate_example_image:preserve_files', 'field_migrate_example_image:source_dir', 'field_migrate_example_image:urlencode', @@ -1418,7 +1417,7 @@ class WineCommentMigration extends AdvancedExampleMigration { // Unmapped destination fields $this->addUnmigratedDestinations(array( - 'comment_body:format', 'comment_body:language', + 'comment_body:format', 'language', 'thread', )); @@ -1551,14 +1550,13 @@ class WineUpdatesMigration extends AdvancedExampleMigration { $this->addFieldMapping('created'); $this->addFieldMapping('changed'); $this->addUnmigratedDestinations(array( - 'body:format', 'body:summary', 'body:language', + 'body:format', 'body:summary', 'comment', 'field_migrate_example_image:alt', 'field_migrate_example_image:destination_dir', 'field_migrate_example_image:destination_file', 'field_migrate_example_image:file_class', 'field_migrate_example_image:file_replace', - 'field_migrate_example_image:language', 'field_migrate_example_image:preserve_files', 'field_migrate_example_image:source_dir', 'field_migrate_example_image:title', @@ -1623,7 +1621,7 @@ class WineCommentUpdatesMigration extends AdvancedExampleMigration { // Unmapped destination fields $this->addUnmigratedDestinations(array( 'changed', - 'comment_body', 'comment_body:format', 'comment_body:language', + 'comment_body', 'comment_body:format', 'created', 'homepage', 'hostname', diff --git a/sites/all/modules/contrib/migrate/migrate/migrate_example_baseball/migrate_example_baseball.info b/sites/all/modules/contrib/migrate/migrate/migrate_example_baseball/migrate_example_baseball.info index 8c26078b..9634ed1b 100644 --- a/sites/all/modules/contrib/migrate/migrate/migrate_example_baseball/migrate_example_baseball.info +++ b/sites/all/modules/contrib/migrate/migrate/migrate_example_baseball/migrate_example_baseball.info @@ -24,9 +24,9 @@ name = "migrate_example_baseball" package = "Migration" php = "5.2.4" -; Information added by Drupal.org packaging script on 2015-02-09 -version = "7.x-2.7" +; Information added by Drupal.org packaging script on 2015-07-01 +version = "7.x-2.8" core = "7.x" project = "migrate" -datestamp = "1423521491" +datestamp = "1435760949" diff --git a/sites/all/modules/contrib/migrate/migrate/migrate_ui/migrate_ui.info b/sites/all/modules/contrib/migrate/migrate/migrate_ui/migrate_ui.info index 97f6201c..fb12ca11 100644 --- a/sites/all/modules/contrib/migrate/migrate/migrate_ui/migrate_ui.info +++ b/sites/all/modules/contrib/migrate/migrate/migrate_ui/migrate_ui.info @@ -6,9 +6,9 @@ core = 7.x dependencies[] = migrate files[] = migrate_ui.wizard.inc -; Information added by Drupal.org packaging script on 2015-02-09 -version = "7.x-2.7" +; Information added by Drupal.org packaging script on 2015-07-01 +version = "7.x-2.8" core = "7.x" project = "migrate" -datestamp = "1423521491" +datestamp = "1435760949" diff --git a/sites/all/modules/contrib/migrate/migrate/migrate_ui/migrate_ui.pages.inc b/sites/all/modules/contrib/migrate/migrate/migrate_ui/migrate_ui.pages.inc index 7760edf5..63478db0 100644 --- a/sites/all/modules/contrib/migrate/migrate/migrate_ui/migrate_ui.pages.inc +++ b/sites/all/modules/contrib/migrate/migrate/migrate_ui/migrate_ui.pages.inc @@ -14,7 +14,7 @@ function migrate_ui_migrate_dashboard($form, &$form_state) { $build['overview'] = array( '#prefix' => '
', - '#markup' => migrate_overview(), + '#markup' => filter_xss_admin(migrate_overview()), '#suffix' => '
', ); @@ -100,7 +100,7 @@ function migrate_ui_migrate_dashboard($form, &$form_state) { l($group_row->title, 'admin/content/migrate/groups/' . $group_row->name); $arguments = unserialize($group_row->arguments); if (!empty($arguments['source_system'])) { - $row['source_system'] = $arguments['source_system']; + $row['source_system'] = filter_xss_admin($arguments['source_system']); } else { $row['source_system'] = ''; @@ -212,9 +212,9 @@ function migrate_ui_migrate_group($form, &$form_state, $group_name) { } $row['machinename'] = l($display_name, "admin/content/migrate/groups/$group_name/$machine_name"); - $row['importrows'] = $total; - $row['imported'] = $imported; - $row['unprocessed'] = $unprocessed; + $row['importrows'] = (int) $total; + $row['imported'] = (int) $imported; + $row['unprocessed'] = (int) $unprocessed; if (user_access(MIGRATE_ACCESS_ADVANCED)) { if (is_subclass_of($migration, 'Migration')) { $num_messages = $migration->messageCount(); @@ -231,13 +231,13 @@ function migrate_ui_migrate_group($form, &$form_state, $group_name) { $row['lastthroughput'] = t('Unknown'); } else { - $row['lastthroughput'] = t('!rate/min', array('!rate' => $rate)); + $row['lastthroughput'] = t('@rate/min', array('@rate' => $rate)); } } else { $row['lastthroughput'] = t('N/A'); } - $row['lastimported'] = $migration->getLastImported(); + $row['lastimported'] = check_plain($migration->getLastImported()); } $rows[$machine_name] = $row; } @@ -497,6 +497,19 @@ function migrate_ui_migrate_submit($form, &$form_state) { } elseif (count($drush_arguments) > 0) { $drush_path = trim(variable_get('migrate_drush_path', '')); + // Check that $drush_path works. See migrate_ui_configure_form(). + if (!is_executable($drush_path)) { + $message = t('To enable running operations in the background with drush, (which is recommended), some configuration must be done on the server. See the documentation on drupal.org.', + array( + '@drush' => 'http://drupal.org/project/drush', + '@recommended' => 'http://drupal.org/node/1806824', + '@config' => 'http://drupal.org/node/1958170', + '@dorg' => 'http://drupal.org/', + ) + ); + drupal_set_message($message); + return; + } $uri = $GLOBALS['base_url']; $uid = $GLOBALS['user']->uid; if ($operation == 'import_background') { @@ -523,7 +536,7 @@ function migrate_ui_migrate_submit($form, &$form_state) { $limit = $limit['value'] . ' ' . $limit['unit']; $drush_command .= " --limit=\"$limit\""; } - $log_file = '/tmp/' . $drush_arguments[0] . $log_suffix; + $log_file = drupal_realpath('temporary://' . $drush_arguments[0] . $log_suffix); $drush_command .= " >$log_file 2>&1 &"; exec($drush_command, $output, $status); if (variable_get('migrate_drush_mail', 0)) { @@ -674,13 +687,24 @@ function migrate_ui_batch_finish($success, $results, $operations) { } } +/** + * Store a message to be displayed later. + * + * Ignore the message if $level is set to 'debug'. + * + * @param string $message + * the message to be displayed + * @param string $level + * the type of the message: 'debug', 'completed', 'failed', or a valid $type + * used by drupal_set_message() + */ function migrate_ui_capture_message($message, $level) { if ($level != 'debug') { // Store each message as an array with keys 'message' and 'level'. global $_migrate_messages; $_migrate_messages[] = array( - 'message' => $message, - 'level' => $level, + 'message' => filter_xss_admin($message), + 'level' => check_plain($level), ); } } @@ -738,8 +762,8 @@ function migrate_migration_info($form, $form_state, $group_name, $migration_name foreach ($team as $group => $list) { $form['overview'][$group] = array( '#type' => 'item', - '#title' => $group, - '#markup' => implode(', ', $list), + '#title' => filter_xss_admin($group), + '#markup' => filter_xss_admin(implode(', ', $list)), ); } @@ -747,7 +771,7 @@ function migrate_migration_info($form, $form_state, $group_name, $migration_name if (count($dependencies) > 0) { $form['overview']['dependencies'] = array( '#title' => t('Dependencies') , - '#markup' => implode(', ', $dependencies), + '#markup' => filter_xss_admin(implode(', ', $dependencies)), '#type' => 'item', ); } @@ -755,14 +779,14 @@ function migrate_migration_info($form, $form_state, $group_name, $migration_name if (count($soft_dependencies) > 0) { $form['overview']['soft_dependencies'] = array( '#title' => t('Soft Dependencies'), - '#markup' => implode(', ', $soft_dependencies), + '#markup' => filter_xss_admin(implode(', ', $soft_dependencies)), '#type' => 'item', ); } $form['overview']['group'] = array( '#title' => t('Group:'), - '#markup' => $migration->getGroup()->getTitle(), + '#markup' => filter_xss_admin($migration->getGroup()->getTitle()), '#type' => 'item', ); @@ -787,7 +811,7 @@ function migrate_migration_info($form, $form_state, $group_name, $migration_name $form['overview']['description'] = array( '#title' => t('Description:'), - '#markup' => $migration->getDescription(), + '#markup' => filter_xss_admin($migration->getDescription()), '#type' => 'item', ); @@ -807,7 +831,7 @@ function migrate_migration_info($form, $form_state, $group_name, $migration_name $form['destination']['type'] = array( '#type' => 'item', '#title' => t('Type'), - '#markup' => (string)$destination, + '#markup' => filter_xss_admin((string) $destination), ); $dest_key = $destination->getKeySchema(); $header = array(t('Machine name'), t('Description')); @@ -822,7 +846,10 @@ function migrate_migration_info($form, $form_state, $group_name, $migration_name // Add class for mapped/unmapped. Used in summary. $classes[] = !isset($destination_fields[$machine_name]) ? 'migrate-error' : ''; } - $rows[] = array(array('data' => $machine_name, 'class' => $classes), array('data' => $description, 'class' => $classes)); + $rows[] = array( + array('data' => check_plain($machine_name), 'class' => $classes), + array('data' => filter_xss_admin($description), 'class' => $classes), + ); } $classes = array(); @@ -848,7 +875,7 @@ function migrate_migration_info($form, $form_state, $group_name, $migration_name $form['source']['query'] = array( '#type' => 'item', '#title' => t('Query'), - '#markup' => '
' . $source . '
', + '#markup' => '
' . filter_xss_admin($source) . '
', ); $source_key = $migration->getMap()->getSourceKey(); $header = array(t('Machine name'), t('Description')); @@ -862,7 +889,10 @@ function migrate_migration_info($form, $form_state, $group_name, $migration_name // Add class for mapped/unmapped. Used in summary. $classes = !isset($source_fields[$machine_name]) ? 'migrate-error' : ''; } - $rows[] = array(array('data' => $machine_name, 'class' => $classes), array('data' => $description, 'class' => $classes)); + $rows[] = array( + array('data' => check_plain($machine_name), 'class' => $classes), + array('data' => filter_xss_admin($description), 'class' => $classes), + ); } $classes = array(); @@ -887,16 +917,16 @@ function migrate_migration_info($form, $form_state, $group_name, $migration_name if (!is_null($source_field) && !isset($source_fields[$source_field])) { drupal_set_message(t('"!source" was used as source field in the "!destination" mapping but is not in list of source fields', array( - '!source' => $source_field, - '!destination' => $destination_field + '!source' => filter_xss_admin($source_field), + '!destination' => filter_xss_admin($destination_field), )), 'warning'); } if (!is_null($destination_field) && !isset($destination_fields[$destination_field])) { drupal_set_message(t('"!destination" was used as destination field in "!source" mapping but is not in list of destination fields', array( - '!source' => $source_field, - '!destination' => $destination_field)), + '!source' => filter_xss_admin($source_field), + '!destination' => filter_xss_admin($destination_field))), 'warning'); } $descriptions[$mapping->getIssueGroup()][] = $mapping; @@ -906,7 +936,7 @@ function migrate_migration_info($form, $form_state, $group_name, $migration_name foreach ($descriptions as $group => $mappings) { $form[$group] = array( '#type' => 'fieldset', - '#title' => t('Mapping: !group', array('!group' => $group)), + '#title' => t('Mapping: !group', array('!group' => filter_xss_admin($group))), '#group' => 'detail', '#attributes' => array('class' => array('migrate-mapping')), ); @@ -918,7 +948,7 @@ function migrate_migration_info($form, $form_state, $group_name, $migration_name } $issue_priority = $mapping->getIssuePriority(); if (!is_null($issue_priority)) { - $classes[] = 'migrate-priority-' . $issue_priority; + $classes[] = 'migrate-priority-' . drupal_html_class($issue_priority); $priority = MigrateFieldMapping::$priorities[$issue_priority]; $issue_pattern = $migration->getIssuePattern(); $issue_number = $mapping->getIssueNumber(); @@ -942,11 +972,11 @@ function migrate_migration_info($form, $form_state, $group_name, $migration_name $source_field = "$source_field"; } $row = array( - array('data' => $destination_field, 'class' => $classes), - array('data' => $source_field, 'class' => $classes), - array('data' => $default, 'class' => $classes), - array('data' => $mapping->getDescription(), 'class' => $classes), - array('data' => $priority, 'class' => $classes), + array('data' => filter_xss_admin($destination_field), 'class' => $classes), + array('data' => filter_xss_admin($source_field), 'class' => $classes), + array('data' => filter_xss_admin($default), 'class' => $classes), + array('data' => filter_xss_admin($mapping->getDescription()), 'class' => $classes), + array('data' => filter_xss_admin($priority), 'class' => $classes), ); $rows[] = $row; $classes = array(); @@ -973,7 +1003,7 @@ function migrate_migration_info($form, $form_state, $group_name, $migration_name */ function migrate_ui_edit_mappings($form, $form_state, $group_name, $migration_name) { - drupal_set_title(t('Edit !migration', array('!migration' => $migration_name))); + drupal_set_title(t('Edit !migration', array('!migration' => filter_xss_admin($migration_name)))); $form = array(); $form['#tree'] = TRUE; @@ -1014,6 +1044,9 @@ function migrate_ui_edit_mappings($form, $form_state, $group_name, '#suffix' => '', ); + // So the theme function knows whether to include the xpath column. + $form['field_mappings']['#is_xml_migration'] = is_a($migration, 'XMLMigration'); + $form['source_fields'] = array( '#type' => 'fieldset', '#title' => t('Source fields'), @@ -1045,10 +1078,14 @@ function migrate_ui_edit_mappings($form, $form_state, $group_name, else { $label_format = '!description'; } - $label = t($label_format, - array('!source_field' => $name, '!description' => $description)); - $short_label = t($label_format, - array('!source_field' => $name, '!description' => $short_description)); + $label = t($label_format, array( + '!source_field' => filter_xss_admin($name), + '!description' => filter_xss_admin($description), + )); + $short_label = t($label_format, array( + '!source_field' => filter_xss_admin($name), + '!description' => filter_xss_admin($short_description), + )); $dnm_value = 0; @@ -1171,6 +1208,16 @@ function migrate_ui_edit_mappings($form, $form_state, $group_name, '#options' => $source_migration_options, '#default_value' => $source_migration, ); + + if (is_a($mapping, 'MigrateXMLFieldMapping')) { + /** @var MigrateXMLFieldMapping $mapping */ + $form['field_mappings'][$name]['xpath'] = array( + '#type' => 'textfield', + '#default_value' => $mapping->getXpath(), + '#size' => 20, + ); + } + } } @@ -1213,7 +1260,7 @@ function migrate_ui_edit_mappings($form, $form_state, $group_name, } $form['dependencies'][$machine_name] = array( '#type' => 'select', - '#title' => $machine_name, + '#title' => check_plain($machine_name), '#default_value' => $default_value, '#options' => $dependency_options, ); @@ -1275,16 +1322,21 @@ function migrate_ui_edit_mappings_submit(&$form, &$form_state) { $field_mappings = array(); $default_values = array(); $issue_group_values = array(); + $xpaths = array(); $migration = Migration::getInstance($machine_name); if (is_a($migration, 'Migration')) { + $xml = is_a($migration, 'XMLMigration') ? TRUE : FALSE; $existing_mappings = $migration->getFieldMappings(); $coded_mappings = $migration->getCodedFieldMappings(); foreach ($form_state['values']['field_mappings'] as $destination_field => $info) { - // Treat an empty string for the default value as NULL. + // Treat empty strings as NULL. if ($info['default_value'] === '') { $info['default_value'] = NULL; } + if ($xml && $info['xpath'] === '') { + $info['xpath'] = NULL; + } // If this mapping matches a coded mapping but not a stored mapping, remove // it entirely (don't store it in the database) so the coded mapping is not @@ -1296,6 +1348,10 @@ function migrate_ui_edit_mappings_submit(&$form, &$form_state) { $coded_mappings[$destination_field]->getDefaultValue(); $coded_source_migration = $coded_mappings[$destination_field]->getSourceMigration(); + if ($xml) { + $coded_xpath = + $coded_mappings[$destination_field]->getXpath(); + } if ($info['mapping'] == '-1') { $info['mapping'] = NULL; } @@ -1312,6 +1368,7 @@ function migrate_ui_edit_mappings_submit(&$form, &$form_state) { if ($info['mapping'] == $coded_source_field && $info['default_value'] == $coded_default_value && $info['source_migration'] == $coded_source_migration && + (!$xml || ($xml && ($info['xpath'] == $coded_xpath))) && $dnm_matches) { continue; } @@ -1321,6 +1378,9 @@ function migrate_ui_edit_mappings_submit(&$form, &$form_state) { $default_values[$destination_field] = $info['default_value']; $source_migrations[$destination_field] = $info['source_migration']; $issue_group_values[$destination_field] = $info['issue_group']; + if ($xml) { + $xpaths[$destination_field] = $info['xpath']; + } } foreach ($field_mappings as $destination_field => $source_field) { @@ -1342,10 +1402,12 @@ function migrate_ui_edit_mappings_submit(&$form, &$form_state) { $mapping = NULL; if (isset($existing_mappings[$destination_field]) && $issue_group_values[$destination_field] != 0) { + /** @var MigrateFieldMapping $old_mapping */ $old_mapping = $existing_mappings[$destination_field]; if ($source_field == $old_mapping->getSourceField() && $default_values[$destination_field] == $old_mapping->getDefaultValue() && - $source_migrations[$destination_field] == $old_mapping->getSourceMigration()) { + $source_migrations[$destination_field] == $old_mapping->getSourceMigration() && + (!$xml || ($xml && ($xpaths[$destination_field] == $old_mapping->getXpath())))) { // First, if this mapping matches a previously-stored mapping, we want to // preserve it as it was originally stored. if ($old_mapping->getMappingSource() == @@ -1362,8 +1424,11 @@ function migrate_ui_edit_mappings_submit(&$form, &$form_state) { // We're not skipping this mapping, or preserving an old one, so create the // new mapping. if (!$mapping) { - $mapping = new MigrateFieldMapping($destination_field, $source_field); + $mapping = _migrate_ui_get_mapping_object($migration, $destination_field, $source_field); $mapping->defaultValue($default); + if ($xml && $xpaths[$destination_field]) { + $mapping->xpath($xpaths[$destination_field]); + } } if ($issue_group_values[$destination_field]) { @@ -1394,7 +1459,7 @@ function migrate_ui_edit_mappings_submit(&$form, &$form_state) { // If it is marked DNM in the UI, but is not ignored in the code, // generate a DNM mapping. if ($value && !$code_ignored) { - $mapping = new MigrateFieldMapping(NULL, $source_field); + $mapping = _migrate_ui_get_mapping_object($migration, NULL, $source_field); $mapping->issueGroup(t('DNM')); $arguments['field_mappings'][] = $mapping; } @@ -1410,7 +1475,7 @@ function migrate_ui_edit_mappings_submit(&$form, &$form_state) { } } if (!$mapping_found) { - $mapping = new MigrateFieldMapping(NULL, $source_field); + $mapping = _migrate_ui_get_mapping_object($migration, NULL, $source_field); $arguments['field_mappings'][] = $mapping; } } @@ -1436,6 +1501,22 @@ function migrate_ui_edit_mappings_submit(&$form, &$form_state) { "admin/content/migrate/groups/$group_name/$machine_name"; } +/** + * Create a field mapping object of the appropriate class. + * + * @param $migration + * + * @return MigrateFieldMapping + */ +function _migrate_ui_get_mapping_object($migration, $destination, $source) { + if (is_a($migration, 'XMLMigration')) { + return new MigrateXMLFieldMapping($destination, $source); + } + else { + return new MigrateFieldMapping($destination, $source); + } +} + /** * Revert callback for the edit mappings form. Remove any field mappings that * were defined through the UI. @@ -1469,6 +1550,9 @@ function theme_migrate_ui_field_mapping_form($variables) { if (!empty($elements)) { $header = array(t('DNM'), t('Destination field'), t('Source field'), t('Default value'), t('Source migration')); + if (!empty($form['#is_xml_migration'])) { + $header[] = t('Xpath'); + } $rows = array(); foreach ($elements as $mapping_key) { $row = array(); @@ -1479,6 +1563,9 @@ function theme_migrate_ui_field_mapping_form($variables) { $row[] = drupal_render($form[$mapping_key]['mapping']); $row[] = drupal_render($form[$mapping_key]['default_value']); $row[] = drupal_render($form[$mapping_key]['source_migration']); + if (!empty($form['#is_xml_migration'])) { + $row[] = drupal_render($form[$mapping_key]['xpath']); + } $rows[] = $row; } $output .= theme('table', array('header' => $header, 'rows' => $rows)); @@ -1536,7 +1623,11 @@ function migrate_ui_messages($group_name, $migration_name) { $header = array(); // Add a table header for each source key in the migration's map. foreach ($source_key as $key => $map_info) { - $header[] = array('data' => $map_info['description'], 'field' => $source_key_map[$key], 'sort' => 'asc'); + $header[] = array( + 'data' => filter_xss_admin($map_info['description']), + 'field' => $source_key_map[$key], + 'sort' => 'asc', + ); } $header[] = array('data' => t('Level'), 'field' => 'level'); @@ -1558,12 +1649,18 @@ function migrate_ui_messages($group_name, $migration_name) { // Add a table column for each source key. foreach ($source_key_map_flipped as $source_key => $source_field) { $row[] = array( - 'data' => $message->{$source_key}, + 'data' => filter_xss_admin($message->{$source_key}), 'class' => $classes, ); } - $row[] = array('data' => $migration->getMessageLevelName($message->level), 'class' => $classes); - $row[] = array('data' => $message->message, 'class' => $classes); + $row[] = array( + 'data' => filter_xss_admin($migration->getMessageLevelName($message->level)), + 'class' => $classes, + ); + $row[] = array( + 'data' => filter_xss_admin($message->message), + 'class' => $classes, + ); $rows[] = $row; @@ -1642,7 +1739,10 @@ function migrate_ui_configure_form($form, &$form_state) { if (!class_exists($row->class_name)) { $migrations[] = $row->machine_name; $migration_list .= '
  • ' . t('!migration (class !class)', - array('!migration' => $row->machine_name, '!class' => $row->class_name)) . "
  • \n"; + array( + '!migration' => filter_xss_admin($row->machine_name), + '!class' => filter_xss_admin($row->class_name), + )) . "\n"; } } @@ -1680,7 +1780,7 @@ function migrate_ui_configure_form($form, &$form_state) { // Configure background imports if the drush command has been set. $drush_path = trim(variable_get('migrate_drush_path', '')); $drush_validated = FALSE; - if ($drush_path) { + if ($drush_path && is_executable($drush_path)) { // Try running a drush status command to verify it's properly configured. $uri = $GLOBALS['base_url']; $uid = $GLOBALS['user']->uid; @@ -1818,9 +1918,9 @@ function migrate_ui_configure_form($form, &$form_state) { ->condition('type', 'class') ->execute() ->fetchField(); - $row['module'] = $module; - $row['class'] = $class_name; - $row['types'] = implode(', ', $handler->getTypesHandled()); + $row['module'] = check_plain($module); + $row['class'] = check_plain($class_name); + $row['types'] = filter_xss_admin(implode(', ', $handler->getTypesHandled())); $default_values[$class_name] = !in_array($class_name, $disabled); $rows[$class_name] = $row; } @@ -1855,9 +1955,9 @@ function migrate_ui_configure_form($form, &$form_state) { ->condition('type', 'class') ->execute() ->fetchField(); - $row['module'] = $module; - $row['class'] = $class_name; - $row['types'] = implode(', ', $handler->getTypesHandled()); + $row['module'] = check_plain($module); + $row['class'] = check_plain($class_name); + $row['types'] = filter_xss_admin(implode(', ', $handler->getTypesHandled())); $default_values[$class_name] = !in_array($class_name, $disabled); $rows[$class_name] = $row; } diff --git a/sites/all/modules/contrib/migrate/migrate/plugins/destinations/comment.inc b/sites/all/modules/contrib/migrate/migrate/plugins/destinations/comment.inc index caca29ab..8bfecd25 100644 --- a/sites/all/modules/contrib/migrate/migrate/plugins/destinations/comment.inc +++ b/sites/all/modules/contrib/migrate/migrate/plugins/destinations/comment.inc @@ -216,7 +216,7 @@ class MigrateDestinationComment extends MigrateDestinationEntity { } // Validate field data prior to saving. - field_attach_validate('comment', $comment); + MigrateDestinationEntity::fieldAttachValidate('comment', $comment); migrate_instrument_start('comment_save'); comment_save($comment); diff --git a/sites/all/modules/contrib/migrate/migrate/plugins/destinations/entity.inc b/sites/all/modules/contrib/migrate/migrate/plugins/destinations/entity.inc index 4e3540e3..e14c5d1e 100644 --- a/sites/all/modules/contrib/migrate/migrate/plugins/destinations/entity.inc +++ b/sites/all/modules/contrib/migrate/migrate/plugins/destinations/entity.inc @@ -176,4 +176,34 @@ abstract class MigrateDestinationEntity extends MigrateDestination { } } } + + /** + * Perform field validation against the field data in an entity. Wraps + * field_attach_validate to handle exceptions cleanly and provide maximum + * information for identifying the cause of validation errors. + * + * @param $entity_type + * The type of $entity; e.g. 'node' or 'user'. + * @param $entity + * The entity with fields to validate. + */ + static public function fieldAttachValidate($entity_type, $entity) { + try { + field_attach_validate($entity_type, $entity); + } + catch (FieldValidationException $e) { + $migration = Migration::currentMigration(); + foreach ($e->errors as $field_name => $field_errors) { + foreach ($field_errors as $langcode => $errors) { + foreach ($errors as $delta => $error_list) { + foreach ($error_list as $index => $error) { + $message = $error['message']; + $migration->saveMessage(t('Field validation error for !field_name: !message', + array('!field_name' => $field_name, '!message' => $message))); + } + } + } + } + } + } } diff --git a/sites/all/modules/contrib/migrate/migrate/plugins/destinations/fields.inc b/sites/all/modules/contrib/migrate/migrate/plugins/destinations/fields.inc index 69dd56e1..6b012066 100644 --- a/sites/all/modules/contrib/migrate/migrate/plugins/destinations/fields.inc +++ b/sites/all/modules/contrib/migrate/migrate/plugins/destinations/fields.inc @@ -342,8 +342,11 @@ class MigrateTextFieldHandler extends MigrateFieldHandler { $fields['format'] = t('Subfield: Text format for the field', array('@doc' => 'http://drupal.org/node/1224042#format')); } - $fields['language'] = t('Subfield: Language for the field', - array('@doc' => 'http://drupal.org/node/1224042#language')); + $field = field_info_field($instance['field_name']); + if (field_is_translatable($instance['entity_type'], $field)) { + $fields['language'] = t('Subfield: Language for the field', + array('@doc' => 'http://drupal.org/node/1224042#language')); + } return $fields; } @@ -469,7 +472,7 @@ class MigrateTaxonomyTermReferenceFieldHandler extends MigrateFieldHandler { else { $arguments = array(); } - if (empty($values[0])) { + if (count($values) == 1 && empty($values[0])) { $values = array(); } @@ -530,7 +533,7 @@ class MigrateTaxonomyTermReferenceFieldHandler extends MigrateFieldHandler { // This term is being created with no fields, but we should still call // field_attach_validate() before saving, as that invokes // hook_field_attach_validate(). - field_attach_validate('taxonomy_term', $new_term); + MigrateDestinationEntity::fieldAttachValidate('taxonomy_term', $new_term); taxonomy_term_save($new_term); $tids[] = $new_term->tid; @@ -588,9 +591,13 @@ abstract class MigrateFileFieldBaseHandler extends MigrateFieldHandler { $fields = array( 'file_class' => t('Option: Implementation of MigrateFile to use', array('@doc' => 'http://drupal.org/node/1540106#file_class')), - 'language' => t('Subfield: Language for the field'), ); + $field = field_info_field($instance['field_name']); + if (field_is_translatable($instance['entity_type'], $field)) { + $fields['language'] = t('Subfield: Language for the field'); + } + // If we can identify the file class mapped to this field, pick up the // subfields specific to that class. if ($migration) { diff --git a/sites/all/modules/contrib/migrate/migrate/plugins/destinations/file.inc b/sites/all/modules/contrib/migrate/migrate/plugins/destinations/file.inc index e351df2b..d9bd901d 100644 --- a/sites/all/modules/contrib/migrate/migrate/plugins/destinations/file.inc +++ b/sites/all/modules/contrib/migrate/migrate/plugins/destinations/file.inc @@ -389,15 +389,16 @@ class MigrateFileUri extends MigrateFile { // Perform the copy operation, with a cleaned-up path. $this->sourcePath = self::urlencode($this->sourcePath); } - if (!@copy($this->sourcePath, $destination)) { - $migration = Migration::currentMigration(); - $migration->saveMessage(t('The specified file %file could not be copied to %destination.', - array('%file' => $this->sourcePath, '%destination' => $destination))); - return FALSE; - } - else { + try { + copy($this->sourcePath, $destination); return TRUE; } + catch (Exception $e) { + $migration = Migration::currentMigration(); + $migration->saveMessage(t('The specified file %file could not be copied to %destination: "%exception_msg"', + array('%file' => $this->sourcePath, '%destination' => $destination, '%exception_msg' => $e->getMessage()))); + return FALSE; + } } /** diff --git a/sites/all/modules/contrib/migrate/migrate/plugins/destinations/node.inc b/sites/all/modules/contrib/migrate/migrate/plugins/destinations/node.inc index 59a9513e..d5a38d23 100644 --- a/sites/all/modules/contrib/migrate/migrate/plugins/destinations/node.inc +++ b/sites/all/modules/contrib/migrate/migrate/plugins/destinations/node.inc @@ -263,7 +263,7 @@ class MigrateDestinationNode extends MigrateDestinationEntity { } // Validate field data prior to saving. - field_attach_validate('node', $node); + MigrateDestinationEntity::fieldAttachValidate('node', $node); migrate_instrument_start('node_save'); node_save($node); diff --git a/sites/all/modules/contrib/migrate/migrate/plugins/destinations/term.inc b/sites/all/modules/contrib/migrate/migrate/plugins/destinations/term.inc index db4fe24d..f01c55fb 100644 --- a/sites/all/modules/contrib/migrate/migrate/plugins/destinations/term.inc +++ b/sites/all/modules/contrib/migrate/migrate/plugins/destinations/term.inc @@ -258,7 +258,7 @@ class MigrateDestinationTerm extends MigrateDestinationEntity { } // Validate field data prior to saving. - field_attach_validate('taxonomy_term', $term); + MigrateDestinationEntity::fieldAttachValidate('taxonomy_term', $term); migrate_instrument_start('taxonomy_term_save'); $status = taxonomy_term_save($term); diff --git a/sites/all/modules/contrib/migrate/migrate/plugins/destinations/user.inc b/sites/all/modules/contrib/migrate/migrate/plugins/destinations/user.inc index b3e9fcd3..448e40c8 100644 --- a/sites/all/modules/contrib/migrate/migrate/plugins/destinations/user.inc +++ b/sites/all/modules/contrib/migrate/migrate/plugins/destinations/user.inc @@ -231,7 +231,7 @@ class MigrateDestinationUser extends MigrateDestinationEntity { } // Validate field data prior to saving. - field_attach_validate('user', $account); + MigrateDestinationEntity::fieldAttachValidate('user', $account); migrate_instrument_start('user_save'); $newaccount = user_save($old_account, (array)$account); diff --git a/sites/all/modules/contrib/migrate/migrate/plugins/sources/list.inc b/sites/all/modules/contrib/migrate/migrate/plugins/sources/list.inc index 7a62fa28..15e3b831 100644 --- a/sites/all/modules/contrib/migrate/migrate/plugins/sources/list.inc +++ b/sites/all/modules/contrib/migrate/migrate/plugins/sources/list.inc @@ -198,8 +198,8 @@ class MigrateSourceList extends MigrateSource { list(, $id) = each($ids); $row->$key_name = $id; } + break; } - break; } return $row; } diff --git a/sites/all/modules/contrib/migrate/migrate/plugins/sources/multiitems.inc b/sites/all/modules/contrib/migrate/migrate/plugins/sources/multiitems.inc index c74f9c53..ce906b2f 100644 --- a/sites/all/modules/contrib/migrate/migrate/plugins/sources/multiitems.inc +++ b/sites/all/modules/contrib/migrate/migrate/plugins/sources/multiitems.inc @@ -187,8 +187,8 @@ class MigrateSourceMultiItems extends MigrateSource { $sourceKey = $this->activeMap->getSourceKey(); $key_name = key($sourceKey); $row->$key_name = $id; + break; } - break; } return $row; } diff --git a/sites/all/modules/contrib/migrate/migrate/plugins/sources/sql.inc b/sites/all/modules/contrib/migrate/migrate/plugins/sources/sql.inc index fbcd05a8..eb3077b3 100644 --- a/sites/all/modules/contrib/migrate/migrate/plugins/sources/sql.inc +++ b/sites/all/modules/contrib/migrate/migrate/plugins/sources/sql.inc @@ -376,14 +376,29 @@ class MigrateSourceSQL extends MigrateSource { // 3. If we are using highwater marks, also include rows above the mark. // But, include all rows if the highwater mark is not set. if (isset($this->highwaterField['name']) && $this->activeMigration->getHighwater() !== '') { - if (isset($this->highwaterField['alias'])) { - $highwater = $this->highwaterField['alias'] . '.' . $this->highwaterField['name']; + // But, if there are any existing items marked as needing update which + // fall below the highwater mark, and map_joinable is FALSE, those + // items will be skipped. Thus, in that case do not add the highwater + // optimization to the query. + $add_highwater_condition = TRUE; + if (!$this->mapJoinable) { + $count_needs_update = db_query('SELECT COUNT(*) FROM {' . + $this->activeMap->getQualifiedMapTable() . '} WHERE needs_update = 1') + ->fetchField(); + if ($count_needs_update > 0) { + $add_highwater_condition = FALSE; + } } - else { - $highwater = $this->highwaterField['name']; + if ($add_highwater_condition) { + if (isset($this->highwaterField['alias'])) { + $highwater = $this->highwaterField['alias'] . '.' . $this->highwaterField['name']; + } + else { + $highwater = $this->highwaterField['name']; + } + $conditions->condition($highwater, $this->activeMigration->getHighwater(), '>'); + $condition_added = TRUE; } - $conditions->condition($highwater, $this->activeMigration->getHighwater(), '>'); - $condition_added = TRUE; } if ($condition_added) { $this->query->condition($conditions); diff --git a/sites/all/modules/contrib/migrate/migrate/plugins/sources/sqlmap.inc b/sites/all/modules/contrib/migrate/migrate/plugins/sources/sqlmap.inc index 0ee2d102..8899fc14 100644 --- a/sites/all/modules/contrib/migrate/migrate/plugins/sources/sqlmap.inc +++ b/sites/all/modules/contrib/migrate/migrate/plugins/sources/sqlmap.inc @@ -69,6 +69,11 @@ class MigrateSQLMap extends MigrateMap { */ protected $ensured; + /** + * Provide caching for Source or Desination Map Lookups. + */ + protected $cacheMapLookups; + /** * Constructor. * @@ -92,6 +97,13 @@ class MigrateSQLMap extends MigrateMap { $this->trackLastImported = TRUE; } + if (isset($options['cache_map_lookups'])) { + $this->cacheMapLookups = $options['cache_map_lookups']; + } + else { + $this->cacheMapLookups = FALSE; + } + $this->connection = Database::getConnection('default', $connection_key); // Default generated table names, limited to 63 characters @@ -302,6 +314,16 @@ class MigrateSQLMap extends MigrateMap { */ public function lookupSourceID(array $destination_id) { migrate_instrument_start('lookupSourceID'); + // Try a cache lookup if enabled. + if ($this->cacheMapLookups) { + $cache = &drupal_static($this->mapTable . '_sourceIDCache'); + $serialized = json_encode($destination_id); + if (isset($cache[$serialized])) { + migrate_instrument_stop('lookupSourceID'); + return $cache[$serialized]; + } + } + $query = $this->connection->select($this->mapTable, 'map') ->fields('map', $this->sourceKeyMap); foreach ($this->destinationKeyMap as $key_name) { @@ -309,6 +331,12 @@ class MigrateSQLMap extends MigrateMap { } $result = $query->execute(); $source_id = $result->fetchAssoc(); + + // Store the id in a cache if enabled. + if ($this->cacheMapLookups) { + $cache[$serialized] = $destination_id; + } + migrate_instrument_stop('lookupSourceID'); return $source_id; } @@ -324,6 +352,16 @@ class MigrateSQLMap extends MigrateMap { */ public function lookupDestinationID(array $source_id) { migrate_instrument_start('lookupDestinationID'); + // Try a cache lookup if enabled. + if ($this->cacheMapLookups) { + $cache = &drupal_static($this->mapTable . '_destinationIDCache'); + $serialized = json_encode($source_id); + if (isset($cache[$serialized])) { + migrate_instrument_stop('lookupDestinationID'); + return $cache[$serialized]; + } + } + $query = $this->connection->select($this->mapTable, 'map') ->fields('map', $this->destinationKeyMap); foreach ($this->sourceKeyMap as $key_name) { @@ -331,10 +369,15 @@ class MigrateSQLMap extends MigrateMap { } $result = $query->execute(); $destination_id = $result->fetchAssoc(); + + // Store the id in a cache if enabled. + if ($this->cacheMapLookups) { + $cache[$serialized] = $destination_id; + } + migrate_instrument_stop('lookupDestinationID'); return $destination_id; } - /** * Called upon import of one record, we record a mapping from the source key * to the destination key. Also may be called, setting the third parameter to diff --git a/sites/all/modules/contrib/migrate/migrate/tests/import/options.test b/sites/all/modules/contrib/migrate/migrate/tests/import/options.test index 14b321b2..cde36c32 100644 --- a/sites/all/modules/contrib/migrate/migrate/tests/import/options.test +++ b/sites/all/modules/contrib/migrate/migrate/tests/import/options.test @@ -40,17 +40,13 @@ class MigrateImportOptionsTest extends DrupalWebTestCase { $result = $migration->processImport($options); - $this->verbose(print_r($timers, 1)); $successes = $migration->importedCount(); - $this->verbose("Total successes: {$successes}"); $assertion = format_plural($limit, 'The migration successfully processed 1 item.', 'The migration successfully processed @count items.'); $this->assertEqual($limit, $successes, $assertion); $prepare_row_count = $timers['BeerTermMigration prepareRow']['count']; - $this->verbose("prepareRow() count: {$prepare_row_count}"); $processed = $migration->processedCount(); - $this->verbose("Total processed count: {$processed}"); $assertion = format_plural($processed, 'The migration executed processRow() on 1 item.', 'The migration executed processRow() on @count items.'); $this->assertEqual($prepare_row_count, $processed, $assertion); diff --git a/sites/all/modules/contrib/views/views_bulk_operations/actions/archive.action.inc b/sites/all/modules/contrib/views/views_bulk_operations/actions/archive.action.inc index 7667e493..f0055274 100644 --- a/sites/all/modules/contrib/views/views_bulk_operations/actions/archive.action.inc +++ b/sites/all/modules/contrib/views/views_bulk_operations/actions/archive.action.inc @@ -18,6 +18,7 @@ function views_bulk_operations_archive_action_info() { // "Create an advanced action" dropdown on admin/config/system/actions. 'configurable' => FALSE, 'vbo_configurable' => TRUE, + 'behavior' => array('views_property'), 'triggers' => array('any'), ); } diff --git a/sites/all/modules/contrib/views/views_bulk_operations/actions/book.action.inc b/sites/all/modules/contrib/views/views_bulk_operations/actions/book.action.inc index add893ef..ad8fa9c4 100644 --- a/sites/all/modules/contrib/views/views_bulk_operations/actions/book.action.inc +++ b/sites/all/modules/contrib/views/views_bulk_operations/actions/book.action.inc @@ -13,11 +13,13 @@ function views_bulk_operations_book_action_info() { 'label' => t('Move to book'), 'configurable' => TRUE, 'behavior' => array('changes_property'), + 'triggers' => array('any'), ); $actions['views_bulk_operations_remove_from_book_action'] = array( 'type' => 'node', 'label' => t('Remove from book'), 'configurable' => FALSE, + 'triggers' => array('any'), ); } diff --git a/sites/all/modules/contrib/views/views_bulk_operations/actions/delete.action.inc b/sites/all/modules/contrib/views/views_bulk_operations/actions/delete.action.inc index 1106ab04..52c72d2f 100644 --- a/sites/all/modules/contrib/views/views_bulk_operations/actions/delete.action.inc +++ b/sites/all/modules/contrib/views/views_bulk_operations/actions/delete.action.inc @@ -19,6 +19,7 @@ function views_bulk_operations_delete_action_info() { 'label' => t('Delete revision'), 'configurable' => FALSE, 'behavior' => array('deletes_property'), + 'triggers' => array('any'), ), ); } diff --git a/sites/all/modules/contrib/views/views_bulk_operations/actions/modify.action.inc b/sites/all/modules/contrib/views/views_bulk_operations/actions/modify.action.inc index 71b6181c..301b17b2 100644 --- a/sites/all/modules/contrib/views/views_bulk_operations/actions/modify.action.inc +++ b/sites/all/modules/contrib/views/views_bulk_operations/actions/modify.action.inc @@ -82,6 +82,11 @@ function views_bulk_operations_modify_action($entity, $context) { // The wrapper will automatically modify $entity itself. $wrapper = entity_metadata_wrapper($context['entity_type'], $entity); foreach ($context['selected']['properties'] as $key) { + if (!$wrapper->$key->access('update')) { + // No access. + continue; + } + if (in_array($key, $context['append']['properties'])) { $old_values = $wrapper->$key->value(); $wrapper->$key->set($context['properties'][$key]); @@ -134,7 +139,7 @@ function views_bulk_operations_modify_action_form($context, &$form_state) { if (!empty($properties)) { $form['properties'] = array( '#type' => 'fieldset', - '#title' => 'Properties', + '#title' => t('Properties'), ); $form['properties']['show_value'] = array( '#suffix' => '
    ', @@ -298,7 +303,7 @@ function views_bulk_operations_modify_action_form($context, &$form_state) { $token_type = str_replace('_', '-', $entity_type); $form['tokens'] = array( '#type' => 'fieldset', - '#title' => 'Available tokens', + '#title' => t('Available tokens'), '#collapsible' => TRUE, '#collapsed' => TRUE, '#weight' => 998, diff --git a/sites/all/modules/contrib/views/views_bulk_operations/actions/user_cancel.action.inc b/sites/all/modules/contrib/views/views_bulk_operations/actions/user_cancel.action.inc index ea8e8ee1..147d2920 100644 --- a/sites/all/modules/contrib/views/views_bulk_operations/actions/user_cancel.action.inc +++ b/sites/all/modules/contrib/views/views_bulk_operations/actions/user_cancel.action.inc @@ -10,6 +10,7 @@ function views_bulk_operations_user_cancel_action_info() { 'label' => t('Cancel user account'), 'configurable' => TRUE, 'behavior' => array('deletes_property'), + 'triggers' => array('any'), )); } diff --git a/sites/all/modules/contrib/views/views_bulk_operations/actions/user_roles.action.inc b/sites/all/modules/contrib/views/views_bulk_operations/actions/user_roles.action.inc index 394597c3..616f13ce 100644 --- a/sites/all/modules/contrib/views/views_bulk_operations/actions/user_roles.action.inc +++ b/sites/all/modules/contrib/views/views_bulk_operations/actions/user_roles.action.inc @@ -45,24 +45,19 @@ function views_bulk_operations_user_roles_action_submit($form, $form_state) { ); } -function views_bulk_operations_user_roles_action(&$user, $context) { - $roles = $user->roles; - $selected = (is_array($context['add_roles']) ? $context['add_roles'] : array()) + - (is_array($context['remove_roles']) ? $context['remove_roles'] : array()); - $result = db_query("SELECT rid, name FROM {role} WHERE rid IN (:selected)", array(':selected' => array_keys($selected))); - foreach ($result as $role) { - if (isset($context['add_roles'][$role->rid])) { - $add_roles[$role->rid] = $role->name; - } - if (isset($context['remove_roles'][$role->rid])) { - $remove_roles[$role->rid] = $role->name; - } +function views_bulk_operations_user_roles_action($user, $context) { + $wrapper = entity_metadata_wrapper('user', $user); + if (!$wrapper->roles->access("update")) { + // No access. + return; } - if (!empty($add_roles)) { - $roles += $add_roles; + $roles = $wrapper->roles->value(); + if (is_array($context['add_roles'])) { + $roles = array_merge($roles, $context['add_roles']); } - if (!empty($remove_roles)) { - $roles = array_diff($roles, $remove_roles); + if (is_array($context['remove_roles'])) { + $roles = array_diff($roles, $context['remove_roles']); } - user_save($user, array('roles' => $roles)); + $wrapper->roles->set($roles); + $wrapper->save(); } diff --git a/sites/all/modules/contrib/views/views_bulk_operations/actions_permissions.info b/sites/all/modules/contrib/views/views_bulk_operations/actions_permissions.info index e57077b0..658d1adc 100644 --- a/sites/all/modules/contrib/views/views_bulk_operations/actions_permissions.info +++ b/sites/all/modules/contrib/views/views_bulk_operations/actions_permissions.info @@ -3,9 +3,9 @@ description = Provides permission-based access control for actions. Used by View package = Administration core = 7.x -; Information added by Drupal.org packaging script on 2013-12-23 -version = "7.x-3.2" +; Information added by Drupal.org packaging script on 2015-07-01 +version = "7.x-3.3" core = "7.x" project = "views_bulk_operations" -datestamp = "1387798183" +datestamp = "1435764542" diff --git a/sites/all/modules/contrib/views/views_bulk_operations/js/views_bulk_operations.js b/sites/all/modules/contrib/views/views_bulk_operations/js/views_bulk_operations.js index aeeecc6f..ca76df86 100644 --- a/sites/all/modules/contrib/views/views_bulk_operations/js/views_bulk_operations.js +++ b/sites/all/modules/contrib/views/views_bulk_operations/js/views_bulk_operations.js @@ -46,21 +46,23 @@ }); // Set up the ability to click anywhere on the row to select it. - $('.views-table tbody tr', form).click(function(event) { - if (event.target.tagName.toLowerCase() != 'input' && event.target.tagName.toLowerCase() != 'a') { - $('input[id^="edit-views-bulk-operations"]:not(:disabled)', this).each(function() { - var checked = this.checked; - // trigger() toggles the checkmark *after* the event is set, - // whereas manually clicking the checkbox toggles it *beforehand*. - // that's why we manually set the checkmark first, then trigger the - // event (so that listeners get notified), then re-set the checkmark - // which the trigger will have toggled. yuck! - this.checked = !checked; - $(this).trigger('click'); - this.checked = !checked; - }); - } - }); + if (Drupal.settings.vbo.row_clickable) { + $('.views-table tbody tr', form).click(function(event) { + if (event.target.tagName.toLowerCase() != 'input' && event.target.tagName.toLowerCase() != 'a') { + $('input[id^="edit-views-bulk-operations"]:not(:disabled)', this).each(function() { + var checked = this.checked; + // trigger() toggles the checkmark *after* the event is set, + // whereas manually clicking the checkbox toggles it *beforehand*. + // that's why we manually set the checkmark first, then trigger the + // event (so that listeners get notified), then re-set the checkmark + // which the trigger will have toggled. yuck! + this.checked = !checked; + $(this).trigger('click'); + this.checked = !checked; + }); + } + }); + } } Drupal.vbo.tableSelectAllPages = function(form) { diff --git a/sites/all/modules/contrib/views/views_bulk_operations/plugins/operation_types/action.class.php b/sites/all/modules/contrib/views/views_bulk_operations/plugins/operation_types/action.class.php index c74d90c3..f3e60a30 100644 --- a/sites/all/modules/contrib/views/views_bulk_operations/plugins/operation_types/action.class.php +++ b/sites/all/modules/contrib/views/views_bulk_operations/plugins/operation_types/action.class.php @@ -20,7 +20,7 @@ class ViewsBulkOperationsAction extends ViewsBulkOperationsBaseOperation { */ public function getAccessMask() { // Assume edit by default. - if (!isset($this->operationInfo['behavior'])) { + if (empty($this->operationInfo['behavior'])) { $this->operationInfo['behavior'] = array('changes_property'); } diff --git a/sites/all/modules/contrib/views/views_bulk_operations/plugins/operation_types/rules_component.class.php b/sites/all/modules/contrib/views/views_bulk_operations/plugins/operation_types/rules_component.class.php index 624c850b..c40da8b2 100644 --- a/sites/all/modules/contrib/views/views_bulk_operations/plugins/operation_types/rules_component.class.php +++ b/sites/all/modules/contrib/views/views_bulk_operations/plugins/operation_types/rules_component.class.php @@ -124,6 +124,8 @@ class ViewsBulkOperationsRulesComponent extends ViewsBulkOperationsBaseOperation else { $element = rules_action('component_' . $this->operationInfo['parameters']['component_key']); } - $element->execute($data); + $wrapper_type = is_array($data) ? "list<{$this->entityType}>" : $this->entityType; + $wrapper = entity_metadata_wrapper($wrapper_type, $data); + $element->execute($wrapper); } } diff --git a/sites/all/modules/contrib/views/views_bulk_operations/views/views_bulk_operations_handler_field_operations.inc b/sites/all/modules/contrib/views/views_bulk_operations/views/views_bulk_operations_handler_field_operations.inc index 9bb983f7..61886d48 100644 --- a/sites/all/modules/contrib/views/views_bulk_operations/views/views_bulk_operations_handler_field_operations.inc +++ b/sites/all/modules/contrib/views/views_bulk_operations/views/views_bulk_operations_handler_field_operations.inc @@ -55,6 +55,7 @@ class views_bulk_operations_handler_field_operations extends views_handler_field 'contains' => array( 'display_type' => array('default' => 0), 'enable_select_all_pages' => array('default' => TRUE), + 'row_clickable' => array('default' => TRUE), 'force_single' => array('default' => FALSE), 'entity_load_capacity' => array('default' => 10), 'skip_batching' => array('default' => 0), @@ -63,11 +64,26 @@ class views_bulk_operations_handler_field_operations extends views_handler_field $options['vbo_operations'] = array( 'default' => array(), 'unpack_translatable' => 'unpack_operations', + 'export' => 'export_vbo_operations', ); return $options; } + function export_vbo_operations($indent, $prefix, $storage, $option, $definition, $parents) { + // Anti-recursion, since we use the parent export helper. + unset($definition['export']); + + // Find and remove all unselected/disabled operations. + foreach ($storage['vbo_operations'] as $operation_id => $operation) { + if (empty($operation['selected'])) { + unset($storage['vbo_operations'][$operation_id]); + } + } + + return parent::export_option($indent, $prefix, $storage, $option, $definition, $parents); + } + function unpack_operations(&$translatable, $storage, $option, $definition, $parents, $keys) { $translatable[] = array( 'value' => t('- Choose an operation -'), @@ -107,6 +123,12 @@ class views_bulk_operations_handler_field_operations extends views_handler_field '#default_value' => $this->options['vbo_settings']['enable_select_all_pages'], '#description' => t('Check this box to enable the ability to select all items on all pages.'), ); + $form['vbo_settings']['row_clickable'] = array( + '#type' => 'checkbox', + '#title' => t('Make the whole row clickable'), + '#default_value' => $this->options['vbo_settings']['row_clickable'], + '#description' => t('Check this box to select an item when its row has been clicked. Requires JavaScript.'), + ); $form['vbo_settings']['force_single'] = array( '#type' => 'checkbox', '#title' => t('Force single'), diff --git a/sites/all/modules/contrib/views/views_bulk_operations/views_bulk_operations.info b/sites/all/modules/contrib/views/views_bulk_operations/views_bulk_operations.info index 2f03a14a..8fbdffaa 100644 --- a/sites/all/modules/contrib/views/views_bulk_operations/views_bulk_operations.info +++ b/sites/all/modules/contrib/views/views_bulk_operations/views_bulk_operations.info @@ -9,9 +9,9 @@ php = 5.2.9 files[] = plugins/operation_types/base.class.php files[] = views/views_bulk_operations_handler_field_operations.inc -; Information added by Drupal.org packaging script on 2013-12-23 -version = "7.x-3.2" +; Information added by Drupal.org packaging script on 2015-07-01 +version = "7.x-3.3" core = "7.x" project = "views_bulk_operations" -datestamp = "1387798183" +datestamp = "1435764542" diff --git a/sites/all/modules/contrib/views/views_bulk_operations/views_bulk_operations.module b/sites/all/modules/contrib/views/views_bulk_operations/views_bulk_operations.module index b25b661c..e74eeb1d 100644 --- a/sites/all/modules/contrib/views/views_bulk_operations/views_bulk_operations.module +++ b/sites/all/modules/contrib/views/views_bulk_operations/views_bulk_operations.module @@ -41,21 +41,20 @@ function views_bulk_operations_load_action_includes() { // The list of VBO actions is fairly static, so it's hardcoded for better // performance (hitting the filesystem with file_scan_directory(), and then // caching the result has its cost). - $path = drupal_get_path('module', 'views_bulk_operations') . '/actions/'; $files = array( - 'archive.action.inc', - 'argument_selector.action.inc', - 'book.action.inc', - 'delete.action.inc', - 'modify.action.inc', - 'script.action.inc', - 'user_roles.action.inc', - 'user_cancel.action.inc', + 'archive.action', + 'argument_selector.action', + 'book.action', + 'delete.action', + 'modify.action', + 'script.action', + 'user_roles.action', + 'user_cancel.action', ); if (!$loaded) { foreach ($files as $file) { - include_once $path . $file; + module_load_include('inc', 'views_bulk_operations', 'actions/' . $file); } $loaded = TRUE; } @@ -77,7 +76,7 @@ function views_bulk_operations_load_action_includes() { function views_bulk_operations_cron() { db_delete('queue') ->condition('name', db_like('views_bulk_operations_active_queue_'), 'LIKE') - ->condition('created', REQUEST_TIME - 864000, '<') + ->condition('created', REQUEST_TIME - 86400, '<') ->execute(); } @@ -358,7 +357,7 @@ function views_bulk_operations_form_alter(&$form, &$form_state, $form_id) { */ function views_bulk_operations_views_post_build(&$view) { $vbo = _views_bulk_operations_get_field($view); - if ($vbo && $vbo->get_selected_operations() < 1) { + if ($vbo && count($vbo->get_selected_operations()) < 1) { $vbo->options['exclude'] = TRUE; } } @@ -420,7 +419,7 @@ function theme_views_bulk_operations_select_all($variables) { if ($enable_select_all_pages) { $form['select_all']['or'] = array( '#type' => 'markup', - '#markup' => 'OR', + '#markup' => '' . t('OR') . '', ); $form['select_all']['all_pages'] = array( '#type' => 'checkbox', @@ -443,6 +442,13 @@ function theme_views_bulk_operations_select_all($variables) { */ function views_bulk_operations_form($form, &$form_state, $vbo) { $form['#attached']['js'][] = drupal_get_path('module', 'views_bulk_operations') . '/js/views_bulk_operations.js'; + $form['#attached']['js'][] = array( + 'data' => array('vbo' => array( + 'row_clickable' => $vbo->get_vbo_option('row_clickable'), + )), + 'type' => 'setting', + ); + $form['#attached']['css'][] = drupal_get_path('module', 'views_bulk_operations') . '/css/views_bulk_operations.css'; // Wrap the form in a div with specific classes for JS targeting and theming. $class = 'vbo-views-form'; @@ -595,14 +601,23 @@ function views_bulk_operations_confirm_form($form, &$form_state, $view, $output) $operation = $form_state['operation']; $rows = $form_state['selection']; $query = drupal_get_query_parameters($_GET, array('q')); + $title = t('Are you sure you want to perform %operation on the selected items?', array('%operation' => $operation->label())); $form = confirm_form($form, - t('Are you sure you want to perform %operation on the selected items?', array('%operation' => $operation->label())), + $title, array('path' => $view->get_url(), 'query' => $query), theme('views_bulk_operations_confirmation', array('rows' => $rows, 'vbo' => $vbo, 'operation' => $operation, 'select_all_pages' => $form_state['select_all_pages'])) ); // Add VBO's submit handler to the Confirm button added by config_form(). $form['actions']['submit']['#submit'] = array('views_bulk_operations_form_submit'); + // We can't set the View title here as $view is just a copy of the original, + // and our settings changes won't "stick" for the first page load of the + // confirmation form. We also can't just call drupal_set_title() directly + // because our title will be clobbered by the actual View title later. So + // let's tuck the title away in the form for use later. + // @see views_bulk_operations_preprocess_views_view() + $form['#vbo_confirm_form_title'] = $title; + return $form; } @@ -618,7 +633,7 @@ function theme_views_bulk_operations_confirmation($variables) { // Load the entities from the current page, and show their titles. $entities = _views_bulk_operations_entity_load($entity_type, array_values($rows), $vbo->revision); foreach ($entities as $entity) { - $items[] = check_plain(_views_bulk_operations_entity_label($entity_type, $entity)); + $items[] = check_plain(entity_label($entity_type, $entity)); } // All rows on all pages have been selected, so show a count of additional items. if ($select_all_pages) { @@ -631,6 +646,29 @@ function theme_views_bulk_operations_confirmation($variables) { return $output; } +/** + * Implements hook_preprocess_page(). + * + * Hide action links on the configure and confirm pages. + */ +function views_bulk_operations_preprocess_page(&$variables) { + if (isset($_POST['select_all'], $_POST['operation'])) { + $variables['action_links'] = array(); + } +} + +/** + * Implements hook_preprocess_views_view(). + */ +function views_bulk_operations_preprocess_views_view($variables) { + // If we've stored a title for the confirmation form, retrieve it here and + // retitle the View. + // @see views_bulk_operations_confirm_form() + if (array_key_exists('rows', $variables) && is_array($variables['rows']) && array_key_exists('#vbo_confirm_form_title', $variables['rows'])) { + $variables['view']->set_title($variables['rows']['#vbo_confirm_form_title']); + } +} + /** * Goes through the submitted values, and returns * an array of selected rows, in the form of @@ -871,7 +909,7 @@ function views_bulk_operations_adjust_selection($queue_name, $operation, $option 'views_row' => array(), 'position' => array( 'current' => ++$context['sandbox']['progress'], - 'total' => $view->total_rows, + 'total' => $context['sandbox']['max'], ), ); // Some operations require full selected rows. @@ -1042,7 +1080,7 @@ function views_bulk_operations_queue_item_process($queue_item_data, &$log = NULL $arguments = array( '%operation' => $operation->label(), '@type' => $entity_type, - '%title' => _views_bulk_operations_entity_label($entity_type, $entity), + '%title' => entity_label($entity_type, $entity), ); if ($log) { @@ -1128,7 +1166,7 @@ function views_bulk_operations_direct_process($operation, $rows, $options) { $context['results']['log'][] = t('Skipped %operation on @type %title due to insufficient permissions.', array( '%operation' => $operation->label(), '@type' => $entity_type, - '%title' => _views_bulk_operations_entity_label($entity_type, $entity), + '%title' => entity_label($entity_type, $entity), )); unset($entities[$id]); } @@ -1236,29 +1274,6 @@ function _views_bulk_operations_entity_load($entity_type, $ids, $revision = FALS return $entities; } -/** - * Label function for entities. - * Core entities don't declare the "label" key, so entity_label() fails, - * and a fallback is needed. This function provides that fallback. - */ -function _views_bulk_operations_entity_label($entity_type, $entity) { - $label = entity_label($entity_type, $entity); - if (!$label) { - $entity_info = entity_get_info($entity_type); - $id_key = $entity_info['entity keys']['id']; - // Many entity types (e.g. "user") have a name which fits the label perfectly. - if (isset($entity->name)) { - $label = $entity->name; - } - elseif (isset($entity->{$id_key})) { - // Fallback to the id key. - $label = $entity->{$id_key}; - } - } - - return $label; -} - /** * Helper function to report an error. */