ran security updates on contrib modules

ctools, video_embed_field, migrate, views_bulk_operations
This commit is contained in:
Bachir Soussi Chiadmi 2015-09-17 14:44:25 +02:00
parent 5d30d9bcee
commit f7cd9c0858
94 changed files with 1242 additions and 413 deletions

View File

@ -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"

View File

@ -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"

View File

@ -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');
}

View File

@ -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"

View File

@ -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"

View File

@ -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.

View File

@ -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"

View File

@ -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"

View File

@ -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);
}

View File

@ -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'];

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -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;

View File

@ -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.

View File

@ -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');

View File

@ -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());

View File

@ -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

View File

@ -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);
}
/**

View File

@ -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('<div id="modalBackdrop" style="z-index: 1000; display: none;"></div><div id="modalContent" style="z-index: 1001; position: absolute;">' + $(content).html() + '</div>');
$('body').append('<div id="modalBackdrop" class="backdrop-' + modalClass + '" style="z-index: 1000; display: none;"></div><div id="modalContent" class="modal-' + modalClass + '" style="z-index: 1001; position: absolute;">' + $(content).html() + '</div>');
// 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.

View File

@ -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);

View File

@ -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"

View File

@ -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);

View File

@ -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.

View File

@ -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;
}

View File

@ -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 {

View File

@ -0,0 +1,74 @@
<?php
/**
* Plugins are described by creating a $plugin array which will be used
* by the system that includes this file.
*/
$plugin = array(
'single' => 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));
}

View File

@ -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;
}

View File

@ -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.');

View File

@ -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'];
}

View File

@ -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';

View File

@ -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';
}

View File

@ -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']);

View File

@ -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.

View File

@ -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.

View File

@ -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 {

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -1,7 +1,7 @@
<?php
/**
* @file
* A cached plugin object that tests inheritence including.
* A cached plugin object that tests inheritance including.
*/
class ctoolsCachedPluginArray {}

View File

@ -1,7 +1,7 @@
<?php
/**
* @file
* A cached plugin object that tests inheritence including.
* A cached plugin object that tests inheritance including.
*/
class ctoolsCachedPluginArray2 extends ctoolsCachedPluginArray {}

View File

@ -1,7 +1,7 @@
<?php
/**
* @file
* A cached plugin object that tests inheritence including.
* A cached plugin object that tests inheritance including.
*/
class ctoolsNotCachedPluginArray extends ctoolsNotCachedPluginArray2 {}

View File

@ -10,9 +10,9 @@ files[] = plugins/views/views_content_plugin_display_ctools_context.inc
files[] = plugins/views/views_content_plugin_display_panel_pane.inc
files[] = plugins/views/views_content_plugin_style_ctools_context.inc
; 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"

View File

@ -13,7 +13,7 @@ $plugin = array(
'access' => '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.',

View File

@ -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"

View File

@ -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 = '<object id="myExperience" class="BrightcoveExperience">
$embed = '<object class="@class" id="myExperience" class="BrightcoveExperience">
<param name="bgcolor" value="#FFFFFF" />
<param name="width" value="@width" />
<param name="height" value="@height" />
@ -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;

View File

@ -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"

View File

@ -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='<iframe src="//www.facebook.com/video/embed?video_id=!id" width="!width" height="!height"></iframe> ';
$embed='<iframe class="@class" src="//www.facebook.com/video/embed?video_id=!id" width="@width" height="@height"></iframe> ';
// 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;

View File

@ -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);
}

View File

@ -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' => '<div class="video-embed-description">',
'#markup' => $item['description'],
'#markup' => check_plain($item['description']),
'#suffix' => '</div>',
);
$alt = $item['description'];

View File

