diff --git a/frontend/drupal/sites/all/modules/ctools/bulk_export/bulk_export.info b/frontend/drupal/sites/all/modules/ctools/bulk_export/bulk_export.info index 7ea996083..db788bed9 100644 --- a/frontend/drupal/sites/all/modules/ctools/bulk_export/bulk_export.info +++ b/frontend/drupal/sites/all/modules/ctools/bulk_export/bulk_export.info @@ -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" diff --git a/frontend/drupal/sites/all/modules/ctools/ctools.info b/frontend/drupal/sites/all/modules/ctools/ctools.info index 498504624..3a6169ca4 100644 --- a/frontend/drupal/sites/all/modules/ctools/ctools.info +++ b/frontend/drupal/sites/all/modules/ctools/ctools.info @@ -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" diff --git a/frontend/drupal/sites/all/modules/ctools/ctools_access_ruleset/ctools_access_ruleset.info b/frontend/drupal/sites/all/modules/ctools/ctools_access_ruleset/ctools_access_ruleset.info index 076bad0c7..380658093 100644 --- a/frontend/drupal/sites/all/modules/ctools/ctools_access_ruleset/ctools_access_ruleset.info +++ b/frontend/drupal/sites/all/modules/ctools/ctools_access_ruleset/ctools_access_ruleset.info @@ -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" diff --git a/frontend/drupal/sites/all/modules/ctools/ctools_ajax_sample/ctools_ajax_sample.info b/frontend/drupal/sites/all/modules/ctools/ctools_ajax_sample/ctools_ajax_sample.info index f52d44eff..b230b44a0 100644 --- a/frontend/drupal/sites/all/modules/ctools/ctools_ajax_sample/ctools_ajax_sample.info +++ b/frontend/drupal/sites/all/modules/ctools/ctools_ajax_sample/ctools_ajax_sample.info @@ -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" diff --git a/frontend/drupal/sites/all/modules/ctools/ctools_custom_content/ctools_custom_content.info b/frontend/drupal/sites/all/modules/ctools/ctools_custom_content/ctools_custom_content.info index 6340319be..c960e774a 100644 --- a/frontend/drupal/sites/all/modules/ctools/ctools_custom_content/ctools_custom_content.info +++ b/frontend/drupal/sites/all/modules/ctools/ctools_custom_content/ctools_custom_content.info @@ -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" diff --git a/frontend/drupal/sites/all/modules/ctools/ctools_plugin_example/ctools_plugin_example.info b/frontend/drupal/sites/all/modules/ctools/ctools_plugin_example/ctools_plugin_example.info index c2f41b338..19574e500 100644 --- a/frontend/drupal/sites/all/modules/ctools/ctools_plugin_example/ctools_plugin_example.info +++ b/frontend/drupal/sites/all/modules/ctools/ctools_plugin_example/ctools_plugin_example.info @@ -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" diff --git a/frontend/drupal/sites/all/modules/ctools/includes/context.inc b/frontend/drupal/sites/all/modules/ctools/includes/context.inc index 0fc620806..e933bec5e 100644 --- a/frontend/drupal/sites/all/modules/ctools/includes/context.inc +++ b/frontend/drupal/sites/all/modules/ctools/includes/context.inc @@ -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']]; } diff --git a/frontend/drupal/sites/all/modules/ctools/includes/export-ui.inc b/frontend/drupal/sites/all/modules/ctools/includes/export-ui.inc index 72e5a2fdf..1158ac8f6 100644 --- a/frontend/drupal/sites/all/modules/ctools/includes/export-ui.inc +++ b/frontend/drupal/sites/all/modules/ctools/includes/export-ui.inc @@ -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); + } } diff --git a/frontend/drupal/sites/all/modules/ctools/includes/export.inc b/frontend/drupal/sites/all/modules/ctools/includes/export.inc index d2fff2fdc..efc356efb 100644 --- a/frontend/drupal/sites/all/modules/ctools/includes/export.inc +++ b/frontend/drupal/sites/all/modules/ctools/includes/export.inc @@ -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"; } diff --git a/frontend/drupal/sites/all/modules/ctools/includes/fields.inc b/frontend/drupal/sites/all/modules/ctools/includes/fields.inc index 0d3256f2d..75e964f1d 100644 --- a/frontend/drupal/sites/all/modules/ctools/includes/fields.inc +++ b/frontend/drupal/sites/all/modules/ctools/includes/fields.inc @@ -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, diff --git a/frontend/drupal/sites/all/modules/ctools/includes/page-wizard.inc b/frontend/drupal/sites/all/modules/ctools/includes/page-wizard.inc index 9690aae07..d45d80421 100644 --- a/frontend/drupal/sites/all/modules/ctools/includes/page-wizard.inc +++ b/frontend/drupal/sites/all/modules/ctools/includes/page-wizard.inc @@ -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; } diff --git a/frontend/drupal/sites/all/modules/ctools/js/ajax-responder.js b/frontend/drupal/sites/all/modules/ctools/js/ajax-responder.js index 6c30b26df..116d4744f 100644 --- a/frontend/drupal/sites/all/modules/ctools/js/ajax-responder.js +++ b/frontend/drupal/sites/all/modules/ctools/js/ajax-responder.js @@ -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 . - 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'); diff --git a/frontend/drupal/sites/all/modules/ctools/js/auto-submit.js b/frontend/drupal/sites/all/modules/ctools/js/auto-submit.js index b658577a4..0e97e2eb5 100644 --- a/frontend/drupal/sites/all/modules/ctools/js/auto-submit.js +++ b/frontend/drupal/sites/all/modules/ctools/js/auto-submit.js @@ -98,5 +98,5 @@ Drupal.behaviors.CToolsAutoSubmit = { }); }); } -} +}; })(jQuery); diff --git a/frontend/drupal/sites/all/modules/ctools/js/collapsible-div.js b/frontend/drupal/sites/all/modules/ctools/js/collapsible-div.js index 4e21a2259..4719d7cc4 100644 --- a/frontend/drupal/sites/all/modules/ctools/js/collapsible-div.js +++ b/frontend/drupal/sites/all/modules/ctools/js/collapsible-div.js @@ -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); diff --git a/frontend/drupal/sites/all/modules/ctools/js/dependent.js b/frontend/drupal/sites/all/modules/ctools/js/dependent.js index 77777e38e..6e4b79670 100644 --- a/frontend/drupal/sites/all/modules/ctools/js/dependent.js +++ b/frontend/drupal/sites/all/modules/ctools/js/dependent.js @@ -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); diff --git a/frontend/drupal/sites/all/modules/ctools/js/dropbutton.js b/frontend/drupal/sites/all/modules/ctools/js/dropbutton.js index f505550b6..6d08d05a5 100644 --- a/frontend/drupal/sites/all/modules/ctools/js/dropbutton.js +++ b/frontend/drupal/sites/all/modules/ctools/js/dropbutton.js @@ -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); diff --git a/frontend/drupal/sites/all/modules/ctools/js/dropdown.js b/frontend/drupal/sites/all/modules/ctools/js/dropdown.js index c829ae2fe..e2488b1ea 100644 --- a/frontend/drupal/sites/all/modules/ctools/js/dropdown.js +++ b/frontend/drupal/sites/all/modules/ctools/js/dropdown.js @@ -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); diff --git a/frontend/drupal/sites/all/modules/ctools/js/jump-menu.js b/frontend/drupal/sites/all/modules/ctools/js/jump-menu.js index 7b0928a68..14852d5ad 100644 --- a/frontend/drupal/sites/all/modules/ctools/js/jump-menu.js +++ b/frontend/drupal/sites/all/modules/ctools/js/jump-menu.js @@ -38,5 +38,5 @@ return false; }); } - } + }; })(jQuery); diff --git a/frontend/drupal/sites/all/modules/ctools/js/modal.js b/frontend/drupal/sites/all/modules/ctools/js/modal.js index 71b4b6aa0..92f8d7860 100644 --- a/frontend/drupal/sites/all/modules/ctools/js/modal.js +++ b/frontend/drupal/sites/all/modules/ctools/js/modal.js @@ -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 += '
' - html += '
' // panels-modal-content + var html = ''; + html += '
'; + html += '
'; // panels-modal-content html += ' '; 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. diff --git a/frontend/drupal/sites/all/modules/ctools/js/stylizer.js b/frontend/drupal/sites/all/modules/ctools/js/stylizer.js index d48f64541..227d4f4be 100644 --- a/frontend/drupal/sites/all/modules/ctools/js/stylizer.js +++ b/frontend/drupal/sites/all/modules/ctools/js/stylizer.js @@ -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); diff --git a/frontend/drupal/sites/all/modules/ctools/page_manager/page_manager.info b/frontend/drupal/sites/all/modules/ctools/page_manager/page_manager.info index 77c9815bc..06720486a 100644 --- a/frontend/drupal/sites/all/modules/ctools/page_manager/page_manager.info +++ b/frontend/drupal/sites/all/modules/ctools/page_manager/page_manager.info @@ -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" diff --git a/frontend/drupal/sites/all/modules/ctools/plugins/access/node_status.inc b/frontend/drupal/sites/all/modules/ctools/plugins/access/node_status.inc index 137f2826c..519619bec 100644 --- a/frontend/drupal/sites/all/modules/ctools/plugins/access/node_status.inc +++ b/frontend/drupal/sites/all/modules/ctools/plugins/access/node_status.inc @@ -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); } /** diff --git a/frontend/drupal/sites/all/modules/ctools/plugins/content_types/form/entity_form_field.inc b/frontend/drupal/sites/all/modules/ctools/plugins/content_types/form/entity_form_field.inc index b4d833813..f2f1e7bd6 100644 --- a/frontend/drupal/sites/all/modules/ctools/plugins/content_types/form/entity_form_field.inc +++ b/frontend/drupal/sites/all/modules/ctools/plugins/content_types/form/entity_form_field.inc @@ -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)) { diff --git a/frontend/drupal/sites/all/modules/ctools/plugins/content_types/user_form/icon_user_form.png b/frontend/drupal/sites/all/modules/ctools/plugins/content_types/user_form/icon_user_form.png new file mode 100644 index 000000000..f0417cb65 Binary files /dev/null and b/frontend/drupal/sites/all/modules/ctools/plugins/content_types/user_form/icon_user_form.png differ diff --git a/frontend/drupal/sites/all/modules/ctools/plugins/content_types/user_form/user_form_actions.inc b/frontend/drupal/sites/all/modules/ctools/plugins/content_types/user_form/user_form_actions.inc new file mode 100644 index 000000000..e11587d81 --- /dev/null +++ b/frontend/drupal/sites/all/modules/ctools/plugins/content_types/user_form/user_form_actions.inc @@ -0,0 +1,65 @@ + 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; +} diff --git a/frontend/drupal/sites/all/modules/ctools/plugins/content_types/user_form/user_form_component.inc b/frontend/drupal/sites/all/modules/ctools/plugins/content_types/user_form/user_form_component.inc new file mode 100644 index 000000000..b4963f99b --- /dev/null +++ b/frontend/drupal/sites/all/modules/ctools/plugins/content_types/user_form/user_form_component.inc @@ -0,0 +1,72 @@ + 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']; +} diff --git a/frontend/drupal/sites/all/modules/ctools/plugins/content_types/user_form/user_form_email.inc b/frontend/drupal/sites/all/modules/ctools/plugins/content_types/user_form/user_form_email.inc new file mode 100644 index 000000000..346555d09 --- /dev/null +++ b/frontend/drupal/sites/all/modules/ctools/plugins/content_types/user_form/user_form_email.inc @@ -0,0 +1,56 @@ + 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; +} diff --git a/frontend/drupal/sites/all/modules/ctools/plugins/content_types/user_form/user_form_notify.inc b/frontend/drupal/sites/all/modules/ctools/plugins/content_types/user_form/user_form_notify.inc new file mode 100644 index 000000000..bb74963c1 --- /dev/null +++ b/frontend/drupal/sites/all/modules/ctools/plugins/content_types/user_form/user_form_notify.inc @@ -0,0 +1,51 @@ + 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; +} diff --git a/frontend/drupal/sites/all/modules/ctools/plugins/content_types/user_form/user_form_password.inc b/frontend/drupal/sites/all/modules/ctools/plugins/content_types/user_form/user_form_password.inc new file mode 100644 index 000000000..89bab949c --- /dev/null +++ b/frontend/drupal/sites/all/modules/ctools/plugins/content_types/user_form/user_form_password.inc @@ -0,0 +1,56 @@ + 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; +} diff --git a/frontend/drupal/sites/all/modules/ctools/plugins/content_types/user_form/user_form_picture.inc b/frontend/drupal/sites/all/modules/ctools/plugins/content_types/user_form/user_form_picture.inc new file mode 100644 index 000000000..ac310bed7 --- /dev/null +++ b/frontend/drupal/sites/all/modules/ctools/plugins/content_types/user_form/user_form_picture.inc @@ -0,0 +1,51 @@ + 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; +} diff --git a/frontend/drupal/sites/all/modules/ctools/plugins/content_types/user_form/user_form_roles.inc b/frontend/drupal/sites/all/modules/ctools/plugins/content_types/user_form/user_form_roles.inc new file mode 100644 index 000000000..47a169f7f --- /dev/null +++ b/frontend/drupal/sites/all/modules/ctools/plugins/content_types/user_form/user_form_roles.inc @@ -0,0 +1,51 @@ + 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; +} diff --git a/frontend/drupal/sites/all/modules/ctools/plugins/content_types/user_form/user_form_signature_settings.inc b/frontend/drupal/sites/all/modules/ctools/plugins/content_types/user_form/user_form_signature_settings.inc new file mode 100644 index 000000000..2a69ea09e --- /dev/null +++ b/frontend/drupal/sites/all/modules/ctools/plugins/content_types/user_form/user_form_signature_settings.inc @@ -0,0 +1,51 @@ + 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; +} diff --git a/frontend/drupal/sites/all/modules/ctools/plugins/content_types/user_form/user_form_status.inc b/frontend/drupal/sites/all/modules/ctools/plugins/content_types/user_form/user_form_status.inc new file mode 100644 index 000000000..7c7f87247 --- /dev/null +++ b/frontend/drupal/sites/all/modules/ctools/plugins/content_types/user_form/user_form_status.inc @@ -0,0 +1,51 @@ + 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; +} diff --git a/frontend/drupal/sites/all/modules/ctools/plugins/content_types/user_form/user_form_timezone.inc b/frontend/drupal/sites/all/modules/ctools/plugins/content_types/user_form/user_form_timezone.inc new file mode 100644 index 000000000..1a0066488 --- /dev/null +++ b/frontend/drupal/sites/all/modules/ctools/plugins/content_types/user_form/user_form_timezone.inc @@ -0,0 +1,51 @@ + 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; +} diff --git a/frontend/drupal/sites/all/modules/ctools/plugins/content_types/user_form/user_form_username.inc b/frontend/drupal/sites/all/modules/ctools/plugins/content_types/user_form/user_form_username.inc new file mode 100644 index 000000000..d64953085 --- /dev/null +++ b/frontend/drupal/sites/all/modules/ctools/plugins/content_types/user_form/user_form_username.inc @@ -0,0 +1,51 @@ + 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; +} diff --git a/frontend/drupal/sites/all/modules/ctools/plugins/contexts/user.inc b/frontend/drupal/sites/all/modules/ctools/plugins/contexts/user.inc index bbdbd1a27..9cd73085e 100644 --- a/frontend/drupal/sites/all/modules/ctools/plugins/contexts/user.inc +++ b/frontend/drupal/sites/all/modules/ctools/plugins/contexts/user.inc @@ -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']); diff --git a/frontend/drupal/sites/all/modules/ctools/plugins/export_ui/ctools_export_ui.class.php b/frontend/drupal/sites/all/modules/ctools/plugins/export_ui/ctools_export_ui.class.php index 0c8145e4b..f78795aad 100644 --- a/frontend/drupal/sites/all/modules/ctools/plugins/export_ui/ctools_export_ui.class.php +++ b/frontend/drupal/sites/all/modules/ctools/plugins/export_ui/ctools_export_ui.class.php @@ -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')), ); diff --git a/frontend/drupal/sites/all/modules/ctools/plugins/relationships/comment_parent.inc b/frontend/drupal/sites/all/modules/ctools/plugins/relationships/comment_parent.inc new file mode 100644 index 000000000..ae29a08ce --- /dev/null +++ b/frontend/drupal/sites/all/modules/ctools/plugins/relationships/comment_parent.inc @@ -0,0 +1,32 @@ + 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); + } +} diff --git a/frontend/drupal/sites/all/modules/ctools/stylizer/stylizer.info b/frontend/drupal/sites/all/modules/ctools/stylizer/stylizer.info index a86ada601..6b7f70f5e 100644 --- a/frontend/drupal/sites/all/modules/ctools/stylizer/stylizer.info +++ b/frontend/drupal/sites/all/modules/ctools/stylizer/stylizer.info @@ -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" diff --git a/frontend/drupal/sites/all/modules/ctools/term_depth/term_depth.info b/frontend/drupal/sites/all/modules/ctools/term_depth/term_depth.info index 4f05954df..5cceeda7a 100644 --- a/frontend/drupal/sites/all/modules/ctools/term_depth/term_depth.info +++ b/frontend/drupal/sites/all/modules/ctools/term_depth/term_depth.info @@ -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" diff --git a/frontend/drupal/sites/all/modules/ctools/tests/ctools_export_test/ctools_export_test.info b/frontend/drupal/sites/all/modules/ctools/tests/ctools_export_test/ctools_export_test.info index bc231dbf1..6bb6063a3 100644 --- a/frontend/drupal/sites/all/modules/ctools/tests/ctools_export_test/ctools_export_test.info +++ b/frontend/drupal/sites/all/modules/ctools/tests/ctools_export_test/ctools_export_test.info @@ -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" diff --git a/frontend/drupal/sites/all/modules/ctools/tests/ctools_plugin_test.info b/frontend/drupal/sites/all/modules/ctools/tests/ctools_plugin_test.info index bdb624c7b..5c955bb41 100644 --- a/frontend/drupal/sites/all/modules/ctools/tests/ctools_plugin_test.info +++ b/frontend/drupal/sites/all/modules/ctools/tests/ctools_plugin_test.info @@ -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" diff --git a/frontend/drupal/sites/all/modules/ctools/views_content/plugins/views/views_content_plugin_display_panel_pane.inc b/frontend/drupal/sites/all/modules/ctools/views_content/plugins/views/views_content_plugin_display_panel_pane.inc index 801d3d61e..af0704f30 100644 --- a/frontend/drupal/sites/all/modules/ctools/views_content/plugins/views/views_content_plugin_display_panel_pane.inc +++ b/frontend/drupal/sites/all/modules/ctools/views_content/plugins/views/views_content_plugin_display_panel_pane.inc @@ -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} */ diff --git a/frontend/drupal/sites/all/modules/ctools/views_content/tests/modules/views_content_test.info b/frontend/drupal/sites/all/modules/ctools/views_content/tests/modules/views_content_test.info new file mode 100644 index 000000000..336876a85 --- /dev/null +++ b/frontend/drupal/sites/all/modules/ctools/views_content/tests/modules/views_content_test.info @@ -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" diff --git a/frontend/drupal/sites/all/modules/ctools/views_content/tests/modules/views_content_test.module b/frontend/drupal/sites/all/modules/ctools/views_content/tests/modules/views_content_test.module new file mode 100644 index 000000000..52998650a --- /dev/null +++ b/frontend/drupal/sites/all/modules/ctools/views_content/tests/modules/views_content_test.module @@ -0,0 +1,15 @@ + 3.0, + ); +} diff --git a/frontend/drupal/sites/all/modules/ctools/views_content/tests/modules/views_content_test.views_default.inc b/frontend/drupal/sites/all/modules/ctools/views_content/tests/modules/views_content_test.views_default.inc new file mode 100644 index 000000000..6dd08494c --- /dev/null +++ b/frontend/drupal/sites/all/modules/ctools/views_content/tests/modules/views_content_test.views_default.inc @@ -0,0 +1,82 @@ +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; +} diff --git a/frontend/drupal/sites/all/modules/ctools/views_content/tests/src/views_content.test b/frontend/drupal/sites/all/modules/ctools/views_content/tests/src/views_content.test new file mode 100644 index 000000000..25fee392b --- /dev/null +++ b/frontend/drupal/sites/all/modules/ctools/views_content/tests/src/views_content.test @@ -0,0 +1,79 @@ + '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); + } + } + +} diff --git a/frontend/drupal/sites/all/modules/ctools/views_content/views_content.info b/frontend/drupal/sites/all/modules/ctools/views_content/views_content.info index 9b348c52a..3ab45e665 100644 --- a/frontend/drupal/sites/all/modules/ctools/views_content/views_content.info +++ b/frontend/drupal/sites/all/modules/ctools/views_content/views_content.info @@ -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" diff --git a/frontend/drupal/sites/all/modules/jquery_update/replace/ui/ui/.jshintrc b/frontend/drupal/sites/all/modules/jquery_update/replace/ui/ui/.jshintrc index c7589525f..3204017c9 100644 --- a/frontend/drupal/sites/all/modules/jquery_update/replace/ui/ui/.jshintrc +++ b/frontend/drupal/sites/all/modules/jquery_update/replace/ui/ui/.jshintrc @@ -8,7 +8,7 @@ "latedef": true, "noarg": true, "onevar": true, - "quotmark": "double", + "quotmark": "single", "trailing": true, "undef": true, "unused": true, diff --git a/frontend/drupal/sites/all/modules/search_api/CHANGELOG.txt b/frontend/drupal/sites/all/modules/search_api/CHANGELOG.txt index c1e66d96e..208e77d84 100644 --- a/frontend/drupal/sites/all/modules/search_api/CHANGELOG.txt +++ b/frontend/drupal/sites/all/modules/search_api/CHANGELOG.txt @@ -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 diff --git a/frontend/drupal/sites/all/modules/search_api/contrib/search_api_facetapi/plugins/facetapi/adapter.inc b/frontend/drupal/sites/all/modules/search_api/contrib/search_api_facetapi/plugins/facetapi/adapter.inc index a5b5cdc0f..f4ff4306d 100644 --- a/frontend/drupal/sites/all/modules/search_api/contrib/search_api_facetapi/plugins/facetapi/adapter.inc +++ b/frontend/drupal/sites/all/modules/search_api/contrib/search_api_facetapi/plugins/facetapi/adapter.inc @@ -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']; diff --git a/frontend/drupal/sites/all/modules/search_api/contrib/search_api_facetapi/plugins/facetapi/query_type_date.inc b/frontend/drupal/sites/all/modules/search_api/contrib/search_api_facetapi/plugins/facetapi/query_type_date.inc index 6aeb83624..6d24064df 100644 --- a/frontend/drupal/sites/all/modules/search_api/contrib/search_api_facetapi/plugins/facetapi/query_type_date.inc +++ b/frontend/drupal/sites/all/modules/search_api/contrib/search_api_facetapi/plugins/facetapi/query_type_date.inc @@ -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)) { diff --git a/frontend/drupal/sites/all/modules/search_api/contrib/search_api_facetapi/plugins/facetapi/query_type_term.inc b/frontend/drupal/sites/all/modules/search_api/contrib/search_api_facetapi/plugins/facetapi/query_type_term.inc index 4c674a8af..25f26b15c 100644 --- a/frontend/drupal/sites/all/modules/search_api/contrib/search_api_facetapi/plugins/facetapi/query_type_term.inc +++ b/frontend/drupal/sites/all/modules/search_api/contrib/search_api_facetapi/plugins/facetapi/query_type_term.inc @@ -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 diff --git a/frontend/drupal/sites/all/modules/search_api/contrib/search_api_facetapi/search_api_facetapi.info b/frontend/drupal/sites/all/modules/search_api/contrib/search_api_facetapi/search_api_facetapi.info index 904a64dcb..6896e0f61 100644 --- a/frontend/drupal/sites/all/modules/search_api/contrib/search_api_facetapi/search_api_facetapi.info +++ b/frontend/drupal/sites/all/modules/search_api/contrib/search_api_facetapi/search_api_facetapi.info @@ -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" diff --git a/frontend/drupal/sites/all/modules/search_api/contrib/search_api_facetapi/search_api_facetapi.module b/frontend/drupal/sites/all/modules/search_api/contrib/search_api_facetapi/search_api_facetapi.module index 3696eae5e..eaf25358a 100644 --- a/frontend/drupal/sites/all/modules/search_api/contrib/search_api_facetapi/search_api_facetapi.module +++ b/frontend/drupal/sites/all/modules/search_api/contrib/search_api_facetapi/search_api_facetapi.module @@ -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. * diff --git a/frontend/drupal/sites/all/modules/search_api/contrib/search_api_views/README.txt b/frontend/drupal/sites/all/modules/search_api/contrib/search_api_views/README.txt index 50cfce1c4..61c9cc1ce 100644 --- a/frontend/drupal/sites/all/modules/search_api/contrib/search_api_views/README.txt +++ b/frontend/drupal/sites/all/modules/search_api/contrib/search_api_views/README.txt @@ -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 diff --git a/frontend/drupal/sites/all/modules/search_api/contrib/search_api_views/includes/handler_filter_date.inc b/frontend/drupal/sites/all/modules/search_api/contrib/search_api_views/includes/handler_filter_date.inc index c921e9f53..d86e0a509 100644 --- a/frontend/drupal/sites/all/modules/search_api/contrib/search_api_views/includes/handler_filter_date.inc +++ b/frontend/drupal/sites/all/modules/search_api/contrib/search_api_views/includes/handler_filter_date.inc @@ -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']; diff --git a/frontend/drupal/sites/all/modules/search_api/contrib/search_api_views/includes/handler_filter_fulltext.inc b/frontend/drupal/sites/all/modules/search_api/contrib/search_api_views/includes/handler_filter_fulltext.inc index dcb3e263d..58a2fbdeb 100644 --- a/frontend/drupal/sites/all/modules/search_api/contrib/search_api_views/includes/handler_filter_fulltext.inc +++ b/frontend/drupal/sites/all/modules/search_api/contrib/search_api_views/includes/handler_filter_fulltext.inc @@ -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; diff --git a/frontend/drupal/sites/all/modules/search_api/contrib/search_api_views/includes/handler_filter_numeric.inc b/frontend/drupal/sites/all/modules/search_api/contrib/search_api_views/includes/handler_filter_numeric.inc new file mode 100644 index 000000000..5c970e4a8 --- /dev/null +++ b/frontend/drupal/sites/all/modules/search_api/contrib/search_api_views/includes/handler_filter_numeric.inc @@ -0,0 +1,248 @@ +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'] : '', + ); + } + +} diff --git a/frontend/drupal/sites/all/modules/search_api/contrib/search_api_views/includes/plugin_content_cache.inc b/frontend/drupal/sites/all/modules/search_api/contrib/search_api_views/includes/plugin_content_cache.inc new file mode 100644 index 000000000..555fe89a1 --- /dev/null +++ b/frontend/drupal/sites/all/modules/search_api/contrib/search_api_views/includes/plugin_content_cache.inc @@ -0,0 +1,146 @@ +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; + } + +} diff --git a/frontend/drupal/sites/all/modules/search_api/contrib/search_api_views/includes/query.inc b/frontend/drupal/sites/all/modules/search_api/contrib/search_api_views/includes/query.inc index ae58b9a72..6fb7ea473 100644 --- a/frontend/drupal/sites/all/modules/search_api/contrib/search_api_views/includes/query.inc +++ b/frontend/drupal/sites/all/modules/search_api/contrib/search_api_views/includes/query.inc @@ -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'], + ); + } } /** diff --git a/frontend/drupal/sites/all/modules/search_api/contrib/search_api_views/search_api_views.info b/frontend/drupal/sites/all/modules/search_api/contrib/search_api_views/search_api_views.info index 2f0bfd727..abe7ba37c 100644 --- a/frontend/drupal/sites/all/modules/search_api/contrib/search_api_views/search_api_views.info +++ b/frontend/drupal/sites/all/modules/search_api/contrib/search_api_views/search_api_views.info @@ -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" diff --git a/frontend/drupal/sites/all/modules/search_api/contrib/search_api_views/search_api_views.views.inc b/frontend/drupal/sites/all/modules/search_api/contrib/search_api_views/search_api_views.views.inc index afec2d866..0e4cd3eb8 100644 --- a/frontend/drupal/sites/all/modules/search_api/contrib/search_api_views/search_api_views.views.inc +++ b/frontend/drupal/sites/all/modules/search_api/contrib/search_api_views/search_api_views.views.inc @@ -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 { diff --git a/frontend/drupal/sites/all/modules/search_api/includes/callback_add_viewed_entity.inc b/frontend/drupal/sites/all/modules/search_api/includes/callback_add_viewed_entity.inc index bb2ae07d1..cc44d68a3 100644 --- a/frontend/drupal/sites/all/modules/search_api/includes/callback_add_viewed_entity.inc +++ b/frontend/drupal/sites/all/modules/search_api/includes/callback_add_viewed_entity.inc @@ -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); diff --git a/frontend/drupal/sites/all/modules/search_api/includes/callback_file_entity_public.inc b/frontend/drupal/sites/all/modules/search_api/includes/callback_file_entity_public.inc new file mode 100644 index 000000000..91ff546fc --- /dev/null +++ b/frontend/drupal/sites/all/modules/search_api/includes/callback_file_entity_public.inc @@ -0,0 +1,42 @@ +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]); + } + } + } + +} diff --git a/frontend/drupal/sites/all/modules/search_api/includes/callback_node_status.inc b/frontend/drupal/sites/all/modules/search_api/includes/callback_node_status.inc index ee57c3875..0822b18b4 100644 --- a/frontend/drupal/sites/all/modules/search_api/includes/callback_node_status.inc +++ b/frontend/drupal/sites/all/modules/search_api/includes/callback_node_status.inc @@ -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]); } } } diff --git a/frontend/drupal/sites/all/modules/search_api/includes/callback_user_status.inc b/frontend/drupal/sites/all/modules/search_api/includes/callback_user_status.inc index 541f1b2d1..06c1bb996 100644 --- a/frontend/drupal/sites/all/modules/search_api/includes/callback_user_status.inc +++ b/frontend/drupal/sites/all/modules/search_api/includes/callback_user_status.inc @@ -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]); } diff --git a/frontend/drupal/sites/all/modules/search_api/includes/datasource.inc b/frontend/drupal/sites/all/modules/search_api/includes/datasource.inc index 9661092c4..2dd99fd63 100644 --- a/frontend/drupal/sites/all/modules/search_api/includes/datasource.inc +++ b/frontend/drupal/sites/all/modules/search_api/includes/datasource.inc @@ -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, diff --git a/frontend/drupal/sites/all/modules/search_api/includes/processor_highlight.inc b/frontend/drupal/sites/all/modules/search_api/includes/processor_highlight.inc index 2ac502a1f..563e6e2ed 100644 --- a/frontend/drupal/sites/all/modules/search_api/includes/processor_highlight.inc +++ b/frontend/drupal/sites/all/modules/search_api/includes/processor_highlight.inc @@ -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])) { diff --git a/frontend/drupal/sites/all/modules/search_api/includes/processor_html_filter.inc b/frontend/drupal/sites/all/modules/search_api/includes/processor_html_filter.inc index 0cc4800d7..ba005a4b6 100644 --- a/frontend/drupal/sites/all/modules/search_api/includes/processor_html_filter.inc +++ b/frontend/drupal/sites/all/modules/search_api/includes/processor_html_filter.inc @@ -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; + } + } diff --git a/frontend/drupal/sites/all/modules/search_api/includes/processor_ignore_case.inc b/frontend/drupal/sites/all/modules/search_api/includes/processor_ignore_case.inc index c600cec97..4a88e73d2 100644 --- a/frontend/drupal/sites/all/modules/search_api/includes/processor_ignore_case.inc +++ b/frontend/drupal/sites/all/modules/search_api/includes/processor_ignore_case.inc @@ -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); } - } } diff --git a/frontend/drupal/sites/all/modules/search_api/search_api.info b/frontend/drupal/sites/all/modules/search_api/search_api.info index b59bc9295..120b6635e 100644 --- a/frontend/drupal/sites/all/modules/search_api/search_api.info +++ b/frontend/drupal/sites/all/modules/search_api/search_api.info @@ -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" diff --git a/frontend/drupal/sites/all/modules/search_api/search_api.module b/frontend/drupal/sites/all/modules/search_api/search_api.module index c1cf4e4bc..07bd94f39 100644 --- a/frontend/drupal/sites/all/modules/search_api/search_api.module +++ b/frontend/drupal/sites/all/modules/search_api/search_api.module @@ -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. Caution: 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]); + } } } diff --git a/frontend/drupal/sites/all/modules/search_api/tests/search_api_test.info b/frontend/drupal/sites/all/modules/search_api/tests/search_api_test.info index 2c5f46e00..0bb2aa762 100644 --- a/frontend/drupal/sites/all/modules/search_api/tests/search_api_test.info +++ b/frontend/drupal/sites/all/modules/search_api/tests/search_api_test.info @@ -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" diff --git a/frontend/drupal/sites/all/modules/search_api/tests/search_api_test_2.info b/frontend/drupal/sites/all/modules/search_api/tests/search_api_test_2.info index 5d36c8fbc..65344f201 100644 --- a/frontend/drupal/sites/all/modules/search_api/tests/search_api_test_2.info +++ b/frontend/drupal/sites/all/modules/search_api/tests/search_api_test_2.info @@ -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" diff --git a/frontend/drupal/sites/all/modules/search_api_db/CHANGELOG.txt b/frontend/drupal/sites/all/modules/search_api_db/CHANGELOG.txt index da676895c..6f9ec5d4e 100644 --- a/frontend/drupal/sites/all/modules/search_api_db/CHANGELOG.txt +++ b/frontend/drupal/sites/all/modules/search_api_db/CHANGELOG.txt @@ -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" diff --git a/frontend/drupal/sites/all/modules/search_api_db/search_api_db.info b/frontend/drupal/sites/all/modules/search_api_db/search_api_db.info index 3a2bd8481..1ea2e7ea1 100644 --- a/frontend/drupal/sites/all/modules/search_api_db/search_api_db.info +++ b/frontend/drupal/sites/all/modules/search_api_db/search_api_db.info @@ -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" diff --git a/frontend/drupal/sites/all/modules/search_api_db/service.inc b/frontend/drupal/sites/all/modules/search_api_db/service.inc index b98d5b793..324cd1010 100644 --- a/frontend/drupal/sites/all/modules/search_api_db/service.inc +++ b/frontend/drupal/sites/all/modules/search_api_db/service.inc @@ -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']); + } + }