From 8d24211bf531ac36ccd349cc72d08e6cb2dc8b0c Mon Sep 17 00:00:00 2001 From: Bachir Soussi Chiadmi Date: Sat, 5 Nov 2016 17:52:37 +0100 Subject: [PATCH] updated synonyms to 1.3 --- .../contrib/taxonomy/synonyms/README.txt | 67 +- .../taxonomy/synonyms/help/synonyms.help.ini | 14 + .../taxonomy/synonyms/help/synonyms.html | 79 + .../synonyms_behavior_implementation.html | 27 + .../synonyms/help/synonyms_behaviors.html | 40 + .../AbstractSynonymsExtractor.class.inc | 116 - .../EntityReferenceSynonymsBehavior.class.inc | 84 + ...EntityReferenceSynonymsExtractor.class.inc | 70 - .../includes/SynonymsBehavior.interface.inc | 188 ++ .../SynonymsSynonymsExtractor.class.inc | 48 - .../TaxonomySynonymsBehavior.class.inc | 88 + .../TaxonomySynonymsExtractor.class.inc | 94 - .../includes/TextSynonymsBehavior.class.inc | 68 + .../synonyms/js/synonyms-autocomplete.js | 24 + .../plugins/arguments/term_synonyms.inc | 147 + .../plugins/behavior/autocomplete.inc | 41 + .../synonyms/plugins/behavior/select.inc | 41 + .../synonyms/plugins/behavior/synonyms.inc | 12 + .../taxonomy/synonyms/synonyms.api.php | 225 +- .../contrib/taxonomy/synonyms/synonyms.css | 1 + .../contrib/taxonomy/synonyms/synonyms.info | 19 +- .../taxonomy/synonyms/synonyms.install | 150 +- .../contrib/taxonomy/synonyms/synonyms.module | 1245 ++++++--- .../taxonomy/synonyms/synonyms.pages.inc | 243 +- .../contrib/taxonomy/synonyms/synonyms.test | 2402 +++++++++++------ ...hEntityReferenceSynonymsBehavior.class.inc | 12 + .../SearchSynonymsBehavior.interface.inc | 14 + .../SearchTaxonomySynonymsBehavior.class.inc | 12 + .../SearchTextSynonymsBehavior.class.inc | 12 + .../plugins/behavior/search.inc | 36 + .../synonyms_search/synonyms_search.info | 20 + .../synonyms_search/synonyms_search.module | 115 + .../synonyms_search/synonyms_search.pages.inc | 68 + .../synonyms_search/synonyms_search.test | 367 +++ .../synonyms/views/synonyms.views.inc | 20 +- ...synonyms_views_handler_filter_term_tid.inc | 174 ++ ...plugin_argument_validate_taxonomy_term.inc | 102 +- 37 files changed, 4562 insertions(+), 1923 deletions(-) create mode 100644 sites/all/modules/contrib/taxonomy/synonyms/help/synonyms.help.ini create mode 100644 sites/all/modules/contrib/taxonomy/synonyms/help/synonyms.html create mode 100644 sites/all/modules/contrib/taxonomy/synonyms/help/synonyms_behavior_implementation.html create mode 100644 sites/all/modules/contrib/taxonomy/synonyms/help/synonyms_behaviors.html delete mode 100644 sites/all/modules/contrib/taxonomy/synonyms/includes/AbstractSynonymsExtractor.class.inc create mode 100644 sites/all/modules/contrib/taxonomy/synonyms/includes/EntityReferenceSynonymsBehavior.class.inc delete mode 100644 sites/all/modules/contrib/taxonomy/synonyms/includes/EntityReferenceSynonymsExtractor.class.inc create mode 100644 sites/all/modules/contrib/taxonomy/synonyms/includes/SynonymsBehavior.interface.inc delete mode 100644 sites/all/modules/contrib/taxonomy/synonyms/includes/SynonymsSynonymsExtractor.class.inc create mode 100644 sites/all/modules/contrib/taxonomy/synonyms/includes/TaxonomySynonymsBehavior.class.inc delete mode 100644 sites/all/modules/contrib/taxonomy/synonyms/includes/TaxonomySynonymsExtractor.class.inc create mode 100644 sites/all/modules/contrib/taxonomy/synonyms/includes/TextSynonymsBehavior.class.inc create mode 100644 sites/all/modules/contrib/taxonomy/synonyms/js/synonyms-autocomplete.js create mode 100644 sites/all/modules/contrib/taxonomy/synonyms/plugins/arguments/term_synonyms.inc create mode 100644 sites/all/modules/contrib/taxonomy/synonyms/plugins/behavior/autocomplete.inc create mode 100644 sites/all/modules/contrib/taxonomy/synonyms/plugins/behavior/select.inc create mode 100644 sites/all/modules/contrib/taxonomy/synonyms/plugins/behavior/synonyms.inc create mode 100644 sites/all/modules/contrib/taxonomy/synonyms/synonyms.css create mode 100644 sites/all/modules/contrib/taxonomy/synonyms/synonyms_search/includes/SearchEntityReferenceSynonymsBehavior.class.inc create mode 100644 sites/all/modules/contrib/taxonomy/synonyms/synonyms_search/includes/SearchSynonymsBehavior.interface.inc create mode 100644 sites/all/modules/contrib/taxonomy/synonyms/synonyms_search/includes/SearchTaxonomySynonymsBehavior.class.inc create mode 100644 sites/all/modules/contrib/taxonomy/synonyms/synonyms_search/includes/SearchTextSynonymsBehavior.class.inc create mode 100644 sites/all/modules/contrib/taxonomy/synonyms/synonyms_search/plugins/behavior/search.inc create mode 100644 sites/all/modules/contrib/taxonomy/synonyms/synonyms_search/synonyms_search.info create mode 100644 sites/all/modules/contrib/taxonomy/synonyms/synonyms_search/synonyms_search.module create mode 100644 sites/all/modules/contrib/taxonomy/synonyms/synonyms_search/synonyms_search.pages.inc create mode 100644 sites/all/modules/contrib/taxonomy/synonyms/synonyms_search/synonyms_search.test create mode 100644 sites/all/modules/contrib/taxonomy/synonyms/views/synonyms_views_handler_filter_term_tid.inc diff --git a/sites/all/modules/contrib/taxonomy/synonyms/README.txt b/sites/all/modules/contrib/taxonomy/synonyms/README.txt index 96bca558..d831e785 100644 --- a/sites/all/modules/contrib/taxonomy/synonyms/README.txt +++ b/sites/all/modules/contrib/taxonomy/synonyms/README.txt @@ -3,11 +3,15 @@ The Synonyms module extends the Drupal core Taxonomy features. Currently the module provides this additional functionality: -* support of synonyms through Field API. Any field, for which synonyms extractor - is created, attached to a term can be enabled as source of synonyms. -* synonym-friendly autocomplete widget for taxonomy_term_reference fields -* integration with Drupal search functionality enabling searching content by - synonyms of the terms that the content references +* support of synonyms through Field API. Any field, for which synonyms behavior + exists, can be enabled as source of synonyms. +* synonyms-friendly autocomplete and select widgets for taxonomy_term_reference + fields +* integration with Drupal search functionality through Synonyms Search + submodule. It enables searching content by synonyms of the terms that the + content references. Synonyms Search submodule also integrates with Term + Search contributed module in a fashion that allows your terms to be found by + their synonyms. * integration with Search API. If you include synonyms of a term into your Search API search index, your clients will be able to find content with search keywords that contain synonyms and not actual names of terms. @@ -16,11 +20,11 @@ the module provides this additional functionality: The Synonyms module requires the following modules: * Taxonomy module -* Text module +* CTools module --- SYNONYMS EXTRACTORS, SUPPORTED FIELD TYPES -- +-- SYNONYMS BEHAVIOR, SUPPORTED FIELD TYPES -- -Module ships with ability to extract synonyms from the following field types: +Module ships with ability to use the following field types as synonyms: * Text * Taxonomy Term Reference * Entity Reference @@ -28,12 +32,38 @@ Module ships with ability to extract synonyms from the following field types: * Float * Decimal -If you want to implement your own synonyms extractor that would enable support -for any other field type, please, refer to synonyms.api.php file for -instructions on how to do it, or file an issue against Synonyms module. We will -try to implement support for your field type too. If you have written your -synonyms extractor, please share by opening an issue, and it will be included -into this module. +If you want to implement your own synonyms behavior that would enable support +for any other field type, refer to synonyms.api.php file for instructions on how +to do it, or file an issue against Synonyms module. We will try to implement +support for your field type too. If you have written your synonyms behavior +implementation, please share by opening an issue, and it will be included into +this module. + +-- GRANULATION WITHIN SYNONYMS BEHAVIOR -- + +In order to achieve greater flexibility, this module introduced additional +granularity into what "synonyms" mean. Then you can enable different synonyms +behaviors for different fields. For example, field "Typos" can be part of +autocomplete behavior, while field "Other spellings" can be part of search +integration behavior. Currently the following synonym behaviors are recognized +(other modules actually can extend this list): +* General synonym - normally we suggest to enable this behavior for all fields + that have enabled at least one another behavior. In technical words, this + behavior is responsible for including content of the field into term synonyms + and also enables ability to add entities as synonym into this field. +* Autocomplete - whether content of this field should participate in + autocomplete suggestions. This module ships an autocomplete synonyms friendly + widget and its autocomplete suggestions will be filled in with the content of + the fields that have enabled this behavior. +* Select - whether content of this field should be included in the synonyms + friendly select widget. +* Search integration (requires Synonyms Search enabled) - allows your content to + be found by synonyms of the terms it references. Your nodes will be found by + all synonyms that have this behavior enabled. + +Therefore, on the vocabulary edit page you will see a table, where rows are +fields that can become synonyms and columns are these "synonym behaviors" and +you decide what synonym behaviors to activate on what fields. -- INSTALLATION -- @@ -42,15 +72,14 @@ into this module. -- CONFIGURATION -- * The module itself does not provide any configuration as of the moment. -Although during creation/editing of a Taxonomy vocabulary you will be able -to enable/disable for that particular vocabulary the additional functionality -this module provides, you will find additional fieldset at the bottom of -vocabulary edit page. +Although during editing of a Taxonomy vocabulary you will be specify for that +particular vocabulary the additional functionality this module provides, you +will find additional fieldset at the bottom of the vocabulary edit page. -- FUTURE DEVELOPMENT -- * If you are interested into converting this module from synonyms for Taxonomy -terms into synonyms for any entity types, please go to this issue +terms into synonyms for any entity type, please go to this issue http://drupal.org/node/1194802 and leave a comment. Once we see some demand for this great feature and the Synonyms module gets a little more mature, we will try to make it happen. diff --git a/sites/all/modules/contrib/taxonomy/synonyms/help/synonyms.help.ini b/sites/all/modules/contrib/taxonomy/synonyms/help/synonyms.help.ini new file mode 100644 index 00000000..c071cca2 --- /dev/null +++ b/sites/all/modules/contrib/taxonomy/synonyms/help/synonyms.help.ini @@ -0,0 +1,14 @@ +[advanced help settings] +line break = TRUE + +[synonyms] +title = Synonyms + +[synonyms_behaviors] +title = Creating a new synonyms behavior +parent = synonyms +weight = -1 + +[synonyms_behavior_implementation] +title = Creating a new behavior implementation +parent = synonyms diff --git a/sites/all/modules/contrib/taxonomy/synonyms/help/synonyms.html b/sites/all/modules/contrib/taxonomy/synonyms/help/synonyms.html new file mode 100644 index 00000000..740a7d0c --- /dev/null +++ b/sites/all/modules/contrib/taxonomy/synonyms/help/synonyms.html @@ -0,0 +1,79 @@ +The Synonyms module provides various functionality targeted at handling synonymous (similar) spelling of the same name. + +Currently the module only provides synonyms for Taxonomy Terms, though there are plans to extend it and to support synonyms for all entity types. There are few key concepts tightly integrated into Synonyms module that are worth knowing for the end user. They are: +
Field independency
+
Synonyms can be stored in various manners in your database within fields attached to your entities. Since the fields are different (entity reference, text, etc) you can keep your synonyms in the most convenient way for you.
+
Synonyms behaviors
+
Having a list of synonyms doesn't really help much; what you want to do is something productive with them. That's where synonyms behaviors whatsoever come on stage. They are abstract self sufficient units of behavior that do something productive with synonyms. For example, here we have the autocomplete behavior, which introduces autocomplete text fields that do look up not only by term name but by its synonyms too. Synonyms behaviors can be added through other contributed modules, you're not locked down to what Synonyms module has to offer you.
+
Synonyms behavior implementations
+
Lastly, here is yet more abstract unit. Behavior implementations bridge between existing synonyms behaviors and existing fields that may contain synonyms. Imagine as if you had a table, where rows are fields attached to your entities and the columns are the existing synonyms behaviors. Behavior implementations fill in the cells of this table. So, for example, you could have behavior implementation for autocomplete feature for the entity reference field. It means that synonyms stored in entity reference fields can participate in the autocomplete functionality.
+ +Let's cover each of these important concepts in details. + +

Field Independency

+ +As stated above, your synonyms can be saved in many different field types. Synonyms module ships support for storing synonyms in the following field types: + + +Other modules may extend this list by implementing "synonyms" behavior for other field types. The "synonyms" behavior is the most basic and general behavior that exists for synonyms. Basically, it's precisely the ability to extract synonyms from fields and to search for existence of a synonym within a field. + +

Synonyms behaviors

+ +Synonyms behaviors are some useful for the end user features that leverage the synonyms data. Synonyms module ships with the following behaviors: + + +Similarly, as with field independency, other modules can introduce their own behaviors. If you are interested in introducing your own behavior, refer to synonyms behaviors page. + +

Behavior Implementations

