Drupal: modules update Search API, ctools

This commit is contained in:
Robert 2021-02-04 12:25:21 +01:00
parent bac4358a13
commit a8e2b9b4fd
78 changed files with 1746 additions and 192 deletions

View File

@ -4,8 +4,8 @@ core = 7.x
dependencies[] = ctools
package = Chaos tool suite
; Information added by Drupal.org packaging script on 2020-10-23
version = "7.x-1.17"
; Information added by Drupal.org packaging script on 2021-01-30
version = "7.x-1.19"
core = "7.x"
project = "ctools"
datestamp = "1603490551"
datestamp = "1611988843"

View File

@ -19,8 +19,8 @@ files[] = tests/object_cache.test
files[] = tests/object_cache_unit.test
files[] = tests/page_tokens.test
; Information added by Drupal.org packaging script on 2020-10-23
version = "7.x-1.17"
; Information added by Drupal.org packaging script on 2021-01-30
version = "7.x-1.19"
core = "7.x"
project = "ctools"
datestamp = "1603490551"
datestamp = "1611988843"

View File

@ -4,8 +4,8 @@ core = 7.x
package = Chaos tool suite
dependencies[] = ctools
; Information added by Drupal.org packaging script on 2020-10-23
version = "7.x-1.17"
; Information added by Drupal.org packaging script on 2021-01-30
version = "7.x-1.19"
core = "7.x"
project = "ctools"
datestamp = "1603490551"
datestamp = "1611988843"

View File

@ -4,8 +4,8 @@ package = Chaos tool suite
dependencies[] = ctools
core = 7.x
; Information added by Drupal.org packaging script on 2020-10-23
version = "7.x-1.17"
; Information added by Drupal.org packaging script on 2021-01-30
version = "7.x-1.19"
core = "7.x"
project = "ctools"
datestamp = "1603490551"
datestamp = "1611988843"

View File

@ -4,8 +4,8 @@ core = 7.x
package = Chaos tool suite
dependencies[] = ctools
; Information added by Drupal.org packaging script on 2020-10-23
version = "7.x-1.17"
; Information added by Drupal.org packaging script on 2021-01-30
version = "7.x-1.19"
core = "7.x"
project = "ctools"
datestamp = "1603490551"
datestamp = "1611988843"

View File

@ -7,8 +7,8 @@ dependencies[] = page_manager
dependencies[] = advanced_help
core = 7.x
; Information added by Drupal.org packaging script on 2020-10-23
version = "7.x-1.17"
; Information added by Drupal.org packaging script on 2021-01-30
version = "7.x-1.19"
core = "7.x"
project = "ctools"
datestamp = "1603490551"
datestamp = "1611988843"

View File

@ -1416,16 +1416,12 @@ function ctools_context_get_context_from_relationships($relationships, &$context
if (is_array($rdata['context'])) {
$rcontexts = array();
foreach ($rdata['context'] as $cid) {
if (empty($contexts[$cid])) {
continue 2;
if (!empty($contexts[$cid])) {
$rcontexts[] = $contexts[$cid];
}
$rcontexts[] = $contexts[$cid];
}
}
else {
if (empty($contexts[$rdata['context']])) {
continue;
}
elseif (!empty($contexts[$rdata['context']])) {
$rcontexts = $contexts[$rdata['context']];
}

View File

@ -456,18 +456,20 @@ function ctools_export_ui_switcher_page($plugin_name, $op) {
// Load the $plugin information.
$plugin = ctools_get_export_ui($plugin_name);
$handler = ctools_export_ui_get_handler($plugin);
if ($handler) {
$method = $op . '_page';
if (method_exists($handler, $method)) {
// Replace the first two arguments:
$args[0] = $js;
$args[1] = $_POST;
return call_user_func_array(array($handler, $method), $args);
}
if (!$plugin) {
return t('Configuration error. No plugin found: %plugin_name.', array('%plugin_name' => $plugin_name));
}
else {
$handler = ctools_export_ui_get_handler($plugin);
if (!$handler) {
return t('Configuration error. No handler found.');
}
$method = $op . '_page';
if (method_exists($handler, $method)) {
// Replace the first two arguments:
$args[0] = $js;
$args[1] = $_POST;
return call_user_func_array(array($handler, $method), $args);
}
}

View File

@ -637,11 +637,19 @@ function ctools_get_default_object($table, $name) {
}
/**
* Get export object defaults.
*
* Call the hook to get all default objects of the given type from the
* export. If configured properly, this could include loading up an API
* to get default objects.
*
* @param string $table
* The name of the table to be loaded. Data is expected to be in the
* schema to make all this work.
* @param array $export
* The export definition from the table's hook_schema() definition.
*/
function _ctools_export_get_defaults($table, $export) {
function _ctools_export_get_defaults($table, array $export) {
$cache = &drupal_static(__FUNCTION__, array());
// If defaults may be cached, first see if we can load from cache.
@ -684,7 +692,8 @@ function _ctools_export_get_defaults($table, $export) {
$cache[$table][$name] = $object;
}
else {
// If version checking is enabled, ensure that the object can be used.
// If version checking is enabled, ensure that the object can be
// used.
if (isset($object->api_version) &&
version_compare($object->api_version, $export['api']['minimum_version']) >= 0 &&
version_compare($object->api_version, $export['api']['current_version']) <= 0) {
@ -866,6 +875,7 @@ function ctools_var_export($var, $prefix = '') {
}
else {
$output = "array(\n";
ksort($var);
foreach ($var as $key => $value) {
$output .= $prefix . " " . ctools_var_export($key) . " => " . ctools_var_export($value, $prefix . ' ') . ",\n";
}

View File

@ -238,8 +238,6 @@ function ctools_field_label($field_name) {
function ctools_field_invoke_field($field_name, $op, $entity_type, $entity, &$a = NULL, &$b = NULL, $options = array()) {
if (is_array($field_name)) {
$instance = $field_name;
$field = empty($field_name['field']) ? field_info_field($instance['field_name']) : $field_name['field'];
$field_name = $instance['field_name'];
}
else {
list(, , $bundle) = entity_extract_ids($entity_type, $entity);
@ -250,6 +248,11 @@ function ctools_field_invoke_field($field_name, $op, $entity_type, $entity, &$a
return;
}
// Keep the variables consistent regardless if we retrieve the field instance
// ourself, or if one is provided to us via the $field_name variable.
$field = field_info_field($instance['field_name']);
$field_name = $instance['field_name'];
// Merge default options.
$default_options = array(
'default' => FALSE,

View File

@ -97,7 +97,7 @@ function page_manager_page_wizard($name, $step = NULL) {
}
// Check for possibly more complex access callback on plugin.
if ($function = ctools_plugin_get_function($plugin, 'access callback') && !$function($plugin)) {
if (($function = ctools_plugin_get_function($plugin, 'access callback')) && !$function($plugin)) {
return MENU_ACCESS_DENIED;
}

View File

@ -26,7 +26,7 @@
// Grab all the links that match this url and add the fetching class.
// This allows the caching system to grab each url once and only once
// instead of grabbing the url once per <a>.
var $objects = $('a[href="' + old_url + '"]')
var $objects = $('a[href="' + old_url + '"]');
$objects.addClass('ctools-fetching');
try {
url = old_url.replace(/\/nojs(\/|$)/g, '/ajax$1');

View File

@ -98,5 +98,5 @@ Drupal.behaviors.CToolsAutoSubmit = {
});
});
}
}
};
})(jQuery);

View File

@ -194,7 +194,7 @@
Drupal.CTools.CollapsibleCallbacksAfterToggle[i]($container, handle, content, toggle);
}
}
}
};
var clickMe = function () {
if (Drupal.CTools.CollapsibleCallbacks) {
@ -222,7 +222,7 @@
}
return false;
}
};
// Let both the toggle and the handle be clickable.
toggle.click(clickMe);
@ -237,5 +237,5 @@
attach: function(context) {
$('.ctools-collapsible-container', context).once('ctools-collapsible', Drupal.CTools.bindCollapsible);
}
}
};
})(jQuery);

View File

@ -14,7 +14,7 @@
* - Checkboxes don't have their own id, so you need to add one in a div
* around the checkboxes via #prefix and #suffix. You actually need to add TWO
* divs because it's the parent that gets hidden. Also be sure to retain the
* 'expand_checkboxes' in the #process array, because the CTools process will
* 'form_process_checkboxes' in the #process array, because the CTools process will
* override it.
*/
@ -34,7 +34,7 @@
}
}
return false;
}
};
Drupal.CTools.dependent.autoAttach = function() {
@ -118,7 +118,7 @@
}
}
return val;
}
};
var setChangeTrigger = function(trigger_id, bind_id) {
// Triggered when change() is clicked.
@ -205,7 +205,7 @@
}
}
}
}
};
$(trigger_id).bind('change.ctools-dependent', function() {
// Trigger the internal change function
@ -214,11 +214,11 @@
});
// Trigger initial reaction
changeTrigger(trigger_id, bind_id);
}
};
setChangeTrigger(trigger_id, bind_id);
}
}
}
};
Drupal.behaviors.CToolsDependent = {
attach: function (context) {
@ -240,5 +240,5 @@
})
.trigger('change.ctools-dependent');
}
}
};
})(jQuery);

View File

@ -69,7 +69,7 @@
$secondaryActions.animate({height: "show", opacity: "show"}, 100);
$dropbutton.addClass('open');
}
}
};
// Hide the secondary actions initially.
$secondaryActions.hide();
@ -90,5 +90,5 @@
);
});
}
}
};
})(jQuery);

View File

@ -61,7 +61,7 @@
$("div.ctools-dropdown-container", $dropdown)
.animate({height: "show", opacity: "show"}, 100);
}
}
};
$("a.ctools-dropdown-link", $dropdown).click(function() {
toggle();
return false;
@ -83,5 +83,5 @@
);
});
}
}
};
})(jQuery);

View File

@ -38,5 +38,5 @@
return false;
});
}
}
};
})(jQuery);

View File