@ -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<min>\d+)m)?((?P<sec>\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<min>\d+)m)?((?P<sec>\d+)s)?((?P<tinsec>\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'] = '<iframe width="' . check_plain($settings['width']) . '" height="' . check_plain($settings['height']) . '" src="//www.youtube.com/embed/' . $id . '?' . $settings_str . '" frameborder="0" allowfullscreen></iframe>';
$output['#markup'] = '<iframe class="' . check_plain($class) . '" width="' . check_plain($settings['width']) . '" height="' . check_plain($settings['height']) . '" src="//www.youtube.com/embed/' . $id . '?' . $settings_str . '" frameborder="0" allowfullscreen></iframe>';
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' => '<iframe id="' . $settings['player_id'] . '" width="' . check_plain($settings['width']) . '" height="' . check_plain($settings['height']) . '" src="//player.vimeo.com/video/' . $id .
'#markup' => '<iframe class="' . check_plain($class) . '" id="' . $settings['player_id'] . '" width="' . check_plain($settings['width']) . '" height="' . check_plain($settings['height']) . '" src="//player.vimeo.com/video/' . $id .
'?' . $settings_str . '" frameborder="0" webkitAllowFullScreen mozallowfullscreen allowfullscreen></iframe>',
);
}
@ -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;
}

View File

@ -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"

View File

@ -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';
}

View File

@ -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']);
}
/**

View File

@ -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.

View File

@ -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.

View File

@ -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

View File

@ -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)) {

View File

@ -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;
}

View File

@ -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();
}
}

View File

@ -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"

View File

@ -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) {

View File

@ -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',

View File

@ -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"

View File

@ -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"

View File

@ -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',

View File

@ -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"

View File

@ -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"

View File

@ -14,7 +14,7 @@ function migrate_ui_migrate_dashboard($form, &$form_state) {
$build['overview'] = array(
'#prefix' => '<div>',
'#markup' => migrate_overview(),
'#markup' => filter_xss_admin(migrate_overview()),
'#suffix' => '</div>',
);
@ -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 <a href="@drush">drush</a>, (which is <a href="@recommended">recommended</a>), some configuration must be done on the server. See the <a href="@config">documentation</a> on <a href="@dorg">drupal.org</a>.',
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' => '<pre>' . $source . '</pre>',
'#markup' => '<pre>' . filter_xss_admin($source) . '</pre>',
);
$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 = "<em>$source_field</em>";
}
$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' => '</div>',
);
// 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 .= '<li>' . t('!migration (class !class)',
array('!migration' => $row->machine_name, '!class' => $row->class_name)) . "</li>\n";
array(
'!migration' => filter_xss_admin($row->machine_name),
'!class' => filter_xss_admin($row->class_name),
)) . "</li>\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;
}

View File

@ -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);

View File

@ -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)));
}
}
}
}
}
}
}

View File

@ -342,8 +342,11 @@ class MigrateTextFieldHandler extends MigrateFieldHandler {
$fields['format'] = t('Subfield: <a href="@doc">Text format for the field</a>',
array('@doc' => 'http://drupal.org/node/1224042#format'));
}
$fields['language'] = t('Subfield: <a href="@doc">Language for the field</a>',
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: <a href="@doc">Language for the field</a>',
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: <a href="@doc">Implementation of MigrateFile to use</a>',
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) {

View File

@ -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;
}
}
/**

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -198,8 +198,8 @@ class MigrateSourceList extends MigrateSource {
list(, $id) = each($ids);
$row->$key_name = $id;
}
break;
}
break;
}
return $row;
}

View File

@ -187,8 +187,8 @@ class MigrateSourceMultiItems extends MigrateSource {
$sourceKey = $this->activeMap->getSourceKey();
$key_name = key($sourceKey);
$row->$key_name = $id;
break;
}
break;
}
return $row;
}

View File

@ -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);

View File

@ -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

View File

@ -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);

View File

@ -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'),
);
}

View File

@ -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'),
);
}

View File

@ -19,6 +19,7 @@ function views_bulk_operations_delete_action_info() {
'label' => t('Delete revision'),
'configurable' => FALSE,
'behavior' => array('deletes_property'),
'triggers' => array('any'),
),
);
}

View File

@ -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' => '<div class="clearfix"></div>',
@ -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,

View File

@ -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'),
));
}

View File

@ -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();
}

View File

@ -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"

View File

@ -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) {

View File

@ -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');
}

View File

@ -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);
}
}

View File

@ -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'),

View File

@ -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"

View File

@ -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' => '<em>OR</em>',
'#markup' => '<em>' . t('OR') . '</em>',
);
$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.
*/