+ +Behavior implementations connect behaviors to field types. Synonyms module ships the following set of behavior implementations: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Field type \ BehaviorGeneral synonymsAutocompleteSelectSearch
Textimplementedimplementedimplementedimplemented
Term referenceimplementedimplementedimplementedimplemented
Entity referenceimplementedimplementedimplementedimplemented
+ +Lastly, you as a website admin can enable or disable certain fields to participate in certain behaviors. So you have full control over how things get set up in your website. Additionally, some behaviors may provide additional configs, for example, the autocomplete behavior will ask you with what wording to suggest a term if it was matched by one of its synonyms. + +The referenced above table may be extended through hooks that Synonyms module provides. Feel free to study the hooks at synonyms.api.php file. Also, you may find it useful to read about writing custom behavior implementation. diff --git a/sites/all/modules/contrib/taxonomy/synonyms/help/synonyms_behavior_implementation.html b/sites/all/modules/contrib/taxonomy/synonyms/help/synonyms_behavior_implementation.html new file mode 100644 index 00000000..4eb7199f --- /dev/null +++ b/sites/all/modules/contrib/taxonomy/synonyms/help/synonyms_behavior_implementation.html @@ -0,0 +1,27 @@ +At this point you must possess good technical knowledge about what synonyms behaviors are. In this article we will show how you can implement an arbitrary behavior for an arbitrary field type. + +By implementing I mean to provide integration between a field type (between how that field type stores and encodes synonyms data in the database) and a synonym behavior (how that behavior requires to work with synonyms data). It must sound a bit too baked, but the ongoing paragraphs should shed more light onto it. + +Throughout writing your own synonyms behavior implementation you can always look into Synonyms module source code to get better understanding. You will find the behavior implementatios in synonyms/includes/*SynonymsBehavior.class.inc files. + +Creating a new implementation pretty much consists of 2 steps: +
    +
  1. Implementing behavior interface for a particular field type.
  2. +
  3. Notifying Synonyms module about your new PHP class and what field types and what behavior that PHP class is responsible for.
  4. +
+ +Now let us see each of the steps in further details. + +

Implementing behavior interface

+ +Look up in behavior cTools plugin definition of your interest what interface it declares. The cTools plugin must be of type "behavior" owned by "synonyms" module. The interface is declared under the "interface" property. Read the documentation for that interface and write a PHP class that implements this interface. We cannot give more precise instructions about this step, because it all depends on the interface of the behavior. + +

Notifying Synonyms module about your new implementation

+ +For the purposes of such notification we have 2 hooks in Synonyms module: + + +Implementing either of the 2 hooks is highly straight forward, you will just inform the Synonyms module for requested behavior for what field types you have implementations in what PHP classes. For more details, refer to synonyms.api.php file or look into synonyms_synonyms_behavior_implementation_info() function. diff --git a/sites/all/modules/contrib/taxonomy/synonyms/help/synonyms_behaviors.html b/sites/all/modules/contrib/taxonomy/synonyms/help/synonyms_behaviors.html new file mode 100644 index 00000000..42306be1 --- /dev/null +++ b/sites/all/modules/contrib/taxonomy/synonyms/help/synonyms_behaviors.html @@ -0,0 +1,40 @@ +If you made it up to here, then we expect you to already have decent understanding about what a synonyms behavior is. This page will try to explain how you could create a new one. + +In technical terms synonyms heaviors are just cTools plugins. Synonyms module defines its own plugin type: behavior, which is owned by synonyms module. + +Throught writing your own synonyms behavior you can always look into Synonyms module behaviors to get better understanding. You will find the plugins in synonyms/plugins/behavior/*.inc files. If you have never worked before with cTools plugins, if is also worthwhile to check out its documentation. + +Your plugin implementation may (or must) have the following keys: +
title
+
Required (string) Human-friendly translated title of your behavior.
+
description
+
(string) Human-friendly translated description of your behavior. Include a couple of words about what exactly it does and what end user functionality has.
+
settings form callback
+
(string) Name of a PHP function that will be invoked to generate settings form of your behavior. End user will fill out the form elements and the settings will be saved in the database for your plugin by Synonyms module. If your behavior does not require any additional configuration, you may omit this parameter. If you do require a settings form, then this callback function should receive the following input arguments:
    +
  1. $form: (array) form array into which your settings form elements will be merged. You do not have to change this $form. It is just provided to you in case you want to traverse through it for whatever reason.
  2. +
  3. &$form_state: (array) form state array of the form into which your settings form elements will be merged. Again, you are not obliged to change or to use this argument, but it is provided to you in case you find it useful to keep some data in form state.
  4. +
  5. $settings: (array) array of settings defined for your behavior. This array may be empty, if your behavior hasn't been saved yet, or it may contain previously saved settings. You should use content of this array as default values for your form elements.
  6. +
+Based on these input arguments, your settings form callback function should generate form elements array and return it.
+
interface
+
Required: (string) Name of a PHP interface that represents your behavior. This interface should declare any methods that support your behavior. Implementations of your behavior will have to implement this interface for specific field types. To make it easier for you, think of the default synonyms behavior: it introduces the "synonyms" property on entities. Whenever that property is requested, the behavior code loops through enabled default synonyms behavior implementations asking to "extract synonyms from field values". So this "extract synonyms" method is declared in the interface. This way behavior implementations can implement the interface and therefore Synonyms without knowing anything specific about the underlying field type will be able to carry out its part - to get a list of synonyms. We advice to name the interfaces in the following fashion: NameOfYourBehaviorHereSynonymsBehavior. Also, your interface must extend the starting point of all synonyms behaviors - the SynonymsBehavior interface. If your behavior depends on functionality provided by other behaviors (for example, many behaviors depend on the general "synonyms" behavior), then your interface may extend the interface of that behavior (which would be to extend the SynonymsSynonymsBehavior in the case of "synonyms" behavior).
+
enabled callback
+
(string) Name of a PHP function that will be invoked to notify your behavior that it has just been enabled on a new field. You could do any set-up routines in this function, if it is required by your behavior. If your behavior does not require any set up logic, you may omit this parameter. If you do specify it, then this callback function should receive the following input arguments:
    +
  1. $behavior_definition: (array) Array of behavior definition. Basically it is your cTools plugin definition.
  2. +
  3. $settings: (array) Array of settings for your behavior that is getting enabled. If your set up procedure depends on the behavior settings, then you can access the settings through this variable.
  4. +
  5. $instance: (array) Array of field instance definition on which your behavior has just been enabled. Similarly as with $settings, if your set up procedure depends on field type, entity type, etc., then you can read this information from this var.
  6. +
+
disabled callback
+
(string) Name of a PHP function that will be invoked to notify your behavior that it has just been disabled on a field. You could do any tear-down routines in this function, if it is required by your behavior. If your behavior does not require any tear down logic, you may omit this parameter. If you do specify it, then this callback function should receive the following input arguments:
    +
  1. $behavior_definition: (array) Array of behavior definition. Basically it is your cTools plugin definition.
  2. +
  3. $behavior_implementation: (array) Array of behavior implementation. It is a return of synonyms_behavior_get() function. This array will contain plenty of information about the context. Among other things it will include settings of your behavior.
  4. +
  5. $instance: (array) Array of field instance definition on which your behavior has just been disabled. If your tear down procedure depends on field type, entity type, etc., then you can read this information from this var.
  6. +
+ +Having said the above, let us wrap up on how you should create a new behavior: +
    +
  1. Think through what you want your behavior to do and what interface will be required to support your behavior.
  2. +
  3. Declare your behavior interface, make sure to document and to well comment the interface methods. The better docs it has, the easier it will be for other developers to implement your behavior for new field types. Also, make sure your interface extends the necessary interfaces from other behaviors (if your behavior depends on the functionality of other behaviors).
  4. +
  5. Write PHP code that will use the interface to achieve some productive action. Depending on what your behavior does, it may be but not limited to implementing a hook, writing a custom PHP function, etc.
  6. +
  7. Probably you will want to implement your freshly created behavior for at least one field type. Otherwise your behavior won't have any synonyms to work with and will be useless. Read about implementing a behavior for new field types here.
  8. +
diff --git a/sites/all/modules/contrib/taxonomy/synonyms/includes/AbstractSynonymsExtractor.class.inc b/sites/all/modules/contrib/taxonomy/synonyms/includes/AbstractSynonymsExtractor.class.inc deleted file mode 100644 index 2fe6a41b..00000000 --- a/sites/all/modules/contrib/taxonomy/synonyms/includes/AbstractSynonymsExtractor.class.inc +++ /dev/null @@ -1,116 +0,0 @@ -processEntityFieldQuery() when for whatever reason - * the object can't alter $query to include matched synonyms. As a fallback it - * should call this method to make sure it filtered everything out. - * - * @param EntityFieldQuery $query - * Query object passed to $this->processEntityFieldQuery() method - */ - protected static function emptyResultsCondition(EntityFieldQuery $query) { - $query->range(0, 0); - } -} diff --git a/sites/all/modules/contrib/taxonomy/synonyms/includes/EntityReferenceSynonymsBehavior.class.inc b/sites/all/modules/contrib/taxonomy/synonyms/includes/EntityReferenceSynonymsBehavior.class.inc new file mode 100644 index 00000000..d1a1f3db --- /dev/null +++ b/sites/all/modules/contrib/taxonomy/synonyms/includes/EntityReferenceSynonymsBehavior.class.inc @@ -0,0 +1,84 @@ + $synonym_entity_id, + )); + } + + public function synonymItemHash($item, $field, $instance) { + return $field['settings']['target_type'] . $item['target_id']; + } + + public function synonymsFind(QueryConditionInterface $condition, $field, $instance) { + if ($field['storage']['type'] != 'field_sql_storage') { + throw new SynonymsSynonymsBehaviorException(t('Not supported storage engine %type in synonymsFind() method.', array( + '%type' => $field['storage']['type'], + ))); + } + $table = array_keys($field['storage']['details']['sql'][FIELD_LOAD_CURRENT]); + $table = reset($table); + $column = $field['storage']['details']['sql'][FIELD_LOAD_CURRENT][$table]['target_id']; + + $query = db_select($table, 'field'); + + $target_entity_type_info = entity_get_info($field['settings']['target_type']); + if (!isset($target_entity_type_info['base table']) || !$target_entity_type_info['base table']) { + throw new SynonymsSynonymsBehaviorException(t('Target entity type %entity_type is not stored in database.', array( + '%entity_type' => $field['settings']['target_type'], + ))); + } + if (!isset($target_entity_type_info['entity keys']['id'])) { + throw new SynonymsSynonymsBehaviorException(t('Target entity type %entity_type does not declare primary key.', array( + '%entity_type' => $field['settings']['target_type'], + ))); + } + if (!isset($target_entity_type_info['entity keys']['label'])) { + throw new SynonymsSynonymsBehaviorException(t('Target entity type %entity_type does not declare label column.', array( + '%entity_type' => $field['settings']['target_type'], + ))); + } + + $target_entity_alias = $query->innerJoin($target_entity_type_info['base table'], 'target_entity', 'field.' . $column . ' = target_entity.' . $target_entity_type_info['entity keys']['id']); + $query->addField($target_entity_alias, $target_entity_type_info['entity keys']['label'], 'synonym'); + $query->fields('field', array('entity_id')); + $query->condition('field.entity_type', $instance['entity_type']); + $query->condition('field.bundle', $instance['bundle']); + + $this->synonymsFindProcessCondition($condition, $target_entity_alias . '.' . $target_entity_type_info['entity keys']['label']); + $query->condition($condition); + return $query->execute(); + } +} diff --git a/sites/all/modules/contrib/taxonomy/synonyms/includes/EntityReferenceSynonymsExtractor.class.inc b/sites/all/modules/contrib/taxonomy/synonyms/includes/EntityReferenceSynonymsExtractor.class.inc deleted file mode 100644 index 301faa79..00000000 --- a/sites/all/modules/contrib/taxonomy/synonyms/includes/EntityReferenceSynonymsExtractor.class.inc +++ /dev/null @@ -1,70 +0,0 @@ -entityCondition('entity_type', $field['settings']['target_type']); - // Additionally we have to figure out which column in the entity table - // represents entity label. - $entity_info = entity_get_info($field['settings']['target_type']); - if (!isset($entity_info['entity keys']['label'])) { - // We can't get any matches if we do not know what column to query - // against. So we add a condition to $query which will 100% yield empty - // results. - self::emptyResultsCondition($query); - return; - } - $efq->propertyCondition($entity_info['entity keys']['label'], '%' . $tag . '%', 'LIKE'); - $result = $efq->execute(); - - if (!isset($result[$field['settings']['target_type']]) || !is_array($result[$field['settings']['target_type']])) { - self::emptyResultsCondition($query); - return; - } - $result = $result[$field['settings']['target_type']]; - $query->fieldCondition($field, 'target_id', array_keys($result)); - } - - public static function mergeEntityAsSynonym($items, $field, $instance, $synonym_entity, $synonym_entity_type) { - // Firstly validating that this entity reference is able to reference to - // that type of entity. - $expected_synonym_entity_type = $field['settings']['target_type']; - if ($expected_synonym_entity_type != $synonym_entity_type) { - return array(); - } - $synonym_entity_id = entity_id($synonym_entity_type, $synonym_entity); - return array(array( - 'target_id' => $synonym_entity_id, - )); - } -} diff --git a/sites/all/modules/contrib/taxonomy/synonyms/includes/SynonymsBehavior.interface.inc b/sites/all/modules/contrib/taxonomy/synonyms/includes/SynonymsBehavior.interface.inc new file mode 100644 index 00000000..2c30bca3 --- /dev/null +++ b/sites/all/modules/contrib/taxonomy/synonyms/includes/SynonymsBehavior.interface.inc @@ -0,0 +1,188 @@ +synonymsFindProcessCondition() method, + * so you won't have to worry much about it + * @param array $field + * Field API field definition array of the field within which the search + * for synonyms should be performed + * @param array $instance + * Field API instance definition array of the instance within which the + * search for synonyms should be performed + * + * @return Traversable + * Traversable result set of found synonyms and entity IDs to which those + * belong. Each element in the result set should be an object and will have + * the following structure: + * - synonym: (string) Synonym that was found and which satisfies the + * provided condition + * - entity_id: (int) ID of the entity to which the found synonym belongs + */ + public function synonymsFind(QueryConditionInterface $condition, $field, $instance); +} + +/** + * Exception thrown by implementations of SynonymsSynonymsBehavior interface. + */ +class SynonymsSynonymsBehaviorException extends Exception {} + +/** + * Starting point for implementing SynonymsSynonymsBehavior interface. + */ +abstract class AbstractSynonymsSynonymsBehavior implements SynonymsSynonymsBehavior { + + /** + * Constant which denotes placeholder of a synonym column. + * + * @var string + */ + const COLUMN_PLACEHOLDER = '***COLUMN***'; + + /** + * Process condition in 'synonymsFind' method. + * + * Process condition in 'synonymsFind' method replacing all references of + * synonym column with the real name of that column. + * + * @param QueryConditionInterface $condition + * Condition that should be processed + * @param string $column + * Real name of the synonym column + */ + protected function synonymsFindProcessCondition(QueryConditionInterface $condition, $column) { + $condition_array = &$condition->conditions(); + foreach ($condition_array as &$v) { + if (is_array($v) && isset($v['field'])) { + if ($v['field'] instanceof QueryConditionInterface) { + // Recursively process this condition too. + $this->synonymsFindProcessCondition($v['field'], $column); + } + else { + $v['field'] = str_replace(self::COLUMN_PLACEHOLDER, $column, $v['field']); + } + } + } + } +} + +/** + * Interface of the autocomplete synonyms behavior. + */ +interface AutocompleteSynonymsBehavior extends SynonymsSynonymsBehavior { +} + +/** + * Interface of the synonyms friendly select behavior. + */ +interface SelectSynonymsBehavior extends SynonymsSynonymsBehavior { +} diff --git a/sites/all/modules/contrib/taxonomy/synonyms/includes/SynonymsSynonymsExtractor.class.inc b/sites/all/modules/contrib/taxonomy/synonyms/includes/SynonymsSynonymsExtractor.class.inc deleted file mode 100644 index 39ab9510..00000000 --- a/sites/all/modules/contrib/taxonomy/synonyms/includes/SynonymsSynonymsExtractor.class.inc +++ /dev/null @@ -1,48 +0,0 @@ -fieldCondition($field, 'value', '%' . $tag . '%', 'LIKE'); - } - - public static function mergeEntityAsSynonym($items, $field, $instance, $synonym_entity, $synonym_entity_type) { - $synonym = entity_label($synonym_entity_type, $synonym_entity); - switch ($field['type']) { - case 'text': - break; - - // We add synonyms for numbers only if $synonym is a number. - case 'number_integer': - case 'number_float': - case 'number_decimal': - if (!is_numeric($synonym)) { - return array(); - } - break; - - } - return array(array( - 'value' => $synonym, - )); - } -} diff --git a/sites/all/modules/contrib/taxonomy/synonyms/includes/TaxonomySynonymsBehavior.class.inc b/sites/all/modules/contrib/taxonomy/synonyms/includes/TaxonomySynonymsBehavior.class.inc new file mode 100644 index 00000000..6e58a6c4 --- /dev/null +++ b/sites/all/modules/contrib/taxonomy/synonyms/includes/TaxonomySynonymsBehavior.class.inc @@ -0,0 +1,88 @@ +vocabulary_machine_name == $setting['vocabulary']) { + if ($setting['parent'] == 0) { + // No need to check parent property as there is no limitation on it. + $is_allowed = TRUE; + break; + } + else { + foreach (taxonomy_get_parents_all($synonym_entity->tid) as $parent) { + if ($parent->tid == $setting['parent']) { + $is_allowed = TRUE; + break(2); + } + } + } + } + } + if (!$is_allowed) { + // Synonym term is from a vocabulary that is not expected by this field, + // or under unexpected parent. + return array(); + } + return array(array( + 'tid' => $synonym_entity->tid, + )); + } + + public function synonymItemHash($item, $field, $instance) { + return $item['tid']; + } + + public function synonymsFind(QueryConditionInterface $condition, $field, $instance) { + if ($field['storage']['type'] != 'field_sql_storage') { + throw new SynonymsSynonymsBehaviorException(t('Not supported storage engine %type in synonymsFind() method.', array( + '%type' => $field['storage']['type'], + ))); + } + $table = array_keys($field['storage']['details']['sql'][FIELD_LOAD_CURRENT]); + $table = reset($table); + $column = $field['storage']['details']['sql'][FIELD_LOAD_CURRENT][$table]['tid']; + + $query = db_select($table, 'field'); + $term_alias = $query->innerJoin('taxonomy_term_data', 'term', 'field.' . $column . ' = term.tid'); + $query->addField($term_alias, 'name', 'synonym'); + $query->fields('field', array('entity_id')); + $query->condition('field.entity_type', $instance['entity_type']); + $query->condition('field.bundle', $instance['bundle']); + + $this->synonymsFindProcessCondition($condition, $term_alias . '.name'); + $query->condition($condition); + return $query->execute(); + } +} diff --git a/sites/all/modules/contrib/taxonomy/synonyms/includes/TaxonomySynonymsExtractor.class.inc b/sites/all/modules/contrib/taxonomy/synonyms/includes/TaxonomySynonymsExtractor.class.inc deleted file mode 100644 index 07d0167b..00000000 --- a/sites/all/modules/contrib/taxonomy/synonyms/includes/TaxonomySynonymsExtractor.class.inc +++ /dev/null @@ -1,94 +0,0 @@ -entityCondition('bundle', $settings['vocabulary']) - ->entityCondition('entity_type', 'taxonomy_term') - ->propertyCondition('name', '%' . $tag . '%', 'LIKE'); - $result = $efd->execute(); - if (isset($result['taxonomy_term'])) { - foreach ($result['taxonomy_term'] as $tid) { - $tids[] = $tid->tid; - } - } - } - - // Now we have tids of terms from the referenced vocabulary which names - // LIKE %$tag%, suggested are the terms that refer to any of these $tids. - if (empty($tids)) { - // No possible suggestions were found. We have to make sure $query yields - // no results. - self::emptyResultsCondition($query); - return; - } - $query->fieldCondition($field, 'tid', $tids); - } - - public static function mergeEntityAsSynonym($items, $field, $instance, $synonym_entity, $synonym_entity_type) { - // Taxonomy term reference supports only referencing of entity types - // 'taxonomy_term'.. duh. - if ($synonym_entity_type != 'taxonomy_term') { - return array(); - } - // Checking that $field is configured to reference the vocabulary of - // $synonym_entity term. - $is_allowed = FALSE; - foreach ($field['settings']['allowed_values'] as $setting) { - if ($synonym_entity->vocabulary_machine_name == $setting['vocabulary']) { - if ($setting['parent'] == 0) { - // No need to check parent property as there is no limitation on it. - $is_allowed = TRUE; - break; - } - else { - foreach (taxonomy_get_parents_all($synonym_entity->tid) as $parent) { - if ($parent->tid == $setting['parent']) { - $is_allowed = TRUE; - break(2); - } - } - } - } - } - if (!$is_allowed) { - // Synonym term is from a vocabulary that is not expected by this field, - // or under unexpected parent. - return array(); - } - return array(array( - 'tid' => $synonym_entity->tid, - )); - } -} diff --git a/sites/all/modules/contrib/taxonomy/synonyms/includes/TextSynonymsBehavior.class.inc b/sites/all/modules/contrib/taxonomy/synonyms/includes/TextSynonymsBehavior.class.inc new file mode 100644 index 00000000..96472e1c --- /dev/null +++ b/sites/all/modules/contrib/taxonomy/synonyms/includes/TextSynonymsBehavior.class.inc @@ -0,0 +1,68 @@ + $synonym, + )); + } + + public function synonymItemHash($item, $field, $instance) { + return $item['value']; + } + + public function synonymsFind(QueryConditionInterface $condition, $field, $instance) { + if ($field['storage']['type'] != 'field_sql_storage') { + throw new SynonymsSynonymsBehaviorException(t('Not supported storage engine %type in synonymsFind() method.', array( + '%type' => $field['storage']['type'], + ))); + } + $table = array_keys($field['storage']['details']['sql'][FIELD_LOAD_CURRENT]); + $table = reset($table); + $column = $field['storage']['details']['sql'][FIELD_LOAD_CURRENT][$table]['value']; + + $this->synonymsFindProcessCondition($condition, $column); + + $query = db_select($table); + $query->fields($table, array('entity_id')); + $query->addField($table, $column, 'synonym'); + return $query->condition($condition) + ->condition('entity_type', $instance['entity_type']) + ->condition('bundle', $instance['bundle']) + ->execute(); + } +} diff --git a/sites/all/modules/contrib/taxonomy/synonyms/js/synonyms-autocomplete.js b/sites/all/modules/contrib/taxonomy/synonyms/js/synonyms-autocomplete.js new file mode 100644 index 00000000..8a254423 --- /dev/null +++ b/sites/all/modules/contrib/taxonomy/synonyms/js/synonyms-autocomplete.js @@ -0,0 +1,24 @@ +(function ($) { + +/** + * Fix the autocomplete core undesired behavior. + * + * The core autocomplete only allows 1 entry per suggestion, i.e. you can't have + * 2 suggestion entries suggest the same key. Synonyms module very well needs + * such ability, since multiple synonyms may point to the same entity. In order + * to bypass this limitation Synonyms module pads the suggestion entries with + * extra spaces on the right until it finds a "free" spot. This JavaScript + * right-trims the entries in order to cancel out the effect. + */ +Drupal.behaviors.synonymsAutocompleteWidget = { + attach: function (context, settings) { + $('input.form-autocomplete.synonyms-autocomplete', context).once('synonyms-autocomplete', function () { + $(this).bind('autocompleteSelect', function() { + var value = $(this).val(); + value = value.replace(/\s+$/, ''); + $(this).val(value); + }); + }); + } +}; +})(jQuery); diff --git a/sites/all/modules/contrib/taxonomy/synonyms/plugins/arguments/term_synonyms.inc b/sites/all/modules/contrib/taxonomy/synonyms/plugins/arguments/term_synonyms.inc new file mode 100644 index 00000000..bc3e1d81 --- /dev/null +++ b/sites/all/modules/contrib/taxonomy/synonyms/plugins/arguments/term_synonyms.inc @@ -0,0 +1,147 @@ + t("Taxonomy term: ID (synonyms-friendly)"), + 'keyword' => 'term', + 'description' => t('Creates a single taxonomy term from a taxonomy term name or one of its synonyms.'), + 'context' => 'synonyms_term_synonyms_context', + 'default' => array('breadcrumb' => TRUE, 'transform' => FALSE), + 'settings form' => 'synonyms_term_synonyms_settings_form', + 'placeholder form' => 'synonyms_term_synonyms_ctools_argument_placeholder', + 'breadcrumb' => 'synonyms_term_synonyms_breadcrumb', +); + +/** + * Discover if this argument gives us the term we crave. + */ +function synonyms_term_synonyms_context($arg = NULL, $conf = NULL, $empty = FALSE) { + // If unset it wants a generic, unfilled context. + if ($empty) { + return ctools_context_create_empty('entity:taxonomy_term'); + } + + $conf['vids'] = is_array($conf['vids']) ? array_filter($conf['vids']) : array(); + + if (is_object($arg)) { + $term = $arg; + + if (!empty($conf['vids']) && empty($conf['vids'][$term->vid])) { + return NULL; + } + } + else { + if ($conf['transform']) { + $tids = db_select('taxonomy_term_data', 't') + ->fields('t', array('tid')) + ->where("REPLACE(t.name, ' ', '-') = :argument", array( + ':argument' => $arg, + )); + if (!empty($conf['vids'])) { + $tids->condition('t.vid', $conf['vids']); + } + $tids = $tids->execute()->fetchCol(); + $terms = taxonomy_term_load_multiple($tids); + } + else { + $terms = taxonomy_get_term_by_name($arg); + } + + if (!empty($conf['vids'])) { + foreach ($terms as $k => $term) { + if (!isset($conf['vids'][$term->vid])) { + unset($terms[$k]); + } + } + } + + if (empty($terms)) { + // We couldn't find the term by name, so we will look it up now by + // synonyms. + $vocabularies = taxonomy_vocabulary_load_multiple(empty($conf['vids']) ? FALSE : $conf['vids']); + foreach ($vocabularies as $vocabulary) { + $condition = db_and(); + if ($conf['transform']) { + $condition->where("REPLACE(" . AbstractSynonymsSynonymsBehavior::COLUMN_PLACEHOLDER . ", ' ', '-') = :argument", array( + ':argument' => $arg, + )); + } + else { + $condition->condition(AbstractSynonymsSynonymsBehavior::COLUMN_PLACEHOLDER, $arg); + } + $rows = synonyms_synonyms_find($condition, 'taxonomy_term', $vocabulary->machine_name); + if (!empty($rows)) { + // We have found a match, no need to search further. + $terms[] = taxonomy_term_load($rows[0]->entity_id); + break; + } + } + } + + if (empty($terms)) { + return NULL; + } + $term = array_shift($terms); + } + + $context = ctools_context_create('entity:taxonomy_term', $term); + $context->original_argument = $arg; + return $context; +} + +/** + * Settings form for the argument. + */ +function synonyms_term_synonyms_settings_form(&$form, &$form_state, $conf) { + $vocabularies = taxonomy_get_vocabularies(); + $options = array(); + foreach ($vocabularies as $vid => $vocab) { + $options[$vid] = $vocab->name; + } + $form['settings']['vids'] = array( + '#title' => t('Limit to these vocabularies'), + '#type' => 'checkboxes', + '#options' => $options, + '#default_value' => !empty($conf['vids']) ? $conf['vids'] : array(), + '#description' => t('If no vocabularies are checked, terms from all vocabularies will be accepted.'), + ); + + $form['settings']['breadcrumb'] = array( + '#title' => t('Inject hierarchy into breadcrumb trail'), + '#type' => 'checkbox', + '#default_value' => !empty($conf['breadcrumb']), + '#description' => t('If checked, taxonomy term parents will appear in the breadcrumb trail.'), + ); + + $form['settings']['transform'] = array( + '#title' => t('Transform dashes in URL to spaces in term name filter values'), + '#type' => 'checkbox', + '#default_value' => !empty($conf['transform']), + ); +} + +/** + * Form fragment to get an argument to convert a placeholder for preview. + */ +function synonyms_term_synonyms_ctools_argument_placeholder($conf) { + return array( + '#type' => 'textfield', + '#description' => t('Enter a taxonomy term name.'), + ); +} + +/** + * Inject the breadcrumb trail if necessary. + */ +function synonyms_term_synonyms_breadcrumb($conf, $context) { + // Outsource the real implementation of breadcrumb to terms argument plugin. + $plugin = ctools_get_plugins('ctools', 'arguments', 'term'); + $function = ctools_plugin_get_function($plugin, 'breadcrumb'); + if ($function) { + call_user_func_array($function, func_get_args()); + } +} diff --git a/sites/all/modules/contrib/taxonomy/synonyms/plugins/behavior/autocomplete.inc b/sites/all/modules/contrib/taxonomy/synonyms/plugins/behavior/autocomplete.inc new file mode 100644 index 00000000..6d62b8aa --- /dev/null +++ b/sites/all/modules/contrib/taxonomy/synonyms/plugins/behavior/autocomplete.inc @@ -0,0 +1,41 @@ + t('Autocomplete'), + 'description' => t('Synonyms friendly autocomplete'), + 'settings form callback' => 'synonyms_behavior_autocomplete_settings_form', + 'interface' => 'AutocompleteSynonymsBehavior', +); + +/** + * Settings form for autocomplete behavior. + */ +function synonyms_behavior_autocomplete_settings_form($form, &$form_state, $settings) { + static $is_first_time = TRUE; + + $element = array(); + + $element['wording'] = array( + '#type' => 'textfield', + '#title' => t('Autocomplete wording'), + '#default_value' => isset($settings['wording']) ? $settings['wording'] : '@synonym is a synonym of @term', + '#description' => t('Specify with what wording the synonyms should be suggested in the autocomplete feature. You may use: '), + '#required' => TRUE, + ); + + if (!$is_first_time) { + // Remove the description, if the element is created more than once on the + // same form. Otherwise the whole form looks too clumsy. + unset($element['wording']['#description']); + } + + $is_first_time = FALSE; + + return $element; +} + diff --git a/sites/all/modules/contrib/taxonomy/synonyms/plugins/behavior/select.inc b/sites/all/modules/contrib/taxonomy/synonyms/plugins/behavior/select.inc new file mode 100644 index 00000000..e10b6d1d --- /dev/null +++ b/sites/all/modules/contrib/taxonomy/synonyms/plugins/behavior/select.inc @@ -0,0 +1,41 @@ + t('Select'), + 'description' => t('Synonyms friendly select'), + 'settings form callback' => 'synonyms_behavior_select_settings_form', + 'interface' => 'SelectSynonymsBehavior', +); + +/** + * Settings form for select behavior. + */ +function synonyms_behavior_select_settings_form($form, &$form_state, $settings) { + static $is_first_time = TRUE; + + $element = array(); + + $element['wording'] = array( + '#type' => 'textfield', + '#title' => t('Select wording'), + '#default_value' => isset($settings['wording']) ? $settings['wording'] : '@synonym', + '#description' => t('Specify with what wording the synonyms should be placed in the select form element. You may use: '), + '#required' => TRUE, + ); + + if (!$is_first_time) { + // Remove the description, if the element is created more than once on the + // same form. Otherwise the whole form looks too clumsy. + unset($element['wording']['#description']); + } + + $is_first_time = FALSE; + + return $element; +} + diff --git a/sites/all/modules/contrib/taxonomy/synonyms/plugins/behavior/synonyms.inc b/sites/all/modules/contrib/taxonomy/synonyms/plugins/behavior/synonyms.inc new file mode 100644 index 00000000..b7c44f7f --- /dev/null +++ b/sites/all/modules/contrib/taxonomy/synonyms/plugins/behavior/synonyms.inc @@ -0,0 +1,12 @@ + t('Include into term synonyms'), + 'description' => t('Basic behavior that includes values of this field into the list of synonyms of its entity.'), + 'interface' => 'SynonymsSynonymsBehavior', +); diff --git a/sites/all/modules/contrib/taxonomy/synonyms/synonyms.api.php b/sites/all/modules/contrib/taxonomy/synonyms/synonyms.api.php index 599f38d2..d81e355b 100644 --- a/sites/all/modules/contrib/taxonomy/synonyms/synonyms.api.php +++ b/sites/all/modules/contrib/taxonomy/synonyms/synonyms.api.php @@ -6,149 +6,124 @@ */ /** - * Provide Synonyms module with names of synonyms extractor classes. + * Hook to collect info about available synonyms behavior implementations. * - * Provide Synonyms module with names of classes that are able to extract - * synonyms from fields. Each of the provided classes should extend - * AbstractSynonymsExtractor base class. + * Hook to collect info about what PHP classes implement provided synonyms + * behavior for different field types. + * + * @param string $behavior + * Name of a synonyms behavior. This string will always be among the keys + * of the return of synonyms_behaviors(), i.e. name of a ctools plugin * * @return array - * Array of strings, where each value is a name of synonyms extractor class + * Array of information about what synonyms behavior implementations your + * module supplies. The return array must contain field types as keys, whereas + * corresponding values should be names of PHP classes that implement the + * provided behavior for that field type. Read more about how to implement a + * specific behavior in the advanced help of this module. In a few words: you + * will have to implement an interface that is defined in the behavior + * definition. Do not forget to make sure your PHP class is visible to Drupal + * auto discovery mechanism */ -function hook_synonyms_extractor_info() { - return array( - // Please see below the definition of ApiSynonymsSynonymsExtractor class - // for your reference. - 'ApiSynonymsSynonymsExtractor', - ); +function hook_synonyms_behavior_implementation_info($behavior) { + switch ($behavior) { + case 'autocomplete': + return array( + 'my-field-type' => 'MyFieldTypeAutocompleteSynonymsBehavior', + ); + break; + + case 'another-behavior': + return array( + 'my-field-type-or-yet-another-field-type' => 'MyFieldTypeAnotherBehaviorSynonymsBehavior', + ); + break; + } + + return array(); } /** - * Dummy synonyms extractor class for documentation purposes. + * Hook to alter info about available synonyms behavior implementations. * - * This is a copy of SynonymsSynonymsExtractor class providing an example of - * how to write your own synonyms extractor class. See the definition of - * AbstractSynonymsExtractor for reference and in code comments. For more - * complicated examples take a look at EntityReferenceSynonymsExtractor class. + * This hook is invoked right after hook_synonyms_behavior_implementation_info() + * and is designed to let modules overwrite implementation info from some other + * modules. For example, if module A provides implementation for some field + * type, but your module has a better version of that implementation, you would + * need to implement this hook and to overwrite the implementation info. + * + * @param array $info + * Array of information about existing synonyms behavior implementations that + * was collected from modules + * @param string $behavior + * Name of the behavior for which the info about implementation is being + * generated */ -class ApiSynonymsSynonymsExtractor extends AbstractSynonymsExtractor { - - /** - * Return array of supported field types for synonyms extraction. - * - * @return array - * Array of Field API field types from which this class is able to extract - * synonyms - */ - static public function fieldTypesSupported() { - return array('text', 'number_integer', 'number_float', 'number_decimal'); +function hook_synonyms_behavior_implementation_info_alter(&$info, $behavior) { + switch ($behavior) { + case 'the-behavior-i-want': + $info['the-field-type-i-want'] = 'MyFieldTypeAutocompleteSynonymsBehavior'; + break; } +} - /** - * Extract synonyms from a field attached to an entity. - * - * We try to pass as many info about context as possible, however, normally - * you will only need $items to extract the synonyms. - * - * @param array $items - * Array of items - * @param array $field - * Array of field definition according to Field API - * @param array $instance - * Array of instance definition according to Field API - * @param object $entity - * Fully loaded entity object to which the $field and $instance with $item - * values is attached to - * @param string $entity_type - * Type of the entity $entity according to Field API definition of entity - * types - * - * @return array - * Array of synonyms extracted from $items - */ - static public function synonymsExtract($items, $field, $instance, $entity, $entity_type) { +/** + * Example of how to implement a synonyms behavior for an arbitrary field type. + */ +class MyFieldTypeAutocompleteSynonymsBehavior extends AbstractSynonymsSynonymsBehavior implements AutocompleteSynonymsBehavior { + + public function extractSynonyms($items, $field, $instance, $entity, $entity_type) { + // Let's say our synonyms is stored in the 'foo' column of the field. $synonyms = array(); - foreach ($items as $item) { - $synonyms[] = $item['value']; + $synonyms[] = $item['foo']; } - return $synonyms; } - /** - * Allow you to hook in during autocomplete suggestions generation. - * - * Allow you to include entities for autocomplete suggestion that are possible - * candidates based on your field as a source of synonyms. This method is - * void, however, you have to alter and add your condition to $query - * parameter. - * - * @param string $tag - * What user has typed in into autocomplete widget. Normally you would - * run LIKE '%$tag%' on your column - * @param EntityFieldQuery $query - * EntityFieldQuery object where you should add your conditions to - * @param array $field - * Array of field definition according to Field API, autocomplete on which - * is fired - * @param array $instance - * Array of instance definition according to Field API, autocomplete on - * which is fired - */ - static public function processEntityFieldQuery($tag, EntityFieldQuery $query, $field, $instance) { - $query->fieldCondition($field, 'value', '%' . $tag . '%', 'LIKE'); - } - - /** - * Add an entity as a synonym into a field of another entity. - * - * Basically this method should be called when you want to add some entity - * as a synonym to another entity (for example when you merge one entity - * into another and besides merging want to add synonym of the merging - * entity into the trunk entity). You should extract synonym value (according - * to what value is expected in this field) and return it. We try to provide - * you with as much context as possible, but normally you would only need - * $synonym_entity and $synonym_entity_type parameters. Return an empty array - * if entity of type $synonym_entity_type cannot be converted into a format - * expected by $field. - * - * @param array $items - * Array items that already exist in the field into which new synonyms is to - * be added - * @param array $field - * Field array definition according to Field API of the field into which new - * synonym is to be added - * @param array $instance - * Instance array definition according to Field API of the instance into - * which new synonym is to be added - * @param object $synonym_entity - * Fully loaded entity object which has to be added as synonym - * @param string $synonym_entity_type - * Entity type of $synonym_entity - * - * @return array - * Array of extra items to be merged into the items that already exist in - * field values - */ - static public function mergeEntityAsSynonym($items, $field, $instance, $synonym_entity, $synonym_entity_type) { - $synonym = entity_label($synonym_entity_type, $synonym_entity); - switch ($field['type']) { - case 'text': - break; - - // We add synonyms for numbers only if $synonym is a number. - case 'number_integer': - case 'number_float': - case 'number_decimal': - if (!is_numeric($synonym)) { - return array(); - } - break; - - } + public function mergeEntityAsSynonym($items, $field, $instance, $synonym_entity, $synonym_entity_type) { + // Let's say we keep the synonyms as strings and under the 'foo' column, to + // keep it consistent with the extractSynonyms() method. + $label = entity_label($synonym_entity_type, $synonym_entity); return array(array( - 'value' => $synonym, + 'foo' => $label, )); } + + public function synonymItemHash($item, $field, $instance) { + // Since we've agreed that the column that stores data in our imaginary + // field type is "foo". Then it suffices just to implement the hash function + // as the value of foo column. + return $item['foo']; + } + + public function synonymsFind(QueryConditionInterface $condition, $field, $instance) { + // We only can find synonyms in SQL storage. If this field is not one, then + // we have full right to throw an exception. + if ($field['storage']['type'] != 'field_sql_storage') { + throw new SynonymsSynonymsBehaviorException(t('Not supported storage engine %type in synonymsFind() method.', array( + '%type' => $field['storage']['type'], + ))); + } + // Now we will figure out in what table $field is stored. We want to + // condition the 'foo' column within that field table. + $table = array_keys($field['storage']['details']['sql'][FIELD_LOAD_CURRENT]); + $table = reset($table); + $column = $field['storage']['details']['sql'][FIELD_LOAD_CURRENT][$table]['foo']; + + // Once we know full path to the column that stores synonyms as plain text, + // we can use a supplementary method from AbstractSynonymsSynonymsBehavior, + // which helps us to convert a column placeholder into its real value within + // the condition we have received from outside. + $this->synonymsFindProcessCondition($condition, $column); + + // Now we are all set to build a SELECT query and return its result set. + $query = db_select($table); + $query->fields($table, array('entity_id')); + $query->addField($table, $column, 'synonym'); + return $query->condition($condition) + ->condition('entity_type', $instance['entity_type']) + ->condition('bundle', $instance['bundle']) + ->execute(); + } } diff --git a/sites/all/modules/contrib/taxonomy/synonyms/synonyms.css b/sites/all/modules/contrib/taxonomy/synonyms/synonyms.css new file mode 100644 index 00000000..395b9ac3 --- /dev/null +++ b/sites/all/modules/contrib/taxonomy/synonyms/synonyms.css @@ -0,0 +1 @@ +.synonyms-behavior-settings .form-item .description {white-space:normal;} diff --git a/sites/all/modules/contrib/taxonomy/synonyms/synonyms.info b/sites/all/modules/contrib/taxonomy/synonyms/synonyms.info index 7b3cc4a3..ed647767 100644 --- a/sites/all/modules/contrib/taxonomy/synonyms/synonyms.info +++ b/sites/all/modules/contrib/taxonomy/synonyms/synonyms.info @@ -3,19 +3,22 @@ description = "Provides synonyms feature for working with Drupal Taxonomy" package = Taxonomy core = 7.x dependencies[] = taxonomy -dependencies[] = text +dependencies[] = ctools files[] = synonyms.test -files[] = includes/AbstractSynonymsExtractor.class.inc -files[] = includes/SynonymsSynonymsExtractor.class.inc -files[] = includes/TaxonomySynonymsExtractor.class.inc -files[] = includes/EntityReferenceSynonymsExtractor.class.inc + +files[] = includes/SynonymsBehavior.interface.inc +files[] = includes/TextSynonymsBehavior.class.inc +files[] = includes/TaxonomySynonymsBehavior.class.inc +files[] = includes/EntityReferenceSynonymsBehavior.class.inc + files[] = views/synonyms.views.inc files[] = views/synonyms_views_plugin_argument_validate_taxonomy_term.inc +files[] = views/synonyms_views_handler_filter_term_tid.inc -; Information added by Drupal.org packaging script on 2014-09-15 -version = "7.x-1.2" +; Information added by Drupal.org packaging script on 2015-11-29 +version = "7.x-1.3" core = "7.x" project = "synonyms" -datestamp = "1410753528" +datestamp = "1448771941" diff --git a/sites/all/modules/contrib/taxonomy/synonyms/synonyms.install b/sites/all/modules/contrib/taxonomy/synonyms/synonyms.install index e10090f7..00134c27 100644 --- a/sites/all/modules/contrib/taxonomy/synonyms/synonyms.install +++ b/sites/all/modules/contrib/taxonomy/synonyms/synonyms.install @@ -5,13 +5,55 @@ * Install, update, and uninstall functions for the Synonyms module. */ +/** + * Implements hook_schema(). + */ +function synonyms_schema() { + $schema = array(); + + $schema['synonyms_settings'] = array( + 'description' => 'Stores synonyms settings for all the entities and fields. Only enabled synonyms behaviors are included in this table.', + 'fields' => array( + 'instance_id' => array( + 'description' => 'Reference to {field_config_instance}.id of the instance, whose synonyms settings are stored in this row.', + 'type' => 'int', + 'not null' => TRUE, + 'unsigned' => TRUE, + ), + 'behavior' => array( + 'description' => 'Name of the synonyms behavior (ctools plugin), whose settings are stored in this row.', + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + ), + 'settings_serialized' => array( + 'description' => 'Settings of the specified synonyms behavior for the specified field instance.', + 'type' => 'text', + 'serialize' => TRUE, + 'not null' => TRUE, + ), + ), + 'unique keys' => array( + 'instance_behavior' => array('instance_id', 'behavior'), + ), + 'foreign keys' => array( + 'field_config_instance' => array( + 'table' => 'field_config_instance', + 'columns' => array('instance_id' => 'id'), + ), + ), + 'indexes' => array( + 'behavior' => array('behavior'), + ), + ); + + return $schema; +} + /** * Implements hook_uninstall(). */ function synonyms_uninstall() { - // We rely on a constant defined in the main module's file, so we include it. - drupal_load('module', 'synonyms'); - field_delete_field(SYNONYMS_DEFAULT_FIELD_NAME); // Cleaning all configure variables. $results = db_select('variable', 'var') ->fields('var', array('name')) @@ -39,8 +81,108 @@ function synonyms_update_7101() { // Enabled synonyms now stored as field names, since the field independency // has been introduced. See issue http://drupal.org/node/1850748. drupal_load('module', 'synonyms'); - $settings['synonyms'] = $settings['synonyms'] ? array(SYNONYMS_DEFAULT_FIELD_NAME) : array(); + $settings['synonyms'] = $settings['synonyms'] ? array('synonyms_synonyms') : array(); variable_set($var->name, $settings); } return t('Updated settings of synonyms.'); } + +/** + * Multiple adjustments in the internal structures of the module. + * + * Unlock the 'synonyms_synonyms' field, because Synonyms module no longer uses + * it. + * Create 'synonyms_settings' table. + * Enable 'synonyms_search' module if the core Search module is enabled. + * Enable all available behaviors on all "source of synonyms" fields with the + * default settings. + */ +function synonyms_update_7102() { + $field = field_info_field('synonyms_synonyms'); + $field['locked'] = FALSE; + field_update_field($field); + + db_create_table('synonyms_settings', array( + 'description' => 'Stores synonyms settings for all the entities and fields. Only enabled synonyms behaviors are included in this table.', + 'fields' => array( + 'instance_id' => array( + 'description' => 'Reference to {field_config_instance}.id of the instance, whose synonyms settings are stored in this row.', + 'type' => 'int', + 'not null' => TRUE, + 'unsigned' => TRUE, + ), + 'behavior' => array( + 'description' => 'Name of the synonyms behavior (ctools plugin), whose settings are stored in this row.', + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + ), + 'settings_serialized' => array( + 'description' => 'Settings of the specified synonyms behavior for the specified field instance.', + 'type' => 'text', + 'serialize' => TRUE, + 'not null' => TRUE, + ), + ), + 'unique keys' => array( + 'instance_behavior' => array('instance_id', 'behavior'), + ), + 'foreign keys' => array( + 'field_config_instance' => array( + 'table' => 'field_config_instance', + 'columns' => array('instance_id' => 'id'), + ), + ), + 'indexes' => array( + 'behavior' => array('behavior'), + ), + )); + + if (module_exists('search')) { + module_enable(array('synonyms_search')); + } + + $vars = db_select('variable', 'v') + ->fields('v', array('name')) + ->condition('name', db_like('synonyms_settings_') . '%', 'LIKE') + ->execute(); + foreach ($vars as $row) { + $var = variable_get($row->name); + $vid = substr($row->name, drupal_strlen('synonyms_settings_')); + $vocabulary = taxonomy_vocabulary_load($vid); + if ($vocabulary) { + $bundle = $vocabulary->machine_name; + foreach ($var['synonyms'] as $field) { + $instance = field_info_instance('taxonomy_term', $field, $bundle); + foreach (synonyms_behaviors() as $behavior) { + switch ($behavior['name']) { + case 'synonyms': + case 'search': + default: + $settings = array(); + break; + + case 'select': + $settings = array( + 'wording' => '@synonym', + ); + break; + + case 'autocomplete': + $settings = array( + 'wording' => '@synonym, synonym of @term', + ); + break; + } + $settings = array( + 'instance_id' => $instance['id'], + 'behavior' => $behavior['name'], + 'settings' => $settings, + ); + synonyms_behavior_settings_save($settings); + } + } + } + variable_del($row->name); + } +} diff --git a/sites/all/modules/contrib/taxonomy/synonyms/synonyms.module b/sites/all/modules/contrib/taxonomy/synonyms/synonyms.module index e66e4831..fe53208c 100644 --- a/sites/all/modules/contrib/taxonomy/synonyms/synonyms.module +++ b/sites/all/modules/contrib/taxonomy/synonyms/synonyms.module @@ -5,11 +5,6 @@ * Provide synonyms feature for Drupal Taxonomy. */ -/** - * The default field name to be used as a source of synonyms for a term. - */ -define('SYNONYMS_DEFAULT_FIELD_NAME', 'synonyms_synonyms'); - /** * Implements hook_menu(). */ @@ -29,33 +24,58 @@ function synonyms_menu() { } /** - * Implements hook_taxonomy_term_update(). + * Implements hook_ctools_plugin_type(). */ -function synonyms_taxonomy_term_update($term) { - // If we notice at least some change in synonyms of this term, we want to - // trigger search re-indexing of nodes, where this term is referenced, since - // change in term synonyms affects nodes ranking in the search. - if (isset($term->original)) { - $current_synonyms = synonyms_get_raw($term); - $previous_synonyms = synonyms_get_raw($term->original); - if (count($current_synonyms) != count($previous_synonyms)) { - // Schedule re-indexing, because amount of synonyms has changed. - module_load_include('inc', 'synonyms', 'synonyms.pages'); - synonyms_reindex_nodes_by_terms(array($term->tid)); - } - else { - foreach ($current_synonyms as $k => $current_synonym) { - if ($current_synonyms[$k] != $previous_synonyms[$k]) { - // Schedule re-indexing, because some synonym has changed. - module_load_include('inc', 'synonyms', 'synonyms.pages'); - synonyms_reindex_nodes_by_terms(array($term->tid)); - break; - } +function synonyms_ctools_plugin_type() { + $plugins = array(); + + $plugins['behavior'] = array( + 'defaults' => array( + 'title' => NULL, + 'description' => NULL, + 'settings form callback' => NULL, + 'interface' => NULL, + 'enabled callback' => NULL, + 'disabled callback' => NULL, + ), + ); + + return $plugins; +} + +/** + * Implements hook_ctools_plugin_directory(). + */ +function synonyms_ctools_plugin_directory($owner, $plugin_type) { + switch ($owner) { + case 'synonyms': + switch ($plugin_type) { + case 'behavior': + return 'plugins/' . $plugin_type; } - } + break; + + case 'ctools': + switch ($plugin_type) { + case 'arguments': + return 'plugins/' . $plugin_type; + } + break; } } +/** + * Implements hook_theme(). + */ +function synonyms_theme() { + return array( + 'synonyms_behaviors_settings' => array( + 'render element' => 'element', + 'file' => 'synonyms.pages.inc', + ), + ); +} + /** * Implements hook_entity_property_info(). */ @@ -75,105 +95,38 @@ function synonyms_entity_property_info() { } /** - * Implements hook_node_update_index(). + * Implements hook_field_delete_instance(). */ -function synonyms_node_update_index($node) { - $output = ''; - foreach (field_info_instances('node', $node->type) as $instance) { - // We go a field by field looking for taxonomy term reference and - // if that vocabulary has enabled synonyms, we add term's synonyms - // to the search index. - $field_info = field_info_field($instance['field_name']); - if ($field_info['type'] == 'taxonomy_term_reference') { - // For each term referenced in this node we have to add synonyms. - $_terms = field_get_items('node', $node, $instance['field_name']); - if (is_array($_terms) && !empty($_terms)) { - $terms = array(); - foreach ($_terms as $v) { - $terms[] = $v['tid']; - } - $terms = taxonomy_term_load_multiple($terms); - foreach ($terms as $term) { - $synonyms = synonyms_get_sanitized($term); - if (!empty($synonyms)) { - $output .= '' . implode(', ', $synonyms) . ''; - } - } - } - } +function synonyms_field_delete_instance($instance) { + // Remove, if necessary, any synonyms behaviors enabled on this instance. + $result = db_select('synonyms_settings', 's') + ->fields('s', array('behavior')) + ->condition('s.instance_id', $instance['id']) + ->execute(); + foreach ($result as $row) { + synonyms_behavior_settings_delete($instance['id'], $row->behavior); } - return $output; } /** - * Implements hook_synonyms_extractor_info(). + * Implements hook_synonyms_behavior_implementation_info(). */ -function synonyms_synonyms_extractor_info() { - // Here we provide synonyms extractors that come along with Synonyms module. - return array( - 'SynonymsSynonymsExtractor', - 'TaxonomySynonymsExtractor', - 'EntityReferenceSynonymsExtractor', - ); -} - -/** - * Public function. - * - * Provide info about what class reports ability to extract synonyms from - * which field type. The output information of this function is collected via - * hook_synonyms_extractor_info(). - * - * @param string $field_type - * Optionally you may specify to get a class responsible for a specific field - * type only. If nothing is supplied, array of field_type => class_extractor - * relation is returned - * @param bool $reset - * Whether collect all the info again from hooks, or cached info is fine - * - * @return array - * Key of this array denotes the field type while value of the array denotes - * which class reports ability to extract synonyms from such field type. - */ -function synonyms_extractor_info($field_type = NULL, $reset = FALSE) { - $cache = &drupal_static(__FUNCTION__); - - // Trying static cache. - if (!is_array($cache) || $reset) { - // Trying Drupal DB cache layer. - $cid = 'synonyms_extractor_info'; - $cache = cache_get($cid); - if (!isset($cache->data) || $reset) { - // No cache has been found at all. So we call hooks and collect data. - $info = array(); - - $extractor_classes = module_invoke_all('synonyms_extractor_info'); - - if (is_array($extractor_classes)) { - foreach ($extractor_classes as $class) { - foreach (call_user_func(array($class, 'fieldTypesSupported')) as $_field_type) { - $info[$_field_type] = $class; - } - } - } - - // Letting whoever wants to implement any changes after preprocessing the - // data. - drupal_alter('synonyms_extractor_info', $info); - - $cache = $info; - cache_set($cid, $cache, 'cache', CACHE_TEMPORARY); - } - else { - $cache = $cache->data; - } +function synonyms_synonyms_behavior_implementation_info($behavior) { + switch ($behavior) { + case 'autocomplete': + case 'select': + case 'synonyms': + return array( + 'number_integer' => 'TextSynonymsBehavior', + 'number_decimal' => 'TextSynonymsBehavior', + 'number_float' => 'TextSynonymsBehavior', + 'text' => 'TextSynonymsBehavior', + 'taxonomy_term_reference' => 'TaxonomySynonymsBehavior', + 'entityreference' => 'EntityReferenceSynonymsBehavior', + ); + break; } - - if (!is_null($field_type) && isset($cache[$field_type])) { - return $cache[$field_type]; - } - - return $cache; + return array(); } /** @@ -184,7 +137,9 @@ function synonyms_form_taxonomy_form_vocabulary_alter(&$form, &$form_state) { return; } - module_load_include('inc', 'synonyms', 'synonyms.pages'); + if (!isset($form['#vocabulary']->vid) || !$form['#vocabulary']->vid) { + return; + } $form['synonyms'] = array( '#type' => 'fieldset', @@ -193,32 +148,98 @@ function synonyms_form_taxonomy_form_vocabulary_alter(&$form, &$form_state) { '#tree' => TRUE, ); - $options = array( - SYNONYMS_DEFAULT_FIELD_NAME => t('Default synonyms field'), + $behaviors = synonyms_behaviors(); + $bundle = field_extract_bundle('taxonomy_term', $form['#vocabulary']); + + $form['synonyms']['behaviors'] = array( + '#theme' => 'synonyms_behaviors_settings', + '#id' => 'synonyms-behaviors-settings-wrapper', ); - if (isset($form['#vocabulary']->vid)) { - $instances = synonyms_instances_extract_applicable($form['#vocabulary']); - foreach ($instances as $instance) { - // Here we prefer some informative text for the default synonyms field - // rather its label. - if ($instance['field_name'] != SYNONYMS_DEFAULT_FIELD_NAME) { - $options[$instance['field_name']] = $instance['label']; + + foreach ($behaviors as $behavior => $behavior_info) { + $form['synonyms']['behaviors'][$behavior] = array( + '#title' => $behavior_info['title'], + ); + + $behavior_implementations = synonyms_behavior_get($behavior, 'taxonomy_term', $bundle); + + foreach ($behavior_implementations as $implementation) { + $instance = field_info_instance($implementation['entity_type'], $implementation['field_name'], $implementation['bundle']); + $form['synonyms']['behaviors'][$behavior][$implementation['instance_id']]['#title'] = $instance['label']; + + if (isset($form_state['values']['synonyms']['behaviors'][$behavior][$implementation['instance_id']])) { + $behavior_settings = (bool) $form_state['values']['synonyms']['behaviors'][$behavior][$implementation['instance_id']]['enabled']; + } + else { + $behavior_settings = !is_null($implementation['settings']); + } + if ($behavior_settings) { + if (isset($form_state['values']['synonyms']['behaviors'][$behavior][$implementation['instance_id']]['settings'])) { + $behavior_settings = $form_state['values']['synonyms']['behaviors'][$behavior][$implementation['instance_id']]['settings']; + } + elseif ($implementation['settings']) { + $behavior_settings = $implementation['settings']; + } + else { + $behavior_settings = array(); + } + } + + $form['synonyms']['behaviors'][$behavior][$implementation['instance_id']]['enabled'] = array( + '#type' => 'checkbox', + '#title' => t('Enable'), + '#default_value' => $behavior_settings !== FALSE, + ); + + $settings_form = ctools_plugin_get_function($behavior_info, 'settings form callback'); + if ($settings_form) { + $form['synonyms']['behaviors'][$behavior][$implementation['instance_id']]['enabled']['#ajax'] = array( + 'callback' => 'synonyms_behaviors_settings_form_ajax', + 'wrapper' => $form['synonyms']['behaviors']['#id'], + ); + + if ($behavior_settings !== FALSE) { + $form['synonyms']['behaviors'][$behavior][$implementation['instance_id']]['settings'] = $settings_form($form, $form_state, $behavior_settings); + } } } } - - $form['synonyms']['synonyms'] = array( - '#type' => 'checkboxes', - '#title' => t('Synonyms Fields'), - '#options' => $options, - '#description' => t('