@ -86,7 +86,7 @@
'width': (width - Drupal.CTools.Modal.currentSettings.modalSize.contentRight) + 'px',
'height': (height - Drupal.CTools.Modal.currentSettings.modalSize.contentBottom) + 'px'
});
}
};
if (!Drupal.CTools.Modal.modal) {
Drupal.CTools.Modal.modal = $(Drupal.theme(settings.modalTheme));
@ -120,9 +120,9 @@
* Provide the HTML to create the modal dialog.
*/
Drupal.theme.prototype.CToolsModalDialog = function () {
var html = ''
html += '<div id="ctools-modal">'
html += ' <div class="ctools-modal-content">' // panels-modal-content
var html = '';
html += '<div id="ctools-modal">';
html += ' <div class="ctools-modal-content">'; // panels-modal-content
html += ' <div class="modal-header">';
html += ' <a class="close" href="#">';
html += Drupal.CTools.Modal.currentSettings.closeText + Drupal.CTools.Modal.currentSettings.closeImage;
@ -135,7 +135,7 @@
html += '</div>';
return html;
}
};
/**
* Provide the HTML to create the throbber.
@ -159,7 +159,7 @@
if (match) {
return match[1];
}
}
};
/**
* Click function for modals that can be cached.
@ -186,7 +186,7 @@
setTimeout(function() { Drupal.CTools.AJAX.ajaxSubmit($form, url); }, 1);
return false;
}
};
/**
* Bind links that will open modals to the appropriate function.
@ -250,7 +250,7 @@
element_settings.url = $this.attr('action');
element_settings.event = 'submit';
element_settings.progress = { 'type': 'throbber' }
element_settings.progress = { 'type': 'throbber' };
var base = $this.attr('id');
Drupal.ajax[base] = new Drupal.ajax(base, this, element_settings);
@ -291,9 +291,15 @@
* AJAX responder command to place HTML within the modal.
*/
Drupal.CTools.Modal.modal_display = function(ajax, response, status) {
var settings = response.settings || ajax.settings || Drupal.settings;
// If the modal does not exist yet, create it.
if ($('#modalContent').length == 0) {
Drupal.CTools.Modal.show(Drupal.CTools.Modal.getSettings(ajax.element));
}
// If the modal exists run detachBehaviors before removing existing content.
else {
Drupal.detachBehaviors($('#modalContent'), settings, 'unload');
}
$('#modal-title').html(response.title);
// Simulate an actual page load by scrolling to the top after adding the
// content. This is helpful for allowing users to see error messages at the
@ -302,7 +308,6 @@
$(document).trigger('CToolsAttachBehaviors', $('#modalContent'));
// 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')) {
@ -315,7 +320,7 @@
// button by the show() function called above.)
$('#modal-content :focusable:first').focus();
}
}
};
/**
* AJAX responder command to dismiss the modal.
@ -323,7 +328,7 @@
Drupal.CTools.Modal.modal_dismiss = function(command) {
Drupal.CTools.Modal.dismiss();
$('link.ctools-temporary-css').remove();
}
};
/**
* Display loading
@ -334,7 +339,7 @@
output: Drupal.theme(Drupal.CTools.Modal.currentSettings.throbberTheme),
title: Drupal.CTools.Modal.currentSettings.loadingText
});
}
};
/**
* Find a URL for an AJAX button.
@ -590,6 +595,7 @@
$('body').unbind( 'keydown', modalTabTrapHandler );
$('.close', $modalHeader).unbind('click', modalContentClose);
$(document).unbind('keydown', modalEventEscapeCloseHandler);
$(document).trigger('CToolsCloseModalBehaviors', $('#modalContent'));
$(document).trigger('CToolsDetachBehaviors', $('#modalContent'));
// Closing animation.
@ -675,7 +681,7 @@
var $modalContent = $('#modalContent');
var $modalHeader = $modalContent.find('.modal-header');
$('.close', $modalHeader).unbind('click', modalContentClose);
$('body').unbind('keypress', modalEventEscapeCloseHandler);
$(document).unbind('keydown', modalEventEscapeCloseHandler);
$(document).trigger('CToolsDetachBehaviors', $modalContent);
// jQuery magic loop through the instances and run the animations or removal.

View File

@ -180,7 +180,7 @@
);
$(this).after(lock);
locks.push(lock);
};
}
// Add hook
var $this = $(this);
@ -216,5 +216,5 @@
$widget.attr('checked', !$widget.attr('checked') || $widget.is('input[type=radio]'));
});
}
}
};
})(jQuery);

View File

@ -6,8 +6,8 @@ package = Chaos tool suite
files[] = tests/head_links.test
; Information added by Drupal.org packaging script on 2020-10-23
version = "7.x-1.17"
; Information added by Drupal.org packaging script on 2021-01-30
version = "7.x-1.19"
core = "7.x"
project = "ctools"
datestamp = "1603490551"
datestamp = "1611988843"

View File

@ -21,7 +21,7 @@ $plugin = array(
* Check for access.
*/
function ctools_node_status_ctools_access_check($conf, $context) {
return (!empty($context->data) && $context->data->status);
return (!empty($context->data->status) && $context->data->status);
}
/**

View File

@ -152,6 +152,12 @@ function ctools_entity_form_field_content_type_render($subtype, $conf, $panel_ar
* Returns the administrative title for a type.
*/
function ctools_entity_form_field_content_type_admin_title($subtype, $conf, $context) {
// Return early because we don't have context to build this field from.
if (!$context || !isset($context->identifier)) {
watchdog('ctools_entity_form_field_content_type_admin_title', 'Context is missing for field: @name', array('@name' => $subtype), WATCHDOG_NOTICE);
return t('Deleted/missing field @name', array('@name' => $subtype));
}
list($entity_type, $field_name) = explode(':', $subtype, 2);
if (!empty($context->restrictions)) {

Binary file not shown.

After

Width:  |  Height:  |  Size: 460 B

View File

@ -0,0 +1,65 @@
<?php
/**
* Plugins are described by creating a $plugin array which will be used
* by the system that includes this file.
*/
$plugin = array(
'single' => TRUE,
'icon' => 'icon_user_form.png',
'title' => t('User form actions / buttons'),
'description' => t('The user form actions / buttons.'),
'required context' => new ctools_context_required(t('Form'), 'form'),
'category' => t('Form'),
);
/**
* Ctools plugin content type render for the picture form field.
*/
function ctools_user_form_actions_content_type_render($subtype, $conf, $panel_args, &$context) {
$block = new stdClass();
$block->module = t('user-form');
$block->delta = 'title-options';
if (isset($context->form)) {
if (!empty($context->form['actions'])) {
$block->content['actions'] = $context->form['actions'];
unset($context->form['actions']);
}
// Because we are adding the submit buttons outside the General form
// we can assume the necessary hidden components should be added as well.
if (!empty($context->form['form_build_id'])) {
$block->content['form_build_id'] = $context->form['form_build_id'];
unset($context->form['form_build_id']);
}
if (!empty($context->form['form_token'])) {
$block->content['form_token'] = $context->form['form_token'];
unset($context->form['form_token']);
}
if (!empty($context->form['form_id'])) {
$block->content['form_id'] = $context->form['form_id'];
unset($context->form['form_id']);
}
}
else {
$block->content = t('User actions / buttons form components.');
}
return $block;
}
/**
* Ctools plugin admin title function for the actions form field.
*/
function ctools_user_form_actions_content_type_admin_title($subtype, $conf, $context) {
return t('"@s" user form actions / buttons field', array('@s' => $context->identifier));
}
/**
* Ctools plugin configuration edit form for the actions form field.
*
* Provide a blank form so we have a place to have context setting.
*/
function ctools_user_form_actions_content_type_edit_form($form, &$form_state) {
return $form;
}

View File

@ -0,0 +1,72 @@
<?php
/**
* Plugins are described by creating a $plugin array which will be used
* by the system that includes this file.
*/
$plugin = array(
'single' => TRUE,
'icon' => 'icon_user_form.png',
'title' => t('User form: add a specific component'),
'description' => t('The user form component by selection.'),
'required context' => new ctools_context_required(t('Form'), 'form'),
'category' => t('Form'),
);
/**
* Ctools plugin content type render for the picture form field.
*/
function ctools_user_form_component_content_type_render($subtype, $conf, $panel_args, &$context) {
$block = new stdClass();
$block->module = t('user-form');
$block->delta = 'title-options';
if (isset($context->form)) {
if (!empty($context->form[$conf['field']])) {
$block->content[$conf['field']] = $context->form[$conf['field']];
unset($context->form[$conf['field']]);
}
}
else {
$block->content = t('User form edit components.');
}
return $block;
}
/**
* Ctools plugin admin title function for the a selectable form field.
*/
function ctools_user_form_component_content_type_admin_title($subtype, $conf, $context) {
return t('"@s" user form @field field', array('@s' => $context->identifier, '@field' => $conf['field']));
}
/**
* Ctools plugin configuration edit form for the selectable form field.
*
* Provide the list of fields in the user profile edit form to select from the
* plugin configuration.
*/
function ctools_user_form_component_content_type_edit_form($form, &$form_state) {
$conf = $form_state['conf'];
$user_form = drupal_get_form('user_profile_form');
$field_keys = element_children($user_form);
$options = array_combine($field_keys, $field_keys);
$form['field'] = array(
'#type' => 'select',
'#title' => t('User form field'),
'#options' => $options,
'#description' => t('Select a form field from the current user form to display in this pane.'),
'#default_value' => !empty($conf['field']) ? $conf['field'] : '',
);
return $form;
}
/**
* Ctools plugin configuration edit form submit handler.
*/
function ctools_user_form_component_content_type_edit_form_submit($form, &$form_state) {
$form_state['conf']['field'] = $form_state['values']['field'];
}

View File

@ -0,0 +1,56 @@
<?php
/**
* Plugins are described by creating a $plugin array which will be used
* by the system that includes this file.
*/
$plugin = array(
'single' => TRUE,
'icon' => 'icon_user_form.png',
'title' => t('User form email field'),
'description' => t('The user email form.'),
'required context' => new ctools_context_required(t('Form'), 'form'),
'category' => t('Form'),
);
/**
* Ctools plugin content type render for the email form field.
*/
function ctools_user_form_email_content_type_render($subtype, $conf, $panel_args, &$context) {
$block = new stdClass();
$block->module = t('user-form');
$block->delta = 'title-options';
if (isset($context->form)) {
// The current password is required to change the email.
if (!empty($context->form['account']['current_pass'])) {
$block->content['current_pass'] = $context->form['account']['current_pass'];
unset($context->form['account']['current_pass']);
}
if (!empty($context->form['account']['mail'])) {
$block->content['mail'] = $context->form['account']['mail'];
unset($context->form['account']['mail']);
}
}
else {
$block->content = t('User email form.');
}
return $block;
}
/**
* Ctools plugin admin title function for the email form field.
*/
function ctools_user_form_email_content_type_admin_title($subtype, $conf, $context) {
return t('"@s" user form email field', array('@s' => $context->identifier));
}
/**
* Ctools plugin configuration edit form for the email form field.
*
* Provide a blank form so we have a place to have context setting.
*/
function ctools_user_form_email_content_type_edit_form($form, &$form_state) {
return $form;
}

View File

@ -0,0 +1,51 @@
<?php
/**
* Plugins are described by creating a $plugin array which will be used
* by the system that includes this file.
*/
$plugin = array(
'single' => TRUE,
'icon' => 'icon_user_form.png',
'title' => t('User form notify field'),
'description' => t('The user notify form.'),
'required context' => new ctools_context_required(t('Form'), 'form'),
'category' => t('Form'),
);
/**
* Ctools plugin content type render for the notify form field.
*/
function ctools_user_form_notify_content_type_render($subtype, $conf, $panel_args, &$context) {
$block = new stdClass();
$block->module = t('user-form');
$block->delta = 'title-options';
if (isset($context->form)) {
if (!empty($context->form['account']['notify'])) {
$block->content['notify'] = $context->form['account']['notify'];
unset($context->form['account']['notify']);
}
}
else {
$block->content = t('User notify form.');
}
return $block;
}
/**
* Ctools plugin admin title function for the notify form field.
*/
function ctools_user_form_notify_content_type_admin_title($subtype, $conf, $context) {
return t('"@s" user form notify field', array('@s' => $context->identifier));
}
/**
* Ctools plugin configuration edit form for the notify form field.
*
* Provide a blank form so we have a place to have context setting.
*/
function ctools_user_form_notify_content_type_edit_form($form, &$form_state) {
return $form;
}

View File

@ -0,0 +1,56 @@
<?php
/**
* Plugins are described by creating a $plugin array which will be used
* by the system that includes this file.
*/
$plugin = array(
'single' => TRUE,
'icon' => 'icon_user_form.png',
'title' => t('User form password field'),
'description' => t('The user password form.'),
'required context' => new ctools_context_required(t('Form'), 'form'),
'category' => t('Form'),
);
/**
* Ctools plugin content type render for the password form field.
*/
function ctools_user_form_password_content_type_render($subtype, $conf, $panel_args, &$context) {
$block = new stdClass();
$block->module = t('user-form');
$block->delta = 'title-options';
if (isset($context->form)) {
// The current password is required to change the password.
if (!empty($context->form['account']['current_pass'])) {
$block->content['current_pass'] = $context->form['account']['current_pass'];
unset($context->form['account']['current_pass']);
}
if (!empty($context->form['account']['pass'])) {
$block->content['pass'] = $context->form['account']['pass'];
unset($context->form['account']['pass']);
}
}
else {
$block->content = t('User password form.');
}
return $block;
}
/**
* Ctools plugin admin title function for the password form field.
*/
function ctools_user_form_password_content_type_admin_title($subtype, $conf, $context) {
return t('"@s" user form password field', array('@s' => $context->identifier));
}
/**
* Ctools plugin configuration edit form for the password form field.
*
* Provide a blank form so we have a place to have context setting.
*/
function ctools_user_form_password_content_type_edit_form($form, &$form_state) {
return $form;
}

View File

@ -0,0 +1,51 @@
<?php
/**
* Plugins are described by creating a $plugin array which will be used
* by the system that includes this file.
*/
$plugin = array(
'single' => TRUE,
'icon' => 'icon_user_form.png',
'title' => t('User form picture field'),
'description' => t('The user picture form.'),
'required context' => new ctools_context_required(t('Form'), 'form'),
'category' => t('Form'),
);
/**
* Ctools plugin content type render for the picture form field.
*/
function ctools_user_form_picture_content_type_render($subtype, $conf, $panel_args, &$context) {
$block = new stdClass();
$block->module = t('user-form');
$block->delta = 'title-options';
if (isset($context->form)) {
if (!empty($context->form['picture'])) {
$block->content['picture'] = $context->form['picture'];
unset($context->form['picture']);
}
}
else {
$block->content = t('User picture form.');
}
return $block;
}
/**
* Ctools plugin admin title function for the picture form field.
*/
function ctools_user_form_picture_content_type_admin_title($subtype, $conf, $context) {
return t('"@s" user form picture field', array('@s' => $context->identifier));
}
/**
* Ctools plugin configuration edit form for the picture form field.
*
* Provide a blank form so we have a place to have context setting.
*/
function ctools_user_form_picture_content_type_edit_form($form, &$form_state) {
return $form;
}

View File

@ -0,0 +1,51 @@
<?php
/**
* Plugins are described by creating a $plugin array which will be used
* by the system that includes this file.
*/
$plugin = array(
'single' => TRUE,
'icon' => 'icon_user_form.png',
'title' => t('User form roles field'),
'description' => t('The user roles form.'),
'required context' => new ctools_context_required(t('Form'), 'form'),
'category' => t('Form'),
);
/**
* Ctools plugin content type render for the roles form field.
*/
function ctools_user_form_roles_content_type_render($subtype, $conf, $panel_args, &$context) {
$block = new stdClass();
$block->module = t('user-form');
$block->delta = 'title-options';
if (isset($context->form)) {
if (!empty($context->form['account']['roles'])) {
$block->content['roles'] = $context->form['account']['roles'];
unset($context->form['account']['roles']);
}
}
else {
$block->content = t('User roles form.');
}
return $block;
}
/**
* Ctools plugin admin title function for the roles form field.
*/
function ctools_user_form_roles_content_type_admin_title($subtype, $conf, $context) {
return t('"@s" user form roles field', array('@s' => $context->identifier));
}
/**
* Ctools plugin configuration edit form for the roles form field.
*
* Provide a blank form so we have a place to have context setting.
*/
function ctools_user_form_roles_content_type_edit_form($form, &$form_state) {
return $form;
}

View File

@ -0,0 +1,51 @@
<?php
/**
* Plugins are described by creating a $plugin array which will be used
* by the system that includes this file.
*/
$plugin = array(
'single' => TRUE,
'icon' => 'icon_user_form.png',
'title' => t('User form signature settings field'),
'description' => t('The user signature settings form.'),
'required context' => new ctools_context_required(t('Form'), 'form'),
'category' => t('Form'),
);
/**
* Ctools plugin content type render for the signature settings form field.
*/
function ctools_user_form_signature_settings_content_type_render($subtype, $conf, $panel_args, &$context) {
$block = new stdClass();
$block->module = t('user-form');
$block->delta = 'title-options';
if (isset($context->form)) {
if (!empty($context->form['signature_settings'])) {
$block->content['signature_settings'] = $context->form['signature_settings'];
unset($context->form['signature_settings']);
}
}
else {
$block->content = t('User signature settings form.');
}
return $block;
}
/**
* Ctools plugin admin title function for the signature settings form field.
*/
function ctools_user_form_signature_settings_content_type_admin_title($subtype, $conf, $context) {
return t('"@s" user form signature settings field', array('@s' => $context->identifier));
}
/**
* Ctools plugin configuration edit form for the signature settings form field.
*
* Provide a blank form so we have a place to have context setting.
*/
function ctools_user_form_signature_settings_content_type_edit_form($form, &$form_state) {
return $form;
}

View File

@ -0,0 +1,51 @@
<?php
/**
* Plugins are described by creating a $plugin array which will be used
* by the system that includes this file.
*/
$plugin = array(
'single' => TRUE,
'icon' => 'icon_user_form.png',
'title' => t('User form status field'),
'description' => t('The user status form.'),
'required context' => new ctools_context_required(t('Form'), 'form'),
'category' => t('Form'),
);
/**
* Ctools plugin content type render for the status form field.
*/
function ctools_user_form_status_content_type_render($subtype, $conf, $panel_args, &$context) {
$block = new stdClass();
$block->module = t('user-form');
$block->delta = 'title-options';
if (isset($context->form)) {
if (!empty($context->form['account']['status'])) {
$block->content['status'] = $context->form['account']['status'];
unset($context->form['account']['status']);
}
}
else {
$block->content = t('User status form.');
}
return $block;
}
/**
* Ctools plugin admin title function for the status form field.
*/
function ctools_user_form_status_content_type_admin_title($subtype, $conf, $context) {
return t('"@s" user form status field', array('@s' => $context->identifier));
}
/**
* Ctools plugin configuration edit form for the status form field.
*
* Provide a blank form so we have a place to have context setting.
*/
function ctools_user_form_status_content_type_edit_form($form, &$form_state) {
return $form;
}

View File

@ -0,0 +1,51 @@
<?php
/**
* Plugins are described by creating a $plugin array which will be used
* by the system that includes this file.
*/
$plugin = array(
'single' => TRUE,
'icon' => 'icon_user_form.png',
'title' => t('User form timezone field'),
'description' => t('The user timezone form.'),
'required context' => new ctools_context_required(t('Form'), 'form'),
'category' => t('Form'),
);
/**
* Ctools plugin content type render for the timezone form field.
*/
function ctools_user_form_timezone_content_type_render($subtype, $conf, $panel_args, &$context) {
$block = new stdClass();
$block->module = t('user-form');
$block->delta = 'title-options';
if (isset($context->form)) {
if (!empty($context->form['timezone'])) {
$block->content['timezone'] = $context->form['timezone'];
unset($context->form['timezone']);
}
}
else {
$block->content = t('User timezone form.');
}
return $block;
}
/**
* Ctools plugin admin title function for the timezone form field.
*/
function ctools_user_form_timezone_content_type_admin_title($subtype, $conf, $context) {
return t('"@s" user form timezone field', array('@s' => $context->identifier));
}
/**
* Ctools plugin configuration edit form for the timezone form field.
*
* Provide a blank form so we have a place to have context setting.
*/
function ctools_user_form_timezone_content_type_edit_form($form, &$form_state) {
return $form;
}

View File

@ -0,0 +1,51 @@
<?php
/**
* Plugins are described by creating a $plugin array which will be used
* by the system that includes this file.
*/
$plugin = array(
'single' => TRUE,
'icon' => 'icon_user_form.png',
'title' => t('User form username field'),
'description' => t('The user username form.'),
'required context' => new ctools_context_required(t('Form'), 'form'),
'category' => t('Form'),
);
/**
* Ctools plugin content type render for the username form field.
*/
function ctools_user_form_username_content_type_render($subtype, $conf, $panel_args, &$context) {
$block = new stdClass();
$block->module = t('user-form');
$block->delta = 'title-options';
if (isset($context->form)) {
if (!empty($context->form['account']['name'])) {
$block->content['name'] = $context->form['account']['name'];
unset($context->form['account']['name']);
}
}
else {
$block->content = t('User username form.');
}
return $block;
}
/**
* Ctools plugin admin title function for the username form field.
*/
function ctools_user_form_username_content_type_admin_title($subtype, $conf, $context) {
return t('"@s" user form username field', array('@s' => $context->identifier));
}
/**
* Ctools plugin configuration edit form for the username form field.
*
* Provide a blank form so we have a place to have context setting.
*/
function ctools_user_form_username_content_type_edit_form($form, &$form_state) {
return $form;
}

View File

@ -41,10 +41,13 @@ function ctools_context_create_user($empty, $data = NULL, $conf = FALSE) {
if ($conf) {
if ($data['type'] == 'current') {
global $user;
$data = user_load($user->uid);
if (user_is_logged_in()) {
$data = user_load($user->uid);
$data->logged_in_user = TRUE;
}
else {
$data = drupal_anonymous_user();
}
}
else {
$data = user_load($data['uid']);

View File

@ -310,7 +310,7 @@ class ctools_export_ui {
$form['bottom row']['reset'] = array(
'#type' => 'submit',
'#id' => 'ctools-export-ui-list-items-apply',
'#id' => 'ctools-export-ui-list-items-reset',
'#value' => t('Reset'),
'#attributes' => array('class' => array('use-ajax-submit')),
);

View File

@ -0,0 +1,32 @@
<?php
/**
* @file relationships/comment_parent.inc
* Plugin to provide a relationship handler for comment parent.
*/
/**
* Plugins are described by creating a $plugin array which will be used
* by the system that includes this file.
*/
$plugin = array(
'title' => t('Parent comment'),
'keyword' => 'parent_comment',
'description' => t('Adds a parent comment from a comment context.'),
'required context' => new ctools_context_required(t('Comment'), 'entity:comment'),
'context' => 'ctools_comment_parent_context',
);
/**
* Return a new context based on an existing context.
*/
function ctools_comment_parent_context($context, $conf) {
if (empty($context->data)) {
return ctools_context_create_empty('entity:comment');
}
if (isset($context->data->pid) && ($context->data->pid !== 0)) {
$parent_comment = comment_load($context->data->pid);
return ctools_context_create('entity:comment', $parent_comment);
}
}

View File

@ -5,8 +5,8 @@ package = Chaos tool suite
dependencies[] = ctools
dependencies[] = color
; Information added by Drupal.org packaging script on 2020-10-23
version = "7.x-1.17"
; Information added by Drupal.org packaging script on 2021-01-30
version = "7.x-1.19"
core = "7.x"
project = "ctools"
datestamp = "1603490551"
datestamp = "1611988843"

View File

@ -4,8 +4,8 @@ core = 7.x
dependencies[] = ctools
package = Chaos tool suite
; Information added by Drupal.org packaging script on 2020-10-23
version = "7.x-1.17"
; Information added by Drupal.org packaging script on 2021-01-30
version = "7.x-1.19"
core = "7.x"
project = "ctools"
datestamp = "1603490551"
datestamp = "1611988843"

View File

@ -7,8 +7,8 @@ hidden = TRUE
files[] = ctools_export.test
; Information added by Drupal.org packaging script on 2020-10-23
version = "7.x-1.17"
; Information added by Drupal.org packaging script on 2021-01-30
version = "7.x-1.19"
core = "7.x"
project = "ctools"
datestamp = "1603490551"
datestamp = "1611988843"

View File

@ -5,8 +5,8 @@ core = 7.x
dependencies[] = ctools
hidden = TRUE
; Information added by Drupal.org packaging script on 2020-10-23
version = "7.x-1.17"
; Information added by Drupal.org packaging script on 2021-01-30
version = "7.x-1.19"
core = "7.x"
project = "ctools"
datestamp = "1603490551"
datestamp = "1611988843"

View File

@ -385,13 +385,6 @@ class views_content_plugin_display_panel_pane extends views_plugin_display {
return (bool) $conf['more_link'];
}
/**
* {@inheritdoc}
*/
public function has_path() {
return TRUE;
}
/**
* {@inheritdoc}
*/

View File

@ -0,0 +1,11 @@
name = Views content panes Test
description = Test module for Views content panes.
package = Views
core = 7.x
dependencies[] = views_content
hidden = TRUE
; Information added by Drupal.org packaging script on 2021-01-30
version = "7.x-1.19"
core = "7.x"
project = "ctools"
datestamp = "1611988843"

View File

@ -0,0 +1,15 @@
<?php
/**
* @file
* Helper module for Views content pane tests.
*/
/**
* Implements hook_views_api().
*/
function views_content_test_views_api() {
return array(
'api' => 3.0,
);
}

View File

@ -0,0 +1,82 @@
<?php
/**
* @file
* Tests views.
*/
/**
* Implements hook_views_default_views().
*/
function views_content_test_views_default_views() {
$view = new view();
$view->name = 'views_content_more_link';
$view->description = '';
$view->tag = 'default';
$view->base_table = 'node';
$view->human_name = 'views_content_more_link';
$view->core = 7;
$view->api_version = '3.0';
$view->disabled = FALSE; /* Edit this to true to make a default view disabled initially */
/* Display: Master */
$handler = $view->new_display('default', 'Master', 'default');
$handler->display->display_options['title'] = 'views_content_more_link';
$handler->display->display_options['use_more'] = TRUE;
$handler->display->display_options['use_more_always'] = FALSE;
$handler->display->display_options['access']['type'] = 'perm';
$handler->display->display_options['cache']['type'] = 'none';
$handler->display->display_options['query']['type'] = 'views_query';
$handler->display->display_options['exposed_form']['type'] = 'basic';
$handler->display->display_options['pager']['type'] = 'none';
$handler->display->display_options['pager']['options']['offset'] = '0';
$handler->display->display_options['style_plugin'] = 'table';
$handler->display->display_options['style_options']['columns'] = array(
'title' => 'title',
);
$handler->display->display_options['style_options']['class'] = '';
$handler->display->display_options['style_options']['default'] = '-1';
$handler->display->display_options['style_options']['info'] = array(
'title' => array(
'sortable' => 0,
'default_sort_order' => 'asc',
'align' => '',
'separator' => '',
'empty_column' => 0,
),
);
/* Field: Content: Title */
$handler->display->display_options['fields']['title']['id'] = 'title';
$handler->display->display_options['fields']['title']['table'] = 'node';
$handler->display->display_options['fields']['title']['field'] = 'title';
$handler->display->display_options['fields']['title']['label'] = '';
$handler->display->display_options['fields']['title']['alter']['word_boundary'] = FALSE;
$handler->display->display_options['fields']['title']['alter']['ellipsis'] = FALSE;
/* Sort criterion: Content: Post date */
$handler->display->display_options['sorts']['created']['id'] = 'created';
$handler->display->display_options['sorts']['created']['table'] = 'node';
$handler->display->display_options['sorts']['created']['field'] = 'created';
$handler->display->display_options['sorts']['created']['order'] = 'DESC';
/* Filter criterion: Content: Published status */
$handler->display->display_options['filters']['status']['id'] = 'status';
$handler->display->display_options['filters']['status']['table'] = 'node';
$handler->display->display_options['filters']['status']['field'] = 'status';
$handler->display->display_options['filters']['status']['value'] = 1;
$handler->display->display_options['filters']['status']['group'] = 1;
$handler->display->display_options['filters']['status']['expose']['operator'] = FALSE;
/* Display: Page */
$handler = $view->new_display('page', 'Page', 'page');
$handler->display->display_options['path'] = 'views-content-more-link';
/* Display: Content pane */
$handler = $view->new_display('panel_pane', 'Content pane', 'panel_pane_1');
$handler->display->display_options['defaults']['pager'] = FALSE;
$handler->display->display_options['pager']['type'] = 'some';
$handler->display->display_options['pager']['options']['items_per_page'] = '3';
$handler->display->display_options['pager']['options']['offset'] = '0';
$views[$view->name] = $view;
return $views;
}

View File

@ -0,0 +1,79 @@
<?php
/**
* @file
* Contains ViewsContentPanesTest.
*/
/**
* Tests rendering views content pane displays.
*/
class ViewsContentPanesTest extends ViewsSqlTest {
/**
* {@inheritdoc}
*/
public static function getInfo() {
return array(
'name' => 'Views content panes tests',
'description' => 'Tests rendering views content pane displays.',
'group' => 'ctools',
'dependencies' => array('ctools', 'views'),
);
}
/**
* {@inheritdoc}
*/
public function setUp(array $modules = array()) {
$modules[] = 'ctools';
$modules[] = 'views_content';
$modules[] = 'views_content_test';
parent::setUp($modules);
}
/**
* Tests rendering a content pane with a more link.
*/
public function testViewMoreLink() {
// The view "views_content_more_link" has two displays: a content pane
// display and a page display. The content pane display displays 3 nodes,
// the page display displays all items.
// On the content pane display a "more" link is configured that should link
// to the page.
$view = views_get_view('views_content_more_link');
// Create four nodes that the view will display. The first three
// will get displayed on the content pane display and the remaining
// on the page display.
for ($i = 0; $i < 4; $i++) {
$this->drupalCreateNode();
}
// Render the content pane display and assert that a more link is shown.
$view->set_display('panel_pane_1');
$rendered_output = $view->preview();
$this->verbose($rendered_output);
$this->storeViewPreview($rendered_output);
$this->assertLink('more');
$this->assertLinkByHref('views-content-more-link');
}
/**
* Stores a view output in the elements.
*
* @param string $output
* The Views HTML output.
*/
protected function storeViewPreview($output) {
$html_dom = new DOMDocument();
@$html_dom->loadHTML($output);
if ($html_dom) {
// It's much easier to work with simplexml than DOM, luckily enough
// we can just simply import our DOM tree.
$this->elements = simplexml_import_dom($html_dom);
}
}
}

View File

@ -8,9 +8,10 @@ package = Chaos tool suite
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
files[] = tests/src/views_content.test
; Information added by Drupal.org packaging script on 2020-10-23
version = "7.x-1.17"
; Information added by Drupal.org packaging script on 2021-01-30
version = "7.x-1.19"
core = "7.x"
project = "ctools"
datestamp = "1603490551"
datestamp = "1611988843"

View File

@ -8,7 +8,7 @@
"latedef": true,
"noarg": true,
"onevar": true,
"quotmark": "double",
"quotmark": "single",
"trailing": true,
"undef": true,
"unused": true,

View File

@ -1,7 +1,40 @@
Search API 1.27 (2021-02-01):
-----------------------------
- #3178035 by mvc, drunken monkey: Fixed OR facets for non-numeric hierarchical
fields.
- #3132404 by janadam, klausi, drunken monkey: Fixed support for latest Date
module version.
- #3123171 by drunken monkey: Fixed wrong Views filter handler used for URI
fields.
- #3109880 by drunken monkey: Fixed unnecessary processing of disabled indexes.
- #3122167 by pandaski, drunken monkey: Provided a processor for excluding
private file entities from being indexed.
- #3119271 by mibfire, drunken monkey: Fixed incomplete whitespace detection in
HTML filter.
- #3052798 by drunken monkey, smhnaji: Fixed errors on duplicate tracking of
new items.
- #3096993 by KarlShea, drunken monkey: Fixed Views "between" filters with only
maximum value.
- #3081180 by mibfire, drunken monkey: Fixed HTML filter sometimes leaving whitespace.
- #3070125 by chrisclark, drunken monkey: Fixed Views "Reset" button validating
minimum keywords length.
- #2378945 by DamienMcKenna, drunken monkey, capysara, JPHuxley, minorOffense:
Added option to keep facets when submitting Views exposed form.
- #3035977 by das-peter, drunken monkey: Fixed edge case problems with "Entity
HTML output" on multilingual sites.
- #3056882 by drunken monkey, WalkingDexter: Fixed highlighting in some edge
cases.
- #3041704 by drunken monkey: Added multi-type support for entity status filter
processors.
- #2290019 by n3or, azinck, drunken monkey: Added support for OR operator to
date facets.
- #3040980 by drunken monkey: Fixed undefined index notice in Highlighting
processor.
Search API 1.26 (2019-03-11):
-----------------------------
- #2324023 by drumm, drunken monkey: Changed Views field definition for to
float.
- #2324023 by drumm, drunken monkey: Changed Views field definition for
Relevance to float.
- #3008849 by pamatt, drunken monkey: Fixed non-exposed numeric and date
filters in Views.
- #3009744 by evgeny.chernyavskiy, drunken monkey: Fixed wrong "continue" in

View File

@ -285,9 +285,6 @@ class SearchApiFacetapiAdapter extends FacetapiAdapter {
'#default_value' => isset($options['date_granularity']) ? $options['date_granularity'] : FACETAPI_DATE_MINUTE,
);
// Date facets don't support the "OR" operator (for now).
$form['global']['operator']['#access'] = FALSE;
$default_value = FACETAPI_DATE_YEAR;
if (isset($options['date_granularity_min'])) {
$default_value = $options['date_granularity_min'];

View File

@ -54,13 +54,29 @@ class SearchApiFacetapiDate extends SearchApiFacetapiTerm implements FacetapiQue
$options['search_api_facets'][$this->facet['name']]['limit'] = -1;
}
if ($active = $this->adapter->getActiveItems($this->facet)) {
$item = end($active);
if ($active_items = $this->adapter->getActiveItems($this->facet)) {
$field = $this->facet['field'];
$filter = $this->createRangeFilter($item['value']);
if ($filter) {
$this->addFacetFilter($query, $field, $filter);
$operator = 'OR';
if ($settings['operator'] !== FACETAPI_OPERATOR_OR) {
$operator = 'AND';
// If the operator is AND, we just need to apply the lowest-level
// filter(s) to make this work correctly. For single-valued fields, this
// will always just be the last value, so just use that to improve
// performance for that case.
$fields = $query->getIndex()->getFields();
if (isset($fields[$field]['type'])
&& !search_api_is_list_type($fields[$field]['type'])) {
$active_items = array(end($active_items));
}
}
$date_query = $query->createFilter($operator, array("facet:$field"));
foreach($active_items as $active_item) {
$filter = $this->createRangeFilter($active_item['value']);
if ($filter) {
$this->addFacetFilter($date_query, $field, $filter);
}
}
$query->filter($date_query);
}
}
@ -224,8 +240,9 @@ class SearchApiFacetapiDate extends SearchApiFacetapiTerm implements FacetapiQue
// Executes query, iterates over results.
if (isset($results['search_api_facets']) && isset($results['search_api_facets'][$this->facet['name']])) {
$values = $results['search_api_facets'][$this->facet['name']];
$mincount = $facet->getSettings()->settings['facet_mincount'];
foreach ($values as $value) {
if ($value['count']) {
if ($value['count'] >= $mincount) {
$filter = $value['filter'];
// We only process single values further. The "missing" filter and
// range filters will be passed on unchanged.
@ -286,9 +303,6 @@ class SearchApiFacetapiDate extends SearchApiFacetapiTerm implements FacetapiQue
$build[$parent]['#item_children'][$value] = &$build[$value];
$build[$value]['#item_parents'][$parent] = $parent;
}
// Stores the last value iterated over.
$parent = $value;
}
if (empty($raw_values)) {

View File

@ -65,7 +65,7 @@ class SearchApiFacetapiTerm extends FacetapiQueryType implements FacetapiQueryTy
foreach (array_reverse($values) as $filter) {
// Skip this filter if it was already removed, or if it is the
// "missing value" filter ("!").
if (!isset($active[$filter]) || !is_numeric($filter)) {
if (!isset($active[$filter]) || $filter == '!') {
continue;
}
// Go through the entire hierarchy of the value and remove all its

View File

@ -9,8 +9,8 @@ files[] = plugins/facetapi/adapter.inc
files[] = plugins/facetapi/query_type_term.inc
files[] = plugins/facetapi/query_type_date.inc
; Information added by Drupal.org packaging script on 2019-03-11
version = "7.x-1.26"
; Information added by Drupal.org packaging script on 2021-02-01
version = "7.x-1.27"
core = "7.x"
project = "search_api"
datestamp = "1552334832"
datestamp = "1612192165"

View File

@ -507,6 +507,62 @@ function search_api_facetapi_search_api_admin_index_fields_submit($form, &$form_
cache_clear_all($cid, 'cache', TRUE);
}
/**
* Implements hook_form_FORM_ID_alter() for views_exposed_form().
*
* Custom integration for facets. When a Views exposed filter is modified on a
* search results page, any facets which have been already selected will be
* removed. This (optionally) adds hidden fields for each facet so their values
* are retained.
*/
function search_api_facetapi_form_views_exposed_form_alter(array &$form, array &$form_state) {
if (empty($form_state['view'])) {
return;
}
$view = $form_state['view'];
// Check if this is a Search API-based view and if the "Preserve facets"
// option is enabled. ("search_api_multi" would be the exact base table name,
// not just a prefix, but since it's just 16 characters long, we can still use
// this check to make the condition less complex.)
$base_table_prefix = substr($view->base_table, 0, 17);
if (in_array($base_table_prefix, array('search_api_index_', 'search_api_multi'))
&& _search_api_preserve_views_facets($view)) {
// Get query parameters.
$query_parameters = drupal_get_query_parameters();
// Check if any facet query parameters are provided.
if (!empty($query_parameters['f'])) {
// Iterate through facet query parameters.
foreach ($query_parameters['f'] as $key => $value) {
// Add hidden form field for facet parameter.
$form['f[' . $key . ']'] = array(
'#type' => 'hidden',
'#value' => $value,
'#weight' => -1,
);
}
}
}
}
/**
* Checks whether "Preserve facets" option is enabled on the given view.
*
* If the view display is overridden, use its configuration. Otherwise, use the
* default configuration.
*
* @param view $view
* The search view.
*
* @return bool
* TRUE if "Preserve facets" is enabled, FALSE otherwise.
*/
function _search_api_preserve_views_facets(view $view) {
$query_options = $view->display_handler->get_option('query');
return !empty($query_options['options']['preserve_facet_query_args']);
}
/**
* Computes the granularity of a date facet filter.
*

View File

@ -137,6 +137,12 @@ Fulltext search" filter or contextual filter using a normal filter on a
fulltext field won't parse the search keys, which means multiple words will only
be found when they appear as that exact phrase.
- Preserve facets while using filters
This is another option under "Advanced" > "Query settings", only available when
the Search Facets module is installed. When enabled, facet filters are persisted
when submitting an exposed filters form. When disabled (the default), exposed
filters will override and reset the selected facet filters.
FAQ: Why „*Indexed* Node“?
--------------------------
The group name used for the search result itself (in fields, filters, etc.) is

View File

@ -104,6 +104,10 @@ class SearchApiViewsHandlerFilterDate extends SearchApiViewsHandlerFilterNumeric
// If we are using the date popup widget, overwrite the settings of the form
// according to what date_popup expects.
elseif ($is_date_popup) {
// Add an "id" for the "value" field, since it is expected in
// date_views_form_views_exposed_form_alter().
// @see date_views_filter_handler_simple::value_form()
$form['value']['#id'] = 'date_views_exposed_filter-' . bin2hex(drupal_random_bytes(16));
$form['value']['#type'] = 'date_popup';
$form['value']['#date_format'] = $this->options['date_popup_format'];
$form['value']['#date_year_range'] = $this->options['year_range'];

View File

@ -100,6 +100,13 @@ class SearchApiViewsHandlerFilterFulltext extends SearchApiViewsHandlerFilterTex
return;
}
// Don't validate on form reset.
if (!empty($form_state['triggering_element'])
&& !empty($form['reset'])
&& $form_state['triggering_element']['#value'] === $form['reset']['#value']) {
return;
}
// We only need to validate if there is a minimum word length set.
if ($this->options['min_length'] < 2) {
return;

View File

@ -0,0 +1,248 @@
<?php
/**
* @file
* Contains SearchApiViewsHandlerFilterNumeric.
*/
/**
* Views filter handler class for handling numeric and "string" fields.
*/
class SearchApiViewsHandlerFilterNumeric extends SearchApiViewsHandlerFilter {
/**
* {@inheritdoc}
*/
public function init(&$view, &$options) {
parent::init($view, $options);
$this->normalizeValue();
}
/**
* {@inheritdoc}
*/
public function option_definition() {
$options = parent::option_definition();
$options['value'] = array(
'contains' => array(
'value' => array('default' => ''),
'min' => array('default' => ''),
'max' => array('default' => ''),
),
);
return $options;
}
/**
* {@inheritdoc}
*/
public function operator_options() {
$operators = parent::operator_options();
$index = search_api_index_load(substr($this->table, 17));
$server = NULL;
try {
if ($index) {
$server = $index->server();
}
}
catch (SearchApiException $e) {
// Ignore.
}
if ($server && $server->supportsFeature('search_api_between')) {
$operators += array(
'between' => t('Is between'),
'not between' => t('Is not between'),
);
}
return $operators;
}
/**
* Provides a form for setting the filter value.
*
* Heavily borrowed from views_handler_filter_numeric.
*
* @see views_handler_filter_numeric::value_form()
*/
public function value_form(&$form, &$form_state) {
$form['value']['#tree'] = TRUE;
$single_field_operators = $this->operator_options();
unset(
$single_field_operators['empty'],
$single_field_operators['not empty'],
$single_field_operators['between'],
$single_field_operators['not between']
);
$between_operators = array('between', 'not between');
// We have to make some choices when creating this as an exposed
// filter form. For example, if the operator is locked and thus
// not rendered, we can't render dependencies; instead we only
// render the form items we need.
$which = 'all';
$source = NULL;
if (!empty($form['operator'])) {
$source = ($form['operator']['#type'] == 'radios') ? 'radio:options[operator]' : 'edit-options-operator';
}
$identifier = NULL;
if (!empty($form_state['exposed'])) {
$identifier = $this->options['expose']['identifier'];
if (empty($this->options['expose']['use_operator']) || empty($this->options['expose']['operator_id'])) {
// Exposed and locked.
$which = in_array($this->operator, $between_operators) ? 'minmax' : 'value';
}
else {
$source = 'edit-' . drupal_html_id($this->options['expose']['operator_id']);
}
}
// Hide the value box if the operator is 'empty' or 'not empty'.
// Radios share the same selector so we have to add some dummy selector.
if ($which == 'all') {
$form['value']['value'] = array(
'#type' => 'textfield',
'#title' => empty($form_state['exposed']) ? t('Value') : '',
'#size' => 30,
'#default_value' => $this->value['value'],
'#dependency' => array($source => array_keys($single_field_operators)),
);
if ($identifier && !isset($form_state['input'][$identifier]['value'])) {
$form_state['input'][$identifier]['value'] = $this->value['value'];
}
}
elseif ($which == 'value') {
// When exposed we drop the value-value and just do value if
// the operator is locked.
$form['value'] = array(
'#type' => 'textfield',
'#title' => empty($form_state['exposed']) ? t('Value') : '',
'#size' => 30,
'#default_value' => isset($this->value['value']) ? $this->value['value'] : '',
);
if ($identifier && !isset($form_state['input'][$identifier])) {
$form_state['input'][$identifier] = isset($this->value['value']) ? $this->value['value'] : '';
}
}
if ($which == 'all' || $which == 'minmax') {
$form['value']['min'] = array(
'#type' => 'textfield',
'#title' => empty($form_state['exposed']) ? t('Min') : '',
'#size' => 30,
'#default_value' => $this->value['min'],
);
$form['value']['max'] = array(
'#type' => 'textfield',
'#title' => empty($form_state['exposed']) ? t('And max') : t('And'),
'#size' => 30,
'#default_value' => $this->value['max'],
);
if ($which == 'all') {
$form['value']['min']['#dependency'] = array($source => $between_operators);
$form['value']['max']['#dependency'] = array($source => $between_operators);
}
if (!empty($form_state['exposed']) && !isset($form_state['input'][$identifier]['min'])) {
$form_state['input'][$identifier]['min'] = $this->value['min'];
}
if (!empty($form_state['exposed']) && !isset($form_state['input'][$identifier]['max'])) {
$form_state['input'][$identifier]['max'] = $this->value['max'];
}
if (!isset($form['value']['value'])) {
// Ensure there is something in the 'value'.
$form['value']['value'] = array(
'#type' => 'value',
'#value' => NULL,
);
}
}
}
/**
* {@inheritdoc}
*/
public function admin_summary() {
if (!empty($this->options['exposed'])) {
return t('exposed');
}
if ($this->operator === 'empty') {
return t('is empty');
}
if ($this->operator === 'not empty') {
return t('is not empty');
}
if (in_array($this->operator, array('between', 'not between'), TRUE)) {
// This is of course wrong for translation purposes, but copied from
// views_handler_filter_numeric::admin_summary() so probably still better
// to re-use this than to do it correctly.
$operator = $this->operator === 'between' ? t('between') : t('not between');
$vars = array(
'@min' => (string) $this->value['min'],
'@max' => (string) $this->value['max'],
);
return $operator . ' ' . t('@min and @max', $vars);
}
return check_plain((string) $this->operator) . ' ' . check_plain((string) $this->value['value']);
}
/**
* {@inheritdoc}
*/
public function query() {
$this->normalizeValue();
if (in_array($this->operator, array('between', 'not between'), TRUE)) {
$min = $this->value['min'];
$max = $this->value['max'];
if ($min !== '' && $max !== '') {
$this->query->condition($this->real_field, array($min, $max), strtoupper($this->operator), $this->options['group']);
}
elseif ($min !== '') {
$operator = $this->operator === 'between' ? '>=' : '<';
$this->query->condition($this->real_field, $min, $operator, $this->options['group']);
}
elseif ($max !== '') {
$operator = $this->operator === 'between' ? '<=' : '>';
$this->query->condition($this->real_field, $max, $operator, $this->options['group']);
}
}
else {
// The parent handler doesn't expect our value structure, just pass the
// scalar value instead.
$this->value = $this->value['value'];
parent::query();
}
}
/**
* Sets $this->value to an array of options as defined by the filter.
*
* @see SearchApiViewsHandlerFilterNumeric::option_definition()
*/
protected function normalizeValue() {
$value = $this->value;
if (is_array($value) && isset($value[0])) {
$value = $value[0];
}
if (!is_array($value)) {
$value = array('value' => $value);
}
$this->value = array(
'value' => isset($value['value']) ? $value['value'] : '',
'min' => isset($value['min']) ? $value['min'] : '',
'max' => isset($value['max']) ? $value['max'] : '',
);
}
}

View File

@ -0,0 +1,146 @@
<?php
/**
* @file
* Contains the SearchApiViewsContentCache class.
*/
/**
* Plugin class for caching Search API views, with additional invalidation.
*/
class SearchApiViewsContentCache extends views_content_cache_plugin_cache {
/**
* Static cache for get_results_key().
*
* @var string
*/
protected $_results_key = NULL;
/**
* Static cache for getSearchApiQuery().
*
* @var SearchApiQueryInterface
*/
protected $search_api_query = NULL;
/**
* Overrides views_plugin_cache::cache_set().
*
* Also stores Search API's internal search results.
*/
public function cache_set($type) {
if ($type != 'results') {
return parent::cache_set($type);
}
$cid = $this->get_results_key();
$results = NULL;
$query_plugin = $this->view->query;
if ($query_plugin instanceof SearchApiViewsQuery) {
$results = $query_plugin->getSearchApiResults();
}
$data = array(
'result' => $this->view->result,
'total_rows' => isset($this->view->total_rows) ? $this->view->total_rows : 0,
'current_page' => $this->view->get_current_page(),
'search_api results' => $results,
);
cache_set($cid, $data, $this->table, $this->cache_set_expire($type));
}
/**
* Overrides views_plugin_cache::cache_get().
*
* Additionally stores successfully retrieved results with
* search_api_current_search().
*/
public function cache_get($type) {
if ($type != 'results') {
return parent::cache_get($type);
}
// Values to set: $view->result, $view->total_rows, $view->execute_time,
// $view->current_page.
if ($cache = cache_get($this->get_results_key(), $this->table)) {
$cutoff = $this->cache_expire($type);
if (!$cutoff || $cache->created > $cutoff) {
$this->view->result = $cache->data['result'];
$this->view->total_rows = $cache->data['total_rows'];
$this->view->set_current_page($cache->data['current_page']);
$this->view->execute_time = 0;
// Trick Search API into believing a search happened, to make facetting
// et al. work.
$query = $this->getSearchApiQuery();
search_api_current_search($query->getOption('search id'), $query, $cache->data['search_api results']);
return TRUE;
}
}
return FALSE;
}
/**
* Overrides views_plugin_cache::get_cache_key().
*
* Use the Search API query as the main source for the key. Note that in
* Views < 3.8, this method does not exist.
*/
public function get_cache_key($key_data = array()) {
global $user;
if (!isset($this->_results_key)) {
$query = $this->getSearchApiQuery();
$query->preExecute();
$key_data += array(
'query' => $query,
'roles' => array_keys($user->roles),
'super-user' => $user->uid == 1, // special caching for super user.
'language' => $GLOBALS['language']->language,
'base_url' => $GLOBALS['base_url'],
'offset' => $this->view->get_current_page() . '*' . $this->view->get_items_per_page() . '+' . $this->view->get_offset(),
);
// Not sure what gets passed in exposed_info, so better include it. All
// other parameters used in the parent method are already reflected in the
// Search API query object we use.
if (isset($_GET['exposed_info'])) {
$key_data['exposed_info'] = $_GET['exposed_info'];
}
}
$key = drupal_hash_base64(serialize($key_data));
return $key;
}
/**
* Overrides views_plugin_cache::get_results_key().
*
* This is unnecessary for Views >= 3.8.
*/
public function get_results_key() {
if (!isset($this->_results_key)) {
$this->_results_key = $this->view->name . ':' . $this->display->id . ':results:' . $this->get_cache_key();
}
return $this->_results_key;
}
/**
* Retrieves the Search API query object associated with the current view.
*
* @return SearchApiQueryInterface|null
* The Search API query object associated with the current view; or NULL if
* there is none.
*/
protected function getSearchApiQuery() {
if (!isset($this->search_api_query)) {
$this->search_api_query = FALSE;
if (isset($this->view->query) && $this->view->query instanceof SearchApiViewsQuery) {
$this->search_api_query = $this->view->query->getSearchApiQuery();
}
}
return $this->search_api_query ? $this->search_api_query : NULL;
}
}

View File

@ -197,6 +197,10 @@ class SearchApiViewsQuery extends views_plugin_query {
'parse_mode' => array(
'default' => 'terms',
),
'preserve_facet_query_args' => array(
'default' => FALSE,
'bool' => TRUE,
),
);
}
@ -243,6 +247,14 @@ class SearchApiViewsQuery extends views_plugin_query {
);
}
}
if (module_exists('facetapi')) {
$form['preserve_facet_query_args'] = array(
'#type' => 'checkbox',
'#title' => t('Preserve facets while using filters'),
'#description' => t('By default, changing an exposed filter would reset all selected facets. This option allows you to prevent this behavior.'),
'#default_value' => $this->options['preserve_facet_query_args'],
);
}
}
/**

View File

@ -29,8 +29,8 @@ files[] = includes/plugin_cache.inc
files[] = includes/plugin_content_cache.inc
files[] = includes/query.inc
; Information added by Drupal.org packaging script on 2019-03-11
version = "7.x-1.26"
; Information added by Drupal.org packaging script on 2021-02-01
version = "7.x-1.27"
core = "7.x"
project = "search_api"
datestamp = "1552334832"
datestamp = "1612192165"

View File

@ -220,7 +220,7 @@ function _search_api_views_add_handlers($id, array $field, EntityMetadataWrapper
$table[$id]['filter']['vocabulary'] = $vocabulary;
}
}
elseif (in_array($inner_type, array('integer', 'decimal', 'duration', 'string'))) {
elseif (in_array($inner_type, array('integer', 'decimal', 'duration', 'string', 'uri'))) {
$table[$id]['filter']['handler'] = 'SearchApiViewsHandlerFilterNumeric';
}
else {

View File

@ -27,7 +27,12 @@ class SearchApiAlterAddViewedEntity extends SearchApiAbstractAlterCallback {
$view_modes[$key] = $mode['label'];
}
}
$this->options += array('mode' => reset($view_modes));
$this->options += array(
'mode' => reset($view_modes),
// Backward compatible definition - if this is an existing config the
// language processing is disabled by default.
'global_language_switch' => !isset($this->options['mode']),
);
if (count($view_modes) > 1) {
$form['mode'] = array(
'#type' => 'select',
@ -55,6 +60,12 @@ class SearchApiAlterAddViewedEntity extends SearchApiAbstractAlterCallback {
);
}
}
$form['global_language_switch'] = array(
'#type' => 'checkbox',
'#title' => t('Adjust environment language when indexing'),
'#description' => t('If enabled, the indexing process will not just set the language for the entity view but also the global environment. This can prevent wrong translations leaking into the indexed data on multi-lingual sites, but causes problems in rare cases. Unless you notice any problems in connection with this, the recommended setting is enabled.'),
'#default_value' => !empty($this->options['global_language_switch']),
);
return $form;
}
@ -62,6 +73,14 @@ class SearchApiAlterAddViewedEntity extends SearchApiAbstractAlterCallback {
// Prevent session information from being saved while indexing.
drupal_save_session(FALSE);
// Language handling.
$languages = language_list();
$global_language = array(
'language' => $GLOBALS['language'],
'language_url' => $GLOBALS['language_url'],
'language_content' => $GLOBALS['language_content'],
);
// Force the current user to anonymous to prevent access bypass in search
// indexes.
$original_user = $GLOBALS['user'];
@ -74,6 +93,23 @@ class SearchApiAlterAddViewedEntity extends SearchApiAbstractAlterCallback {
// we use try/catch. This will at least prevent some errors, even though
// it's no protection against fatal errors and the like.
try {
// Check if the global language switch is enabled.
if (!empty($this->options['global_language_switch'])) {
// Language handling. We need to overwrite the global language
// configuration because parts of entity rendering won't rely on the
// passed in language (for instance, URL aliases).
if (isset($languages[$item->search_api_language])) {
$GLOBALS['language'] = $languages[$item->search_api_language];
$GLOBALS['language_url'] = $languages[$item->search_api_language];
$GLOBALS['language_content'] = $languages[$item->search_api_language];
}
else {
$GLOBALS['language'] = $global_language['language'];
$GLOBALS['language_url'] = $global_language['language_url'];
$GLOBALS['language_content'] = $global_language['language_content'];
}
}
$render = entity_view($type, array(entity_id($type, $item) => $item), $mode, $item->search_api_language);
$text = render($render);
if (!$text) {
@ -87,6 +123,13 @@ class SearchApiAlterAddViewedEntity extends SearchApiAbstractAlterCallback {
}
}
// Restore global language settings.
if (!empty($this->options['global_language_switch'])) {
$GLOBALS['language'] = $global_language['language'];
$GLOBALS['language_url'] = $global_language['language_url'];
$GLOBALS['language_content'] = $global_language['language_content'];
}
// Restore the user.
$GLOBALS['user'] = $original_user;
drupal_save_session(TRUE);

View File

@ -0,0 +1,42 @@
<?php
/**
* @file
* Contains SearchApiAlterFileEntityPublic.
*/
/**
* Excludes file entities in the private folder from being indexed.
*/
class SearchApiAlterFileEntityPublic extends SearchApiAbstractAlterCallback {
/**
* {@inheritdoc}
*/
public function supportsIndex(SearchApiIndex $index) {
if ($this->isMultiEntityIndex($index)) {
return in_array('file', $index->options['datasource']['types']);
}
return $index->getEntityType() === 'file';
}
/**
* {@inheritdoc}
*/
public function alterItems(array &$items) {
$multi_types = $this->isMultiEntityIndex($this->index);
foreach ($items as $id => $item) {
$file = $item;
if ($multi_types) {
if ($item->item_type !== 'file') {
continue;
}
$file = $item->file;
}
if (empty($file->uri) || substr($file->uri, 0, 10) === 'private://') {
unset($items[$id]);
}
}
}
}

View File

@ -22,6 +22,9 @@ class SearchApiAlterNodeStatus extends SearchApiAbstractAlterCallback {
* TRUE if the callback can run on the given index; FALSE otherwise.
*/
public function supportsIndex(SearchApiIndex $index) {
if ($this->isMultiEntityIndex($index)) {
return in_array('node', $index->options['datasource']['types']);
}
return $index->getEntityType() === 'node';
}
@ -35,9 +38,17 @@ class SearchApiAlterNodeStatus extends SearchApiAbstractAlterCallback {
* An array of items to be altered, keyed by item IDs.
*/
public function alterItems(array &$items) {
foreach ($items as $nid => &$item) {
if (empty($item->status)) {
unset($items[$nid]);
$multi_types = $this->isMultiEntityIndex($this->index);
foreach ($items as $id => $item) {
$node = $item;
if ($multi_types) {
if ($item->item_type !== 'node') {
continue;
}
$node = $item->node;
}
if (empty($node->status)) {
unset($items[$id]);
}
}
}

View File

@ -14,6 +14,9 @@ class SearchApiAlterUserStatus extends SearchApiAbstractAlterCallback {
* {@inheritdoc}
*/
public function supportsIndex(SearchApiIndex $index) {
if ($this->isMultiEntityIndex($index)) {
return in_array('user', $index->options['datasource']['types']);
}
return $index->getEntityType() == 'user';
}
@ -21,7 +24,15 @@ class SearchApiAlterUserStatus extends SearchApiAbstractAlterCallback {
* {@inheritdoc}
*/
public function alterItems(array &$items) {
foreach ($items as $id => $account) {
$multi_types = $this->isMultiEntityIndex($this->index);
foreach ($items as $id => $item) {
$account = $item;
if ($multi_types) {
if ($item->item_type !== 'user') {
continue;
}
$account = $item->user;
}
if (empty($account->status)) {
unset($items[$id]);
}

View File

@ -605,8 +605,22 @@ abstract class SearchApiAbstractDataSourceController implements SearchApiDataSou
foreach (array_chunk($item_ids, 1000) as $chunk) {
$insert = db_insert($this->table)
->fields(array($this->itemIdColumn, $this->indexIdColumn, $this->changedColumn));
foreach ($chunk as $item_id) {
foreach ($indexes as $index) {
foreach ($indexes as $index) {
// We have to make sure we don't try to insert duplicate items.
$select = db_select($this->table, 't');
$select->addField('t', $this->itemIdColumn);
$select->condition($this->indexIdColumn, $index->id);
$select->condition($this->itemIdColumn, $chunk, 'IN');
$existing = $select
->execute()
->fetchCol();
$existing = array_flip($existing);
foreach ($chunk as $item_id) {
if (isset($existing[$item_id])) {
continue;
}
$insert->values(array(
$this->itemIdColumn => $item_id,
$this->indexIdColumn => $index->id,

View File

@ -270,7 +270,7 @@ class SearchApiHighlight extends SearchApiAbstractProcessor {
$keywords = drupal_map_assoc(array_filter($keywords));
// Remove quotes from keywords.
foreach ($keywords as $key) {
$keywords[$key] = trim($key, "'\"");
$keywords[$key] = trim($key, "'\" ");
}
return drupal_map_assoc(array_filter($keywords));
}
@ -298,7 +298,7 @@ class SearchApiHighlight extends SearchApiAbstractProcessor {
$keywords += $this->flattenKeysArray($key);
}
else {
$keywords[$key] = $key;
$keywords[$key] = trim($key);
}
}
@ -360,7 +360,7 @@ class SearchApiHighlight extends SearchApiAbstractProcessor {
// and ends with a space.
$matches = array();
if (!$this->options['highlight_partial']) {
if (empty($this->options['highlight_partial'])) {
$found_position = FALSE;
$regex = '/' . static::$boundary . preg_quote($key, '/') . static::$boundary . '/iu';
if (preg_match($regex, ' ' . $text . ' ', $matches, PREG_OFFSET_CAPTURE, $look_start[$key])) {

View File

@ -101,9 +101,7 @@ class SearchApiHtmlFilter extends SearchApiAbstractProcessor {
$value = $this->parseText($text);
}
else {
$value = html_entity_decode(strip_tags($text));
// Remove any multiple or leading/trailing spaces we might have introduced.
$value = preg_replace('/\s\s+/', ' ', trim($value));
$value = $this->decodeHtml(strip_tags($text));
}
}
@ -111,11 +109,9 @@ class SearchApiHtmlFilter extends SearchApiAbstractProcessor {
$ret = array();
while (($pos = strpos($text, '<')) !== FALSE) {
if ($boost && $pos > 0) {
$token = html_entity_decode(substr($text, 0, $pos), ENT_QUOTES, 'UTF-8');
// Remove any multiple or leading/trailing spaces we might have introduced.
$token = preg_replace('/\s\s+/', ' ', trim($token));
$token = substr($text, 0, $pos);
$ret[] = array(
'value' => $token,
'value' => $this->decodeHtml($token),
'score' => $boost,
);
}
@ -137,11 +133,8 @@ class SearchApiHtmlFilter extends SearchApiAbstractProcessor {
}
}
if ($text) {
$token = html_entity_decode($text, ENT_QUOTES, 'UTF-8');
// Remove any multiple or leading/trailing spaces we might have introduced.
$token = preg_replace('/\s\s+/', ' ', trim($token));
$ret[] = array(
'value' => $token,
'value' => $this->decodeHtml($text),
'score' => $boost,
);
$text = '';
@ -149,4 +142,23 @@ class SearchApiHtmlFilter extends SearchApiAbstractProcessor {
return $ret;
}
/**
* Decodes HTML entities in a token and normalizes whitespace.
*
* All whitespace in the token will be converted to single spaces, with no
* leading or trailing whitespace.
*
* @param string $token
* The token to process.
*
* @return string
* The processed token.
*/
protected function decodeHtml($token) {
$token = html_entity_decode($token, ENT_QUOTES, 'UTF-8');
// Remove any multiple/leading/trailing spaces we might have introduced.
$token = trim(preg_replace('/[\pZ\pC]+/u', ' ', $token));
return $token;
}
}

View File

@ -13,17 +13,8 @@ class SearchApiIgnoreCase extends SearchApiAbstractProcessor {
protected function process(&$value) {
// We don't touch integers, NULL values or the like.
if (is_string($value)) {
// 1ka substring
if (strlen ($value) > 5){
$value = mb_substr($value, 0, -2, 'UTF-8');
}elseif (strlen ($value) > 2) {
$value = mb_substr($value, 0, -1, 'UTF-8');
}
$value = drupal_strtolower($value);
}
}
}

View File

@ -12,6 +12,7 @@ files[] = includes/callback_add_url.inc
files[] = includes/callback_add_viewed_entity.inc
files[] = includes/callback_bundle_filter.inc
files[] = includes/callback_comment_access.inc
files[] = includes/callback_file_entity_public.inc
files[] = includes/callback_language_control.inc
files[] = includes/callback_node_access.inc
files[] = includes/callback_node_status.inc
@ -38,8 +39,8 @@ files[] = includes/service.inc
configure = admin/config/search/search_api
; Information added by Drupal.org packaging script on 2019-03-11
version = "7.x-1.26"
; Information added by Drupal.org packaging script on 2021-02-01
version = "7.x-1.27"
core = "7.x"
project = "search_api"
datestamp = "1552334832"
datestamp = "1612192165"

View File

@ -947,7 +947,12 @@ function search_api_entity_delete($entity, $type) {
* "Comment access" data alteration.
*/
function search_api_node_access_records_alter(&$grants, $node) {
foreach (search_api_index_load_multiple(FALSE) as $index) {
$conditions = array(
'enabled' => 1,
'read_only' => 0,
);
$indexes = search_api_index_load_multiple(FALSE, $conditions);
foreach ($indexes as $index) {
$item_ids = array();
if (!empty($index->options['data_alter_callbacks']['search_api_alter_node_access']['status'])) {
$item_id = $index->datasource()->getItemId($node);
@ -965,8 +970,11 @@ function search_api_node_access_records_alter(&$grants, $node) {
}
if ($item_ids) {
$indexes = array($index->machine_name => $index);
search_api_track_item_change_for_indexes($index->item_type, $item_ids, $indexes);
search_api_track_item_change_for_indexes(
$index->item_type,
$item_ids,
array($index->machine_name => $index)
);
}
}
}
@ -1121,6 +1129,11 @@ function search_api_search_api_alter_callback_info() {
'description' => t('Allows to index hierarchical fields along with all their ancestors.'),
'class' => 'SearchApiAlterAddHierarchy',
);
$callbacks['search_api_alter_file_entity_public'] = array(
'name' => t('Exclude private files'),
'description' => t('Exclude file entities in the private files folder from being indexed. <strong>Caution:</strong> This only affects the indexed file entities themselves. If an indexed entity has references to file entities in the private folder, those will still be indexed (or displayed) normally.'),
'class' => 'SearchApiAlterFileEntityPublic',
);
$callbacks['search_api_alter_language_control'] = array(
'name' => t('Language control'),
'description' => t('Lets you determine the language of items in the index.'),
@ -3188,7 +3201,9 @@ function _search_api_index_queued_items() {
if ($queue) {
$indexes = search_api_index_load_multiple(array_keys($queue));
foreach ($indexes as $index_id => $index) {
search_api_index_specific_items($index, $queue[$index_id]);
if ($index->enabled && !$index->read_only) {
search_api_index_specific_items($index, $queue[$index_id]);
}
}
}

View File

@ -9,8 +9,8 @@ files[] = search_api_test.module
hidden = TRUE
; Information added by Drupal.org packaging script on 2019-03-11
version = "7.x-1.26"
; Information added by Drupal.org packaging script on 2021-02-01
version = "7.x-1.27"
core = "7.x"
project = "search_api"
datestamp = "1552334832"
datestamp = "1612192165"

View File

@ -9,8 +9,8 @@ files[] = search_api_test_service_2.module
hidden = TRUE
; Information added by Drupal.org packaging script on 2019-03-11
version = "7.x-1.26"
; Information added by Drupal.org packaging script on 2021-02-01
version = "7.x-1.27"
core = "7.x"
project = "search_api"
datestamp = "1552334832"
datestamp = "1612192165"

View File

@ -1,3 +1,12 @@
Search API Database Search 1.8 (2021-01-11):
--------------------------------------------
- #3191489 by drunken monkey, borisson_, Amir Simantov: Fixed searches with
special characters in keywords.
- #3142667 by drunken monkey, donquixote: Fixed reused variable name in
SearchApiDbService::indexItem().
- #3119370 by drunken monkey: Fixed errors during indexing cause by
non-standard whitespace.
Search API Database Search 1.7 (2018-09-17):
--------------------------------------------
- #2982443 by KarlShea, drunken monkey: Added support for the "(not) between"

View File

@ -6,8 +6,8 @@ package = Search
files[] = search_api_db.test
files[] = service.inc
; Information added by Drupal.org packaging script on 2018-09-17
version = "7.x-1.7"
; Information added by Drupal.org packaging script on 2021-01-11
version = "7.x-1.8"
core = "7.x"
project = "search_api_db"
datestamp = "1537173484"
datestamp = "1610364554"

View File

@ -891,21 +891,23 @@ class SearchApiDbService extends SearchApiAbstractService {
// at 500 words and 0.3 at 1000 words.
$focus = min(1, .01 + 3.5 / (2 + count($words) * .015));
$value = &$token['value'];
if (is_numeric($value)) {
$value = ltrim($value, '-0');
$token_value = &$token['value'];
$token_value = trim(preg_replace('/[\pZ\pC]+/u', ' ', $token_value));
if (is_numeric($token_value)) {
$token_value = ltrim($token_value, '-0');
}
elseif (drupal_strlen($value) < $this->options['min_chars']) {
elseif (drupal_strlen($token_value) < $this->options['min_chars']) {
continue;
}
$value = drupal_strtolower($value);
$token_value = drupal_strtolower($token_value);
$token['score'] *= $focus;
if (!isset($words[$value])) {
$words[$value] = $token;
if (!isset($words[$token_value])) {
$words[$token_value] = $token;
}
else {
$words[$value]['score'] += $token['score'];
$words[$token_value]['score'] += $token['score'];
}
unset($token_value);
}
if ($words) {
$field_name = self::getTextFieldName($name);
@ -1263,7 +1265,8 @@ class SearchApiDbService extends SearchApiAbstractService {
protected function createDbQuery(SearchApiQueryInterface $query, array $fields) {
$keys = &$query->getKeys();
$keys_set = (boolean) $keys;
$keys = $this->prepareKeys($keys);
$tokenizer_active = static::isTokenizerActive($query->getIndex());
$keys = $this->prepareKeys($keys, $tokenizer_active);
// Special case: if the outermost $keys array has "#negation" set, we can't
// handle it like other negated subkeys. To avoid additional complexity
// later, we just wrap $keys so it becomes a subkey.
@ -1317,7 +1320,7 @@ class SearchApiDbService extends SearchApiAbstractService {
$filter = $query->getFilter();
if ($filter->getFilters()) {
$condition = $this->createFilterCondition($filter, $fields, $db_query);
$condition = $this->createFilterCondition($filter, $fields, $db_query, $query->getIndex());
if ($condition) {
$db_query->condition($condition);
}
@ -1341,24 +1344,28 @@ class SearchApiDbService extends SearchApiAbstractService {
*
* @param array|string|null $keys
* The keys which should be preprocessed.
* @param bool $tokenizer_active
* (optional) TRUE if we can rely on the "Tokenizer" processor already
* having preprocessed the keywords.
*
* @return array|string|null
* The preprocessed keys.
*/
protected function prepareKeys($keys) {
protected function prepareKeys($keys, $tokenizer_active = FALSE) {
if (is_scalar($keys)) {
$keys = $this->splitKeys($keys);
$keys = $this->splitKeys($keys, $tokenizer_active);
return is_array($keys) ? $this->eliminateDuplicates($keys) : $keys;
}
elseif (!$keys) {
return NULL;
}
$keys = $this->eliminateDuplicates($this->splitKeys($keys));
$keys = $this->splitKeys($keys, $tokenizer_active);
$keys = $this->eliminateDuplicates($keys);
$conj = $keys['#conjunction'];
$neg = !empty($keys['#negation']);
foreach ($keys as $i => &$nested) {
if (is_array($nested)) {
$nested = $this->prepareKeys($nested);
$nested = $this->prepareKeys($nested, $tokenizer_active);
if (is_array($nested) && $neg == !empty($nested['#negation'])) {
if ($nested['#conjunction'] == $conj) {
unset($nested['#conjunction'], $nested['#negation']);
@ -1390,11 +1397,14 @@ class SearchApiDbService extends SearchApiAbstractService {
*
* @param array|string|null $keys
* The keys to split.
* @param bool $tokenizer_active
* (optional) TRUE if we can rely on the "Tokenizer" processor already
* having preprocessed the keywords.
*
* @return array|string|null
* The keys split into separate words.
*/
protected function splitKeys($keys) {
protected function splitKeys($keys, $tokenizer_active = FALSE) {
if (is_scalar($keys)) {
$proc = drupal_strtolower(trim($keys));
if (is_numeric($proc)) {
@ -1404,9 +1414,16 @@ class SearchApiDbService extends SearchApiAbstractService {
$this->ignored[$keys] = 1;
return NULL;
}
$words = preg_split('/[^\p{L}\p{N}]+/u', $proc, -1, PREG_SPLIT_NO_EMPTY);
if ($tokenizer_active) {
$words = array_filter(explode(' ', $proc), 'strlen');
}
else {
$words = preg_split('/[^\p{L}\p{N}]+/u', $proc, -1, PREG_SPLIT_NO_EMPTY);
}
if (count($words) > 1) {
$proc = $this->splitKeys($words);
$proc = $this->splitKeys($words, $tokenizer_active);
if ($proc) {
$proc['#conjunction'] = 'AND';
}
@ -1418,7 +1435,7 @@ class SearchApiDbService extends SearchApiAbstractService {
}
foreach ($keys as $i => $key) {
if (element_child($i)) {
$keys[$i] = $this->splitKeys($key);
$keys[$i] = $this->splitKeys($key, $tokenizer_active);
}
}
return array_filter($keys);
@ -1670,6 +1687,8 @@ class SearchApiDbService extends SearchApiAbstractService {
* Internal information about the index's fields.
* @param SelectQueryInterface $db_query
* The database query to which the condition will be added.
* @param SearchApiIndex $index
* (optional) The search index whose settings should be used.
*
* @return DatabaseCondition|null
* The condition to set on the query, or NULL if none is necessary.
@ -1677,7 +1696,7 @@ class SearchApiDbService extends SearchApiAbstractService {
* @throws SearchApiException
* If an unknown field was used in the filter.
*/
protected function createFilterCondition(SearchApiQueryFilterInterface $filter, array $fields, SelectQueryInterface $db_query) {
protected function createFilterCondition(SearchApiQueryFilterInterface $filter, array $fields, SelectQueryInterface $db_query, SearchApiIndex $index = NULL) {
$cond = db_condition($filter->getConjunction());
// Store whether a JOIN already occurred for a field, so we don't JOIN
// repeatedly for OR filters.
@ -1686,7 +1705,7 @@ class SearchApiDbService extends SearchApiAbstractService {
$tables = array();
foreach ($filter->getFilters() as $f) {
if (is_object($f)) {
$c = $this->createFilterCondition($f, $fields, $db_query);
$c = $this->createFilterCondition($f, $fields, $db_query, $index);
if ($c) {
$cond->condition($c);
}
@ -1712,7 +1731,10 @@ class SearchApiDbService extends SearchApiAbstractService {
continue;
}
if ($text_type) {
$keys = $this->prepareKeys($value);
if (!isset($tokenizer_active)) {
$tokenizer_active = $index && static::isTokenizerActive($index);
}
$keys = $this->prepareKeys($value, $tokenizer_active);
if (!isset($keys)) {
continue;
}
@ -2104,7 +2126,8 @@ class SearchApiDbService extends SearchApiAbstractService {
// Decide which methods we want to use.
if ($incomplete_key && $settings['suggest_suffix']) {
$processed_key = $this->splitKeys($incomplete_key);
$tokenizer_active = static::isTokenizerActive($index);
$processed_key = $this->splitKeys($incomplete_key, $tokenizer_active);
if ($processed_key) {
// In case the $incomplete_key turned out to be more than one word, add
// all but the last one to the user input.
@ -2292,4 +2315,18 @@ class SearchApiDbService extends SearchApiAbstractService {
return substr($str, $start, $length);
}
/**
* Determines whether the "Tokenizer" processor is enabled for an index.
*
* @param SearchApiIndex $index
* The index to check.
*
* @return bool
* TRUE if the built-in "Tokenizer" processor is enabled on the given index,
* FALSE otherwise.
*/
protected static function isTokenizerActive(SearchApiIndex $index) {
return !empty($index->options['processors']['search_api_tokenizer']['status']);
}
}