This option allows you to assign synonym fields for each term of the vocabulary, allowing to reduce the amount of duplicates.

Important note: unticking %default_field on a production website will result in loss of your synonyms.

', array('%default_field' => $options[SYNONYMS_DEFAULT_FIELD_NAME])), - '#default_value' => synonyms_synonyms_fields($form['#vocabulary']), - ); - - // Adding our own submit handler. $form['#submit'][] = 'synonyms_taxonomy_form_vocabulary_submit'; } +/** + * Submit handler for Taxonomy vocabulary edit form. + * + * Store synonyms behavior settings. + */ +function synonyms_taxonomy_form_vocabulary_submit($form, &$form_state) { + $values = $form_state['values']; + + if ($values['op'] == $form['actions']['submit']['#value']) { + foreach ($values['synonyms']['behaviors'] as $behavior => $settings) { + foreach ($settings as $instance_id => $behavior_settings) { + if ($behavior_settings['enabled']) { + synonyms_behavior_settings_save(array( + 'instance_id' => $instance_id, + 'behavior' => $behavior, + 'settings' => isset($behavior_settings['settings']) ? $behavior_settings['settings'] : NULL, + )); + } + else { + synonyms_behavior_settings_delete($instance_id, $behavior); + } + } + } + } +} + +/** + * Ajax callback function for synonyms behavior settings form. + */ +function synonyms_behaviors_settings_form_ajax($form, &$form_state) { + return $form['synonyms']['behaviors']; +} + /** * Implements hook_field_widget_info(). */ @@ -229,7 +250,7 @@ function synonyms_field_widget_info() { 'field types' => array('taxonomy_term_reference'), 'settings' => array( 'size' => 60, - 'autocomplete_path' => 'synonyms/autocomplete', + 'synonyms_autocomplete_path' => 'synonyms/autocomplete', 'suggestion_size' => 10, 'suggest_only_unique' => FALSE, 'auto_creation' => 1, @@ -238,6 +259,16 @@ function synonyms_field_widget_info() { 'multiple values' => FIELD_BEHAVIOR_CUSTOM, ), ), + 'synonyms_select' => array( + 'label' => t('Synonyms friendly select list'), + 'field types' => array('taxonomy_term_reference'), + 'settings' => array( + 'sort' => 'weight', + ), + 'behaviors' => array( + 'multiple values' => FIELD_BEHAVIOR_CUSTOM, + ), + ), ); } @@ -246,32 +277,49 @@ function synonyms_field_widget_info() { */ function synonyms_field_widget_settings_form($field, $instance) { $widget = $instance['widget']; - $settings = $widget['settings']; + $settings = $widget['settings'] + field_info_widget_settings($widget['type']); $form = array(); - $form['auto_creation'] = array( - '#type' => 'checkbox', - '#title' => t('Allow auto-creation?'), - '#description' => t('Whether users may create a new term by typing in a non-existing name into this field.'), - '#default_value' => $settings['auto_creation'], - ); + switch ($widget['type']) { + case 'synonyms_autocomplete': + $form['auto_creation'] = array( + '#type' => 'checkbox', + '#title' => t('Allow auto-creation?'), + '#description' => t('Whether users may create a new term by typing in a non-existing name into this field.'), + '#default_value' => $settings['auto_creation'], + ); - $form['suggestion_size'] = array( - '#type' => 'textfield', - '#title' => t('Suggestions Size'), - '#description' => t('Please, enter how many suggested entities to show in the autocomplete textfield.'), - '#required' => TRUE, - '#element_validate' => array('element_validate_integer_positive'), - '#default_value' => $settings['suggestion_size'], - ); + $form['suggestion_size'] = array( + '#type' => 'textfield', + '#title' => t('Suggestions Size'), + '#description' => t('Please, enter how many suggested entities to show in the autocomplete textfield.'), + '#required' => TRUE, + '#element_validate' => array('element_validate_integer_positive'), + '#default_value' => $settings['suggestion_size'], + ); - $form['suggest_only_unique'] = array( - '#type' => 'checkbox', - '#title' => t('Suggest only one entry per term'), - '#description' => t('If you want to include only term name or a single synonym, suggesting a particular term, while disregarding all ongoing ones, please, tick this checkbox on.'), - '#default_value' => $settings['suggest_only_unique'], - ); + $form['suggest_only_unique'] = array( + '#type' => 'checkbox', + '#title' => t('Suggest only one entry per term'), + '#description' => t('If you want to include only term name or a single synonym, suggesting a particular term, while disregarding all ongoing ones, please, tick this checkbox on.'), + '#default_value' => $settings['suggest_only_unique'], + ); + break; + + case 'synonyms_select': + $form['sort'] = array( + '#type' => 'radios', + '#title' => t('Sort'), + '#description' => t('Choose by what criterion the items within select should be sorted.'), + '#options' => array( + 'weight' => t('As in taxonomy vocabulary (by weight)'), + 'name' => t('By name of terms and their synonyms'), + ), + '#default_value' => $settings['sort'], + ); + break; + } return $form; } @@ -280,20 +328,76 @@ function synonyms_field_widget_settings_form($field, $instance) { * Implements hook_field_widget_form(). */ function synonyms_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) { - $tags = array(); + $default_value = array(); foreach ($items as $item) { - $tags[$item['tid']] = isset($item['taxonomy_term']) ? $item['taxonomy_term'] : taxonomy_term_load($item['tid']); + $default_value[] = $item['tid']; } - $element += array( - '#type' => 'textfield', - '#default_value' => taxonomy_implode_tags($tags), - '#autocomplete_path' => $instance['widget']['settings']['autocomplete_path'] . '/' . $field['field_name'] . '/' . $instance['entity_type'] . '/' . $instance['bundle'], - '#size' => $instance['widget']['settings']['size'], - '#maxlength' => 1024, - '#element_validate' => array('taxonomy_autocomplete_validate', 'synonyms_autocomplete_validate'), - '#auto_creation' => $instance['widget']['settings']['auto_creation'], - ); + switch ($instance['widget']['type']) { + case 'synonyms_autocomplete': + $tags = taxonomy_term_load_multiple($default_value); + + $element += array( + '#type' => 'textfield', + '#default_value' => taxonomy_implode_tags($tags), + '#autocomplete_path' => $instance['widget']['settings']['synonyms_autocomplete_path'] . '/' . $field['field_name'] . '/' . $instance['entity_type'] . '/' . $instance['bundle'], + '#size' => $instance['widget']['settings']['size'], + '#maxlength' => 1024, + '#element_validate' => array('taxonomy_autocomplete_validate', 'synonyms_autocomplete_validate'), + '#auto_creation' => $instance['widget']['settings']['auto_creation'], + '#attached' => array( + 'js' => array( + drupal_get_path('module', 'synonyms') . '/js/synonyms-autocomplete.js' => array(), + ), + ), + '#attributes' => array( + 'class' => array('synonyms-autocomplete'), + ), + ); + break; + + case 'synonyms_select': + $multiple = $field['cardinality'] > 1 || $field['cardinality'] == FIELD_CARDINALITY_UNLIMITED; + + $options = array(); + foreach ($field['settings']['allowed_values'] as $tree) { + if ($vocabulary = taxonomy_vocabulary_machine_name_load($tree['vocabulary'])) { + switch ($instance['widget']['settings']['sort']) { + case 'weight': + if ($terms = taxonomy_get_tree($vocabulary->vid, $tree['parent'], NULL, TRUE)) { + $behavior_implementations = synonyms_behavior_get('select', 'taxonomy_term', field_extract_bundle('taxonomy_term', $vocabulary), TRUE); + foreach ($terms as $term) { + $options[] = synonyms_select_option($term); + foreach ($behavior_implementations as $implementation) { + foreach (synonyms_extract_synonyms($term, $implementation) as $synonym) { + $options[] = synonyms_select_option($term, $synonym, $implementation); + } + } + } + } + break; + + case 'name': + // TODO: is there any way to leverage DB for the sorting routine? + $options = synonyms_select_sort_name_options_recursive($vocabulary, $tree['parent']); + break; + } + } + } + + if (!$multiple && !$element['#required']) { + $options = array('' => t('- None -')) + $options; + } + + $element += array( + '#type' => 'select', + '#multiple' => $multiple, + '#options' => $options, + '#default_value' => $default_value, + '#element_validate' => array('synonyms_select_form_to_storage'), + ); + break; + } return $element; } @@ -316,14 +420,26 @@ function synonyms_autocomplete_validate($element, &$form_state) { // (a) Double-check that those terms are not synonyms. // (b) Check that synonyms' configurable auto-creation option is enabled. $value = drupal_array_get_nested_value($form_state['values'], $element['#parents']); + + $field = field_widget_field($element, $form_state); foreach ($value as $delta => $term) { if ($term['tid'] == 'autocreate') { - $vocabulary = taxonomy_vocabulary_load($term['vid']); - $synonym_tid = synonyms_get_term_by_synonym($term['name'], $vocabulary); + $synonym_tid = 0; + foreach ($field['settings']['allowed_values'] as $tree) { + $behavior_implementations = synonyms_behavior_get('autocomplete', 'taxonomy_term', $tree['vocabulary'], TRUE); + foreach ($behavior_implementations as $behavior_implementation) { + $synonyms = synonyms_synonyms_find_behavior(db_and()->condition(AbstractSynonymsSynonymsBehavior::COLUMN_PLACEHOLDER, $term['name']), $behavior_implementation); + foreach ($synonyms as $synonym) { + $synonym_tid = $synonym->entity_id; + break(2); + } + } + } + if ($synonym_tid != 0) { $value[$delta]['tid'] = $synonym_tid; } - elseif (empty($element['#auto_creation'])) { + elseif (!$element['#auto_creation']) { unset($value[$delta]); } } @@ -335,8 +451,6 @@ function synonyms_autocomplete_validate($element, &$form_state) { /** * Try to find a term by its name or synonym. * - * To maximize the match trimming and case-insensitive comparison is used. - * * @param string $name * The string to be searched for its {taxonomy_term_data}.tid * @param object $vocabulary @@ -351,20 +465,28 @@ function synonyms_autocomplete_validate($element, &$form_state) { * found term, otherwise returns 0 */ function synonyms_get_term_by_synonym($name, $vocabulary, $parent = 0) { - $name = mb_strtoupper(trim($name), 'UTF-8'); + $name = trim($name); - $tree = taxonomy_get_tree($vocabulary->vid, $parent, NULL, TRUE); - foreach ($tree as $term) { - if (mb_strtoupper($term->name, 'UTF-8') == $name) { + $terms = taxonomy_get_term_by_name($name, $vocabulary->machine_name); + foreach ($terms as $term) { + if (!$parent || synonyms_taxonomy_term_is_child_of($term->tid, $parent)) { + // TODO: actually it could be so that there is more than 1 term that + // satisfies the search query, i.e. the name and parent constraints. At + // the moment we are going to return the first one we encounter, though + // something better could be thought of in the future. return $term->tid; } + } - // We additionally scan through the synonyms. - $synonyms = synonyms_get_sanitized($term); - foreach ($synonyms as $item) { - if (mb_strtoupper($item, 'UTF-8') == $name) { - return $term->tid; - } + // We have failed to find a term with the provided $name. So let's search now + // among the term synonyms. + $bundle = field_extract_bundle('taxonomy_term', $vocabulary); + $synonyms = synonyms_synonyms_find(db_and()->condition(AbstractSynonymsSynonymsBehavior::COLUMN_PLACEHOLDER, $name), 'taxonomy_term', $bundle); + foreach ($synonyms as $synonym) { + if (!$parent || synonyms_taxonomy_term_is_child_of($synonym->entity_id, $parent)) { + // TODO: similarly here, as above, we could have more than 1 match, but + // for now we will simply return the first one encountered. + return $synonym->entity_id; } } @@ -374,7 +496,7 @@ function synonyms_get_term_by_synonym($name, $vocabulary, $parent = 0) { } /** - * Look up a term considering synonyms and if nothing found add one. + * Look up a term considering synonyms and if nothing is found add one. * * This function is useful for automated creation of new terms as it won't * generate the same terms over and over again. @@ -454,6 +576,9 @@ function synonyms_get_raw($item) { /** * Public function for retrieving synonyms of a taxonomy term. * + * You are encouraged to use synonyms_get_sanitized() or synonyms_get_raw() + * instead. This function soon will be removed from the source code. + * * @param object $term * Fully loaded taxonomy term for which the synonyms are desired * @@ -463,30 +588,151 @@ function synonyms_get_raw($item) { * the following keys: * - value: (string) the value of a synonym as it was input by user * - safe_value: (string) a sanitized value of a synonym + * + * @deprecated */ function synonyms_get_term_synonyms($term) { $synonyms = array(); $vocabulary = taxonomy_vocabulary_load($term->vid); - foreach (synonyms_synonyms_fields($vocabulary) as $field) { - $bundle = field_extract_bundle('taxonomy_term', $vocabulary); - $instance = field_info_instance('taxonomy_term', $field, $bundle); - $field = field_info_field($field); - $items = field_get_items('taxonomy_term', $term, $field['field_name']); + $bundle = field_extract_bundle('taxonomy_term', $vocabulary); - if (is_array($items) && !empty($items)) { - $class = synonyms_extractor_info($field['type']); - foreach (call_user_func(array($class, 'synonymsExtract'), $items, $field, $instance, $term, 'taxonomy_term') as $synonym) { + $behavior_implementations = synonyms_behavior_get('synonyms', 'taxonomy_term', $bundle, TRUE); + foreach ($behavior_implementations as $implementation) { + foreach (synonyms_extract_synonyms($term, $implementation) as $synonym) { $synonyms[] = array( 'value' => $synonym, 'safe_value' => check_plain($synonym), ); } } - } return $synonyms; } +/** + * Extract synonyms of an entity within a certain field and behavior. + * + * Do not use this function, if you want to get synonyms of an entity, unless + * you know what you are doing. This function extracts the synonyms from a field + * that is specified by $behavior_implementation parameter. The behavior may not + * necessarily be of 'synonyms' type, so it's not 100% valid to say that the + * entity has the returned array as its synonyms. However, this function is very + * useful for behaviors that "extend" the basic synonyms behavior. + * + * @param object $entity + * Fully loaded entity, synonyms from which should be extracted + * @param array $behavior_implementation + * Fully loaded behavior implementation. Supply here one of the values from + * the return of synonyms_behavior_get() function + * + * @return array + * Array of synonyms that reside in the field dictated by + * $behavior_implementation parameter + */ +function synonyms_extract_synonyms($entity, $behavior_implementation) { + $synonyms = array(); + $field = field_info_field($behavior_implementation['field_name']); + $instance = field_info_instance($behavior_implementation['entity_type'], $behavior_implementation['field_name'], $behavior_implementation['bundle']); + $items = field_get_items($behavior_implementation['entity_type'], $entity, $field['field_name']); + + if (is_array($items) && !empty($items)) { + $object = synonyms_behavior_implementation_class($behavior_implementation['behavior'], $field); + $object = new $object(); + + $synonyms = array_merge($synonyms, $object->extractSynonyms($items, $field, $instance, $entity, $behavior_implementation['entity_type'])); + } + return $synonyms; +} + +/** + * Look up entities by their synonyms. + * + * @param QueryConditionInterface $condition + * Object of QueryConditionInterface that specifies conditions by which you + * want to find synonyms. When building this condition object, use + * AbstractSynonymsSynonymsBehavior::COLUMN_PLACEHOLDER as a placeholder for + * real column name that contains synonym as text. For example, if you were to + * find all entities with synonyms that begin with "synonym-come-here" + * substring, case insensitive and replacing all spaces in original synonym + * string by a dash sign, then you would have to create the following + * condition object: + * db_and() + * ->where("LOWER(REPLACE(" . AbstractSynonymsSynonymsBehavior::COLUMN_PLACEHOLDER . ", ' ', '-')) LIKE 'synonym-come-here%'") + * And then just supply this object as an input parameter to this function + * @param string $entity_type + * Among synonyms of what entity type to search + * @param string $bundle + * Among synonyms of what bundle to search + * + * @return array + * Array of found synonyms and entity IDs to which those belong. Each element + * in the array will be an object and will have the following structure: + * - synonym: (string) Synonym that was found and which satisfies the + * $condition you specified + * - entity_id: (int) ID of the entity to which the found synonym belongs + */ +function synonyms_synonyms_find(QueryConditionInterface $condition, $entity_type, $bundle) { + $rows = array(); + + $behavior_implementations = synonyms_behavior_get('synonyms', $entity_type, $bundle, TRUE); + foreach ($behavior_implementations as $behavior_implementation) { + foreach (synonyms_synonyms_find_behavior($condition, $behavior_implementation) as $row) { + $rows[] = $row; + } + } + + return $rows; +} + +/** + * Find entities with a provided synonym within certain behavior implementation. + * + * Do not use this function, if you want to find entities that have specific + * synonyms, unless you know what you are doing. This function searches for the + * entities with synonyms from a field that is specified by + * $behavior_implementation parameter. The behavior may not necessarily be of + * 'synonyms' type, so it's not 100% valid to say that the returned entities + * have the specified synonyms. However, this function is very useful for + * behaviors that "extend" the basic synonyms behavior. + * + * You have full SQL flexibility to specify parameters of how to search for + * synonyms. You can create arbitrary set of SQL conditions that will be plugged + * into specific SELECT queries by behavior implementations. + * + * @param QueryConditionInterface $condition + * Object of QueryConditionInterface that specifies conditions by which you + * want to find synonyms. When building this condition object, use + * AbstractSynonymsSynonymsBehavior::COLUMN_PLACEHOLDER as a placeholder for + * real column name that contains synonym as text. For example, if you were to + * find all entities with synonyms that begin with "synonym-come-here" + * substring, case insensitive and replacing all spaces in original synonym + * string by a dash sign, then you would have to create the following + * condition object: + * db_and() + * ->where("LOWER(REPLACE(" . AbstractSynonymsSynonymsBehavior::COLUMN_PLACEHOLDER . ", ' ', '-')) LIKE 'synonym-come-here%'") + * And then just supply this object as an input parameter to this function + * @param array $behavior_implementation + * Fully loaded behavior implementation. Supply here one of the values from + * the return of synonyms_behavior_get() function + * + * @return Traversable + * Traversable result set of found synonyms and entity IDs to which those + * belong. Each element in the result set will be an object and will have the + * following structure: + * - synonym: (string) Synonym that was found and which satisfies the + * $condition you specified + * - entity_id: (int) ID of the entity to which the found synonym belongs + */ +function synonyms_synonyms_find_behavior(QueryConditionInterface $condition, $behavior_implementation) { + $field = field_info_field($behavior_implementation['field_name']); + $instance = field_info_instance($behavior_implementation['entity_type'], $behavior_implementation['field_name'], $behavior_implementation['bundle']); + + $object = synonyms_behavior_implementation_class($behavior_implementation['behavior'], $field); + $object = new $object(); + + return $object->synonymsFind($condition, $field, $instance); +} + /** * Allow to merge $synonym_entity as a synonym into $trunk_entity. * @@ -502,8 +748,8 @@ function synonyms_get_term_synonyms($term) { * @param string $trunk_entity_type * Entity type of $trunk_entity * @param string $field - * Field name that should exist in $trunk_entity and be enabled as a synonym - * source. Into this field synonym will be added + * Field name that should exist in $trunk_entity and have enabled the + * "synonyms" behavior. Into this field synonym will be added * @param object $synonym_entity * Fully loaded entity object which will be added as a synonym * @param string $synonym_entity_type @@ -517,26 +763,47 @@ function synonyms_add_entity_as_synonym($trunk_entity, $trunk_entity_type, $fiel // Currently synonyms module only operates on taxonomy terms. return FALSE; } - if (!in_array($field, synonyms_synonyms_fields(taxonomy_vocabulary_load($trunk_entity->vid)))) { - // $field either doesn't exist in the $trunk_entity or it's not enabled as - // a source of synonyms. + + $bundle = entity_extract_ids($trunk_entity_type, $trunk_entity); + $bundle = $bundle[2]; + $behavior_implementations = synonyms_behavior_get('synonyms', $trunk_entity_type, $bundle, TRUE); + + $field = field_info_field($field); + $instance = field_info_instance($trunk_entity_type, $field['field_name'], $bundle); + if (!isset($behavior_implementations[$instance['id']])) { + // $field either doesn't exist in the $trunk_entity or it does not have + // enabled the behavior of synonyms. return FALSE; } - // Preparing arguments for calling a method of Extractor class. - $field = field_info_field($field); - $extractor = synonyms_extractor_info($field['type']); + $items = field_get_items($trunk_entity_type, $trunk_entity, $field['field_name']); $items = is_array($items) ? $items : array(); - $trunk_entity_ids = entity_extract_ids($trunk_entity_type, $trunk_entity); - $instance = field_info_instance($trunk_entity_type, $field['field_name'], $trunk_entity_ids[2]); - $extra_items = call_user_func(array($extractor, 'mergeEntityAsSynonym'), $items, $field, $instance, $synonym_entity, $synonym_entity_type); + $object = synonyms_behavior_implementation_class('synonyms', $field); + $object = new $object(); + $extra_items = $object->mergeEntityAsSynonym($items, $field, $instance, $synonym_entity, $synonym_entity_type); + + if (empty($extra_items)) { + // For some reason the behavior implementation couldn't merge it. Otherwise + // it would have not returned an empty array. + return FALSE; + } // Merging extracted synonym items into the values of the field that already // exist. // @todo: Currently we hardcode to LANGUAGE_NONE, but in future it would be // nice to have multilanguage support. $items = array_merge($items, $extra_items); + + // Now we want to keep only unique values of the $items. Since we know nothing + // about what determines uniqueness of an item, we will ask the synonym + // behavior to hash each of them and then will compare hashes. + $unique_items = array(); + foreach ($items as $item) { + $unique_items[$object->synonymItemHash($item, $field, $instance)] = $item; + } + $items = array_values($unique_items); + $trunk_entity->{$field['field_name']}[LANGUAGE_NONE] = $items; // In future if this module eventually becomes a gateway for synonyms for any // entity types, we'll substitute it with entity_save(). @@ -547,169 +814,28 @@ function synonyms_add_entity_as_synonym($trunk_entity, $trunk_entity_type, $fiel /** * Return array of field names that are sources of synonyms. * - * Return array of field names that are currently enabled as source of - * synonyms in the supplied vocabulary. + * Return array of field names that are currently have enabled the synonyms + * behavior in the supplied vocabulary. This function is deprecated and shortly + * will be removed from the code. All clients of this function are encourage to + * use synonyms_behavior_get() function, which provides a richer set of + * functionality than this one. * * @param object $vocabulary * Fully loaded taxonomy vocabulary object * * @return array * Array of field names + * + * @deprecated */ function synonyms_synonyms_fields($vocabulary) { - $settings = synonyms_vocabulary_settings($vocabulary); - if (!isset($settings['synonyms']) || !is_array($settings['synonyms'])) { - // Weird as normally we expect to see here at least an empty array but - // no problem. We simply initialize it. - $settings['synonyms'] = array(); - } - - // It's not just as easy as pulling up already saved value. After this - // we have to make sure that all the fields are still present and have not - // been deleted from the vocabulary. + $fields = array(); $bundle = field_extract_bundle('taxonomy_term', $vocabulary); - $instances = field_info_instances('taxonomy_term', $bundle); - $settings['synonyms'] = array_intersect($settings['synonyms'], array_keys($instances)); - - return $settings['synonyms']; -} - -/** - * Enforce the setting "synonyms". - * - * @param object $vocabulary - * Fully loaded entity of a taxonomy vocabulary - * @param array $fields - * Array of fields that are enabled as a source of synonyms - */ -function synonyms_synonyms_enforce($vocabulary, $fields) { - $bundle = field_extract_bundle('taxonomy_term', $vocabulary); - - // Normally all the fields already exist, we just need to assure that - // default synonyms field exists if it is enabled as a source of synonyms. - // Otherwise we make sure we delete instance of the default field in the - // vocabulary. - $instance = field_info_instance('taxonomy_term', SYNONYMS_DEFAULT_FIELD_NAME, $bundle); - if (in_array(SYNONYMS_DEFAULT_FIELD_NAME, $fields)) { - if (is_null($instance)) { - // Make sure the field exists. - synonyms_default_field_ensure(); - - field_create_instance(array( - 'field_name' => SYNONYMS_DEFAULT_FIELD_NAME, - 'entity_type' => 'taxonomy_term', - 'bundle' => $bundle, - 'label' => t('Synonyms'), - 'description' => t('Please, enter the synonyms which should be related to this term.'), - )); - } - + $behavior_implementations = synonyms_behavior_get('synonyms', 'taxonomy_term', $bundle, TRUE); + foreach ($behavior_implementations as $v) { + $fields[] = $v['field_name']; } - elseif (!is_null($instance)) { - // Deleting the instance, we will delete the field separately when the - // module gets uninstalled. - field_delete_instance($instance, FALSE); - } -} - -/** - * Return the current settings for the supplied $vocabulary. - * - * @param object $vocabulary - * Fully loaded entity of a taxonomy vocabulary - * - * @return array - * Array of current synonyms settings for the supplied vocabulary. - * Should include the following keys: - * - synonyms: (array) array of field names that are enabled as source of - * synonyms - */ -function synonyms_vocabulary_settings($vocabulary) { - $settings = array(); - - if (isset($vocabulary->vid)) { - $settings = variable_get('synonyms_settings_' . $vocabulary->vid, array( - 'synonyms' => array(), - )); - } - - return $settings; -} - -/** - * Save the current settings for the supplied $vocabulary. - * - * @param object $vocabulary - * Fully loaded entity of a taxonomy vocabulary - * @param array $settings - * Settings the have to be stored. The structure of this array has to be - * identical to the output array of the function - * synonyms_vocabulary_settings() - */ -function synonyms_vocabulary_settings_save($vocabulary, $settings) { - // If source of synonyms has changed for this vocabulary, we have to trigger - // search re-indexing on all the nodes that reference at least 1 term from - // this vocabulary. - $previous_settings = synonyms_vocabulary_settings($vocabulary); - if (implode('', $settings['synonyms']) != implode('', $previous_settings['synonyms'])) { - module_load_include('inc', 'synonyms', 'synonyms.pages'); - synonyms_reindex_nodes_by_vocabulary($vocabulary); - } - - variable_set('synonyms_settings_' . $vocabulary->vid, $settings); - - // Now enforcing each setting. - foreach ($settings as $k => $v) { - switch ($k) { - case 'synonyms': - synonyms_synonyms_enforce($vocabulary, $v); - break; - } - } -} - -/** - * Make sure the default synonyms field exists. - * - * If the field doesn't exist function creates one, if the field exists, - * the function does nothing. - */ -function synonyms_default_field_ensure() { - $field = field_info_field(SYNONYMS_DEFAULT_FIELD_NAME); - if (is_null($field)) { - $field = array( - 'field_name' => SYNONYMS_DEFAULT_FIELD_NAME, - 'cardinality' => FIELD_CARDINALITY_UNLIMITED, - 'locked' => TRUE, - 'type' => 'text', - ); - field_create_field($field); - } -} - -/** - * Extract instances that are applicable for being source of synonyms. - * - * Walk through all instances of a vocabulary and extract only valid candidates - * for becoming a source of synonyms for the vocabulary terms. - * - * @param object $vocabulary - * Fully loaded vocabulary object - * - * @return array - * Array of instance arrays - */ -function synonyms_instances_extract_applicable($vocabulary) { - $applicable_field_types = array_keys(synonyms_extractor_info()); - $applicable = array(); - $bundle = field_extract_bundle('taxonomy_term', $vocabulary); - foreach (field_info_instances('taxonomy_term', $bundle) as $instance) { - $field = field_info_field($instance['field_name']); - if (in_array($field['type'], $applicable_field_types)) { - $applicable[] = $instance; - } - } - return $applicable; + return $fields; } /** @@ -721,3 +847,400 @@ function synonyms_views_api() { 'path' => drupal_get_path('module', 'synonyms') . '/views', ); } + +/** + * Load function for existing implementations of synonyms behaviors. + * + * @param string $behavior + * Name of the synonyms behavior whose existing implementations should be + * loaded. Basically it has to be name of a ctools plugin of "behavior" type. + * @param string $entity_type + * Optional filter to limit the search for existing implementations only to + * those that apply to the provided entity type + * @param string $bundle + * Optional filter to limit the search for existing implementations only to + * those that apply to the provided bundle and entity type (the $entity_type + * argument) + * @param bool $only_enabled + * Optional filter to limit the search for existing implementations only to + * those that are currently enabled + * @param bool $include_deleted + * Optional filter to include the behaviors from deleted instances + * + * @return array + * Array of loaded existing synonyms behavior implementations. It is keyed + * by ID of the field instance to which the behavior implementation applies. + * The underlying array will have the following structure: + * - behavior: (string) Behavior name of this behavior implementation, i.e. + * name of a ctools plugin of "behavior" type + * - settings: (mixed) Behavior settings, its internal structure depends on + * the type of behavior. If this value is NULL, it means the behavior + * implementation is currently disabled for the field instance + * - entity_type: (string) Entity type to which this behavior implementation + * applies + * - bundle: (string) Bundle name to which this behavior implementation + * applies + * - field_name: (string) Name of a field to which this behavior + * implementation applies + * - instance_id: (int) ID of the instance to which this behavior + * implementation applies + */ +function synonyms_behavior_get($behavior, $entity_type = NULL, $bundle = NULL, $only_enabled = FALSE, $include_deleted = FALSE) { + $behavior_implementation_info = synonyms_behavior_implementation_info($behavior); + $supported_field_types = array_keys($behavior_implementation_info); + + if (empty($supported_field_types)) { + return array(); + } + + $query = db_select('field_config_instance', 'i'); + $field_alias = $query->innerJoin('field_config', 'f', 'f.id = i.field_id'); + $query->condition($field_alias . '.type', $supported_field_types); + if ($entity_type) { + $query->condition('i.entity_type', $entity_type); + } + if ($bundle) { + $query->condition('i.bundle', $bundle); + } + if (!$include_deleted) { + $query->condition('i.deleted', 0); + } + + $settings_alias = $query->addJoin($only_enabled ? 'INNER' : 'LEFT OUTER', 'synonyms_settings', 's', 's.instance_id = i.id AND s.behavior = :behavior', array( + ':behavior' => $behavior, + )); + $query->fields($settings_alias, array('behavior', 'settings_serialized')); + + $query->fields('i', array('entity_type', 'bundle', 'field_name')); + $query->addField('i', 'id', 'instance_id'); + $result = $query->execute(); + $result = $result->fetchAllAssoc('instance_id', PDO::FETCH_ASSOC); + + return synonyms_behavior_settings_unpack($result); +} + +/** + * Retrieve information about all ctools plugins of type 'synonyms behavior'. + * + * @return array + * Array of information on all available synonyms behavior plugins + */ +function synonyms_behaviors() { + ctools_include('plugins'); + return ctools_get_plugins('synonyms', 'behavior'); +} + +/** + * Fetch information about synonyms behaviors implementations per field type. + * + * Fetch the map between field types and the PHP classes that implement synonyms + * behaviors for them. + * + * @param string $behavior + * What specific behavior is queried. Supply here keys from the return of + * synonyms_behaviors() function + * + * @return array + * Array of information about what field types implement the provided behavior + * through what PHP classes. Keys of this array are field types, whereas their + * values are names of PHP classes that implement the provided behavior for + * that particular field type + */ +function synonyms_behavior_implementation_info($behavior) { + $info = module_invoke_all('synonyms_behavior_implementation_info', $behavior); + drupal_alter('synonyms_behavior_implementation_info', $info, $behavior); + return $info; +} + +/** + * Determine what PHP class implements specific behavior for specific field. + * + * @param string $behavior + * Name of the behavior, implementation of which is requested. It should be + * one of the keys of the return of synonyms_behaviors() function. + * @param array $field + * Field definition array for which PHP class implementing $behavior is + * requested + * + * @return string + * Name of the PHP class that implements $behavior for $field field + */ +function synonyms_behavior_implementation_class($behavior, $field) { + $map = synonyms_behavior_implementation_info($behavior); + return $map[$field['type']]; +} + +/** + * Execute unpacking operation on the just loaded synonyms behavior settings. + * + * @param array $settings + * Array of the just loaded settings. Each sub array should contain the + * following keys: + * - instance_id: (int) ID of the instance to which it applies + * - behavior: (string) name of the synonyms behavior to which these settings + * apply + * - settings_serialized: (string) serialized content of the settings + * + * @return array + * Unpacked version of the provided $settings + */ +function synonyms_behavior_settings_unpack($settings) { + foreach ($settings as &$setting) { + $setting['settings'] = $setting['settings_serialized'] ? unserialize($setting['settings_serialized']) : NULL; + } + return $settings; +} + +/** + * Save the provided synonyms behavior settings into the database. + * + * @param array $settings + * Array of settings. It must have the following structure: + * - instance_id: (int) ID of the instance to which it applies + * - behavior: (string) name of the synonyms behavior to which it applies + * - settings: (mixed) the content of settings themselves + */ +function synonyms_behavior_settings_save($settings) { + if (!isset($settings['settings'])) { + $settings['settings'] = array(); + } + $settings['settings_serialized'] = serialize($settings['settings']); + $result = db_merge('synonyms_settings') + ->key(array( + 'instance_id' => $settings['instance_id'], + 'behavior' => $settings['behavior'], + )) + ->fields(array( + 'instance_id' => $settings['instance_id'], + 'behavior' => $settings['behavior'], + 'settings_serialized' => $settings['settings_serialized'], + )) + ->execute(); + + switch ($result) { + case MergeQuery::STATUS_INSERT: + $behavior_definition = synonyms_behaviors(); + $behavior_definition = $behavior_definition[$settings['behavior']]; + $enabled_callback = ctools_plugin_get_function($behavior_definition, 'enabled callback'); + if ($enabled_callback) { + $enabled_callback($behavior_definition, $settings['settings'], synonyms_instance_id_load($settings['instance_id'])); + } + break; + } +} + +/** + * Delete settings for specific behavior and field instance. + * + * @param int $instance_id + * ID of the instance for which settings should be removed + * @param string $behavior + * Name of behavior for which settings should be removed + */ +function synonyms_behavior_settings_delete($instance_id, $behavior) { + $behavior_definition = synonyms_behaviors(); + $behavior_definition = $behavior_definition[$behavior]; + $disabled_callback = ctools_plugin_get_function($behavior_definition, 'disabled callback'); + if ($disabled_callback) { + $instance = synonyms_instance_id_load($instance_id); + $behavior_implementation = synonyms_behavior_get($behavior, $instance['entity_type'], $instance['bundle'], FALSE, TRUE); + $behavior_implementation = $behavior_implementation[$instance_id]; + $disabled_callback($behavior_definition, $behavior_implementation, $instance); + } + db_delete('synonyms_settings') + ->condition('instance_id', $instance_id) + ->condition('behavior', $behavior) + ->execute(); +} + +/** + * Supportive function to load a field instance by its ID. + * + * @param int $instance_id + * ID of the instance that should be loaded + * + * @return array + * Instance definition array + */ +function synonyms_instance_id_load($instance_id) { + $result = db_select('field_config_instance', 'i') + ->fields('i', array('entity_type', 'bundle', 'field_name')) + ->condition('id', $instance_id) + ->execute() + ->fetchAssoc(); + return field_info_instance($result['entity_type'], $result['field_name'], $result['bundle']); +} + +/** + * Convert synonyms friendly select widget values for storage friendly format. + * + * It acts similar to what the _options_form_to_storage() function does - + * bridges between how values are returned from form API to how they are + * expected by Field module. + */ +function synonyms_select_form_to_storage($element, &$form_state) { + $value = array(); + if ($element['#multiple']) { + $value = $element['#value']; + } + else { + $value[] = $element['#value']; + } + + foreach ($value as $k => $v) { + // For the cases when a synonym was selected and not a term option, we + // process the selected values stripping everything that goes after + // semicolon. + if (!is_numeric($v)) { + $tid = explode(':', $v); + $value[$k] = $tid[0]; + } + } + + // The user also might have selected multiple times the same term, given that + // a term can be represented by more than 1 option (a term and its synonym), + // then it's possible in theory, so we should be ready for this scenario. + $value = array_unique($value); + + $form_state_value = array(); + foreach ($value as $tid) { + $form_state_value[] = array('tid' => $tid); + } + + form_set_value($element, $form_state_value, $form_state); +} + +/** + * Check whether a taxonomy term $tid is a child of a taxonomy term $parent_tid. + * + * Supportive function, used throughout this module for parent constrains. + * + * @param int $tid + * {taxonomy_term}.tid of the term that is tested for being a child of the + * $parent_tid term + * @param int $parent_tid + * {taxonomy_term}.tid of the term that is tested for being parent of the $tid + * term + * + * @return bool + * Whether $tid is a child of $parent_tid + */ +function synonyms_taxonomy_term_is_child_of($tid, $parent_tid) { + $term_parents = taxonomy_get_parents_all($tid); + + // Dropping out the term itself from its array of parents. + array_shift($term_parents); + + foreach ($term_parents as $term_parent) { + if ($term_parent->tid == $parent_tid) { + return TRUE; + } + } + + return FALSE; +} + +/** + * Format an option for select form element. + * + * @param object $term + * Fully loaded taxonomy term which is represented by this option + * @param string $synonym + * If the provided term is represented in this option by a synonym, then + * provide it here + * @param array $behavior_implementation + * Behavior implementation array from which the $synonym comes from + * + * @return object + * An option for select form element + */ +function synonyms_select_option($term, $synonym = NULL, $behavior_implementation = NULL) { + $key = $synonym ? $term->tid . ':' . drupal_html_class($synonym) : $term->tid; + $wording = $term->name; + if ($synonym) { + $instance = field_info_instance($behavior_implementation['entity_type'], $behavior_implementation['field_name'], $behavior_implementation['bundle']); + $wording = format_string($behavior_implementation['settings']['wording'], array( + '@synonym' => $synonym, + '@term' => $term->name, + '@field_name' => drupal_strtolower($instance['label']), + )); + } + return (object) array( + 'option' => array($key => str_repeat('-', $term->depth) . $wording), + ); +} + +/** + * Supportive function to build options array with sorting by name logic. + * + * The function starts from the 0-depth level and starts to recursively build + * the options and to sort the labels on each level, then it merges the bottom + * to up all the levels maintaining correct order within the final options + * array. + * + * @param object $vocabulary + * Within which vocabulary to execute the function. Supply here the fully + * loaded taxonomy vocabulary object + * @param int $parent + * Only children of this term will be included in the output. You can supply + * 0 which means to include all the terms from the vocabulary + * @param int $depth + * Used for internal purposes. Clients of this function should supply here 0, + * unless they know what they are doing. It is used internally to keep track + * of the nesting level + * + * @return array + * Array of options that can be inserted directly into 'select' form element. + * The options will be sorted by name (term or synonym), respecting the + * hierarchy restrictions + */ +function synonyms_select_sort_name_options_recursive($vocabulary, $parent = 0, $depth = 0) { + // We statically cache behavior implementations in order to not DDOS the data + // base. + $behavior_implementations = drupal_static(__FUNCTION__, array()); + + $bundle = field_extract_bundle('taxonomy_term', $vocabulary); + if (!isset($behavior_implementations[$bundle])) { + $behavior_implementations[$bundle] = synonyms_behavior_get('select', 'taxonomy_term', $bundle, TRUE); + } + + $options = array(); + if ($terms = taxonomy_get_tree($vocabulary->vid, $parent, 1, TRUE)) { + $options = array(); + foreach ($terms as $term) { + $term->depth = $depth; + $options[] = synonyms_select_option($term); + foreach ($behavior_implementations[$bundle] as $implementation) { + foreach (synonyms_extract_synonyms($term, $implementation) as $synonym) { + $options[] = synonyms_select_option($term, $synonym, $implementation); + } + } + } + + usort($options, 'synonyms_select_sort_name'); + + // Now recursively go one level nested into each of the terms that we have + // on this level. + $options_copy = $options; + $i = 0; + foreach ($options_copy as $v) { + $i++; + $tid = array_keys($v->option); + $tid = $tid[0]; + if (is_numeric($tid)) { + $nested_options = synonyms_select_sort_name_options_recursive($vocabulary, $tid, $depth + 1); + $options = array_merge(array_slice($options, 0, $i), $nested_options, array_slice($options, $i)); + } + } + } + return $options; +} + +/** + * Supportive function. + * + * It is used for string comparison within synonyms friendly select widget. + */ +function synonyms_select_sort_name($a, $b) { + return strcasecmp(reset($a->option), reset($b->option)); +} diff --git a/sites/all/modules/contrib/taxonomy/synonyms/synonyms.pages.inc b/sites/all/modules/contrib/taxonomy/synonyms/synonyms.pages.inc index bae61219..04e6ec2c 100644 --- a/sites/all/modules/contrib/taxonomy/synonyms/synonyms.pages.inc +++ b/sites/all/modules/contrib/taxonomy/synonyms/synonyms.pages.inc @@ -56,18 +56,31 @@ function synonyms_autocomplete($field_name, $entity_type, $bundle, $tags_typed = exit; } + $widget = $instance['widget']['type'] == 'synonyms_autocomplete' ? $instance['widget']['settings'] : field_info_widget_settings('synonyms_autocomplete'); + // How many suggestions maximum we are able to output. - $max_suggestions = $instance['widget']['settings']['suggestion_size']; + $max_suggestions = $widget['suggestion_size']; // Whether we are allowed to suggest more than one entry per term, shall that // entry be either term name itself or one of its synonyms. - $suggest_only_unique = $instance['widget']['settings']['suggest_only_unique']; + $suggest_only_unique = $widget['suggest_only_unique']; // The user enters a comma-separated list of tags. We only autocomplete the // last tag. $tags_typed = drupal_explode_tags($tags_typed); $tag_last = drupal_strtolower(array_pop($tags_typed)); + $tags_typed_tids = array(); + if (!empty($tags_typed)) { + $efq = new EntityFieldQuery(); + $efq->entityCondition('entity_type', 'taxonomy_term'); + $efq->propertyCondition('name', $tags_typed); + $tags_typed_tids = $efq->execute(); + if (isset($tags_typed_tids['taxonomy_term'])) { + $tags_typed_tids = array_keys($tags_typed_tids['taxonomy_term']); + } + } + $term_matches = array(); if ($tag_last != '') { // Part of the criteria for the query come from the field's own settings. @@ -79,14 +92,11 @@ function synonyms_autocomplete($field_name, $entity_type, $bundle, $tags_typed = $vocabularies = taxonomy_vocabulary_load_multiple(array_keys($vocabularies)); // Array of found suggestions. Each subarray of this array will represent - // a single suggestion entry. The sub array must at least contain the - // following keys: - // tid - tid of the suggested term - // name - name of the suggested term - // And optionally, if the term is suggested based on its synonym, the sub - // array should also include the additional key: - // synonym - string representation of the synonym based on which the - // suggested term was included. + // a single suggestion entry. The sub array must contain the following keys: + // - tid: (int) tid of the suggested term + // - name: (string) name of the suggested term + // - wording: (string) human friendly XSS escaped text of the suggestion + // entry $tags_return = array(); // Firstly getting a list of tids that match by $term->name. @@ -95,8 +105,8 @@ function synonyms_autocomplete($field_name, $entity_type, $bundle, $tags_typed = $query->addTag('term_access'); // Do not select already entered terms. - if (!empty($tags_typed)) { - $query->condition('t.name', $tags_typed, 'NOT IN'); + if (!empty($tags_typed_tids)) { + $query->condition('t.tid', $tags_typed_tids, 'NOT IN'); } // Select rows that match by term name. $result = $query @@ -104,78 +114,67 @@ function synonyms_autocomplete($field_name, $entity_type, $bundle, $tags_typed = ->condition('t.vid', array_keys($vocabularies)) ->condition('t.name', '%' . db_like($tag_last) . '%', 'LIKE') ->range(0, $max_suggestions) - ->execute() - ->fetchAllKeyed(); - - // Converting results into another format. - foreach ($result as $tid => $name) { - $tags_return[] = array( - 'name' => $name, - 'tid' => $tid, - ); + ->execute(); + foreach ($result as $v) { + $v = (array) $v; + $v['wording'] = check_plain($v['name']); + $tags_return[] = $v; } - $synonym_tids = array(); // Now we go vocabulary by vocabulary looking through synonym fields. foreach ($vocabularies as $vocabulary) { // Now we go a synonym field by synonym field gathering suggestions. - // Since officially EntityFieldQuery doesn't support OR conditions - // we are enforced to go a field by field querying multiple times the DB. $bundle = field_extract_bundle('taxonomy_term', $vocabulary); - foreach (synonyms_synonyms_fields($vocabulary) as $field) { - $field = field_info_field($field); - $instance = field_info_instance('taxonomy_term', $field['field_name'], $bundle); - $query = new EntityFieldQuery(); - $query->entityCondition('entity_type', 'taxonomy_term') - ->entityCondition('bundle', $bundle); - // We let the class that defines this field type as a source of synonyms - // filter out and provide its suggestions based on this field. - $class = synonyms_extractor_info($field['type']); - call_user_func(array($class, 'processEntityFieldQuery'), $tag_last, $query, $field, $instance); + $behavior_implementations = synonyms_behavior_get('autocomplete', 'taxonomy_term', $bundle, TRUE); + foreach ($behavior_implementations as $implementation) { + $condition = db_and(); + $condition->condition(AbstractSynonymsSynonymsBehavior::COLUMN_PLACEHOLDER, '%' . db_like($tag_last) . '%', 'LIKE'); - if (!empty($tags_typed)) { - $query->propertyCondition('name', $tags_typed, 'NOT IN'); + if (!empty($tags_typed_tids)) { + $condition->condition('entity_id', $tags_typed_tids, 'NOT IN'); } if ($suggest_only_unique && !empty($tags_return)) { $tmp = array(); foreach ($tags_return as $tag_return) { $tmp[] = $tag_return['tid']; } - // We don't want to search among the terms already found by term name. - $query->entityCondition('entity_id', $tmp, 'NOT IN'); + $condition->condition('entity_id', $tmp, 'NOT IN'); } - if ($suggest_only_unique && !empty($synonym_tids)) { - // We also don't want to search among the terms already matched by - // previous synonym fields. - $query->entityCondition('entity_id', $synonym_tids, 'NOT IN'); - } - $tmp = $query->execute(); - if (!empty($tmp)) { - // Merging the results. - $tmp = array_keys($tmp['taxonomy_term']); - $synonym_tids = array_merge($synonym_tids, $tmp); + + $new_tids = array(); + foreach (synonyms_synonyms_find_behavior($condition, $implementation) as $synonym) { + if (!$suggest_only_unique || !in_array($synonym->entity_id, $new_tids)) { + $tags_return[] = array( + 'tid' => $synonym->entity_id, + 'name' => '', + 'synonym' => $synonym->synonym, + 'implementation' => $implementation, + ); + $new_tids[] = $synonym->entity_id; + } } } } - if (!empty($synonym_tids)) { - foreach (taxonomy_term_load_multiple($synonym_tids) as $synonym_term) { - $tmp = array( - 'name' => $synonym_term->name, - 'tid' => $synonym_term->tid, - ); - // Additionally we have to find out which synonym triggered inclusion of - // this term. - foreach (synonyms_get_sanitized($synonym_term) as $item) { - if (strpos(mb_strtoupper($item, 'UTF-8'), mb_strtoupper($tag_last, 'UTF-8')) !== FALSE) { - $tags_return[] = array('synonym' => $item) + $tmp; - if ($suggest_only_unique) { - // We just want to output 1 single suggestion entry per term, so - // one synonym is enough. - break; - } - } + $synonym_terms = array(); + foreach ($tags_return as $v) { + if (isset($v['synonym'])) { + $synonym_terms[] = $v['tid']; + } + } + + if (!empty($synonym_terms)) { + $synonym_terms = taxonomy_term_load_multiple($synonym_terms); + foreach ($tags_return as &$v) { + if (isset($v['synonym'])) { + $instance = field_info_instance($v['implementation']['entity_type'], $v['implementation']['field_name'], $v['implementation']['bundle']); + $v['name'] = $synonym_terms[$v['tid']]->name; + $v['wording'] = format_string(filter_xss($v['implementation']['settings']['wording']), array( + '@synonym' => $v['synonym'], + '@term' => $v['name'], + '@field_name' => drupal_strtolower($instance['label']), + )); } } } @@ -192,98 +191,50 @@ function synonyms_autocomplete($field_name, $entity_type, $bundle, $tags_typed = if (strpos($info['name'], ',') !== FALSE || strpos($info['name'], '"') !== FALSE) { $n = '"' . str_replace('"', '""', $info['name']) . '"'; } - - if (isset($info['synonym'])) { - $display_name = t('@synonym, synonym of %term', array('@synonym' => $info['synonym'], '%term' => $info['name'])); - } - else { - $display_name = check_plain($info['name']); - } while (isset($term_matches[$prefix . $n])) { $n .= ' '; } - $term_matches[$prefix . $n] = $display_name; + $term_matches[$prefix . $n] = $info['wording']; } } drupal_json_output($term_matches); } /** - * Mark nodes that reference specific terms for search re-indexing. - * - * This is particularly useful, when the terms in question have been changed. - * - * @param $tids array - * Array of tids of the terms + * Default theme implementation for behavior settings form element. */ -function synonyms_reindex_nodes_by_terms($tids) { - // In order to speed up the process, we will query DB for nid's that reference - // handled to us tids, and at the end we'll trigger their re-indexing in just - // a single SQL query. Probably it is better to use search_touch_node(), but - // that would imply a big amount of SQL queries on some websites. - $found_nids = array(); - foreach (field_info_field_map() as $field_name => $v) { - if ($v['type'] == 'taxonomy_term_reference' && isset($v['bundles']['node'])) { - // This field is taxonomy term reference and it is attached to nodes, so - // we will run EntityFieldQuery on it. - $query = new EntityFieldQuery(); - $result = $query->entityCondition('entity_type', 'node') - ->fieldCondition($field_name, 'tid', $tids, 'IN') - ->execute(); - if (isset($result['node'])) { - $found_nids = array_merge($found_nids, array_keys($result['node'])); +function theme_synonyms_behaviors_settings($variables) { + drupal_add_css(drupal_get_path('module', 'synonyms') . '/synonyms.css'); + + $element = &$variables['element']; + + $table = array( + 'header' => array(t('Field')), + 'rows' => array(), + 'empty' => t('Seems like there are no fields for which synonyms functionality is available. Try adding a text field to get started.'), + ); + + $instance_ids = array(); + foreach (element_children($element) as $behavior) { + $table['header'][] = check_plain($element[$behavior]['#title']); + $instance_ids = array_unique(array_merge($instance_ids, element_children($element[$behavior]))); + } + + foreach ($instance_ids as $instance_id) { + $row = array(); + $row_title = ''; + foreach (element_children($element) as $behavior) { + if (isset($element[$behavior][$instance_id]['#title']) && !$row_title) { + $row_title = check_plain($element[$behavior][$instance_id]['#title']); } + $row[] = array( + 'data' => isset($element[$behavior][$instance_id]) ? drupal_render($element[$behavior][$instance_id]) : t('Not implemented'), + 'class' => array('synonyms-behavior-settings', 'synonyms-behavior-settings-' . $behavior), + ); } + array_unshift($row, $row_title); + $table['rows'][] = $row; } - if (!empty($found_nids)) { - db_update('search_dataset') - ->fields(array('reindex' => REQUEST_TIME)) - ->condition('type', 'node') - ->condition('sid', $found_nids, 'IN') - ->execute(); - } -} - -/** - * Mark nodes that reference terms from a vocabulary for search re-indexing. - * - * @param $vocabulary object - * Fully loaded vocabulary object - */ -function synonyms_reindex_nodes_by_vocabulary($vocabulary) { - $tids = db_select('taxonomy_term_data', 't') - ->fields('t', array('tid')) - ->condition('vid', $vocabulary->vid) - ->execute() - ->fetchCol(); - if (!empty($tids)) { - synonyms_reindex_nodes_by_terms($tids); - } -} - -/** - * Submit handler for Taxonomy vocabulary edit form. - * - * Store extra values attached to form in this module. - */ -function synonyms_taxonomy_form_vocabulary_submit($form, &$form_state) { - $values = $form_state['values']; - - if ($values['op'] == $form['actions']['submit']['#value']) { - if (isset($form['#vocabulary']->vid)) { - $vocabulary = taxonomy_vocabulary_load($form['#vocabulary']->vid); - } - else { - // As a fallback, if this is a just created vocabulary, we try to pull it - // up by the just submitted machine name. - $vocabulary = taxonomy_vocabulary_machine_name_load($values['machine_name']); - } - - // Preprocessing values keeping only field names of the checked fields. - $values['synonyms']['synonyms'] = array_values(array_filter($values['synonyms']['synonyms'])); - - $settings = synonyms_vocabulary_settings($vocabulary); - $settings = $values['synonyms'] + $settings; - synonyms_vocabulary_settings_save($vocabulary, $settings); - } + + return '
' . theme('table', $table) . drupal_render_children($element) . '
'; } diff --git a/sites/all/modules/contrib/taxonomy/synonyms/synonyms.test b/sites/all/modules/contrib/taxonomy/synonyms/synonyms.test index 22cb905c..296929a2 100644 --- a/sites/all/modules/contrib/taxonomy/synonyms/synonyms.test +++ b/sites/all/modules/contrib/taxonomy/synonyms/synonyms.test @@ -17,11 +17,67 @@ abstract class SynonymsWebTestCase extends DrupalWebTestCase { */ protected $admin; + /** + * Taxonomy vocabulary within which the whole testing happens. + * + * @var object + */ + protected $vocabulary; + + /** + * Text fields that can be used for general purpose testing of behaviors. + * + * The var is firstly keyed by either 'enabled' or 'disabled', correspondingly + * representing whether the behavior is enabled or disabled on that field. And + * the inner array has 2 keys: + * - field: (array) field definition array + * - instance: (array) instance definition array + * + * @var array + */ + protected $fields = array( + 'enabled' => array( + 'field' => array( + 'field_name' => 'text_enabled', + 'type' => 'text', + 'cardinality' => FIELD_CARDINALITY_UNLIMITED, + ), + 'instance' => array( + 'entity_type' => 'taxonomy_term', + ), + ), + 'disabled' => array( + 'field' => array( + 'field_name' => 'text_disabled', + 'type' => 'text', + 'cardinality' => FIELD_CARDINALITY_UNLIMITED, + ), + 'instance' => array( + 'entity_type' => 'taxonomy_term', + ), + ), + ); + + /** + * Name of a synonyms behavior that is being tested. + * + * @var string + */ + protected $behavior; + + /** + * Settings for the behavior that is being tested. + * + * @var array + */ + protected $behavior_settings = array(); + /** * SetUp method. */ - public function setUp() { - parent::setUp(array('synonyms')); + public function setUp($modules = array()) { + $modules[] = 'synonyms'; + parent::setUp($modules); $this->admin = $this->drupalCreateUser(array( 'administer taxonomy', @@ -29,6 +85,35 @@ abstract class SynonymsWebTestCase extends DrupalWebTestCase { 'bypass node access', 'search content', )); + + // Creating vocabularies. + $this->drupalLogin($this->admin); + $this->vocabulary = (object) array( + 'name' => $this->randomName(), + 'machine_name' => 'synonyms_test', + 'description' => $this->randomName(), + ); + taxonomy_vocabulary_save($this->vocabulary); + + $this->fields['enabled']['field'] = field_create_field($this->fields['enabled']['field']); + $this->fields['enabled']['field'] = field_info_field($this->fields['enabled']['field']['field_name']); + $this->fields['enabled']['instance']['bundle'] = $this->vocabulary->machine_name; + $this->fields['enabled']['instance']['field_name'] = $this->fields['enabled']['field']['field_name']; + $this->fields['enabled']['instance'] = field_create_instance($this->fields['enabled']['instance']); + $this->fields['enabled']['instance'] = field_info_instance($this->fields['enabled']['instance']['entity_type'], $this->fields['enabled']['instance']['field_name'], $this->fields['enabled']['instance']['bundle']); + + $this->fields['disabled']['field'] = field_create_field($this->fields['disabled']['field']); + $this->fields['disabled']['field'] = field_info_field($this->fields['disabled']['field']['field_name']); + $this->fields['disabled']['instance']['bundle'] = $this->vocabulary->machine_name; + $this->fields['disabled']['instance']['field_name'] = $this->fields['disabled']['field']['field_name']; + $this->fields['disabled']['instance'] = field_create_instance($this->fields['disabled']['instance']); + $this->fields['disabled']['instance'] = field_info_instance($this->fields['disabled']['instance']['entity_type'], $this->fields['disabled']['instance']['field_name'], $this->fields['disabled']['instance']['bundle']); + + synonyms_behavior_settings_save(array( + 'instance_id' => $this->fields['enabled']['instance']['id'], + 'behavior' => $this->behavior, + 'settings' => $this->behavior_settings, + )); } /** @@ -42,18 +127,11 @@ abstract class SynonymsWebTestCase extends DrupalWebTestCase { * the specified vocabulary */ protected function getLastTerm($vocabulary) { - drupal_static_reset(); - $tree = taxonomy_get_tree($vocabulary->vid); - $max = 0; - $term = NULL; - foreach ($tree as $v) { - if ($v->tid > $max) { - $max = $v->tid; - $term = $v; - } - } - $term = entity_load_unchanged('taxonomy_term', $term->tid); - return $term; + $tid = db_select('taxonomy_term_data', 't'); + $tid->addExpression('MAX(t.tid)'); + $tid->condition('vid', $vocabulary->vid); + $tid = $tid->execute()->fetchField(); + return entity_load_unchanged('taxonomy_term', $tid); } } @@ -62,64 +140,19 @@ abstract class SynonymsWebTestCase extends DrupalWebTestCase { */ class SynonymsSynonymsWebTestCase extends SynonymsWebTestCase { - protected $vocabularies = array( - 'enabled' => TRUE, - 'disabled' => FALSE, - ); + protected $behavior = 'synonyms'; /** * GetInfo method. */ - public function getInfo() { + public static function getInfo() { return array( 'name' => 'Taxonomy synonyms', - 'description' => 'Ensure that the feature "synonyms" works correctly with taxonomy terms.', + 'description' => 'Ensure that the general "synonyms" behavior works correctly.', 'group' => 'Synonyms', ); } - /** - * SetUp method. - */ - public function setUp() { - parent::setUp(); - - // Creating vocabularies. - $this->drupalLogin($this->admin); - foreach ($this->vocabularies as $k => $v) { - $name = $this->randomName(); - $this->drupalPost('admin/structure/taxonomy/add', array( - 'name' => $name, - 'machine_name' => $k, - 'description' => $this->randomName(), - 'synonyms[synonyms][' . SYNONYMS_DEFAULT_FIELD_NAME . ']' => $v, - ), 'Save'); - $this->vocabularies[$k] = taxonomy_vocabulary_machine_name_load($k); - } - // Flushing cache. - _field_info_collate_fields(TRUE); - } - - /** - * Test the disabled taxonomy synonyms feature. - */ - public function testSynonymsDisabled() { - $this->drupalGet('admin/structure/taxonomy/disabled/add'); - $this->assertNoFieldById('edit-synonyms-synonyms-und-0-value'); - - $this->drupalGet('admin/structure/taxonomy/enabled/add'); - $this->assertFieldById('edit-synonyms-synonyms-und-0-value'); - - // Making sure that after disabling "synonyms" the synonyms field - // is no longer available on taxonomy term add page. - $this->drupalPost('admin/structure/taxonomy/enabled/edit', array( - 'synonyms[synonyms][' . SYNONYMS_DEFAULT_FIELD_NAME . ']' => FALSE, - ), 'Save'); - - $this->drupalGet('admin/structure/taxonomy/enabled/add'); - $this->assertNoFieldById('edit-synonyms-synonyms-und-0-value'); - } - /** * Test the functionality of synonyms. */ @@ -131,49 +164,75 @@ class SynonymsSynonymsWebTestCase extends SynonymsWebTestCase { // Creating terms for testing synonyms_get_term_synonyms(). $synonym1 = $this->randomName(); $synonym2 = $this->randomName(); - $this->drupalPost('admin/structure/taxonomy/enabled/add', array( - 'name' => $this->randomName(), - 'description[value]' => $this->randomName(), - ), 'Save'); - $no_synonyms_term = $this->getLastTerm($this->vocabularies['enabled']); - $this->drupalPost('admin/structure/taxonomy/enabled/add', array( - 'name' => $this->randomName(), - SYNONYMS_DEFAULT_FIELD_NAME . '[' . LANGUAGE_NONE . '][0][value]' => $synonym1, - ), 'Save'); - $one_synonym_term = $this->getLastTerm($this->vocabularies['enabled']); - $this->drupalPost('admin/structure/taxonomy/enabled/add', array( - 'name' => $this->randomName(), - SYNONYMS_DEFAULT_FIELD_NAME . '[' . LANGUAGE_NONE . '][0][value]' => $synonym1, - ), 'Add another item'); - $this->drupalPost(NULL, array( - SYNONYMS_DEFAULT_FIELD_NAME . '[' . LANGUAGE_NONE . '][1][value]' => $synonym2, - ), 'Save'); - $two_synonyms_term = $this->getLastTerm($this->vocabularies['enabled']); - // Creating an identical parent term in order to test - // $parent parameter in our functions. - $this->drupalPost('admin/structure/taxonomy/enabled/add', array( + $no_synonyms_term = (object) array( + 'vid' => $this->vocabulary->vid, + 'name' => $this->randomName(), + 'description' => $this->randomName(), + $this->fields['disabled']['field']['field_name'] => array( + LANGUAGE_NONE => array(array('value' => $this->randomName())), + ), + ); + taxonomy_term_save($no_synonyms_term); + + $one_synonym_term = (object) array( + 'vid' => $this->vocabulary->vid, + 'name' => $this->randomName(), + $this->fields['enabled']['field']['field_name'] => array( + LANGUAGE_NONE => array(array('value' => $synonym1)), + ), + $this->fields['disabled']['field']['field_name'] => array( + LANGUAGE_NONE => array(array('value' => $this->randomName())), + ), + ); + taxonomy_term_save($one_synonym_term); + + $two_synonyms_term = (object) array( + 'vid' => $this->vocabulary->vid, + 'name'=> $this->randomName(), + $this->fields['enabled']['field']['field_name'] => array( + LANGUAGE_NONE => array( + array('value' => $synonym1), + array('value' => $synonym2), + ), + ), + $this->fields['disabled']['field']['field_name'] => array( + LANGUAGE_NONE => array(array('value' => $this->randomName())), + ), + ); + taxonomy_term_save($two_synonyms_term); + + // Creating an identical parent term in order to test $parent parameter in + // our functions. + $term_parent = (object) array( + 'vid' => $this->vocabulary->vid, 'name' => $name, - 'description[value]' => $this->randomName(), - SYNONYMS_DEFAULT_FIELD_NAME . '[' . LANGUAGE_NONE . '][0][value]' => $synonym, - ), 'Add another item'); - $this->drupalPost(NULL, array( - SYNONYMS_DEFAULT_FIELD_NAME . '[' . LANGUAGE_NONE . '][1][value]' => $parent_synonym, - ), 'Save'); - $term_parent = $this->getLastTerm($this->vocabularies['enabled']); + $this->fields['enabled']['field']['field_name'] => array( + LANGUAGE_NONE => array( + array('value' => $synonym), + array('value' => $parent_synonym), + ), + ), + $this->fields['disabled']['field']['field_name'] => array( + LANGUAGE_NONE => array(array('value' => $this->randomName())), + ), + ); + taxonomy_term_save($term_parent); - $this->drupalPost('admin/structure/taxonomy/enabled/add', array( + $term = (object) array( + 'vid' => $this->vocabulary->vid, 'name' => $name, - 'description[value]' => $this->randomName(), - SYNONYMS_DEFAULT_FIELD_NAME . '[' . LANGUAGE_NONE . '][0][value]' => $synonym, - 'parent[]' => array($term_parent->tid), - ), 'Save'); - - $term = $this->getLastTerm($this->vocabularies['enabled']); - - $this->drupalGet('taxonomy/term/' . $term->tid); - // Asserting the presence of synonym string. - $this->assertText($synonym, 'The synonym string is present on taxonomy term view page'); + 'parent' => $term_parent->tid, + $this->fields['enabled']['field']['field_name'] => array( + LANGUAGE_NONE => array( + array('value' => $synonym), + ), + ), + $this->fields['disabled']['field']['field_name'] => array( + LANGUAGE_NONE => array(array('value' => $this->randomName())), + ), + ); + taxonomy_term_save($term); // Testing the 'synonyms' property of 'taxonomy_term' entity. $synonyms = synonyms_get_sanitized($no_synonyms_term); @@ -184,65 +243,33 @@ class SynonymsSynonymsWebTestCase extends SynonymsWebTestCase { $this->assertTrue(count($synonyms) == 2 && $synonyms[0] == $synonym1 && $synonyms[1] == $synonym2, 'Successfully retrieved synonyms_get_sanitized() for a term with 2 synonyms.'); // Testing the function synonyms_get_term_by_synonym(). - $tid = synonyms_get_term_by_synonym(drupal_strtoupper($synonym), $this->vocabularies['enabled'], $term_parent->tid); + $tid = synonyms_get_term_by_synonym(drupal_strtoupper($synonym), $this->vocabulary, $term_parent->tid); $this->assertEqual($tid, $term->tid, 'Successfully looked up term by its synonym.'); - $tid = synonyms_get_term_by_synonym(drupal_strtoupper($name), $this->vocabularies['enabled'], $term_parent->tid); + $tid = synonyms_get_term_by_synonym(drupal_strtoupper($name), $this->vocabulary, $term_parent->tid); $this->assertEqual($tid, $term->tid, 'Successfully looked up term by its name.'); // Now submitting a non-existing name. - $tid = synonyms_get_term_by_synonym($parent_synonym, $this->vocabularies['enabled'], $term_parent->tid); - $this->assertEqual($tid, 0, 'synonyms_get_term_by_synonym() returns 0 if the term is not found (considering $parent parameter).'); + $tid = synonyms_get_term_by_synonym($parent_synonym, $this->vocabulary, $term_parent->tid); + $this->assertEqual($tid, 0, 'synonyms_get_term_by_synonym() returns 0 if the term is not found (due to $parent parameter).'); + + $tid = synonyms_get_term_by_synonym($term->{$this->fields['disabled']['field']['field_name']}[LANGUAGE_NONE][0]['value'], $this->vocabulary); + $this->assertEqual($tid, 0, 'synonyms_get_term_by_synonym() returns 0 if the term is not found (due to a field not being engaged in "synonyms" behavior).'); // Testing the function synonyms_add_term_by_synonym(). - $tid = synonyms_add_term_by_synonym(drupal_strtolower($name), $this->vocabularies['enabled'], $term_parent->tid); + $tid = synonyms_add_term_by_synonym(drupal_strtolower($name), $this->vocabulary, $term_parent->tid); $this->assertEqual($tid, $term->tid, 'Successfully called synonyms_add_term_by_synonym() on an existing title and no new term was created.'); - $tid = synonyms_add_term_by_synonym(drupal_strtolower($synonym), $this->vocabularies['enabled'], $term_parent->tid); + $tid = synonyms_add_term_by_synonym(drupal_strtolower($synonym), $this->vocabulary, $term_parent->tid); $this->assertEqual($tid, $term->tid, 'Successfully called synonyms_add_term_by_synonym() on an existing synonym and no new term was created.'); drupal_static_reset(); - $tid = synonyms_add_term_by_synonym($parent_synonym, $this->vocabularies['enabled'], $term_parent->tid); + $tid = synonyms_add_term_by_synonym($parent_synonym, $this->vocabulary, $term_parent->tid); $new_term = taxonomy_term_load($tid); $new_term_parents = array_keys(taxonomy_get_parents($new_term->tid)); - $this->assertEqual($parent_synonym, $new_term->name, 'Successfully called synonyms_add_term_by_synonym() on a new title and a new term was created.'); + $this->assertEqual($parent_synonym, $new_term->name, 'Successfully called synonyms_add_term_by_synonym() on a new title and a new term was created (due to parent restriction).'); $this->assertNotEqual($new_term->tid, $term_parent->tid, 'Successfully called synonyms_add_term_by_synonym() on a synonym of $parent. New term was created instead of returning $parent\'s tid.'); $this->assertTrue(in_array($term_parent->tid, $new_term_parents), 'Successfully called synonyms_add_term_by_synonym(). New term is assigned as a child to supplied $parent parameter.'); - // Disabling functionality of synonyms for "enabled" vocabulary - // and making sure it has cleaned up all its functionality. - $this->drupalPost('admin/structure/taxonomy/enabled/edit', array( - 'synonyms[synonyms][' . SYNONYMS_DEFAULT_FIELD_NAME . ']' => FALSE, - ), 'Save'); - - $this->drupalGet('taxonomy/term/' . $term->tid); - // Asserting the absence of synonym string. - $this->assertNoText($synonym, 'The synonym string is no longer present on taxonomy term view page after disabling "synonyms" feature for a vocabulary'); - $term = array_pop(entity_load('taxonomy_term', array($term->tid), array(), TRUE)); - $this->assertFalse(isset($term->{SYNONYMS_DEFAULT_FIELD_NAME}), 'The term no longer has synonyms field after disabling "synonyms" feature for a vocabulary'); - - // Testing the 'synonyms' property of 'taxonomy_term' entity. - $synonyms = synonyms_get_sanitized($no_synonyms_term); - $this->assertTrue(empty($synonyms), 'Successfully retrieved synonyms_get_sanitized() on a term without synonyms after disabling "synonyms" feature'); - $synonyms = synonyms_get_sanitized($one_synonym_term); - $this->assertTrue(empty($synonyms), 'Successfully retrieved synonyms_get_sanitized() on a term with a single synonym after disabling "synonyms" feature'); - $synonyms = synonyms_get_sanitized($two_synonyms_term); - $this->assertTrue(empty($synonyms), 'Successfully retrieved synonyms_get_sanitized() on a term with 2 synonyms after disabling "synonyms" feature'); - - $tid = synonyms_get_term_by_synonym(drupal_strtoupper($synonym), $this->vocabularies['enabled']); - $this->assertEqual($tid, 0, 'synonyms_get_term_by_synonym() returns 0 after disabling "synonyms" feature for a vocabulary'); - $tid = synonyms_get_term_by_synonym(drupal_strtoupper($name), $this->vocabularies['enabled'], $term_parent->tid); - $this->assertEqual($tid, $term->tid, 'synonyms_get_term_by_synonym() returns $term->tid even after disabling "synonyms" feature if looking up by term title'); - - // Testing synonyms_add_term_by_synonym() function. - $tid = synonyms_add_term_by_synonym(drupal_strtolower($name), $this->vocabularies['enabled'], $term_parent->tid); - $this->assertEqual($tid, $term->tid, 'Successfully called synonyms_add_term_by_synonym() on an existing title and no new term was created after disabling "synonyms" feature.'); - $tid = synonyms_add_term_by_synonym(drupal_strtolower($synonym), $this->vocabularies['enabled'], $term_parent->tid); + $tid = synonyms_add_term_by_synonym($term->{$this->fields['disabled']['field']['field_name']}[LANGUAGE_NONE][0]['value'], $this->vocabulary); $new_term = taxonomy_term_load($tid); - $new_term_parents = array_keys(taxonomy_get_parents($new_term->tid)); - $this->assertFalse(in_array($new_term->tid, array($term->tid, $term_parent->tid)), 'Successfully called synonyms_add_term_by_synonym() using previous synonym and a new term was created (because the vocabulary has disabled "synonyms" feature)'); - $this->assertTrue(in_array($term_parent->tid, $new_term_parents), 'Successfully called synonyms_add_term_by_synonym(). New term is assigned as a child to supplied $parent parameter with disabled "synonyms" feature.'); - - $tid = synonyms_add_term_by_synonym($parent_synonym, $this->vocabularies['enabled']); - $new_term = array_pop(entity_load('taxonomy_term', array($tid), array(), TRUE)); - $this->assertEqual($parent_synonym, $new_term->name, 'Successfully called synonyms_add_term_by_synonym() on an new title and a new term was created.'); - $this->assertFalse(in_array($new_term->tid, array($term->tid, $term_parent->tid)), 'Successfully called synonyms_add_term_by_synonym() on the synonyn of parent term. New term was created (because the vocabulary has disabled "synonyms" feature)'); + $this->assertNotEqual($new_term->tid, $term->tid, 'Successfully called synonyms_add_term_by_synonym() on a new title and a new term was created (due to a field not being engaged in "synonyms" behavior).'); } } @@ -251,30 +278,18 @@ class SynonymsSynonymsWebTestCase extends SynonymsWebTestCase { */ class AutocompleteSynonymsWebTestCase extends SynonymsWebTestCase { - /** - * Array of fully loaded vocabulary entities to be used in this test. - * - * Array is keyed by the corresponding vocabulary's machine name. - * - * @var array - */ - protected $vocabularies = array( - 'enabled' => TRUE, - 'disabled' => FALSE, + protected $behavior = 'autocomplete'; + + protected $behavior_settings = array( + 'wording' => '@synonym @field_name @term', ); /** * Array of fully loaded taxonomy term entities to be used in this test. * - * Term entities are grouped by machine name of the vocabulary to which they - * belong. - * * @var array */ - protected $terms = array( - 'enabled' => array(), - 'disabled' => array(), - ); + protected $terms = array(); /** * Entity type to which a term reference field with tested widget is attached. @@ -290,10 +305,18 @@ class AutocompleteSynonymsWebTestCase extends SynonymsWebTestCase { */ protected $bundle = 'synonyms_test_content'; + /** + * Field definition array of the field that will be attached to + * $this->entity_type with synonyms-friendly autocomplete widget. + * + * @var array + */ + protected $term_reference_field = array(); + /** * GetInfo method. */ - public function getInfo() { + public static function getInfo() { return array( 'name' => 'Taxonomy synonyms autocomplete', 'description' => 'Ensure that the "synonym friendly autocomplete" widget works correctly with taxonomy terms.', @@ -304,20 +327,8 @@ class AutocompleteSynonymsWebTestCase extends SynonymsWebTestCase { /** * SetUp method. */ - public function setUp() { - parent::setUp(); - // Creating vocabularies. - $this->drupalLogin($this->admin); - foreach ($this->vocabularies as $k => $v) { - $name = $this->randomName(); - $this->drupalPost('admin/structure/taxonomy/add', array( - 'name' => $name, - 'machine_name' => $k, - 'description' => $this->randomName(), - 'synonyms[synonyms][' . SYNONYMS_DEFAULT_FIELD_NAME . ']' => $v, - ), 'Save'); - $this->vocabularies[$k] = taxonomy_vocabulary_machine_name_load($k); - } + public function setUp($modules = array()) { + parent::setUp($modules); // Creating a test content type. $this->drupalPost('admin/structure/types/add', array( @@ -325,85 +336,177 @@ class AutocompleteSynonymsWebTestCase extends SynonymsWebTestCase { 'type' => $this->bundle, ), 'Save content type'); - // Attaching each vocabulary term reference field to the new content type. - foreach ($this->vocabularies as $k => $v) { - $this->drupalPost('admin/structure/types/manage/synonyms_test_content/fields', array( - 'fields[_add_new_field][label]' => 'Synonym Terms ' . $k, - 'fields[_add_new_field][field_name]' => 'synonyms_term_' . $k, - 'fields[_add_new_field][type]' => 'taxonomy_term_reference', - 'fields[_add_new_field][widget_type]' => 'synonyms_autocomplete', - ), 'Save'); - $this->drupalPost(NULL, array( - 'field[settings][allowed_values][0][vocabulary]' => $v->machine_name, - ), 'Save field settings'); - $this->drupalPost(NULL, array( - 'field[cardinality]' => FIELD_CARDINALITY_UNLIMITED, - ), 'Save settings'); - } - // Flushing static cache. - _field_info_collate_fields(TRUE); + $this->term_reference_field = array( + 'type' => 'taxonomy_term_reference', + 'field_name' => 'synonyms_term_enabled', + 'cardinality' => FIELD_CARDINALITY_UNLIMITED, + 'settings' => array( + 'allowed_values' => array( + array( + 'vocabulary' => $this->vocabulary->machine_name, + 'parent' => 0, + ), + ), + ), + ); + $this->term_reference_field = field_create_field($this->term_reference_field); - // Now creating taxonomy tree for each vocabulary. - // For synonym-disabled vocabulary just a few terms is good enough. + $instance = array( + 'field_name' => $this->term_reference_field['field_name'], + 'entity_type' => 'node', + 'bundle' => $this->bundle, + 'label' => 'Synonym Terms Autcomplete', + 'widget' => array( + 'type' => 'synonyms_autocomplete', + ), + ); + $instance = field_create_instance($instance); + + drupal_static_reset(); + + // Now creating taxonomy tree. $name = $this->randomName(); - $this->drupalPost('admin/structure/taxonomy/disabled/add', array( + + $term = (object) array( + 'vid' => $this->vocabulary->vid, 'name' => $name, - ), 'Save'); - $this->terms['disabled']['term1'] = $this->getLastTerm($this->vocabularies['disabled']); + $this->fields['disabled']['field']['field_name'] => array( + LANGUAGE_NONE => array( + array('value' => $this->randomName()), + ), + ), + ); + taxonomy_term_save($term); + $this->terms['term1'] = $term; + $name .= $this->randomName(); - $this->drupalPost('admin/structure/taxonomy/disabled/add', array( - 'name' => $name, - ), 'Save'); - $this->terms['disabled']['term1_longer_name'] = $this->getLastTerm($this->vocabularies['disabled']); - $this->drupalPost('admin/structure/taxonomy/disabled/add', array( - 'name' => $this->randomName(), - ), 'Save'); - $this->terms['disabled']['term2'] = $this->getLastTerm($this->vocabularies['disabled']); - // For synonym-enabled vocabulary we have to create such a set of input, - // that would cover all possible branches of the autocomplete callback. - $this->drupalPost('admin/structure/taxonomy/enabled/add', array( + $term = (object) array( + 'vid' => $this->vocabulary->vid, + 'name'=> $name, + $this->fields['disabled']['field']['field_name'] => array( + LANGUAGE_NONE => array( + array('value' => $this->randomName()), + ), + ), + ); + taxonomy_term_save($term); + $this->terms['term1_longer_name'] = $term; + + $term = (object) array( + 'vid' => $this->vocabulary->vid, 'name' => $this->randomName(), - ), 'Save'); - $this->terms['enabled']['no_synonyms'] = $this->getLastTerm($this->vocabularies['enabled']); - $this->drupalPost('admin/structure/taxonomy/enabled/add', array( + $this->fields['disabled']['field']['field_name'] => array( + LANGUAGE_NONE => array( + array('value' => $this->randomName()), + ), + ), + ); + taxonomy_term_save($term); + $this->terms['no_synonyms'] = $term; + + $term = (object) array( + 'vid' => $this->vocabulary->vid, 'name' => $this->randomName(), - SYNONYMS_DEFAULT_FIELD_NAME . '[' . LANGUAGE_NONE . '][0][value]' => $this->randomName(), - ), 'Save'); - $this->terms['enabled']['one_synonym'] = $this->getLastTerm($this->vocabularies['enabled']); - $this->drupalPost('admin/structure/taxonomy/enabled/add', array( + $this->fields['enabled']['field']['field_name'] => array( + LANGUAGE_NONE => array( + array('value' => $this->randomName()), + ), + ), + $this->fields['disabled']['field']['field_name'] => array( + LANGUAGE_NONE => array( + array('value' => $this->randomName()), + ), + ), + ); + taxonomy_term_save($term); + $this->terms['one_synonym'] = $term; + + $term = (object) array( + 'vid' => $this->vocabulary->vid, 'name' => $this->randomName(), - SYNONYMS_DEFAULT_FIELD_NAME . '[' . LANGUAGE_NONE . '][0][value]' => $this->randomName(), - ), 'Add another item'); - $this->drupalPost(NULL, array( - SYNONYMS_DEFAULT_FIELD_NAME . '[' . LANGUAGE_NONE . '][1][value]' => $this->randomName(), - ), 'Save'); - $this->terms['enabled']['two_synonyms'] = $this->getLastTerm($this->vocabularies['enabled']); + $this->fields['enabled']['field']['field_name'] => array( + LANGUAGE_NONE => array( + array('value' => $this->randomName()), + array('value' => $this->randomName()), + ), + ), + $this->fields['disabled']['field']['field_name'] => array( + LANGUAGE_NONE => array( + array('value' => $this->randomName()), + ), + ), + ); + taxonomy_term_save($term); + $this->terms['two_synonyms'] = $term; + $name = $this->randomName(); - $this->drupalPost('admin/structure/taxonomy/enabled/add', array( + $term = (object) array( + 'vid' => $this->vocabulary->vid, 'name' => $name, - SYNONYMS_DEFAULT_FIELD_NAME . '[' . LANGUAGE_NONE . '][0][value]' => $name . $this->randomName(), - ), 'Save'); - $this->terms['enabled']['name_similar_synonym'] = $this->getLastTerm($this->vocabularies['enabled']); + $this->fields['enabled']['field']['field_name'] => array( + LANGUAGE_NONE => array( + array('value' => $name . $this->randomName()), + ), + ), + $this->fields['disabled']['field']['field_name'] => array( + LANGUAGE_NONE => array( + array('value' => $this->randomName()), + ), + ), + ); + taxonomy_term_save($term); + $this->terms['name_similar_synonym'] = $term; + $name = 'similar_synonyms_'; - $this->drupalPost('admin/structure/taxonomy/enabled/add', array( + $term = (object) array( + 'vid' => $this->vocabulary->vid, 'name' => $this->randomName(), - SYNONYMS_DEFAULT_FIELD_NAME . '[' . LANGUAGE_NONE . '][0][value]' => $name . $this->randomName(), - ), 'Add another item'); - $this->drupalPost(NULL, array( - SYNONYMS_DEFAULT_FIELD_NAME . '[' . LANGUAGE_NONE . '][1][value]' => $name . $this->randomName(), - ), 'Save'); - $this->terms['enabled']['similar_synonyms'] = $this->getLastTerm($this->vocabularies['enabled']); + $this->fields['enabled']['field']['field_name'] => array( + LANGUAGE_NONE => array( + array('value' => $name . $this->randomName()), + array('value' => $name . $this->randomName()), + ), + ), + $this->fields['disabled']['field']['field_name'] => array( + LANGUAGE_NONE => array( + array('value' => $this->randomName()), + ), + ), + ); + taxonomy_term_save($term); + $this->terms['similar_synonyms'] = $term; + $name = 'one_term_name_another_synonym_'; - $this->drupalPost('admin/structure/taxonomy/enabled/add', array( + + $term = (object) array( + 'vid' => $this->vocabulary->vid, 'name' => $name . $this->randomName(), - ), 'Save'); - $this->terms['enabled']['name_another_synonym'] = $this->getLastTerm($this->vocabularies['enabled']); - $this->drupalPost('admin/structure/taxonomy/enabled/add', array( + $this->fields['disabled']['field']['field_name'] => array( + LANGUAGE_NONE => array( + array('value' => $this->randomName()), + ), + ), + ); + taxonomy_term_save($term); + $this->terms['name_another_synonym'] = $term; + + $term = (object) array( + 'vid' => $this->vocabulary->vid, 'name' => $this->randomName(), - SYNONYMS_DEFAULT_FIELD_NAME . '[' . LANGUAGE_NONE . '][0][value]' => $name . $this->randomName(), - ), 'Save'); - $this->terms['enabled']['synonym_another_name'] = $this->getLastTerm($this->vocabularies['enabled']); + $this->fields['enabled']['field']['field_name'] => array( + LANGUAGE_NONE => array( + array('value' => $name . $this->randomName()), + ), + ), + $this->fields['disabled']['field']['field_name'] => array( + LANGUAGE_NONE => array( + array('value' => $this->randomName()), + ), + ), + ); + taxonomy_term_save($term); + $this->terms['synonym_another_name'] = $term; } /** @@ -416,35 +519,35 @@ class AutocompleteSynonymsWebTestCase extends SynonymsWebTestCase { */ public function testAutoCreation() { // Trying enabled auto creation. - $this->drupalPost('admin/structure/types/manage/synonyms-test-content/fields/field_synonyms_term_enabled', array( + $this->drupalPost('admin/structure/types/manage/synonyms-test-content/fields/synonyms_term_enabled', array( 'instance[widget][settings][auto_creation]' => TRUE, ), 'Save settings'); - $new_term_name = $this->randomName(); + $new_term_name = $this->terms['no_synonyms']->{$this->fields['disabled']['field']['field_name']}[LANGUAGE_NONE][0]['value']; $this->drupalPost('node/add/synonyms-test-content', array( 'title' => $this->randomName(), - 'field_synonyms_term_enabled[' . LANGUAGE_NONE . ']' => $this->terms['enabled']['no_synonyms']->name . ', ' . $new_term_name . ', ' . $this->terms['enabled']['one_synonym']->{SYNONYMS_DEFAULT_FIELD_NAME}[LANGUAGE_NONE][0]['value'], + 'synonyms_term_enabled[' . LANGUAGE_NONE . ']' => $this->terms['no_synonyms']->name . ', ' . $new_term_name . ', ' . $this->terms['one_synonym']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE][0]['value'], ), 'Save'); - $this->assertText($this->terms['enabled']['no_synonyms']->name, 'Existing term was assigned to the new node'); + $this->assertText($this->terms['no_synonyms']->name, 'Existing term was assigned to the new node'); $this->assertText($new_term_name, 'Auto created term was assigned to the new node when Auto creation is on.'); - $this->assertText($this->terms['enabled']['one_synonym']->name, 'Submitting a synonym into autocomplete widget results in the term, to which the synonym belongs, being assigned to the just created entity (when Auto creation is on).'); - $term = $this->getLastTerm($this->vocabularies['enabled']); + $this->assertText($this->terms['one_synonym']->name, 'Submitting a synonym into autocomplete widget results in the term, to which the synonym belongs, being assigned to the just created entity (when Auto creation is on).'); + $term = $this->getLastTerm($this->vocabulary); $this->assertEqual($term->name, $new_term_name, 'The auto created term has been created when Auto creation is on.'); // Trying disabled auto creation. - $this->drupalPost('admin/structure/types/manage/synonyms-test-content/fields/field_synonyms_term_enabled', array( + $this->drupalPost('admin/structure/types/manage/synonyms-test-content/fields/synonyms_term_enabled', array( 'instance[widget][settings][auto_creation]' => FALSE, ), 'Save settings'); - $new_term_name = $this->randomName(); + $new_term_name = $this->terms['term1']->{$this->fields['disabled']['field']['field_name']}[LANGUAGE_NONE][0]['value']; $this->drupalPost('node/add/synonyms-test-content', array( 'title' => $this->randomName(), - 'field_synonyms_term_enabled[' . LANGUAGE_NONE . ']' => $this->terms['enabled']['no_synonyms']->name . ', ' . $new_term_name . ', ' . $this->terms['enabled']['one_synonym']->{SYNONYMS_DEFAULT_FIELD_NAME}[LANGUAGE_NONE][0]['value'], + 'synonyms_term_enabled[' . LANGUAGE_NONE . ']' => $this->terms['no_synonyms']->name . ', ' . $new_term_name . ', ' . $this->terms['one_synonym']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE][0]['value'], ), 'Save'); - $this->assertText($this->terms['enabled']['no_synonyms']->name, 'Existing term was assigned to the new node'); + $this->assertText($this->terms['no_synonyms']->name, 'Existing term was assigned to the new node'); $this->assertNoText($new_term_name, 'Auto created term was not assigned to the new node when Auto creation is off.'); - $this->assertText($this->terms['enabled']['one_synonym']->name, 'Submitting a synonym into autocomplete widget results in the term, to which the synonym belongs, being assigned to the just created entity (when Auto creation is off).'); - $term = $this->getLastTerm($this->vocabularies['enabled']); + $this->assertText($this->terms['one_synonym']->name, 'Submitting a synonym into autocomplete widget results in the term, to which the synonym belongs, being assigned to the just created entity (when Auto creation is off).'); + $term = $this->getLastTerm($this->vocabulary); $this->assertNotEqual($term->name, $new_term_name, 'The auto created term has not been created when Auto creation is off.'); } @@ -455,227 +558,141 @@ class AutocompleteSynonymsWebTestCase extends SynonymsWebTestCase { * in order to test its performance. */ public function testAutocompleteMenuPath() { - $assertions = array(); - // Testing empty and non-existing name arguments. - foreach ($this->vocabularies as $v) { - $assertions[] = array( - 'vocabulary' => $v->machine_name, - 'input' => '', - 'response' => array(), - 'message' => 'Submitting empty string into a ' . $v->machine_name . ' synonyms vocabulary', - ); - $assertions[] = array( - 'vocabulary' => $v->machine_name, - 'input' => $this->randomName(), - 'response' => array(), - 'message' => 'Submitting non existing name into a ' . $v->machine_name . ' synonyms vocabulary', - ); - } + $this->assertAutocompleteMenuPath('', array(), 'Submitting empty string into autocomplete path returns empty result.'); + $this->assertAutocompleteMenuPath($this->randomName(), array(), 'Submitting a non existing name into autocomplete path returns empty result.'); + $this->assertAutocompleteMenuPath($this->terms['term1']->{$this->fields['disabled']['field']['field_name']}[LANGUAGE_NONE][0]['value'], array(), 'Submitting a value for a field with disabled autocomplete behavior yields empty result.'); - // Testing the synonym-disabled vocabulary. - $terms = $this->terms['disabled']; - $assertions[] = array( - 'vocabulary' => 'disabled', - 'input' => drupal_strtoupper(drupal_substr($terms['term1']->name, 1, -1)), - 'response' => array($terms['term1']->name => $terms['term1']->name, $terms['term1_longer_name']->name => $terms['term1_longer_name']->name), - 'message' => 'Submitting a name similar to 2 existing term names into a disabled synonyms vocabulary', - ); - $assertions[] = array( - 'vocabulary' => 'disabled', - 'input' => $terms['term1']->name . ',' . drupal_strtoupper(drupal_substr($terms['term1']->name, 1, -1)), - 'response' => array($terms['term1']->name . ', ' . $terms['term1_longer_name']->name => $terms['term1_longer_name']->name), - 'message' => 'Submitting one term already chosen along with a name similar to 2 existing term names into a disabled synonyms vocabulary', - ); - $assertions[] = array( - 'vocabulary' => 'disabled', - 'input' => drupal_strtoupper(drupal_substr($terms['term2']->name, 1, -1)), - 'response' => array($terms['term2']->name => $terms['term2']->name), - 'message' => 'Submitting a name similar to one existing term name into a disabled synonyms vocabulary', - ); - $assertions[] = array( - 'vocabulary' => 'disabled', - 'input' => drupal_strtolower($terms['term2']->name) . ',' . drupal_strtoupper(drupal_substr($terms['term2']->name, 1, -1)), - 'response' => array(), - 'message' => 'Submitting the same term over again into a disabled synonyms vocabulary', - ); + $this->assertAutocompleteMenuPath(drupal_strtoupper(drupal_substr($this->terms['term1']->name, 1, -1)), array( + $this->terms['term1']->name => $this->terms['term1']->name, + $this->terms['term1_longer_name']->name => $this->terms['term1_longer_name']->name, + ), 'Submitting a name similar to 2 existing term names yields both terms included in the autocomplete response.'); + + $this->assertAutocompleteMenuPath($this->terms['term1']->name . ', ' . drupal_strtoupper(drupal_substr($this->terms['term1']->name, 1, -1)), array( + $this->terms['term1']->name . ', ' . $this->terms['term1_longer_name']->name => $this->terms['term1_longer_name']->name, + ), 'Submitting one term already chosen along with a name similar to 2 existing term names yields only suggested a new term.'); + + $this->assertAutocompleteMenuPath(drupal_strtoupper(drupal_substr($this->terms['no_synonyms']->name, 1, -1)), array( + $this->terms['no_synonyms']->name => $this->terms['no_synonyms']->name, + ), 'Submitting a name similar to one existing term name into autocomplete path yields that term included.'); + + $this->assertAutocompleteMenuPath(drupal_strtolower($this->terms['no_synonyms']->name) . ', ' . drupal_strtoupper(drupal_substr($this->terms['no_synonyms']->name, 1, -1)), array(), 'Submitting the same term over again into autocomplete path yields no results.'); + + $this->assertAutocompleteMenuPath($this->terms['one_synonym']->name . ', ' . $this->terms['one_synonym']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE][0]['value'], array(), 'Submitting a synonym of a term over again into autocomplete path yields no results.'); - // Testing the synonym-enabled vocabulary. - $terms = $this->terms['enabled']; - $assertions[] = array( - 'vocabulary' => 'enabled', - 'input' => drupal_strtoupper($terms['no_synonyms']->name) . ',' . drupal_strtolower(drupal_substr($terms['no_synonyms']->name, 1, -1)), - 'response' => array(), - 'message' => 'Submitting the same term over again into an enabled synonyms vocabulary', - ); - $assertions[] = array( - 'vocabulary' => 'enabled', - 'input' => $terms['one_synonym']->name . ',' . $terms['one_synonym']->{SYNONYMS_DEFAULT_FIELD_NAME}[LANGUAGE_NONE][0]['safe_value'], - 'response' => array(), - 'message' => 'Submitting a synonym of a term over again into an enabled synonyms vocabulary', - ); foreach (array('no_synonyms', 'one_synonym', 'two_synonyms') as $k) { - $assertions[] = array( - 'vocabulary' => 'enabled', - 'input' => drupal_strtolower(drupal_substr($terms[$k]->name, 1, -1)), - 'response' => array($terms[$k]->name => $terms[$k]->name), - 'message' => 'Submitting a name similar to ' . $k . ' term into an enabled synonyms vocabulary', - ); + $this->assertAutocompleteMenuPath(drupal_strtolower(drupal_substr($this->terms[$k]->name, 1, -1)), array( + $this->terms[$k]->name => $this->terms[$k]->name, + ), 'Submitting a name similar to ' . $k . ' term into autocomplete path yields the term included.'); - $synonyms = field_get_items('taxonomy_term', $terms[$k], SYNONYMS_DEFAULT_FIELD_NAME); + $synonyms = field_get_items('taxonomy_term', $this->terms[$k], $this->fields['enabled']['field']['field_name']); if (is_array($synonyms)) { foreach ($synonyms as $delta => $item) { - $assertions[] = array( - 'vocabulary' => 'enabled', - 'input' => drupal_strtolower(drupal_substr($item['safe_value'], 1, -1)), - 'response' => array($terms[$k]->name => $this->synonymAutocompleteResult($terms[$k], $item['safe_value'])), - 'message' => 'Submitting a name similar to synonym#' . $delta . ' of the term ' . $k . ' into an enabled synonyms vocabulary', - ); + $this->assertAutocompleteMenuPath(drupal_strtolower(drupal_substr($item['value'], 1, -1)), array( + $this->terms[$k]->name => $this->synonymAutocompleteResult($this->terms[$k], $item['value'], $this->fields['enabled']['instance']), + ), 'Submitting a name similar to synonym#' . $delta . ' of the term ' . $k . ' into autocomplete path yields the term included.'); } } } - $assertions[] = array( - 'vocabulary' => 'enabled', - 'input' => 'one_term_name_another_synonym_', - 'response' => array( - $terms['name_another_synonym']->name => $terms['name_another_synonym']->name, - $terms['synonym_another_name']->name => $this->synonymAutocompleteResult($terms['synonym_another_name'], $terms['synonym_another_name']->{SYNONYMS_DEFAULT_FIELD_NAME}[LANGUAGE_NONE][0]['safe_value']), - ), - 'message' => 'Submitting a keyword similar to name of one term and synonym of another into an enabled synonyms vocabulary yields both included in the results', - ); - foreach ($assertions as $v) { - $this->assertAutocompleteMenuPath($v); - } + $this->assertAutocompleteMenuPath('one_term_name_another_synonym_', array( + $this->terms['name_another_synonym']->name => $this->terms['name_another_synonym']->name, + $this->terms['synonym_another_name']->name => $this->synonymAutocompleteResult($this->terms['synonym_another_name'], $this->terms['synonym_another_name']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE][0]['value'], $this->fields['enabled']['instance']), + ), 'Submitting a name similar to name of one term and synonym of another into autocomplete path yields both terms included.'); + + // Enabling another field in the autocomplete suggestions to make sure 2 and + // more fields can participate in the action. + synonyms_behavior_settings_save(array( + 'instance_id' => $this->fields['disabled']['instance']['id'], + 'behavior' => $this->behavior, + 'settings' => $this->behavior_settings, + )); + $this->terms['one_synonym']->{$this->fields['disabled']['field']['field_name']} = $this->terms['one_synonym']->{$this->fields['enabled']['field']['field_name']}; + taxonomy_term_save($this->terms['one_synonym']); + $this->assertAutocompleteMenuPath($this->terms['one_synonym']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE][0]['value'], array( + $this->terms['one_synonym']->name => $this->synonymAutocompleteResult($this->terms['one_synonym'], $this->terms['one_synonym']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE][0]['value'], $this->fields['enabled']['instance']), + $this->terms['one_synonym']->name . ' ' => $this->synonymAutocompleteResult($this->terms['one_synonym'], $this->terms['one_synonym']->{$this->fields['disabled']['field']['field_name']}[LANGUAGE_NONE][0]['value'], $this->fields['disabled']['instance']), + ), 'Autocomplete works correctly when more than 1 field participates in the autocomplete behavior.'); } /** * Test 'Suggestions Size' setting of synonyms-friendly autocomplete widget. */ - function testWidgetSettingsSuggestionSize() { + public function testWidgetSettingsSuggestionSize() { $suggestion_size = 1; - $this->drupalPost('admin/structure/types/manage/synonyms-test-content/fields/field_synonyms_term_disabled', array( + $this->drupalPost('admin/structure/types/manage/synonyms-test-content/fields/synonyms_term_enabled', array( 'instance[widget][settings][suggestion_size]' => $suggestion_size, ), 'Save settings'); - $this->drupalPost('admin/structure/types/manage/synonyms-test-content/fields/field_synonyms_term_enabled', array( - 'instance[widget][settings][suggestion_size]' => $suggestion_size, - ), 'Save settings'); - - $assertions = array(); // If size was bigger than 1, we'd get suggested 2 terms: 'term1' and // 'term1_longer_name'. - $assertions[] = array( - 'vocabulary' => 'disabled', - 'input' => $this->terms['disabled']['term1']->name, - 'response' => array($this->terms['disabled']['term1']->name => $this->terms['disabled']['term1']->name), - 'message' => 'Suggestions Size option is respected in autocomplete widget for term suggestion entries.', - ); + $this->assertAutocompleteMenuPath($this->terms['term1']->name, array( + $this->terms['term1']->name => $this->terms['term1']->name, + ), 'Suggestions Size option is respected in autocomplete widget for term suggestion entries.'); - $assertions[] = array( - 'vocabulary' => 'enabled', - 'input' => $this->terms['enabled']['name_similar_synonym']->name, - 'response' => array($this->terms['enabled']['name_similar_synonym']->name => $this->terms['enabled']['name_similar_synonym']->name), - 'message' => 'Suggestions Size option is respected in autocomplete widget for term and synonym suggestion entries.' - ); + $this->assertAutocompleteMenuPath($this->terms['name_similar_synonym']->name, array( + $this->terms['name_similar_synonym']->name => $this->terms['name_similar_synonym']->name, + ), 'Suggestions Size option is respected in autocomplete widget for term and synonym suggestion entries.'); - $assertions[] = array( - 'vocabulary' => 'enabled', - 'input' => drupal_substr($this->terms['enabled']['similar_synonyms']->{SYNONYMS_DEFAULT_FIELD_NAME}[LANGUAGE_NONE][0]['value'], 0, 8), - 'response' => array($this->terms['enabled']['similar_synonyms']->name => $this->synonymAutocompleteResult($this->terms['enabled']['similar_synonyms'], $this->terms['enabled']['similar_synonyms']->{SYNONYMS_DEFAULT_FIELD_NAME}[LANGUAGE_NONE][0]['value'])), - 'message' => 'Suggestions Size option is respected in autocomplete widget for synonyms suggestion entries.' - ); + $this->assertAutocompleteMenuPath(drupal_substr($this->terms['similar_synonyms']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE][0]['value'], 0, 8), array( + $this->terms['similar_synonyms']->name => $this->synonymAutocompleteResult($this->terms['similar_synonyms'], $this->terms['similar_synonyms']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE][0]['value'], $this->fields['enabled']['instance']), + ), 'Suggestions Size option is respected in autocomplete widget for synonyms suggestion entries.'); - foreach ($assertions as $assertion) { - $this->assertAutocompleteMenuPath($assertion); - } + $this->assertAutocompleteMenuPath('one_term_name_another_synonym_', array( + $this->terms['name_another_synonym']->name => $this->terms['name_another_synonym']->name, + ), 'Suggestions Size option is respected in autocomplete widget for the case when there is match by term name and by synonyms; and preference is given to the match by term name.'); } /** * Test 'Suggest only one entry per term' setting of autocomplete widget. */ - function testWidgetSettingsSuggestOnlyUnique() { - $assertions = array(); - + public function testWidgetSettingsSuggestOnlyUnique() { // Testing disabled "Suggest only one entry per term" setting. - $assertions[] = array( - 'vocabulary' => 'enabled', - 'input' => $this->terms['enabled']['name_similar_synonym']->name, - 'response' => array( - $this->terms['enabled']['name_similar_synonym']->name => $this->terms['enabled']['name_similar_synonym']->name, - $this->terms['enabled']['name_similar_synonym']->name . ' ' => $this->synonymAutocompleteResult($this->terms['enabled']['name_similar_synonym'], $this->terms['enabled']['name_similar_synonym']->{SYNONYMS_DEFAULT_FIELD_NAME}[LANGUAGE_NONE][0]['value']), - ), - 'message' => 'Both term and its synonym are shown when "Suggest only one entry per term" is off.' - ); + $this->assertAutocompleteMenuPath($this->terms['name_similar_synonym']->name, array( + $this->terms['name_similar_synonym']->name => $this->terms['name_similar_synonym']->name, + $this->terms['name_similar_synonym']->name . ' ' => $this->synonymAutocompleteResult($this->terms['name_similar_synonym'], $this->terms['name_similar_synonym']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE][0]['value'], $this->fields['enabled']['instance']), + ), 'Both term and its synonym are shown when "Suggest only one entry per term" is off.'); - $assertions[] = array( - 'vocabulary' => 'enabled', - 'input' => drupal_substr($this->terms['enabled']['similar_synonyms']->{SYNONYMS_DEFAULT_FIELD_NAME}[LANGUAGE_NONE][0]['value'], 0, 8), - 'response' => array( - $this->terms['enabled']['similar_synonyms']->name => $this->synonymAutocompleteResult($this->terms['enabled']['similar_synonyms'], $this->terms['enabled']['similar_synonyms']->{SYNONYMS_DEFAULT_FIELD_NAME}[LANGUAGE_NONE][0]['value']), - $this->terms['enabled']['similar_synonyms']->name . ' ' => $this->synonymAutocompleteResult($this->terms['enabled']['similar_synonyms'], $this->terms['enabled']['similar_synonyms']->{SYNONYMS_DEFAULT_FIELD_NAME}[LANGUAGE_NONE][1]['value']), - ), - 'message' => 'Multiple synonyms are shown when "Suggest only one entry per term" is off.' - ); - - foreach ($assertions as $assertion) { - $this->assertAutocompleteMenuPath($assertion); - } + $this->assertAutocompleteMenuPath(drupal_substr($this->terms['similar_synonyms']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE][0]['value'], 0, 8), array( + $this->terms['similar_synonyms']->name => $this->synonymAutocompleteResult($this->terms['similar_synonyms'], $this->terms['similar_synonyms']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE][0]['value'], $this->fields['enabled']['instance']), + $this->terms['similar_synonyms']->name . ' ' => $this->synonymAutocompleteResult($this->terms['similar_synonyms'], $this->terms['similar_synonyms']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE][1]['value'], $this->fields['enabled']['instance']), + ), 'Multiple synonyms are shown when "Suggest only one entry per term" is off.'); // Testing enabled "Suggest only one entry per term" setting. - $this->drupalPost('admin/structure/types/manage/synonyms-test-content/fields/field_synonyms_term_disabled', array( - 'instance[widget][settings][suggest_only_unique]' => TRUE, - ), 'Save settings'); - $this->drupalPost('admin/structure/types/manage/synonyms-test-content/fields/field_synonyms_term_enabled', array( + $this->drupalPost('admin/structure/types/manage/synonyms-test-content/fields/synonyms_term_enabled', array( 'instance[widget][settings][suggest_only_unique]' => TRUE, ), 'Save settings'); - $assertions = array(); + $this->assertAutocompleteMenuPath($this->terms['name_similar_synonym']->name, array( + $this->terms['name_similar_synonym']->name => $this->terms['name_similar_synonym']->name, + ), 'Only term is shown and synonym is not shown when "Suggest only one entry per term" is on.'); - $assertions[] = array( - 'vocabulary' => 'enabled', - 'input' => $this->terms['enabled']['name_similar_synonym']->name, - 'response' => array( - $this->terms['enabled']['name_similar_synonym']->name => $this->terms['enabled']['name_similar_synonym']->name, - ), - 'message' => 'Only term is shown and synonym is not shown when "Suggest only one entry per term" is on.' - ); - - $assertions[] = array( - 'vocabulary' => 'enabled', - 'input' => drupal_substr($this->terms['enabled']['similar_synonyms']->{SYNONYMS_DEFAULT_FIELD_NAME}[LANGUAGE_NONE][0]['value'], 0, 8), - 'response' => array( - $this->terms['enabled']['similar_synonyms']->name => $this->synonymAutocompleteResult($this->terms['enabled']['similar_synonyms'], $this->terms['enabled']['similar_synonyms']->{SYNONYMS_DEFAULT_FIELD_NAME}[LANGUAGE_NONE][0]['value']), - ), - 'message' => 'Only single synonym is shown when "Suggest only one entry per term" is on.' - ); - - foreach ($assertions as $assertion) { - $this->assertAutocompleteMenuPath($assertion); - } + $this->assertAutocompleteMenuPath(drupal_substr($this->terms['similar_synonyms']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE][0]['value'], 0, 8), array( + $this->terms['similar_synonyms']->name => $this->synonymAutocompleteResult($this->terms['similar_synonyms'], $this->terms['similar_synonyms']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE][0]['value'], $this->fields['enabled']['instance']), + ), 'Only single synonym is shown when "Suggest only one entry per term" is on.'); } /** * Assert output of synonym friendly autocomplete path. * - * @param array $assertion - * Specially encoded array of assertion. Should include the following keys: - * vocabulary - machine name of vocabulary whose field is asserted - * input - input string to be fed to autocomplete menu path - * response - JSON decoded expected response of autocomplete menu path - * message - Drupal assertion message to be displayed on test results - * page + * @param string $input + * String of input to supply to the autocomplete path + * @param array $standard + * Expected output from the autocomplete path. Supply it as an associative + * array + * @param string $message + * Drupal assertion message to be displayed on the rest results page */ - protected function assertAutocompleteMenuPath($assertion) { - $response = $this->drupalGet('synonyms/autocomplete/field_synonyms_term_' . $assertion['vocabulary'] . '/' . $this->entity_type . '/' . $this->bundle . '/' . $assertion['input']); + protected function assertAutocompleteMenuPath($input, $standard, $message) { + $response = $this->drupalGet('synonyms/autocomplete/' . $this->term_reference_field['field_name'] . '/' . $this->entity_type . '/' . $this->bundle . '/' . $input); if (!$response) { - $this->fail($assertion['message'], 'Autocomplete Menu Path'); + $this->fail($message, 'Autocomplete Menu Path'); return; } $response = (array) json_decode($response); - $is_the_same = count($response) == count($assertion['response']); - $is_the_same = $is_the_same && count(array_intersect_assoc($response, $assertion['response'])) == count($assertion['response']); + $is_the_same = count($response) == count($standard); + $is_the_same = $is_the_same && count(array_intersect_assoc($response, $standard)) == count($standard); - $this->assertTrue($is_the_same, $assertion['message'], 'Autocomplete Menu Path'); + $this->assertTrue($is_the_same, $message, 'Autocomplete Menu Path'); } /** @@ -687,422 +704,707 @@ class AutocompleteSynonymsWebTestCase extends SynonymsWebTestCase { * Fully loaded taxonomy term object for which the result is generated. * @param string $synonym * Synonym by which the term was hit in the search + * @param array $instance + * Instance definition array which the $synonym originates from * * @return string * Formatted autocomplete result */ - protected function synonymAutocompleteResult($term, $synonym) { - return t('@synonym, synonym of %term', array('@synonym' => $synonym, '%term' => $term->name)); + protected function synonymAutocompleteResult($term, $synonym, $instance) { + return format_string($this->behavior_settings['wording'], array( + '@synonym' => $synonym, + '@term' => $term->name, + '@field_name' => drupal_strtolower($instance['label']), + )); } } /** - * Test Synonyms module integration with Drupal search functionality. + * Test "Synonyms friendly select" widget of Synonyms module. */ -class SearchIndexSynonymsWebTestCase extends SynonymsWebTestCase { - protected $vocabularies = array( - 'enabled' => TRUE, +class SelectSynonymsWebTestCase extends SynonymsWebTestCase { + + protected $behavior = 'select'; + + protected $behavior_settings = array( + 'wording' => '@synonym @term @field_name', ); /** - * GetInfo method. + * Array of fully loaded taxonomy term entities to be used in this test. + * + * @var array */ - public function getInfo() { + protected $terms = array(); + + /** + * Entity type to which a term reference field with tested widget is attached. + * + * @var string + */ + protected $entity_type = 'node'; + + /** + * Bundle to which a term reference field with tested widget is attached. + * + * @var string + */ + protected $bundle = 'synonyms_test_content'; + + /** + * Field definition array of the field that will be attached to + * $this->entity_type with synonyms-friendly select widget. + * + * @var array + */ + protected $term_reference_field = array(); + + public static function getInfo() { return array( - 'name' => 'Synonyms search integration', - 'description' => 'Ensure that Synonyms module correctly integrates with the Drupal search functionality.', + 'name' => 'Synonyms friendly select', + 'description' => 'Ensure that the "synonym friendly select" widget works correctly with taxonomy terms.', 'group' => 'Synonyms', ); } - /** - * SetUp method. - */ - public function setUp() { - parent::setUp(); - // Creating vocabularies. - $this->drupalLogin($this->admin); - foreach ($this->vocabularies as $k => $v) { - $name = $this->randomName(); - $this->drupalPost('admin/structure/taxonomy/add', array( - 'name' => $name, - 'machine_name' => $k, - 'description' => $this->randomName(), - 'synonyms[synonyms][' . SYNONYMS_DEFAULT_FIELD_NAME . ']' => $v, - ), 'Save'); - $this->vocabularies[$k] = taxonomy_vocabulary_machine_name_load($k); - } + public function setUp($modules = array()) { + parent::setUp($modules); // Creating a test content type. $this->drupalPost('admin/structure/types/add', array( 'name' => 'Synonyms Test Content', - 'type' => 'synonyms_test_content', + 'type' => $this->bundle, ), 'Save content type'); - // Attaching each vocabulary term reference field to the new content type. - foreach ($this->vocabularies as $k => $v) { - $this->drupalPost('admin/structure/types/manage/synonyms_test_content/fields', array( - 'fields[_add_new_field][label]' => 'Synonym Terms ' . $k, - 'fields[_add_new_field][field_name]' => 'synonyms_term_' . $k, - 'fields[_add_new_field][type]' => 'taxonomy_term_reference', - 'fields[_add_new_field][widget_type]' => 'synonyms_autocomplete', - ), 'Save'); - $this->drupalPost(NULL, array( - 'field[settings][allowed_values][0][vocabulary]' => $v->machine_name, - ), 'Save field settings'); - $this->drupalPost(NULL, array( - 'field[cardinality]' => FIELD_CARDINALITY_UNLIMITED, - ), 'Save settings'); - } - // Flushing static cache. - _field_info_collate_fields(TRUE); + $this->term_reference_field = array( + 'type' => 'taxonomy_term_reference', + 'field_name' => 'synonyms_term', + 'cardinality' => FIELD_CARDINALITY_UNLIMITED, + 'settings' => array( + 'allowed_values' => array( + array( + 'vocabulary' => $this->vocabulary->machine_name, + 'parent' => 0, + ), + ), + ), + ); + $this->term_reference_field = field_create_field($this->term_reference_field); + + $instance = array( + 'field_name' => $this->term_reference_field['field_name'], + 'entity_type' => 'node', + 'bundle' => $this->bundle, + 'label' => 'Synonym Terms Select', + 'widget' => array( + 'type' => 'synonyms_select', + ), + ); + $instance = field_create_instance($instance); + + $this->terms['parent_term'] = (object) array( + 'vid' => $this->vocabulary->vid, + 'name' => 'Parent Term', + $this->fields['enabled']['field']['field_name'] => array( + LANGUAGE_NONE => array( + array('value' => 'Parent TermA' . $this->randomName()), + array('value' => 'Parent TermZ' . $this->randomName()), + ), + ), + ); + taxonomy_term_save($this->terms['parent_term']); + + $this->terms['child_term'] = (object) array( + 'vid' => $this->vocabulary->vid, + 'name' => 'Child Term', + 'parent' => $this->terms['parent_term']->tid, + $this->fields['enabled']['field']['field_name'] => array( + LANGUAGE_NONE => array( + array('value' => 'Child TermZ' . $this->randomName()), + array('value' => 'Child TermA' . $this->randomName()), + ), + ), + ); + taxonomy_term_save($this->terms['child_term']); + + $this->terms['normal_term'] = (object) array( + 'vid' => $this->vocabulary->vid, + 'name' => 'Normal Term', + $this->fields['enabled']['field']['field_name'] => array( + LANGUAGE_NONE => array( + array('value' => 'Normal TermA' . $this->randomName()), + array('value' => 'Normal TermZ' . $this->randomName()), + ), + ), + ); + taxonomy_term_save($this->terms['normal_term']); } /** - * Test searching by a term synonym. - * - * Since logically term and its synonyms represent the same entity, the idea - * is that searching by a term synonym should trigger all content referencing - * that term to be included in search results. Additionally we test that when - * a synonym is deleted/edited in a term, corresponding content is no longer - * encountered when searched by ex-synonym. + * Test sorting options of the widget. */ - public function testSearchTermSynonym() { - // Create a few terms and synonyms. - $terms = array(); - $term = (object) array( - 'vid' => $this->vocabularies['enabled']->vid, - 'name' => $this->randomName(), + public function testWidgetSorting() { + $cardinality = array( + 1 => 1, + FIELD_CARDINALITY_UNLIMITED => 'unlimited', ); - taxonomy_term_save($term); - $terms['no_synonyms'] = $this->getLastTerm($this->vocabularies['enabled']); - - $term = (object) array( - 'vid' => $this->vocabularies['enabled']->vid, - 'name' => $this->randomName(), - SYNONYMS_DEFAULT_FIELD_NAME => array( - LANGUAGE_NONE => array( - array('value' => $this->randomName()), - ), - ) + $required = array( + TRUE => 'required', + FALSE => 'not required', ); - taxonomy_term_save($term); - $terms['one_synonym'] = $this->getLastTerm($this->vocabularies['enabled']); - $term = (object) array( - 'vid' => $this->vocabularies['enabled']->vid, - 'name' => $this->randomName(), - SYNONYMS_DEFAULT_FIELD_NAME => array( - LANGUAGE_NONE => array( - array('value' => $this->randomName()), - array('value' => $this->randomName()), - ), - ) - ); - taxonomy_term_save($term); - $terms['two_synonyms'] = $this->getLastTerm($this->vocabularies['enabled']); + foreach ($cardinality as $cardinality_k => $cardinality_v) { + foreach ($required as $required_k => $required_v) { + $this->term_reference_field['cardinality'] = $cardinality_k; + field_update_field($this->term_reference_field); - // Creating a node, which references to all the terms we have. - $title = $this->randomName(); - $this->drupalPost('node/add/synonyms-test-content', array( - 'title' => $title, - 'field_synonyms_term_enabled[' . LANGUAGE_NONE . ']' => $terms['no_synonyms']->name . ', ' . $terms['one_synonym']->name . ', ' . $terms['two_synonyms']->name, - ), 'Save'); - $node = $this->drupalGetNodeByTitle($title); + $instance = field_info_instance($this->entity_type, $this->term_reference_field['field_name'], $this->bundle); + $instance['required'] = $required_k; + $instance['widget']['settings']['sort'] = 'weight'; + field_update_instance($instance); - // Rebuilding Search index. - $this->cronRun(); + $this->terms['parent_term']->weight = 0; + taxonomy_term_save($this->terms['parent_term']); - foreach ($terms as $k => $term) { - $this->assertSearchResults($term->name, array($node), 'Searching by name of the term ' . $k); - foreach (synonyms_get_sanitized($term) as $delta => $synonym) { - $this->assertSearchResults($synonym, array($node), 'Searching by synonym #' . $delta . ' of the term ' . $k); + $options = array(); + $options[] = array( + 'term' => $this->terms['normal_term'], + ); + $options[] = array( + 'term' => $this->terms['normal_term'], + 'synonym' => $this->terms['normal_term']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE][0]['value'], + ); + $options[] = array( + 'term' => $this->terms['normal_term'], + 'synonym' => $this->terms['normal_term']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE][1]['value'], + ); + $options[] = array( + 'term' => $this->terms['parent_term'], + ); + $options[] = array( + 'term' => $this->terms['parent_term'], + 'synonym' => $this->terms['parent_term']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE][0]['value'], + ); + $options[] = array( + 'term' => $this->terms['parent_term'], + 'synonym' => $this->terms['parent_term']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE][1]['value'], + ); + $options[] = array( + 'term' => $this->terms['child_term'], + ); + $options[] = array( + 'term' => $this->terms['child_term'], + 'synonym' => $this->terms['child_term']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE][0]['value'], + ); + $options[] = array( + 'term' => $this->terms['child_term'], + 'synonym' => $this->terms['child_term']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE][1]['value'], + ); + $this->drupalGet('node/add/synonyms-test-content'); + $this->assertSynonymsSelect($options, 'Synonyms select sorting by weight works for the cardinality of ' . $cardinality_v . ' and ' . $required_v); + + $this->terms['parent_term']->weight = -1000; + taxonomy_term_save($this->terms['parent_term']); + $options = array(); + $options[] = array( + 'term' => $this->terms['parent_term'], + ); + $options[] = array( + 'term' => $this->terms['parent_term'], + 'synonym' => $this->terms['parent_term']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE][0]['value'], + ); + $options[] = array( + 'term' => $this->terms['parent_term'], + 'synonym' => $this->terms['parent_term']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE][1]['value'], + ); + $options[] = array( + 'term' => $this->terms['child_term'], + ); + $options[] = array( + 'term' => $this->terms['child_term'], + 'synonym' => $this->terms['child_term']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE][0]['value'], + ); + $options[] = array( + 'term' => $this->terms['child_term'], + 'synonym' => $this->terms['child_term']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE][1]['value'], + ); + $options[] = array( + 'term' => $this->terms['normal_term'], + ); + $options[] = array( + 'term' => $this->terms['normal_term'], + 'synonym' => $this->terms['normal_term']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE][0]['value'], + ); + $options[] = array( + 'term' => $this->terms['normal_term'], + 'synonym' => $this->terms['normal_term']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE][1]['value'], + ); + $this->drupalGet('node/add/synonyms-test-content'); + $this->assertSynonymsSelect($options, 'Synonyms select sorting by weight works after changing weights of terms for the cardinality of ' . $cardinality_v . ' and ' . $required_v); + + $instance = field_info_instance($this->entity_type, $this->term_reference_field['field_name'], $this->bundle); + $instance['widget']['settings']['sort'] = 'name'; + field_update_instance($instance); + + $options = array(); + $options[] = array( + 'term' => $this->terms['normal_term'], + ); + $options[] = array( + 'term' => $this->terms['normal_term'], + 'synonym' => $this->terms['normal_term']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE][0]['value'], + ); + $options[] = array( + 'term' => $this->terms['normal_term'], + 'synonym' => $this->terms['normal_term']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE][1]['value'], + ); + $options[] = array( + 'term' => $this->terms['parent_term'], + ); + $options[] = array( + 'term' => $this->terms['child_term'], + ); + $options[] = array( + 'term' => $this->terms['child_term'], + 'synonym' => $this->terms['child_term']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE][1]['value'], + ); + $options[] = array( + 'term' => $this->terms['child_term'], + 'synonym' => $this->terms['child_term']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE][0]['value'], + ); + $options[] = array( + 'term' => $this->terms['parent_term'], + 'synonym' => $this->terms['parent_term']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE][0]['value'], + ); + $options[] = array( + 'term' => $this->terms['parent_term'], + 'synonym' => $this->terms['parent_term']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE][1]['value'], + ); + $this->drupalGet('node/add/synonyms-test-content'); + $this->assertSynonymsSelect($options, 'Synonyms select sorting by name works for the cardinality of ' . $cardinality_v . ' and ' . $required_v); } } + } - // Removing a synonym from the term. Then asserting node got re-indexed - // with new values of synonyms. - $deleted_synonym = array_pop($terms['one_synonym']->{SYNONYMS_DEFAULT_FIELD_NAME}[LANGUAGE_NONE]); - taxonomy_term_save($terms['one_synonym']); - $this->cronRun(); - $this->assertSearchResults($deleted_synonym['value'], array(), 'Searching by recently deleted synonym of a taxonomy term yields no results.'); - - // Editing a synonym in a term. Then asserting node got re-indexed with new - // values of synonyms. - $ex_synonym = $terms['two_synonyms']->{SYNONYMS_DEFAULT_FIELD_NAME}[LANGUAGE_NONE][0]['value']; - $terms['two_synonyms']->{SYNONYMS_DEFAULT_FIELD_NAME}[LANGUAGE_NONE][0]['value'] = $this->randomName(); - taxonomy_term_save($terms['two_synonyms']); - $this->cronRun(); - $this->assertSearchResults($ex_synonym, array(), 'Searching by recently changed synonym of a taxonomy term yields no results.'); - - // We disable entire field from being source of synonyms and make sure for - // all synonyms search results are empty. - $this->drupalPost('admin/structure/taxonomy/enabled/edit', array( - 'synonyms[synonyms][' . SYNONYMS_DEFAULT_FIELD_NAME . ']' => FALSE, + /** + * Test main functionality of the widget. + */ + public function testWidget() { + $name = $this->randomName(); + $this->drupalPost('node/add/synonyms-test-content', array( + 'title' => $name, + $this->term_reference_field['field_name'] . '[' . LANGUAGE_NONE . '][]' => array( + $this->synonymSelectKey($this->terms['parent_term'], $this->terms['parent_term']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE][0]['value']), + $this->terms['child_term']->tid, + $this->terms['normal_term']->tid, + $this->synonymSelectKey($this->terms['normal_term'], $this->terms['normal_term']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE][0]['value']), + ), ), 'Save'); - $this->cronRun(); - foreach ($terms as $k => $term) { - $items = field_get_items('taxonomy_term', $term, SYNONYMS_DEFAULT_FIELD_NAME); - if (is_array($items)) { - foreach ($items as $synonym) { - $this->assertSearchResults($synonym['value'], array(), 'Searching by ' . $k . ' term synonym, which field was recently disabled as source of synonyms in vocabulary yields no results.'); + + $node = $this->drupalGetNodeByTitle($name); + $this->drupalGet('node/' . $node->nid); + $this->assertText($this->terms['parent_term']->name, 'Term is saved when its synonym is submitted through synonyms friendly select for the unlimited cardinality.'); + $this->assertText($this->terms['child_term']->name, 'Term is saved when it is submitted through synonyms friendly select for the unlimited cardinality.'); + $this->assertUniqueText($this->terms['normal_term']->name, 'Term is saved only once when the term and its synonym are submitted through synonyms friendly select for the unlimited cardinality.'); + + $options = array(); + $options[] = array( + 'term' => $this->terms['normal_term'], + 'selected' => TRUE, + ); + $options[] = array( + 'term' => $this->terms['normal_term'], + 'synonym' => $this->terms['normal_term']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE][0]['value'], + ); + $options[] = array( + 'term' => $this->terms['normal_term'], + 'synonym' => $this->terms['normal_term']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE][1]['value'], + ); + $options[] = array( + 'term' => $this->terms['parent_term'], + 'selected' => TRUE, + ); + $options[] = array( + 'term' => $this->terms['parent_term'], + 'synonym' => $this->terms['parent_term']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE][0]['value'], + ); + $options[] = array( + 'term' => $this->terms['parent_term'], + 'synonym' => $this->terms['parent_term']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE][1]['value'], + ); + $options[] = array( + 'term' => $this->terms['child_term'], + 'selected' => TRUE, + ); + $options[] = array( + 'term' => $this->terms['child_term'], + 'synonym' => $this->terms['child_term']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE][0]['value'], + ); + $options[] = array( + 'term' => $this->terms['child_term'], + 'synonym' => $this->terms['child_term']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE][1]['value'], + ); + $this->drupalGet('node/' . $node->nid . '/edit'); + $this->assertSynonymsSelect($options, 'Default values are set correctly in the synonyms friendly select widget when working with field cardinality more than 1.'); + + $this->term_reference_field['cardinality'] = 2; + field_update_field($this->term_reference_field); + $name = $this->randomName(); + $this->drupalPost('node/add/synonyms-test-content', array( + 'title' => $name, + $this->term_reference_field['field_name'] . '[' . LANGUAGE_NONE . '][]' => array( + $this->synonymSelectKey($this->terms['parent_term'], $this->terms['parent_term']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE][0]['value']), + $this->terms['child_term']->tid, + $this->terms['normal_term']->tid, + ), + ), 'Save'); + $this->assertText('this field cannot hold more than 2 values.', 'Submitting 3 entries into a field with cardinality of 2, that refer to 3 terms, results in a form error.'); + + $this->drupalPost('node/add/synonyms-test-content', array( + 'title' => $name, + $this->term_reference_field['field_name'] . '[' . LANGUAGE_NONE . '][]' => array( + $this->synonymSelectKey($this->terms['parent_term'], $this->terms['parent_term']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE][0]['value']), + $this->terms['normal_term']->tid, + $this->synonymSelectKey($this->terms['normal_term'], $this->terms['normal_term']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE][0]['value']), + ), + ), 'Save'); + $node = $this->drupalGetNodeByTitle($name); + $this->drupalGet('node/' . $node->nid); + $this->assertUniqueText($this->terms['parent_term']->name, 'Submitting 3 entries into a field with cardinality of 2, that refer to only 2 terms, results in form getting submitted. Term #1 is saved.'); + $this->assertUniqueText($this->terms['normal_term']->name, 'Term #2 is saved.'); + + $this->term_reference_field['cardinality'] = 1; + field_update_field($this->term_reference_field); + $name = $this->randomName(); + $this->drupalPost('node/add/synonyms-test-content', array( + 'title' => $name, + $this->term_reference_field['field_name'] . '[' . LANGUAGE_NONE . ']' => $this->synonymSelectKey($this->terms['parent_term'], $this->terms['parent_term']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE][0]['value']), + ), 'Save'); + $node = $this->drupalGetNodeByTitle($name); + $this->drupalGet('node/' . $node->nid); + $this->assertText($this->terms['parent_term']->name, 'Term is saved when its synonym is submitted through synonyms friendly select for the cardinality of 1.'); + + $options = array(); + $options[] = array( + 'term' => $this->terms['normal_term'], + ); + $options[] = array( + 'term' => $this->terms['normal_term'], + 'synonym' => $this->terms['normal_term']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE][0]['value'], + ); + $options[] = array( + 'term' => $this->terms['normal_term'], + 'synonym' => $this->terms['normal_term']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE][1]['value'], + ); + $options[] = array( + 'term' => $this->terms['parent_term'], + 'selected' => TRUE, + ); + $options[] = array( + 'term' => $this->terms['parent_term'], + 'synonym' => $this->terms['parent_term']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE][0]['value'], + ); + $options[] = array( + 'term' => $this->terms['parent_term'], + 'synonym' => $this->terms['parent_term']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE][1]['value'], + ); + $options[] = array( + 'term' => $this->terms['child_term'], + ); + $options[] = array( + 'term' => $this->terms['child_term'], + 'synonym' => $this->terms['child_term']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE][0]['value'], + ); + $options[] = array( + 'term' => $this->terms['child_term'], + 'synonym' => $this->terms['child_term']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE][1]['value'], + ); + $this->drupalGet('node/' . $node->nid . '/edit'); + $this->assertSynonymsSelect($options, 'Default values are set correctly in the synonyms friendly select widget when working with the field cardinality of 1.'); + + $this->drupalPost('node/' . $node->nid . '/edit', array( + $this->term_reference_field['field_name'] . '[' . LANGUAGE_NONE . ']' => $this->terms['child_term']->tid, + ), 'Save'); + $this->drupalGet('node/' . $node->nid); + $this->assertNoText($this->terms['parent_term']->name, 'After updating entity the old term is removed.'); + $this->assertText($this->terms['child_term']->name, 'Term is saved when it is submitted through synonyms friendly select for the cardinality of 1.'); + + $options = array(); + $options[] = array( + 'term' => $this->terms['normal_term'], + ); + $options[] = array( + 'term' => $this->terms['normal_term'], + 'synonym' => $this->terms['normal_term']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE][0]['value'], + ); + $options[] = array( + 'term' => $this->terms['normal_term'], + 'synonym' => $this->terms['normal_term']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE][1]['value'], + ); + $options[] = array( + 'term' => $this->terms['parent_term'], + ); + $options[] = array( + 'term' => $this->terms['parent_term'], + 'synonym' => $this->terms['parent_term']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE][0]['value'], + ); + $options[] = array( + 'term' => $this->terms['parent_term'], + 'synonym' => $this->terms['parent_term']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE][1]['value'], + ); + $options[] = array( + 'term' => $this->terms['child_term'], + 'selected' => TRUE, + ); + $options[] = array( + 'term' => $this->terms['child_term'], + 'synonym' => $this->terms['child_term']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE][0]['value'], + ); + $options[] = array( + 'term' => $this->terms['child_term'], + 'synonym' => $this->terms['child_term']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE][1]['value'], + ); + $this->drupalGet('node/' . $node->nid . '/edit'); + $this->assertSynonymsSelect($options, 'Default values are set correctly in the synonyms friendly select widget when working with the field cardinality of 1.'); + } + + /** + * Assert correctness of the synonyms-friendly select widget. + * + * @param array $options + * Array of what options must be present in the select form element. It + * should consist of arrays that follow such structure: + * - term: (object) Term object this option represents + * - synonym: (string) If the option comes from a term, then include it here + * - selected: (bool) Place here TRUE if this option should be selected by + * default + * @param string $message + * Assert message that will be passed on to SimpleTest internals + */ + protected function assertSynonymsSelect($options, $message = '') { + $instance = field_info_instance($this->entity_type, $this->term_reference_field['field_name'], $this->bundle); + $multiple = $this->term_reference_field['cardinality'] > 1 || $this->term_reference_field['cardinality'] == FIELD_CARDINALITY_UNLIMITED; + $required = $instance['required']; + + $element = array( + '#options' => array(), + '#value' => $multiple ? array() : 'nothing', + ); + + if (!$multiple && !$required) { + $element['#options'][''] = t('- None -'); + } + + foreach ($options as $v) { + $key = $this->synonymSelectKey($v['term'], isset($v['synonym']) ? $v['synonym'] : NULL); + $label = $v['term']->name; + if (isset($v['synonym'])) { + $label = format_string($this->behavior_settings['wording'], array( + '@synonym' => $v['synonym'], + '@term'=> $v['term']->name, + '@field_name' => $this->fields['enabled']['instance']['label'], + )); + } + + if (isset($v['selected']) && $v['selected']) { + if ($multiple) { + $element['#value'][] = $key; + } + else { + $element['#value'] = $key; } } + + $depth = count(taxonomy_get_parents_all($v['term']->tid)) - 1; + $element['#options'][$key] = str_repeat('-', $depth) . $label; } + $this->assertRaw('>' . form_select_options($element) . '', $message, 'Synonyms friendly select'); } /** - * Assert search results. + * Form a key for the option of a synonyms friendly select. * - * @param $keyword string - * Keyword to supply to the search mechanism - * @param $results array - * Array of fully loaded nodes that are expected to be on search results - * @param $message string - * Drupal assertion message to display on test results page + * @param object $term + * Fully loaded taxonomy term for which to generate the key + * @param string $synonym + * If the option, whose key is being generated, comes from a synonym, then + * supply it here + * + * @return string + * Key for the option of a synonym friendly select */ - protected function assertSearchResults($keyword, $results, $message) { - $response = $this->drupalGet('search/node/' . $keyword); - $matches = array(); - preg_match_all('#\]+class="search-result"[^>]*\>(.*?)\#si', $response, $matches); - $matches = $matches[1]; - if (count($matches) != count($results)) { - $this->fail($message); - return; + protected function synonymSelectKey($term, $synonym = NULL) { + $key = $term->tid; + if ($synonym) { + $key .= ':' . drupal_html_class($synonym); } - $matches = implode('', $matches); - foreach ($results as $node) { - if (strpos($matches, 'node/' . $node->nid) === FALSE) { + return $key; + } +} + +/** + * Base class for all tests that test Synonyms behavior implementation classes. + */ +abstract class AbstractSynonymsBehaviorWebTestCase extends SynonymsWebTestCase { + + protected $behavior = 'synonyms'; + + /** + * Test synonymsExtract() method. + * + * @param array $items + * Array of field items to be saved in tested term + * @param array $standard + * Expected return of synonymsExtract() method + * @param string $message + * Any custom message to be added to the standard one and passed to + * SimpleTest assertion method + */ + protected function assertSynonymsExtract($items, $standard, $message = '') { + $behavior_implementation = synonyms_behavior_implementation_class('synonyms', $this->fields['enabled']['field']); + $behavior_implementation = new $behavior_implementation(); + + $term = (object) array( + 'name' => $this->randomName(), + 'vid' => $this->vocabulary->vid, + ); + $term->{$this->fields['enabled']['field']['field_name']} = $items; + taxonomy_term_save($term); + $items = field_get_items('taxonomy_term', $term, $this->fields['enabled']['field']['field_name']); + + $synonyms = is_array($items) ? $behavior_implementation->extractSynonyms($items, $this->fields['enabled']['field'], $this->fields['enabled']['instance'], $term, 'taxonomy_term') : array(); + $this->assertTrue(count(array_intersect($standard, $synonyms)) == count($standard), get_class($behavior_implementation) . '::extractSynonyms() passed: ' . $message); + // Cleaning up. + taxonomy_term_delete($term->tid); + } + + /** + * Test mergeEntityAsSynonym method. + * + * @param array $items + * Parameter will be passed directly to the behavior implementation object + * @param object $synonym_entity + * Parameter will be passed directly to the behavior implementation object + * @param string $synonym_entity_type + * Parameter will be passed directly to the behavior implementation object + * @param array $standard + * Array that is expected to be returned by the tested method + * @param string $message + * Any custom message to be added to the standard one and passed to + * SimpleTest assertion method + */ + protected function assertMergeEntityAsSynonym($items, $synonym_entity, $synonym_entity_type, $standard, $message = '') { + $behavior_implementation = synonyms_behavior_implementation_class('synonyms', $this->fields['enabled']['field']); + $behavior_implementation = new $behavior_implementation(); + + $message = get_class($behavior_implementation) . '::mergeEntityAsSynonym() passed: ' . $message; + + $extra_items = $behavior_implementation->mergeEntityAsSynonym($items, $this->fields['enabled']['field'], $this->fields['enabled']['instance'], $synonym_entity, $synonym_entity_type); + foreach ($standard as $k => $v) { + if (count(array_intersect($standard[$k], $extra_items[$k])) != count($standard[$k])) { $this->fail($message); return; } } $this->pass($message); } -} -/** - * Base class for all test cases that test Synonyms Extractor classes. - */ -abstract class AbstractSynonymsExtractorWebTestCase extends DrupalWebTestCase { /** - * Taxonomy vocabulary object on whose term all synonyms extracting tests will - * occur. - * - * @var object - */ - protected $vocabulary; - - /** - * Class name of a synonyms extractor that is being tested. - * - * @var string - */ - protected $extractor; - - /** - * Field API field definition array of the field that is tested right now. - * - * @var array - */ - protected $field; - - /** - * Field API instance definition array of the instance that is tested now. - * - * @var array - */ - protected $instance; - - /** - * SetUp method. - * - * @param string $class - * Name of class that is being tested - * @param array $modules - * Array of extra modules to install for testing a particular synonyms - * extractor - */ - public function setUp($class, $modules = array()) { - array_unshift($modules, 'synonyms'); - parent::setUp($modules); - - $this->vocabulary = (object) array( - 'name' => 'Test Synonyms Extractor', - 'machine_name' => 'synonyms_extractor', - ); - taxonomy_vocabulary_save($this->vocabulary); - - $this->extractor = $class; - } - - /** - * Completely set field in the tested vocabulary. - * - * Create a field in Field API (if does not exist yet), then create an - * instance and relate it to our tested vocabulary. Lastly enable this field - * as a source of synonyms for our tested vocabulary. - * - * @param array $field - * Field definition array as expected by Field API - * @param array $instance - * Instance definition array as expected by Field API - */ - protected function addFieldInstance($field, $instance) { - $field = field_create_field($field); - $instance['entity_type'] = 'taxonomy_term'; - $instance['bundle'] = field_extract_bundle('taxonomy_term', $this->vocabulary); - field_create_instance($instance); - $settings = synonyms_vocabulary_settings($this->vocabulary); - $settings['synonyms'][] = $field['field_name']; - synonyms_vocabulary_settings_save($this->vocabulary, $settings); - $this->field = $field; - $this->instance = $instance; - } - - /** - * Test synonymsExtract() method of class. - * - * @param array $items - * Array of field items to be saved in tested term - * @param array $etalon - * Expected return of synonymsExtract() method of class - * @param string $message - * Any custom message to be added to the standard one and passed to - * SimpleTest assertion method - */ - protected function assertSynonymsExtract($items, $etalon, $message = '') { - $term = (object) array( - 'name' => $this->randomName(), - 'vid' => $this->vocabulary->vid, - ); - $term->{$this->field['field_name']} = $items; - taxonomy_term_save($term); - $items = field_get_items('taxonomy_term', $term, $this->field['field_name']); - - $synonyms = is_array($items) ? call_user_func(array($this->extractor, 'synonymsExtract'), $items, $this->field, $this->instance, $term, 'taxonomy_term') : array(); - $this->assertTrue(count(array_intersect($etalon, $synonyms)) == count($etalon), 'Synonyms Extractor ' . $this->extractor . ' passed synonymsExtract() method: ' . $message); - // Cleaning up. - taxonomy_term_delete($term->tid); - } - - /** - * Test processEntityFieldQuery method of class. + * Test synonymFind method. * * @param array $meta_data * Array of meta data. Each subarray represents a single term and whether it - * is expected to be included in the results of EntityFieldQuery. Should - * have the following structure: - * items - array of field items. Terms will be automatically created with - * those items - * expected - bool, whether the created term should be expected in the - * results of EntityFieldQuery - * @param string $tag - * Corresponding parameter to be passed to processEntityFieldQuery() method + * is expected to be included in the return of the method. Should have the + * following structure: + * - items: (array) Array of field items. Terms will be automatically + * created with those items + * - found_synonyms: (array) Array of synonyms that are expected to be found + * for the given term, i.e. if "found_synonyms" is empty, it means the + * term should not be found for the given $condition. If the + * "found_synonyms" is not empty, then each of the elements in this array + * should trigger appearance of the term in the results for the given + * $condition + * @param QueryConditionInterface $condition + * Database condition that will be passed to the synonymsFind method * @param string $message * Any custom message to be added to the standard one and passed to * SimpleTest assertion method */ - protected function assertProcessEntityFieldQuery($meta_data, $tag, $message = '') { - // Creating taxonomy terms according to the meta data. + protected function assertSynonymsFind($meta_data, QueryConditionInterface $condition, $message = '') { + $behavior_implementation = synonyms_behavior_implementation_class('synonyms', $this->fields['enabled']['field']); + $behavior_implementation = new $behavior_implementation(); + + $message = get_class($behavior_implementation) . '::synonymsFind() pass: ' . $message; + $terms = array(); foreach ($meta_data as $v) { $term = (object) array( 'name' => $this->randomName(), 'vid' => $this->vocabulary->vid, + $this->fields['enabled']['field']['field_name'] => $v['items'], ); taxonomy_term_save($term); - $term->{$this->field['field_name']} = $v['items']; - taxonomy_term_save($term); - $term->expected = $v['expected']; + $term->found_synonyms = $v['found_synonyms']; $terms[] = $term; } - // Preparing and running EntityFieldQuery. - $efq = new EntityFieldQuery(); - $efq->entityCondition('entity_type', 'taxonomy_term') - ->entityCondition('bundle', $this->vocabulary->machine_name); - call_user_func(array($this->extractor, 'processEntityFieldQuery'), $tag, $efq, $this->field, $this->instance); - $result = $efq->execute(); - $result = isset($result['taxonomy_term']) ? array_keys($result['taxonomy_term']) : array(); - // Asserting results of EntityFieldQuery. - $pass = TRUE; - foreach ($terms as $term) { - $tmp = $term->expected ? in_array($term->tid, $result) : !in_array($term->tid, $result); - $pass = $pass && $tmp; + + $return = $behavior_implementation->synonymsFind($condition, $this->fields['enabled']['field'], $this->fields['enabled']['instance']); + + $rows = array(); + foreach ($return as $row) { + $rows[] = $row; } - $this->assertTrue($pass, 'Synonyms Extractor ' . $this->extractor . ' passed processEntityFieldQuery() method: ' . $message); + + $success = TRUE; + $total_rows_standard = 0; + $total_rows = 0; + foreach ($terms as $term) { + foreach ($term->found_synonyms as $found_synonym) { + $total_rows_standard++; + $is_found = FALSE; + $total_rows = 0; + foreach ($rows as $row) { + $total_rows++; + if ($row->entity_id == $term->tid && $row->synonym == $found_synonym) { + $is_found = TRUE; + } + } + $success = $success && $is_found; + } + } + + $success = $success && $total_rows_standard == $total_rows; + + $this->assertTrue($success, $message); + // Cleaning up. foreach ($terms as $term) { taxonomy_term_delete($term->tid); } } - - /** - * Test mergeEntityAsSynonym method of class. - * - * @param array $items - * Parameter will be passed directly to the extractor class method - * @param object $synonym_entity - * Parameter will be passed directly to the extractor class method - * @param string $synonym_entity_type - * Parameter will be passed directly to the extractor class method - * @param array $etalon - * Array that is expected to be returned by the tested method - * @param string $message - * Any custom message to be added to the standard one and passed to - * SimpleTest assertion method - */ - protected function assertMergeEntityAsSynonym($items, $synonym_entity, $synonym_entity_type, $etalon, $message = '') { - $extra_items = call_user_func(array($this->extractor, 'mergeEntityAsSynonym'), $items, $this->field, $this->instance, $synonym_entity, $synonym_entity_type); - foreach ($etalon as $k => $v) { - if (count(array_intersect($etalon[$k], $extra_items[$k])) != count($etalon[$k])) { - $this->fail('Synonyms Extractor ' . $this->extractor . ' passed mergeEntityAsSynonym() method: ' . $message); - return; - } - } - $this->pass('Synonyms Extractor ' . $this->extractor . ' passed mergeEntityAsSynonym() method: ' . $message); - } } /** - * Test SynonymsSynonymsExtractor class. + * Test TextSynonymsBehavior class. */ -class SynonymsSynonymsExtractorWebTestCase extends AbstractSynonymsExtractorWebTestCase { +class TextSynonymsBehaviorWebTestCase extends AbstractSynonymsBehaviorWebTestCase { /** * GetInfo method. */ - public function getInfo() { + public static function getInfo() { return array( - 'name' => 'SynonymsSynonymsExtractor', + 'name' => 'TextSynonymsBehavior', 'description' => 'Ensure that the synonyms module extracts synonyms from text and number fields correctly.', 'group' => 'Synonyms', ); } - /** - * SetUp method. - */ - public function setUp() { - parent::setUp('SynonymsSynonymsExtractor'); - } - /** * Test synonyms extraction for 'text' field type. */ public function testText() { - $this->addFieldInstance(array( - 'field_name' => 'text', - 'cardinality' => FIELD_CARDINALITY_UNLIMITED, - 'type' => 'text', - ), array( - 'field_name' => 'text', - 'entity_type' => 'taxonomy_term', - 'bundle' => $this->vocabulary->machine_name, - 'label' => $this->randomName(), - )); - // Testing synonymsExtract(). $this->assertSynonymsExtract(array(), array(), 'on empty field.'); @@ -1113,34 +1415,6 @@ class SynonymsSynonymsExtractorWebTestCase extends AbstractSynonymsExtractorWebT ), ), array($synonym), 'on a field that holds one value.'); - // Testing processEntityFieldQuery(). - $this->assertProcessEntityFieldQuery(array(), $this->randomName(), 'on empty field.'); - - $tag = $this->randomName(); - $meta_data = array(); - $meta_data[] = array( - 'items' => array( - LANGUAGE_NONE => array( - 0 => array('value' => $tag . $this->randomName()), - 1 => array('value' => $this->randomName()), - ), - ), - 'expected' => TRUE, - ); - $meta_data[] = array( - 'items' => array( - LANGUAGE_NONE => array( - 0 => array('value' => $this->randomName()), - ), - ), - 'expected' => FALSE, - ); - $meta_data[] = array( - 'items' => array(), - 'expected' => FALSE, - ); - $this->assertProcessEntityFieldQuery($meta_data, $tag, 'on a field with values.'); - // Testing mergeEntityAsSynonym() method. $node = (object) array( 'title' => $this->randomName(), @@ -1148,14 +1422,170 @@ class SynonymsSynonymsExtractorWebTestCase extends AbstractSynonymsExtractorWebT ); node_save($node); $this->assertMergeEntityAsSynonym(array(), $node, 'node', array(array('value' => $node->title)), 'on a node entity.'); - } + // Testing synonymFind() method. + $this->assertSynonymsFind(array(), db_and()->condition(AbstractSynonymsSynonymsBehavior::COLUMN_PLACEHOLDER, $this->randomName()), 'on empty field.'); + + $meta_data = array(); + $meta_data[] = array( + 'items' => array(), + 'found_synonyms' => array(), + ); + $this->assertSynonymsFind($meta_data, db_and()->condition(AbstractSynonymsSynonymsBehavior::COLUMN_PLACEHOLDER, $this->randomName()), 'on a field without values.'); + + $meta_data = array(); + $meta_data[] = array( + 'items' => array( + LANGUAGE_NONE => array( + array('value' => $this->randomName()), + ), + ), + 'found_synonyms' => array(), + ); + $this->assertSynonymsFind($meta_data, db_and()->condition(AbstractSynonymsSynonymsBehavior::COLUMN_PLACEHOLDER, $this->randomName()), 'on a field with a value, but when searching for another string.'); + + $meta_data = array(); + $synonym = $this->randomName(); + $meta_data[] = array( + 'items' => array( + LANGUAGE_NONE => array( + array('value' => $synonym), + ), + ), + 'found_synonyms' => array($synonym), + ); + $this->assertSynonymsFind($meta_data, db_and()->condition(AbstractSynonymsSynonymsBehavior::COLUMN_PLACEHOLDER, $synonym), 'on a field with a single value searching for that string'); + + $meta_data = array(); + $synonym = $this->randomName(); + $meta_data[] = array( + 'items' => array( + LANGUAGE_NONE => array( + array('value' => $synonym), + array('value' => $this->randomName()), + ), + ), + 'found_synonyms' => array($synonym), + ); + $this->assertSynonymsFind($meta_data, db_and()->condition(AbstractSynonymsSynonymsBehavior::COLUMN_PLACEHOLDER, $synonym), 'on a field with 2 values searching for one of those 2 values'); + + $meta_data = array(); + $synonym = $this->randomName(); + $meta_data[] = array( + 'items' => array( + LANGUAGE_NONE => array( + array('value' => $synonym), + array('value' => $this->randomName()), + ), + ), + 'found_synonyms' => array($synonym), + ); + $meta_data[] = array( + 'items' => array( + LANGUAGE_NONE => array( + array('value' => $this->randomName()), + array('value' => $this->randomName()), + ), + ), + 'found_synonyms' => array(), + ); + $this->assertSynonymsFind($meta_data, db_and()->condition(AbstractSynonymsSynonymsBehavior::COLUMN_PLACEHOLDER, $synonym), 'on 2 fields with 2 values each searching for one of those values'); + + $meta_data = array(); + $synonym = $this->randomName(); + $meta_data[] = array( + 'items' => array( + LANGUAGE_NONE => array( + array('value' => $synonym), + ), + ), + 'found_synonyms' => array($synonym), + ); + $this->assertSynonymsFind($meta_data, db_and()->condition(AbstractSynonymsSynonymsBehavior::COLUMN_PLACEHOLDER, '%' . db_like(drupal_substr($synonym, 1, -1)) . '%', 'LIKE'), 'on a field with a value searching for a string LIKE the %value%'); + + $meta_data = array(); + $tag = $this->randomName(); + $synonym1 = $tag . $this->randomName(); + $synonym2 = $tag . $this->randomName(); + $meta_data[] = array( + 'items' => array( + LANGUAGE_NONE => array( + array('value' => $synonym1), + array('value' => $synonym2), + ), + ), + 'found_synonyms' => array($synonym1, $synonym2), + ); + $this->assertSynonymsFind($meta_data, db_and()->condition(AbstractSynonymsSynonymsBehavior::COLUMN_PLACEHOLDER, db_like($tag) . '%', 'LIKE'), 'on a field with 2 similar values searching a string like %both values%'); + + $meta_data = array(); + $synonym1 = $this->randomName(); + $synonym2 = $this->randomName(); + $meta_data[] = array( + 'items' => array( + LANGUAGE_NONE => array( + array('value' => $synonym1), + array('value' => $synonym2), + ), + ), + 'found_synonyms' => array($synonym1, $synonym2), + ); + $this->assertSynonymsFind($meta_data, db_or()->condition(AbstractSynonymsSynonymsBehavior::COLUMN_PLACEHOLDER, $synonym1)->condition(AbstractSynonymsSynonymsBehavior::COLUMN_PLACEHOLDER, $synonym2), 'on a field with 2 values searching for value1 or value2'); + + $meta_data = array(); + $synonym = $this->randomName(); + $meta_data[] = array( + 'items' => array( + LANGUAGE_NONE => array( + array('value' => $synonym), + array('value' => $this->randomName()), + ), + ), + 'found_synonyms' => array($synonym), + ); + $this->assertSynonymsFind($meta_data, db_and(AbstractSynonymsSynonymsBehavior::COLUMN_PLACEHOLDER, $synonym)->condition(AbstractSynonymsSynonymsBehavior::COLUMN_PLACEHOLDER, db_like(drupal_substr($synonym, 0, -1)) . '%', 'LIKE'), 'on a field with 2 values searching for value1 and LIKE value1%'); + + $meta_data = array(); + $synonym1 = $this->randomName(); + $synonym2 = $this->randomName(); + $meta_data[] = array( + 'items' => array( + LANGUAGE_NONE => array( + array('value' => $synonym1), + array('value' => $synonym2), + ), + ), + 'found_synonyms' => array($synonym1, $synonym2), + ); + $condition = db_or(); + $condition->condition(AbstractSynonymsSynonymsBehavior::COLUMN_PLACEHOLDER, $synonym1); + $condition->condition(db_and()->condition(AbstractSynonymsSynonymsBehavior::COLUMN_PLACEHOLDER, $synonym2)->condition(AbstractSynonymsSynonymsBehavior::COLUMN_PLACEHOLDER, db_like(drupal_substr($synonym2, 0, -1)) . '%', 'LIKE')); + $this->assertSynonymsFind($meta_data, $condition, 'on a field with 2 values searching for (value1 or (value2 AND value2%))'); + + $meta_data = array(); + $synonym1 = $this->randomName() . ' ' . $this->randomName() . ' ' . $this->randomName(); + $synonym2 = str_replace(' ', '-', $synonym1); + $meta_data[] = array( + 'items' => array( + LANGUAGE_NONE => array( + array('value' => $synonym1), + array('value' => $synonym2), + ), + ), + 'found_synonyms' => array($synonym1, $synonym2), + ); + $condition = db_and() + ->where("REPLACE(" . AbstractSynonymsSynonymsBehavior::COLUMN_PLACEHOLDER . ", ' ', '-') = :synonym", array( + ':synonym' => $synonym2, + )); + $this->assertSynonymsFind($meta_data, $condition, "on a field with 2 values, where 2nd value replaces spaces with dashes in the 1st value, searching for REPLACE(column, ' ', '-') = value2"); + } } /** - * Test TaxonomySynonymsExtractor class. + * Test TaxonomySynonymsBehavior class. */ -class TaxonomySynonymsExtractorWebTestCase extends AbstractSynonymsExtractorWebTestCase { +class TaxonomySynonymsBehaviorWebTestCase extends AbstractSynonymsBehaviorWebTestCase { /** * Taxonomy vocabulary object terms. @@ -1169,9 +1599,9 @@ class TaxonomySynonymsExtractorWebTestCase extends AbstractSynonymsExtractorWebT /** * GetInfo method. */ - public function getInfo() { + public static function getInfo() { return array( - 'name' => 'TaxonomySynonymsExtractor', + 'name' => 'TaxonomySynonymsBehavior', 'description' => 'Ensure that the synonyms module extracts synonyms from taxonomy term reference fields correctly.', 'group' => 'Synonyms', ); @@ -1180,8 +1610,22 @@ class TaxonomySynonymsExtractorWebTestCase extends AbstractSynonymsExtractorWebT /** * SetUp method. */ - public function setUp() { - parent::setUp('TaxonomySynonymsExtractor'); + public function setUp($modules = array()) { + $this->fields['enabled']['field'] = array( + 'field_name' => 'term', + 'cardinality' => FIELD_CARDINALITY_UNLIMITED, + 'type' => 'taxonomy_term_reference', + 'settings' => array( + 'allowed_values' => array( + array( + 'vocabulary' => 'source_vocabulary', + 'parent' => 0, + ), + ), + ), + ); + parent::setUp($modules); + $this->vocabularySource = (object) array( 'name' => $this->randomName(), 'machine_name' => 'source_vocabulary', @@ -1193,25 +1637,6 @@ class TaxonomySynonymsExtractorWebTestCase extends AbstractSynonymsExtractorWebT * Test synonyms extraction for 'taxonomy_term_reference' field type. */ public function testTaxonomyTermReference() { - $this->addFieldInstance(array( - 'field_name' => 'term', - 'cardinality' => FIELD_CARDINALITY_UNLIMITED, - 'type' => 'taxonomy_term_reference', - 'settings' => array( - 'allowed_values' => array( - array( - 'vocabulary' => $this->vocabularySource->machine_name, - 'parent' => 0, - ), - ), - ), - ), array( - 'field_name' => 'term', - 'entity_type' => 'taxonomy_term', - 'bundle' => $this->vocabulary->machine_name, - 'label' => $this->randomName(), - )); - // Testing synonymsExtract(). $this->assertSynonymsExtract(array(), array(), 'on empty field.'); @@ -1224,37 +1649,168 @@ class TaxonomySynonymsExtractorWebTestCase extends AbstractSynonymsExtractorWebT ), ), array($synonym_term->name), 'on a field that holds one value.'); - // Testing processEntityFieldQuery(). - $this->assertProcessEntityFieldQuery(array(), $this->randomName(), 'on empty field.'); + // Testing mergeEntityAsSynonym() method. + $synonym_term = $this->createSynonymTerm(); + $this->assertMergeEntityAsSynonym(array(), $synonym_term, 'taxonomy_term', array(array('tid' => $synonym_term->tid)), 'on a term from referenced vocabulary.'); + + // Testing synonymFind() method. + $this->assertSynonymsFind(array(), db_and()->condition(AbstractSynonymsSynonymsBehavior::COLUMN_PLACEHOLDER, $this->randomName()), 'on empty field'); + + $meta_data = array(); + $meta_data[] = array( + 'items' => array(), + 'found_synonyms' => array(), + ); + $this->assertSynonymsFind($meta_data, db_and()->condition(AbstractSynonymsSynonymsBehavior::COLUMN_PLACEHOLDER, $this->randomName()), 'on a field without values'); - $tag = $this->randomName(); $meta_data = array(); $meta_data[] = array( 'items' => array( LANGUAGE_NONE => array( - array('tid' => $this->createSynonymTerm($tag . $this->randomName())->tid), array('tid' => $this->createSynonymTerm()->tid), ), ), - 'expected' => TRUE, + 'found_synonyms' => array(), + ); + $this->assertSynonymsFind($meta_data, db_and()->condition(AbstractSynonymsSynonymsBehavior::COLUMN_PLACEHOLDER, $this->randomName()), 'on a field with a value but searching for another string'); + + $meta_data = array(); + $synonym_term = $this->createSynonymTerm(); + $meta_data[] = array( + 'items' => array( + LANGUAGE_NONE => array( + array('tid' => $synonym_term->tid), + ), + ), + 'found_synonyms' => array($synonym_term->name), + ); + $this->assertSynonymsFind($meta_data, db_and()->condition(AbstractSynonymsSynonymsBehavior::COLUMN_PLACEHOLDER, $synonym_term->name), 'on a field with a single value searching for that string'); + + $meta_data = array(); + $synonym_term = $this->createSynonymTerm(); + $meta_data[] = array( + 'items' => array( + LANGUAGE_NONE => array( + array('tid' => $this->createSynonymTerm()->tid), + array('tid' => $synonym_term->tid), + ), + ), + 'found_synonyms' => array($synonym_term->name), + ); + $this->assertSynonymsFind($meta_data, db_and()->condition(AbstractSynonymsSynonymsBehavior::COLUMN_PLACEHOLDER, $synonym_term->name), 'on a field with 2 values searching for one of those 2 values'); + + $meta_data = array(); + $synonym_term = $this->createSynonymTerm(); + $meta_data[] = array( + 'items' => array( + LANGUAGE_NONE => array( + array('tid' => $synonym_term->tid), + array('tid' => $this->createSynonymTerm()->tid), + ), + ), + 'found_synonyms' => array($synonym_term->name), ); $meta_data[] = array( 'items' => array( LANGUAGE_NONE => array( array('tid' => $this->createSynonymTerm()->tid), + array('tid' => $this->createSynonymTerm()->tid), ), ), - 'expected' => FALSE, + 'found_synonyms' => array(), ); - $meta_data[] = array( - 'items' => array(), - 'expected' => FALSE, - ); - $this->assertProcessEntityFieldQuery($meta_data, $tag, 'on a field with values.'); + $this->assertSynonymsFind($meta_data, db_and()->condition(AbstractSynonymsSynonymsBehavior::COLUMN_PLACEHOLDER, $synonym_term->name), 'on 2 fields with 2 values each searching for one of those values'); - // Testing mergeEntityAsSynonym() method. + $meta_data = array(); $synonym_term = $this->createSynonymTerm(); - $this->assertMergeEntityAsSynonym(array(), $synonym_term, 'taxonomy_term', array(array('tid' => $synonym_term->tid)), 'on a term from referenced vocabulary.'); + $meta_data[] = array( + 'items' => array( + LANGUAGE_NONE => array( + array('tid' => $synonym_term->tid), + ), + ), + 'found_synonyms' => array($synonym_term->name), + ); + $this->assertSynonymsFind($meta_data, db_and()->condition(AbstractSynonymsSynonymsBehavior::COLUMN_PLACEHOLDER, '%' . db_like(drupal_substr($synonym_term->name, 1, -1)) . '%', 'LIKE'), 'on a field with a value searching for a string LIKE the %value%'); + + $meta_data = array(); + $tag = $this->randomName(); + $synonym_term1 = $this->createSynonymTerm($tag . $this->randomName()); + $synonym_term2 = $this->createSynonymTerm($tag . $this->randomName()); + $meta_data[] = array( + 'items' => array( + LANGUAGE_NONE => array( + array('tid' => $synonym_term1->tid), + array('tid' => $synonym_term2->tid), + ), + ), + 'found_synonyms' => array($synonym_term1->name, $synonym_term2->name), + ); + $this->assertSynonymsFind($meta_data, db_and()->condition(AbstractSynonymsSynonymsBehavior::COLUMN_PLACEHOLDER, db_like($tag) . '%', 'LIKE'), 'on a field with 2 similar values searching a string like %both values%'); + + $meta_data = array(); + $synonym_term1 = $this->createSynonymTerm(); + $synonym_term2 = $this->createSynonymTerm(); + $meta_data[] = array( + 'items' => array( + LANGUAGE_NONE => array( + array('tid' => $synonym_term1->tid), + array('tid' => $synonym_term2->tid), + ), + ), + 'found_synonyms' => array($synonym_term1->name, $synonym_term2->name), + ); + $this->assertSynonymsFind($meta_data, db_or()->condition(AbstractSynonymsSynonymsBehavior::COLUMN_PLACEHOLDER, $synonym_term1->name)->condition(AbstractSynonymsSynonymsBehavior::COLUMN_PLACEHOLDER, $synonym_term2->name), 'on a field with 2 values searching for value1 or value2'); + + $meta_data = array(); + $synonym_term = $this->createSynonymTerm(); + $meta_data[] = array( + 'items' => array( + LANGUAGE_NONE => array( + array('tid' => $synonym_term->tid), + array('tid' => $this->createSynonymTerm()->tid), + ), + ), + 'found_synonyms' => array($synonym_term->name), + ); + $this->assertSynonymsFind($meta_data, db_and()->condition(AbstractSynonymsSynonymsBehavior::COLUMN_PLACEHOLDER, $synonym_term->name)->condition(AbstractSynonymsSynonymsBehavior::COLUMN_PLACEHOLDER, '%' . db_like(drupal_substr($synonym_term->name, 1, -1)) . '%', 'LIKE'), 'on a field with 2 values searching for value1 and LIKE value1%'); + + $meta_data = array(); + $synonym_term1 = $this->createSynonymTerm(); + $synonym_term2 = $this->createSynonymTerm(); + $meta_data[] = array( + 'items' => array( + LANGUAGE_NONE => array( + array('tid' => $synonym_term1->tid), + array('tid' => $synonym_term2->tid), + ), + ), + 'found_synonyms' => array($synonym_term1->name, $synonym_term2->name), + ); + $condition = db_or(); + $condition->condition(AbstractSynonymsSynonymsBehavior::COLUMN_PLACEHOLDER, $synonym_term1->name); + $condition->condition(db_and() + ->condition(AbstractSynonymsSynonymsBehavior::COLUMN_PLACEHOLDER, $synonym_term2->name) + ->condition(AbstractSynonymsSynonymsBehavior::COLUMN_PLACEHOLDER, '%' . db_like(drupal_substr($synonym_term2->name, 1 -1)) . '%', 'LIKE')); + $this->assertSynonymsFind($meta_data, $condition, 'on a field with 2 values searching for (value1 or (value2 AND value2%))'); + + $meta_data = array(); + $synonym_term1 = $this->createSynonymTerm($this->randomName() . ' ' . $this->randomName() . ' ' . $this->randomName()); + $synonym_term2 = $this->createSynonymTerm(str_replace(' ', '-', $synonym_term1->name)); + $meta_data[] = array( + 'items' => array( + LANGUAGE_NONE => array( + array('tid' => $synonym_term1->tid), + array('tid' => $synonym_term2->tid), + ), + ), + 'found_synonyms' => array($synonym_term1->name, $synonym_term2->name), + ); + $condition = db_and() + ->where("REPLACE(" . AbstractSynonymsSynonymsBehavior::COLUMN_PLACEHOLDER . ", ' ', '-') = :synonym", array( + ':synonym' => $synonym_term2->name, + )); + $this->assertSynonymsFind($meta_data, $condition, "on a field with 2 values, where 2nd value replaces spaces with dashes in the 1st value, searching for REPLACE(column, ' ', '-') = value2"); } /** @@ -1284,16 +1840,16 @@ class TaxonomySynonymsExtractorWebTestCase extends AbstractSynonymsExtractorWebT } /** - * Test EntityReferenceSynonymsExtractor class. + * Test EntityReferenceSynonymsBehavior class. */ -class EntityReferenceSynonymsExtractorWebTestCase extends AbstractSynonymsExtractorWebTestCase { +class EntityReferenceSynonymsBehaviorWebTestCase extends AbstractSynonymsBehaviorWebTestCase { /** * GetInfo method. */ - public function getInfo() { + public static function getInfo() { return array( - 'name' => 'EntityReferenceSynonymsExtractor', + 'name' => 'EntityReferenceSynonymsBehavior', 'description' => 'Ensure that the synonyms module extracts synonyms from entity reference fields correctly.', 'group' => 'Synonyms', ); @@ -1302,15 +1858,10 @@ class EntityReferenceSynonymsExtractorWebTestCase extends AbstractSynonymsExtrac /** * SetUp method. */ - public function setUp() { - parent::setUp('EntityReferenceSynonymsExtractor', array('entityreference')); - } + public function setUp($modules = array()) { + $modules[] = 'entityreference'; - /** - * Test synonyms extraction for 'entityreference' field type. - */ - public function testEntityReference() { - $this->addFieldInstance(array( + $this->fields['enabled']['field'] = array( 'field_name' => 'reference', 'cardinality' => FIELD_CARDINALITY_UNLIMITED, 'type' => 'entityreference', @@ -1324,13 +1875,15 @@ class EntityReferenceSynonymsExtractorWebTestCase extends AbstractSynonymsExtrac 'sort' => array('type' => 'none'), ), ), - ), array( - 'field_name' => 'reference', - 'entity_type' => 'taxonomy_term', - 'bundle' => $this->vocabulary->machine_name, - 'label' => $this->randomName(), - )); + ); + parent::setUp($modules); + } + + /** + * Test synonyms extraction for 'entityreference' field type. + */ + public function testEntityReference() { // Testing synonymsExtract(). $this->assertSynonymsExtract(array(), array(), 'on empty field.'); @@ -1343,41 +1896,184 @@ class EntityReferenceSynonymsExtractorWebTestCase extends AbstractSynonymsExtrac ), ), array(entity_label('node', $synonym_entity)), 'on a field that holds one value.'); - // Testing processEntityFieldQuery(). - $this->assertProcessEntityFieldQuery(array(), $this->randomName(), 'on empty field.'); + // Testing mergeEntityAsSynonym() method. + $node = $this->createNode(); + $this->assertMergeEntityAsSynonym(array(), $node, 'node', array(array('target_id' => $node->nid)), 'on a node.'); + + // Testing synonymFind() method. + $this->assertSynonymsFind(array(), db_and()->condition(AbstractSynonymsSynonymsBehavior::COLUMN_PLACEHOLDER, $this->randomName()), 'on empty field'); - $tag = $this->randomName(); $meta_data = array(); $meta_data[] = array( - 'items' => array( - LANGUAGE_NONE => array( - array( - 'target_id' => entity_id('node', $this->createNode($tag . $this->randomName())), - ), - array( - 'target_id' => entity_id('node', $this->createNode()), - ), - ), - ), - 'expected' => TRUE, + 'items' => array(), + 'found_synonyms' => array(), ); + $this->assertSynonymsFind($meta_data, db_and()->condition(AbstractSynonymsSynonymsBehavior::COLUMN_PLACEHOLDER, $this->randomName()), 'on a field without values'); + + $meta_data = array(); $meta_data[] = array( 'items' => array( LANGUAGE_NONE => array( array('target_id' => entity_id('node', $this->createNode())), ), ), - 'expected' => FALSE, + 'found_synonyms' => array(), + ); + $this->assertSynonymsFind($meta_data, db_and()->condition(AbstractSynonymsSynonymsBehavior::COLUMN_PLACEHOLDER, $this->randomName()), 'on a field with a value but searching for another string'); + + $meta_data = array(); + $synonym_entity = $this->createNode(); + $meta_data[] = array( + 'items' => array( + LANGUAGE_NONE => array( + array('target_id' => entity_id('node', $synonym_entity)), + ), + ), + 'found_synonyms' => array(entity_label('node', $synonym_entity)), + ); + $this->assertSynonymsFind($meta_data, db_and()->condition(AbstractSynonymsSynonymsBehavior::COLUMN_PLACEHOLDER, entity_label('node', $synonym_entity)), 'on a field with a single value searching for that string'); + + $meta_data = array(); + $synonym_entity = $this->createNode(); + $meta_data[] = array( + 'items' => array( + LANGUAGE_NONE => array( + array('target_id' => entity_id('node', $synonym_entity)), + array('target_id' => entity_id('node', $this->createNode())), + ), + ), + 'found_synonyms' => array(entity_label('node', $synonym_entity)), + ); + $this->assertSynonymsFind($meta_data, db_and()->condition(AbstractSynonymsSynonymsBehavior::COLUMN_PLACEHOLDER, entity_label('node', $synonym_entity)), 'on a field with 2 values searching for one of those 2 values'); + + $meta_data = array(); + $synonym_entity = $this->createNode(); + $meta_data[] = array( + 'items' => array( + LANGUAGE_NONE => array( + array('target_id' => entity_id('node', $synonym_entity)), + array('target_id' => entity_id('node', $this->createNode())), + ), + ), + 'found_synonyms' => array(entity_label('node', $synonym_entity)), ); $meta_data[] = array( - 'items' => array(), - 'expected' => FALSE, + 'items' => array( + LANGUAGE_NONE => array( + array('target_id' => entity_id('node', $this->createNode())), + array('target_id' => entity_id('node', $this->createNode())), + ), + ), + 'found_synonyms' => array(), ); - $this->assertProcessEntityFieldQuery($meta_data, $tag, 'on a field with values.'); + $this->assertSynonymsFind($meta_data, db_and()->condition(AbstractSynonymsSynonymsBehavior::COLUMN_PLACEHOLDER, entity_label('node', $synonym_entity)), 'on 2 fields with 2 values each searching for one of those values'); - // Testing mergeEntityAsSynonym() method. - $node = $this->createNode(); - $this->assertMergeEntityAsSynonym(array(), $node, 'node', array(array('target_id' => $node->nid)), 'on a node.'); + $meta_data = array(); + $synonym_entity = $this->createNode(); + $meta_data[] = array( + 'items' => array( + LANGUAGE_NONE => array( + array('target_id' => entity_id('node', $synonym_entity)), + ), + ), + 'found_synonyms' => array(entity_label('node', $synonym_entity)), + ); + $this->assertSynonymsFind($meta_data, db_and()->condition(AbstractSynonymsSynonymsBehavior::COLUMN_PLACEHOLDER, '%' . db_like(drupal_substr(entity_label('node', $synonym_entity), 1, -1)) . '%', 'LIKE'), 'on a field with a value searching for a string LIKE the %value%'); + + $meta_data = array(); + $tag = $this->randomName(); + $synonym_entity1 = $this->createNode($tag . $this->randomName()); + $synonym_entity2 = $this->createNode($tag . $this->randomName()); + $meta_data[] = array( + 'items' => array( + LANGUAGE_NONE => array( + array('target_id' => entity_id('node', $synonym_entity1)), + array('target_id' => entity_id('node', $synonym_entity2)), + ), + ), + 'found_synonyms' => array( + entity_label('node', $synonym_entity1), + entity_label('node', $synonym_entity2), + ), + ); + $this->assertSynonymsFind($meta_data, db_and()->condition(AbstractSynonymsSynonymsBehavior::COLUMN_PLACEHOLDER, db_like($tag) . '%', 'LIKE'), 'on a field with 2 similar values searching a string like %both values%'); + + $meta_data = array(); + $synonym_entity1 = $this->createNode(); + $synonym_entity2 = $this->createNode(); + $meta_data[] = array( + 'items' => array( + LANGUAGE_NONE => array( + array('target_id' => entity_id('node', $synonym_entity1)), + array('target_id' => entity_id('node', $synonym_entity2)), + ), + ), + 'found_synonyms' => array( + entity_label('node', $synonym_entity1), + entity_label('node', $synonym_entity2), + ), + ); + $condition = db_or() + ->condition(AbstractSynonymsSynonymsBehavior::COLUMN_PLACEHOLDER, entity_label('node', $synonym_entity1)) + ->condition(AbstractSynonymsSynonymsBehavior::COLUMN_PLACEHOLDER, entity_label('node', $synonym_entity2)); + $this->assertSynonymsFind($meta_data, $condition, 'on a field with 2 values searching for value1 or value2'); + + $meta_data = array(); + $synonym_entity = $this->createNode(); + $meta_data[] = array( + 'items' => array( + LANGUAGE_NONE => array( + array('target_id' => entity_id('node', $synonym_entity)), + array('target_id' => entity_id('node', $this->createNode())), + ), + ), + 'found_synonyms' => array(entity_label('node', $synonym_entity)), + ); + $condition = db_and() + ->condition(AbstractSynonymsSynonymsBehavior::COLUMN_PLACEHOLDER, entity_label('node', $synonym_entity)) + ->condition(AbstractSynonymsSynonymsBehavior::COLUMN_PLACEHOLDER, '%' . db_like(drupal_substr(entity_label('node', $synonym_entity), 1, -1)) . '%', 'LIKE'); + $this->assertSynonymsFind($meta_data, $condition, 'on a field with 2 values searching for value1 and LIKE value1%'); + + $meta_data = array(); + $synonym_entity1 = $this->createNode(); + $synonym_entity2 = $this->createNode(); + $meta_data[] = array( + 'items' => array( + LANGUAGE_NONE => array( + array('target_id' => entity_id('node', $synonym_entity1)), + array('target_id' => entity_id('node', $synonym_entity2)), + ), + ), + 'found_synonyms' => array( + entity_label('node', $synonym_entity1), + entity_label('node', $synonym_entity2), + ), + ); + $condition = db_or() + ->condition(AbstractSynonymsSynonymsBehavior::COLUMN_PLACEHOLDER, entity_label('node', $synonym_entity1)) + ->condition(db_and()->condition(AbstractSynonymsSynonymsBehavior::COLUMN_PLACEHOLDER, entity_label('node', $synonym_entity2))->condition(AbstractSynonymsSynonymsBehavior::COLUMN_PLACEHOLDER, '%' . db_like(drupal_substr(entity_label('node', $synonym_entity2), 1, -1)) . '%', 'LIKE')); + $this->assertSynonymsFind($meta_data, $condition, 'on a field with 2 values searching for (value1 or (value2 AND value2%))'); + + $meta_data = array(); + $synonym_entity1 = $this->createNode($this->randomName() . ' ' . $this->randomName() . ' ' . $this->randomName()); + $synonym_entity2 = $this->createNode(str_replace(' ', '-', entity_label('node', $synonym_entity1))); + $meta_data[] = array( + 'items' => array( + LANGUAGE_NONE => array( + array('target_id' => entity_id('node', $synonym_entity1)), + array('target_id' => entity_id('node', $synonym_entity2)), + ), + ), + 'found_synonyms' => array( + entity_label('node', $synonym_entity1), + entity_label('node', $synonym_entity2), + ), + ); + $condition = db_and() + ->where("REPLACE(" . AbstractSynonymsSynonymsBehavior::COLUMN_PLACEHOLDER . ", ' ', '-') = :synonym", array( + ':synonym' => entity_label('node', $synonym_entity2), + )); + $this->assertSynonymsFind($meta_data, $condition, "on a field with 2 values, where 2nd value replaces spaces with dashes in the 1st value, searching for REPLACE(column, ' ', '-') = value2"); } /** diff --git a/sites/all/modules/contrib/taxonomy/synonyms/synonyms_search/includes/SearchEntityReferenceSynonymsBehavior.class.inc b/sites/all/modules/contrib/taxonomy/synonyms/synonyms_search/includes/SearchEntityReferenceSynonymsBehavior.class.inc new file mode 100644 index 00000000..cca5b99c --- /dev/null +++ b/sites/all/modules/contrib/taxonomy/synonyms/synonyms_search/includes/SearchEntityReferenceSynonymsBehavior.class.inc @@ -0,0 +1,12 @@ + t('Search'), + 'description' => t('Integrate synonyms with Search module'), + 'interface' => 'SearchSynonymsBehavior', + 'enabled callback' => 'synonyms_search_behavior_search_enabled', + 'disabled callback' => 'synonyms_search_behavior_search_disabled', +); + +/** + * Callback for when the behavior is enabled. + * + * Trigger re-indexing of all the nodes that reference terms from the vocabulary + * where the change has taken place. + */ +function synonyms_search_behavior_search_enabled($behavior_definition, $settings, $instance) { + module_load_include('inc', 'synonyms_search', 'synonyms_search.pages'); + synonyms_search_reindex_nodes_by_vocabulary(taxonomy_vocabulary_machine_name_load($instance['bundle'])); +} + +/** + * Callback for when the behavior is disabled. + * + * Trigger re-indexing of all the nodes that reference terms from the vocabulary + * where the change has taken place. + */ +function synonyms_search_behavior_search_disabled($behavior_definition, $behavior_implementation, $instance) { + module_load_include('inc', 'synonyms_search', 'synonyms_search.pages'); + synonyms_search_reindex_nodes_by_vocabulary(taxonomy_vocabulary_machine_name_load($instance['bundle'])); +} diff --git a/sites/all/modules/contrib/taxonomy/synonyms/synonyms_search/synonyms_search.info b/sites/all/modules/contrib/taxonomy/synonyms/synonyms_search/synonyms_search.info new file mode 100644 index 00000000..99cc2df7 --- /dev/null +++ b/sites/all/modules/contrib/taxonomy/synonyms/synonyms_search/synonyms_search.info @@ -0,0 +1,20 @@ +name = Synonyms Search +description = "Provides synonyms integration with searching." +package = Taxonomy +core = 7.x +dependencies[] = synonyms +dependencies[] = search + +files[] = synonyms_search.test + +files[] = includes/SearchSynonymsBehavior.interface.inc +files[] = includes/SearchTextSynonymsBehavior.class.inc +files[] = includes/SearchTaxonomySynonymsBehavior.class.inc +files[] = includes/SearchEntityReferenceSynonymsBehavior.class.inc + +; Information added by Drupal.org packaging script on 2015-11-29 +version = "7.x-1.3" +core = "7.x" +project = "synonyms" +datestamp = "1448771941" + diff --git a/sites/all/modules/contrib/taxonomy/synonyms/synonyms_search/synonyms_search.module b/sites/all/modules/contrib/taxonomy/synonyms/synonyms_search/synonyms_search.module new file mode 100644 index 00000000..835f2769 --- /dev/null +++ b/sites/all/modules/contrib/taxonomy/synonyms/synonyms_search/synonyms_search.module @@ -0,0 +1,115 @@ +original)) { + $bundle = field_extract_bundle('taxonomy_term', $term); + $behavior_implementations = synonyms_behavior_get('search', 'taxonomy_term', $bundle, TRUE); + + $current_search_synonyms = array(); + $previous_search_synonyms = array(); + + foreach ($behavior_implementations as $behavior_implementation) { + $current_search_synonyms = array_merge($current_search_synonyms, synonyms_extract_synonyms($term, $behavior_implementation)); + $previous_search_synonyms = array_merge($previous_search_synonyms, synonyms_extract_synonyms($term->original, $behavior_implementation)); + } + $diff = array_diff($current_search_synonyms, $previous_search_synonyms); + if (!empty($diff) || count($current_search_synonyms) != count($previous_search_synonyms)) { + module_load_include('inc', 'synonyms_search', 'synonyms_search.pages'); + synonyms_search_reindex_nodes_by_terms(array($term->tid)); + } + } +} + +/** + * Implements hook_node_update_index(). + */ +function synonyms_search_node_update_index($node) { + $output = array(); + foreach (field_info_instances('node', $node->type) as $instance) { + // We go a field by field looking for taxonomy term reference and if that + // vocabulary has enabled search synonyms, we add them to the search index. + $field_info = field_info_field($instance['field_name']); + if ($field_info['type'] == 'taxonomy_term_reference') { + // For each term referenced in this node we have to add synonyms. + $terms = field_get_items('node', $node, $instance['field_name']); + if (is_array($terms) && !empty($terms)) { + foreach ($terms as $v) { + $output[] = $v['tid']; + } + } + } + } + + if (!empty($output)) { + $terms = taxonomy_term_load_multiple($output); + $output = array(); + foreach ($terms as $term) { + $bundle = field_extract_bundle('taxonomy_term', $term); + $behavior_implementations = synonyms_behavior_get('search', 'taxonomy_term', $bundle, TRUE); + foreach ($behavior_implementations as $implementation) { + $output = array_merge($output, synonyms_extract_synonyms($term, $implementation)); + } + } + } + + return empty($output) ? '' : '' . implode($output, ', ') . ''; +} + +/** + * Implements hook_term_update_index(). + */ +function synonyms_search_term_update_index($term) { + $bundle = taxonomy_vocabulary_load($term->vid); + $bundle = field_extract_bundle('taxonomy_term', $bundle); + $behavior_implementations = synonyms_behavior_get('search', 'taxonomy_term', $bundle, TRUE); + + $synonyms = array(); + foreach ($behavior_implementations as $implementation) { + $synonyms = array_merge($synonyms, synonyms_extract_synonyms($term, $implementation)); + } + return implode(', ', $synonyms); +} + +/** + * Implements hook_synonyms_behavior_implementation_info(). + */ +function synonyms_search_synonyms_behavior_implementation_info($behavior) { + switch ($behavior) { + case 'search': + return array( + 'number_integer' => 'SearchTextSynonymsBehavior', + 'number_decimal' => 'SearchTextSynonymsBehavior', + 'number_float' => 'SearchTextSynonymsBehavior', + 'text' => 'SearchTextSynonymsBehavior', + 'taxonomy_term_reference' => 'SearchTaxonomySynonymsBehavior', + 'entityreference' => 'SearchEntityReferenceSynonymsBehavior', + ); + break; + } + return array(); +} diff --git a/sites/all/modules/contrib/taxonomy/synonyms/synonyms_search/synonyms_search.pages.inc b/sites/all/modules/contrib/taxonomy/synonyms/synonyms_search/synonyms_search.pages.inc new file mode 100644 index 00000000..b6876f31 --- /dev/null +++ b/sites/all/modules/contrib/taxonomy/synonyms/synonyms_search/synonyms_search.pages.inc @@ -0,0 +1,68 @@ + $v) { + if ($v['type'] == 'taxonomy_term_reference' && isset($v['bundles']['node'])) { + // This field is taxonomy term reference and it is attached to nodes, so + // we will run EntityFieldQuery on it. + $query = new EntityFieldQuery(); + $result = $query->entityCondition('entity_type', 'node') + ->fieldCondition($field_name, 'tid', $tids, 'IN') + ->execute(); + if (isset($result['node'])) { + $found_nids = array_merge($found_nids, array_keys($result['node'])); + } + } + } + if (!empty($found_nids)) { + db_update('search_dataset') + ->fields(array('reindex' => REQUEST_TIME)) + ->condition('type', 'node') + ->condition('sid', $found_nids, 'IN') + ->execute(); + } + + // Integration with Term Search module: reset terms index too. + if (module_exists('term_search')) { + db_update('search_dataset') + ->fields(array('reindex' => REQUEST_TIME)) + ->condition('type', 'term') + ->condition('sid', $tids) + ->execute(); + } +} + +/** + * Mark nodes that reference terms from a vocabulary for search re-indexing. + * + * @param $vocabulary object + * Fully loaded vocabulary object + */ +function synonyms_search_reindex_nodes_by_vocabulary($vocabulary) { + $tids = db_select('taxonomy_term_data', 't') + ->fields('t', array('tid')) + ->condition('vid', $vocabulary->vid) + ->execute() + ->fetchCol(); + if (!empty($tids)) { + synonyms_search_reindex_nodes_by_terms($tids); + } +} diff --git a/sites/all/modules/contrib/taxonomy/synonyms/synonyms_search/synonyms_search.test b/sites/all/modules/contrib/taxonomy/synonyms/synonyms_search/synonyms_search.test new file mode 100644 index 00000000..5477d9a7 --- /dev/null +++ b/sites/all/modules/contrib/taxonomy/synonyms/synonyms_search/synonyms_search.test @@ -0,0 +1,367 @@ + $this->vocabulary->vid, + 'name' => $this->randomName(), + $this->fields['disabled']['field']['field_name'] => array( + LANGUAGE_NONE => array( + array('value' => $this->randomName()), + ), + ), + ); + taxonomy_term_save($term); + $this->terms['no_synonyms'] = $term; + + $term = (object) array( + 'vid' => $this->vocabulary->vid, + 'name' => $this->randomName(), + $this->fields['enabled']['field']['field_name'] => array( + LANGUAGE_NONE => array( + array('value' => $this->randomName()), + ), + ), + $this->fields['disabled']['field']['field_name'] => array( + LANGUAGE_NONE => array( + array('value' => $this->randomName()), + ), + ), + ); + taxonomy_term_save($term); + $this->terms['one_synonym'] = $term; + + $term = (object) array( + 'vid' => $this->vocabulary->vid, + 'name' => $this->randomName(), + $this->fields['enabled']['field']['field_name'] => array( + LANGUAGE_NONE => array( + array('value' => $this->randomName()), + array('value' => $this->randomName()), + ), + ), + $this->fields['disabled']['field']['field_name'] => array( + LANGUAGE_NONE => array( + array('value' => $this->randomName()), + ), + ), + ); + taxonomy_term_save($term); + $this->terms['two_synonyms'] = $term; + } + + /** + * Retrieve search results. + * + * @param $keyword string + * Keyword to supply to the search mechanism + * + * @return array + * Array of HTML search results. Each element in this array is a single + * search result represented in HTML code as Drupal search mechanism outputs + * it + */ + protected function getSearchResults($keyword) { + $response = $this->drupalGet('search/' . $this->search_type . '/' . $keyword); + $matches = array(); + preg_match_all('#\]+class="search-result"[^>]*\>(.*?)\#si', $response, $matches); + return $matches[1]; + } +} + +/** + * Test Synonyms module integration with Drupal search functionality for nodes. + */ +class NodeSearchSynonymsWebTestCase extends AbstractSearchSynonymsWebTestCase { + + /** + * GetInfo method. + */ + public static function getInfo() { + return array( + 'name' => 'Synonyms node search integration', + 'description' => 'Ensure that Synonyms module correctly integrates with the Drupal search functionality.', + 'group' => 'Synonyms', + ); + } + + /** + * SetUp method. + */ + public function setUp($modules = array()) { + parent::setUp($modules); + + // Creating a test content type. + $this->drupalPost('admin/structure/types/add', array( + 'name' => 'Synonyms Test Content', + 'type' => 'synonyms_test_content', + ), 'Save content type'); + + // Attaching term reference field to the new content type. + $field = array( + 'type' => 'taxonomy_term_reference', + 'field_name' => 'synonyms_term_enabled', + 'cardinality' => FIELD_CARDINALITY_UNLIMITED, + 'settings' => array( + 'allowed_values' => array( + array( + 'vocabulary' => $this->vocabulary->machine_name, + 'parent' => 0, + ), + ), + ), + ); + $field = field_create_field($field); + + $instance = array( + 'field_name' => $field['field_name'], + 'entity_type' => 'node', + 'bundle' => 'synonyms_test_content', + 'label' => 'Synonym Terms', + 'widget' => array( + 'type' => 'synonyms_autocomplete', + ), + ); + + field_create_instance($instance); + } + + /** + * Test searching nodes by a term synonym. + * + * Since logically term and its synonyms represent the same entity, the idea + * is that searching by a term synonym should trigger all content referencing + * that term to be included in search results. Additionally we test that when + * a synonym is deleted/edited in a term, corresponding content is no longer + * encountered when searched by ex-synonym. + */ + public function testSearchTermSynonym() { + // Creating a node, which references all the terms we have. + $node = (object) array( + 'type' => 'synonyms_test_content', + 'title' => $this->randomName(), + 'synonyms_term_enabled' => array(LANGUAGE_NONE => array( + array('tid' => $this->terms['no_synonyms']->tid), + array('tid' => $this->terms['one_synonym']->tid), + array('tid' => $this->terms['two_synonyms']->tid), + )), + ); + node_save($node); + + // Rebuilding Search index. + $this->cronRun(); + + foreach ($this->terms as $k => $term) { + $this->assertSearchResults($term->name, array($node), 'Searching by name of the term ' . $k); + $items = field_get_items('taxonomy_term', $term, $this->fields['disabled']['field']['field_name']); + if (is_array($items)) { + foreach ($items as $delta => $item) { + $this->assertSearchResults($item['value'], array(), 'Searching by not enabled search integration field value #' . $delta . ' of term ' . $k); + } + } + + $items = field_get_items('taxonomy_term', $term, $this->fields['enabled']['field']['field_name']); + if (is_array($items)) { + foreach ($items as $delta => $item) { + $this->assertSearchResults($item['value'], array($node), 'Searching by synonym #' . $delta . ' of the term ' . $k); + } + } + } + + // Removing a synonym from the term. Then asserting node got re-indexed with + // new values of synonyms. + $deleted_synonym = array_pop($this->terms['one_synonym']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE]); + taxonomy_term_save($this->terms['one_synonym']); + $this->cronRun(); + $this->assertSearchResults($deleted_synonym['value'], array(), 'Searching by recently deleted synonym of a taxonomy term yields no results.'); + + // Editing a synonym in a term. Then asserting node got re-indexed with new + // values of synonyms. + $ex_synonym = $this->terms['two_synonyms']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE][0]['value']; + $this->terms['two_synonyms']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE][0]['value'] = $this->randomName(); + taxonomy_term_save($this->terms['two_synonyms']); + $this->cronRun(); + $this->assertSearchResults($ex_synonym, array(), 'Searching by recently changed synonym of a taxonomy term yields no results.'); + + // We disable entire field from search integration and make sure for all + // synonyms search results are empty. + synonyms_behavior_settings_delete($this->fields['enabled']['instance']['id'], $this->behavior); + $this->cronRun(); + foreach ($this->terms as $k => $term) { + $items = field_get_items('taxonomy_term', $term, $this->fields['enabled']['field']['field_name']); + if (is_array($items)) { + foreach ($items as $synonym) { + $this->assertSearchResults($synonym['value'], array(), 'Searching by ' . $k . ' term synonym, which field was recently disabled from search behavior yields no results.'); + } + } + } + } + + /** + * Assert search results. + * + * @param $keyword string + * Keyword to supply to the search mechanism + * @param $results array + * Array of fully loaded nodes that are expected to be on search results + * @param $message string + * Drupal assertion message to display on test results page + */ + protected function assertSearchResults($keyword, $results, $message) { + $matches = $this->getSearchResults($keyword); + if (count($matches) != count($results)) { + $this->fail($message); + return; + } + $matches = implode('', $matches); + foreach ($results as $node) { + if (strpos($matches, 'node/' . $node->nid) === FALSE) { + $this->fail($message); + return; + } + } + $this->pass($message); + } +} + +/** + * Test Synonyms module integration with Drupal search for taxonomy terms. + */ +class TermSearchSynonymsWebTestCase extends AbstractSearchSynonymsWebTestCase { + + protected $search_type = 'term'; + + /** + * GetInfo method. + */ + public static function getInfo() { + return array( + 'name' => 'Synonyms term search integration', + 'description' => 'Ensure that Synonyms module correctly integrates with the Term Search module.', + 'group' => 'Synonyms', + ); + } + + /** + * SetUp method. + */ + public function setUp($modules = array()) { + $modules[] = 'term_search'; + parent::setUp($modules); + $active_searches = variable_get('search_active_modules', array('node', 'user')); + $active_searches[] = 'term_search'; + variable_set('search_active_modules', $active_searches); + } + + /** + * Test searching terms by their synonyms. + */ + public function testSearchTermSynonym() { + // Rebuilding Search index. + $this->cronRun(); + + foreach ($this->terms as $k => $term) { + $this->assertSearchResults($term->name, array($term), 'Searching by name of the term ' . $k); + $items = field_get_items('taxonomy_term', $term, $this->fields['disabled']['field']['field_name']); + if (is_array($items)) { + foreach ($items as $delta => $item) { + $this->assertSearchResults($item['value'], array(), 'Searching by not enabled search integration field value #' . $delta . ' of term ' . $k); + } + } + + $items = field_get_items('taxonomy_term', $term, $this->fields['enabled']['field']['field_name']); + if (is_array($items)) { + foreach ($items as $delta => $item) { + $this->assertSearchResults($item['value'], array($term), 'Searching by synonym #' . $delta . ' of the term ' . $k); + } + } + } + + // Removing a synonym from the term. Then asserting it got re-indexed with + // new values of synonyms. + $deleted_synonym = array_pop($this->terms['one_synonym']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE]); + taxonomy_term_save($this->terms['one_synonym']); + $this->cronRun(); + $this->assertSearchResults($deleted_synonym['value'], array(), 'Searching by recently deleted synonym of a taxonomy term yields no results.'); + + // Editing a synonym in a term. Then asserting it got re-indexed with new + // values of synonyms. + $ex_synonym = $this->terms['two_synonyms']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE][0]['value']; + $this->terms['two_synonyms']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE][0]['value'] = $this->randomName(); + taxonomy_term_save($this->terms['two_synonyms']); + $this->cronRun(); + $this->assertSearchResults($ex_synonym, array(), 'Searching by recently changed synonym of a taxonomy term yields no results.'); + + // We disable entire field from search integration and make sure for all + // synonyms search results are empty. + synonyms_behavior_settings_delete($this->fields['enabled']['instance']['id'], $this->behavior); + $this->cronRun(); + foreach ($this->terms as $k => $term) { + $items = field_get_items('taxonomy_term', $term, $this->fields['enabled']['field']['field_name']); + if (is_array($items)) { + foreach ($items as $synonym) { + $this->assertSearchResults($synonym['value'], array(), 'Searching by ' . $k . ' term synonym, which field was recently disabled from search behavior yields no results.'); + } + } + } + } + + /** + * Assert search results. + * + * @param $keyword string + * Keyword to supply to the search mechanism + * @param $results array + * Array of fully loaded terms that are expected to be on search results + * @param $message string + * Drupal assertion message to display on test results page + */ + protected function assertSearchResults($keyword, $results, $message) { + $matches = $this->getSearchResults($keyword); + if (count($matches) != count($results)) { + $this->fail($message); + return; + } + $matches = implode('', $matches); + foreach ($results as $term) { + if (strpos($matches, 'taxonomy/term/' . $term->tid) === FALSE) { + $this->fail($message); + return; + } + } + $this->pass($message); + } + +} diff --git a/sites/all/modules/contrib/taxonomy/synonyms/views/synonyms.views.inc b/sites/all/modules/contrib/taxonomy/synonyms/views/synonyms.views.inc index 5f377aef..eaf80164 100644 --- a/sites/all/modules/contrib/taxonomy/synonyms/views/synonyms.views.inc +++ b/sites/all/modules/contrib/taxonomy/synonyms/views/synonyms.views.inc @@ -7,11 +7,27 @@ /** * Implements hook_views_plugins_alter(). - * - * @see hook_views_plugins_alter() */ function synonyms_views_plugins_alter(&$plugins) { // Replace default taxonomy term argument validator with our extended version, // which can also handle a term synonym as an argument. $plugins['argument validator']['taxonomy_term']['handler'] = 'synonyms_views_plugin_argument_validate_taxonomy_term'; } + +/** + * Implements hook_field_views_data_alter(). + */ +function synonyms_field_views_data_alter(&$result, $field, $module) { + if ($field['type'] == 'taxonomy_term_reference') { + // Add synonyms friendly autocomplete filter. + foreach ($field['storage']['details']['sql'] as $table) { + $tid_column = reset($table); + $tid_column = $tid_column['tid']; + $table = array_keys($table); + $table = $table[0]; + if (isset($result[$table][$tid_column]['filter'])) { + $result[$table][$tid_column]['filter']['handler'] = 'synonyms_views_handler_filter_term_tid'; + } + } + } +} diff --git a/sites/all/modules/contrib/taxonomy/synonyms/views/synonyms_views_handler_filter_term_tid.inc b/sites/all/modules/contrib/taxonomy/synonyms/views/synonyms_views_handler_filter_term_tid.inc new file mode 100644 index 00000000..4fafd5dd --- /dev/null +++ b/sites/all/modules/contrib/taxonomy/synonyms/views/synonyms_views_handler_filter_term_tid.inc @@ -0,0 +1,174 @@ +options['type'] == 'synonyms_autocomplete'; + if ($restore_value) { + $this->options['type'] = 'textfield'; + } + parent::value_form($form, $form_state); + + if ($restore_value) { + // We need to determine the entity type onto which this field is attached + // that is used in this view. + $entity_type_base_table = $this->view->base_table; + // TODO: it would be nice to consider the existence of relationships, but + // I just couldn't figure it out at that time. + + $entity_info = entity_get_info(); + $field_entity_type = FALSE; + $field = field_info_field($this->definition['field_name']); + + foreach ($field['bundles'] as $entity_type => $bundles) { + if ($entity_info[$entity_type]['base table'] == $entity_type_base_table) { + $field_entity_type = $entity_type; + break; + } + } + + if (!$field_entity_type) { + // Seems like we failed to determine the entity type which is used for + // this field in the view. Well, it's not a fatal fail, we'll just use + // whatever then. + $field_entity_type = array_keys($field['bundles']); + $field_entity_type = $field_entity_type[0]; + } + + // We just grab the first instance of this field within the determined + // entity type. + $bundle = $field['bundles'][$field_entity_type][0]; + + $instance = field_info_instance($field_entity_type, $field['field_name'], $bundle); + if ($instance['widget']['type'] == 'synonyms_autocomplete') { + $widget = $instance['widget']['settings']; + } + else { + $widget = field_info_widget_settings('synonyms_autocomplete'); + } + $autocomplete_path = $widget['synonyms_autocomplete_path']; + $size = $widget['size']; + + $form['value']['#autocomplete_path'] = $autocomplete_path . '/' . $this->definition['field_name'] . '/' . $field_entity_type . '/' . $bundle; + $form['value']['#size'] = $size; + $form['value']['#auto_creation'] = FALSE; + $form['value']['#attributes']['class'][] = 'synonyms-autocomplete'; + $form['value']['#attached']['js'][drupal_get_path('module', 'synonyms') . '/js/synonyms-autocomplete.js'] = array(); + $this->options['type'] = 'synonyms_autocomplete'; + } + } + + function value_validate($form, &$form_state) { + if ($this->options['type'] == 'synonyms_autocomplete') { + $values = drupal_explode_tags($form_state['values']['options']['value']); + $tids = $this->synonyms_validate_term_strings($form['value'], $values); + + if ($tids) { + $form_state['values']['options']['value'] = $tids; + } + } + else { + parent::value_validate($form, $form_state); + } + } + + function exposed_validate(&$form, &$form_state) { + if ($this->options['type'] == 'synonyms_autocomplete') { + if (empty($this->options['exposed'])) { + return; + } + if (empty($this->options['expose']['identifier'])) { + return; + } + + $identifier = $this->options['expose']['identifier']; + $values = drupal_explode_tags($form_state['values'][$identifier]); + $tids = $this->synonyms_validate_term_strings($form[$identifier], $values); + + if ($tids) { + $this->validated_exposed_input = $tids; + } + } + else { + parent::exposed_validate($form, $form_state); + } + } + + /** + * Validate the user string. + * + * In a great extend it does the same job as parent::validate_term_strings(), + * just that this implementation is synonyms-aware. + * + * @param $element + * The form element which is used, either the views ui or the exposed + * filters. + * @param $values + * The taxonomy names/synonyms which will be converted to tids. + * + * @return array + * The taxonomy ids for all validated terms. + */ + protected function synonyms_validate_term_strings($element, $values) { + if (empty($values)) { + return array(); + } + + $values = array_map('drupal_strtolower', $values); + $missing = array_flip($values); + + $tids = array(); + + $vocabulary = taxonomy_vocabulary_machine_name_load($this->options['vocabulary']); + + // Firstly looking up the entered tags as if they were term names. Then, + // the remaining tags are looked up as if they were synonyms of terms. + // Lastly, if any tags are left at this point, we mark form validation + // error. + $query = db_select('taxonomy_term_data', 'td'); + $query->fields('td', array('tid', 'name')); + $query->condition('td.vid', $vocabulary->vid); + $query->condition('td.name', $values); + $query->addTag('term_access'); + $result = $query->execute(); + foreach ($result as $term) { + unset($missing[drupal_strtolower($term->name)]); + $tids[] = $term->tid; + } + + $behavior_implementations = synonyms_behavior_get('autocomplete', 'taxonomy_term', $vocabulary->machine_name, TRUE); + foreach ($behavior_implementations as $behavior_implementation) { + if (!empty($missing)) { + $condition = db_or(); + foreach ($missing as $tag => $v) { + $condition->condition(AbstractSynonymsSynonymsBehavior::COLUMN_PLACEHOLDER, $tag); + } + $synonyms = synonyms_synonyms_find_behavior($condition, $behavior_implementation); + foreach ($synonyms as $synonym) { + $synonym->synonym = drupal_strtolower($synonym->synonym); + unset($missing[$synonym->synonym]); + $tids[] = $synonym->entity_id; + } + } + } + + if (!empty($missing) && !empty($this->options['error_message'])) { + form_error($element, format_plural(count($missing), 'Unable to find term: @terms', 'Unable to find terms: @terms', array('@terms' => implode(', ', array_keys($missing))))); + } + + return $tids; + } +} diff --git a/sites/all/modules/contrib/taxonomy/synonyms/views/synonyms_views_plugin_argument_validate_taxonomy_term.inc b/sites/all/modules/contrib/taxonomy/synonyms/views/synonyms_views_plugin_argument_validate_taxonomy_term.inc index 6066ea3d..18fcb795 100644 --- a/sites/all/modules/contrib/taxonomy/synonyms/views/synonyms_views_plugin_argument_validate_taxonomy_term.inc +++ b/sites/all/modules/contrib/taxonomy/synonyms/views/synonyms_views_plugin_argument_validate_taxonomy_term.inc @@ -27,18 +27,10 @@ class synonyms_views_plugin_argument_validate_taxonomy_term extends views_plugin switch ($type) { case 'synonym': case 'synonym_tid': - // We are requested to do synonyms friendly validation. We will have to - // go through each of allowed vocabularies, collecting terms whose - // synonyms might look like the argument we have. However, it is not - // that easy to achieve due to the fact that synonyms may be kept in - // whatever format in whatever column and only corresponding Synonyms - // extractor class has the knowledge how it is organized. Firstly we are - // going to query database trying to find a term with our argument's + // We are requested to do synonyms friendly validation. Firstly we are + // going to query the database trying to find a term with our argument's // name. If we find one, it is great and we stop right there. Otherwise, - // the nightmare starts: we are going to use - // AbstractSynonymsExtractor::processEntityFieldQuery() method to find - // synonyms that are similar to our argument. Then we will load those - // terms and manually in PHP will determine whether it is a match. + // we look up by synonyms. $query = db_select('taxonomy_term_data', 't') ->fields('t', array('tid', 'name')); if (!empty($vocabularies)) { @@ -46,7 +38,7 @@ class synonyms_views_plugin_argument_validate_taxonomy_term extends views_plugin $query->condition('v.machine_name', $vocabularies); } if ($transform) { - $query->where("replace(t.name, ' ', '-') = :name", array(':name' => $argument)); + $query->where("REPLACE(t.name, ' ', '-') = :name", array(':name' => $argument)); } else { $query->condition('t.name', $argument, '='); @@ -61,76 +53,32 @@ class synonyms_views_plugin_argument_validate_taxonomy_term extends views_plugin return TRUE; } + $bundles = $vocabularies; if (empty($vocabularies)) { - // At this point we want to convert an empty $vocabulary (implicitly - // meaning "all") into actually a list of all fully loaded - // vocabularies. - $vocabularies = taxonomy_vocabulary_load_multiple(FALSE); + // At this point we want to convert an empty $vocabularies (implicitly + // meaning "all") into actually a list of all vocabularies. + $bundles = array_keys(taxonomy_vocabulary_get_names()); } - else { - // We need to load the filtered vocabularies based on their machine - // names. - foreach ($vocabularies as $v) { - $vocabularies[$v] = taxonomy_vocabulary_machine_name_load($v); + + foreach ($bundles as $bundle) { + $condition = db_and(); + if ($transform) { + $condition->where("REPLACE(" . AbstractSynonymsSynonymsBehavior::COLUMN_PLACEHOLDER . ", ' ', '-') = :argument", array( + ':argument' => $argument, + )); + } + else { + $condition->condition(AbstractSynonymsSynonymsBehavior::COLUMN_PLACEHOLDER, $argument); + } + $synonyms = synonyms_synonyms_find($condition, 'taxonomy_term', $bundle); + if (!empty($synonyms)) { + $term = taxonomy_term_load($synonyms[0]->entity_id); + $this->argument->argument = $this->synonyms_get_term_property($term); + $this->argument->validated_title = check_plain(entity_label('taxonomy_term', $term)); + return TRUE; } } - // We haven't had much luck, seems like we have to do the hard part. We - // will pull up terms that are similar to our argument using the same - // functionality as for synonyms friendly autocomplete widget and then - // will find those who actually match among this narrowed set of terms. - // Since $match will be use as value for LIKE SQL operator, we are not - // sure whether we need to substitute dash (-) with spacebar, or keep - // the dash, to match both we will use underscore (_) since this symbol - // matches one symbol in LIKE operator. - $match = $transform ? str_replace('-', '_', $argument) : $argument; - $tids = array(); - // Unfortunately we have to query multiple times the database for each - // vocabulary for each synonyms-source field. - foreach ($vocabularies as $vocabulary) { - $bundle = field_extract_bundle('taxonomy_term', $vocabulary); - foreach (synonyms_synonyms_fields($vocabulary) as $field) { - $field = field_info_field($field); - $instance = field_info_instance('taxonomy_term', $field['field_name'], $bundle); - $query = new EntityFieldQuery(); - $query->entityCondition('entity_type', 'taxonomy_term') - ->entityCondition('bundle', $vocabulary->machine_name); - - // We let the class that defines this field type as a source of - // synonyms filter out and provide its suggestions based on this - // field. - $class = synonyms_extractor_info($field['type']); - call_user_func(array($class, 'processEntityFieldQuery'), $match, $query, $field, $instance); - if (!empty($tids)) { - $query->entityCondition('entity_id', $tids, 'NOT IN'); - } - $result = $query->execute(); - if (isset($result['taxonomy_term'])) { - $tids = array_merge($tids, array_keys($result['taxonomy_term'])); - } - } - } - $terms = taxonomy_term_load_multiple($tids); - // We have an array of terms whose synonyms look like they could be - // equal to our argument, let us iterate over each term's synonyms to - // actually see if they match. - $argument = mb_strtoupper($argument, 'UTF-8'); - foreach ($terms as $term) { - foreach (synonyms_get_term_synonyms($term) as $synonym) { - $synonym = mb_strtoupper($synonym['safe_value'], 'UTF-8'); - if ($transform) { - $synonym = str_replace(' ', '-', $synonym); - } - if ($synonym == $argument) { - // Finally we found a synonym that matches our argument. We are - // going to use the corresponding term and there is no point to - // continue searching. - $this->argument->argument = $this->synonyms_get_term_property($term); - $this->argument->validated_title = check_plain(entity_label('taxonomy_term', $term)); - return TRUE; - } - } - } // We haven't found any synonym that matched our argument, so there is // no term to return. return FALSE;