diff --git a/sites/all/modules/contrib/taxonomy/synonyms/README.txt b/sites/all/modules/contrib/taxonomy/synonyms/README.txt index d831e785..35e616b3 100644 --- a/sites/all/modules/contrib/taxonomy/synonyms/README.txt +++ b/sites/all/modules/contrib/taxonomy/synonyms/README.txt @@ -1,69 +1,84 @@ -- SUMMARY -- -The Synonyms module extends the Drupal core Taxonomy features. Currently -the module provides this additional functionality: +The Synonyms module enriches Entities with the notion of synonyms. Currently the +module provides the following functionality: * 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 + implementation exists, can be enabled as source of synonyms. +* support of synonyms through Entity properties. Entity properties stored in + database (as opposed to being calculated on-the-fly) can be enabled as source + of synonyms. +* synonyms-friendly autocomplete and select widgets for taxonomy_term_reference, + entityreference, and commerce_product_reference (through Synonyms Commerce + submodule) 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. + 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 entity synonyms 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 entities. +* integration with Views. Synonyms module provides a few filters and contextual + filters that allow filtering not only by entity name but also by one of its + synonyms. Synonyms module also provides a Views field for all eligible + entities that contains a list of synonyms associated with the entity in + question. -- REQUIREMENTS -- The Synonyms module requires the following modules: -* Taxonomy module -* CTools module +* Entity API module --- SYNONYMS BEHAVIOR, SUPPORTED FIELD TYPES -- - -Module ships with ability to use the following field types as synonyms: -* Text -* Taxonomy Term Reference +The Synonyms module integrates with (but does not require) the following +modules: +* Taxonomy +* Search +* Term Search * Entity Reference -* Number -* Float -* Decimal +* Views +* Commerce +* Features +* Term Merge -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. +-- SUPPORTED SYNONYMS PROVIDERS -- + +Module ships with ability to provide synonyms from the following locations: +* "Text" field type +* "Taxonomy Term Reference" field type +* "Entity Reference" field type +* "Commerce Product Reference" field type +* "Number" field type +* "Float" field type +* "Decimal" field type +* Entity properties stored in database + +Worth mentioning here: this list is easily extended further by implementing new +synonyms providers in your code. Refer to Synonyms advanced help for more +details on how to accomplish it. -- 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. +granularity into what "synonyms" mean. This granularity is expressed via +"synonyms behavior" idea whatsoever. Then you can enable different synonyms +behaviors for different synonyms providers. 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): +* Autocomplete - whether synonyms from this provider should participate in + autocomplete suggestions. This module ships with autocomplete synonyms + friendly widgets and their autocomplete suggestions will be filled in with the + synonyms of providers that have this behavior enabled. +* Select - whether synonyms from this provider should be included in the + synonyms friendly select widgets. * 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. + 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. +Therefore, on the Synonyms configuration page you will see a table, where rows +are synonym providers and columns are these "synonym behaviors" and you decide +what synonym behaviors to activate on what synonym providers. -- INSTALLATION -- @@ -71,15 +86,11 @@ you decide what synonym behaviors to activate on what fields. -- CONFIGURATION -- -* The module itself does not provide any configuration as of the moment. -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. +* You can configure synonyms of all eligible entity types by going to Admin -> + Structure -> Synonyms (admin/structure/synonyms) -- FUTURE DEVELOPMENT -- -* If you are interested into converting this module from synonyms for Taxonomy -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. +* No good directions for future development are known at the moment. If you + would like to suggest one, report an issue (future request) against Synonyms + issue queue on Drupal.org. diff --git a/sites/all/modules/contrib/taxonomy/synonyms/help/synonyms.help.ini b/sites/all/modules/contrib/taxonomy/synonyms/help/synonyms.help.ini index c071cca2..7c21194c 100644 --- a/sites/all/modules/contrib/taxonomy/synonyms/help/synonyms.help.ini +++ b/sites/all/modules/contrib/taxonomy/synonyms/help/synonyms.help.ini @@ -12,3 +12,7 @@ weight = -1 [synonyms_behavior_implementation] title = Creating a new behavior implementation parent = synonyms + +[synonyms_behavior_implementation_field_based] +title = Creating a new behavior implementation from a field +parent = synonyms_behavior_implementation diff --git a/sites/all/modules/contrib/taxonomy/synonyms/help/synonyms.html b/sites/all/modules/contrib/taxonomy/synonyms/help/synonyms.html index 740a7d0c..1cc031e0 100644 --- a/sites/all/modules/contrib/taxonomy/synonyms/help/synonyms.html +++ b/sites/all/modules/contrib/taxonomy/synonyms/help/synonyms.html @@ -1,18 +1,38 @@ -The Synonyms module provides various functionality targeted at handling synonymous (similar) spelling of the same name. +The Synonyms module provides various functionality targeted at handling synonymous (similar) spelling of the same name. This functionality is provided for all Drupal entities. -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.
+There are few key concepts tightly integrated into Synonyms module that are worth knowing for the end user. They are: +
Storage abstraction
+
Synonyms can be stored in any way your business logic requires it... It may go from fields attached to entities down to custom tables and then all the way up to querying an external API. Putting it simple, 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.
+
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 operating on top of synonyms data that do something productive with it. For example, here we have the autocomplete behavior, which introduces autocomplete widgets that do look up not only by entity 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.
+
Lastly, here is yet more abstract unit. Behavior implementations bridge between existing synonyms behaviors and the synonyms storage abstraction. Imagine as if you had a bipartite graph, one side of the graph are behaviors, the other side is storage abstractions and lastly the edges of this graph would be implementations of behaviors for specific storage.
-Let's cover each of these important concepts in details. +

Let's cover each of these important concepts in details.

-

Field Independency

+

Synonyms behaviors

-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: +

Synonyms behaviors are some useful for the end user features that leverage the synonyms data. Synonyms module ships with the following behaviors:

+ + +

Other modules can introduce their own behaviors. If you are interested in introducing your own behavior, refer to synonyms behaviors page.

+ +

Storage abstraction

+ +As stated above, your synonyms can be stored in about any kind of storage. On the other hand, no matter where they are stored, they can be used in synonyms behaviors and treated equally independently of their origin. Such decoupling of synonyms storage and synonyms manipulation gives an extra degree of freedom. Synonyms module provides the following types of storage: + +
Entity properties
+
As long as your entity property is stored in database, "Entity Property Synonyms Provider" submodule can extract them from there and give them good usage with synonyms behaviors. One good example would be to enable user's "email" field in "autocomplete" behavior, so users can find one another through autocomplete widget not only by username but by the email as well.
+
Fields
+
Many entities may have fields attached to them. "Field Synonyms Provider" submodule exposes field values as possible synonyms for the entities they belong to. Again, a good example would be to introduce a text field "Typos" and have it enabled in "autocomplete" behavior. This way you can cover at least the most common misspellings in your autocomplete widget.
+
Anything else
+
Synonyms module is built to be easily extended to cover custom needs of its client base. If you want to store your synonyms elsewhere, you can do so with a minimal required amount of code. Refer to writing custom behavior implementation for documentation on how to do it.
+ +

The "Field Synonyms Provider" submodule acts as a framework that exposes values from different field types into Synonyms core infrastructure. Out of the box this submodule covers 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. +

Other modules may extend this list by implementing behaviors for other field types. Refer to writing custom field-based behavior implementation for more details.

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
+

Behavior implementations connect behaviors to different types of synonyms storage. Out of the box Synonyms module provides enough behavior implementations to probably cover majority of use cases. As stated above, be it not the case, you can always extend coverage further through implementing custom behavior implementations in your own module. Feel free to study the hooks at synonyms.api.php file. Also, you may find it useful to read about writing custom behavior implementation.

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

Lastly, you as a website admin can enable or disable certain behavior implementations to participate in certain behaviors. So you have full control over how things get set up in your website. Additionally, some behaviors may provide configs, for example, the autocomplete behavior will ask you with what wording to suggest an entity if it was matched by one of its synonyms.

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 index 4eb7199f..051034eb 100644 --- a/sites/all/modules/contrib/taxonomy/synonyms/help/synonyms_behavior_implementation.html +++ b/sites/all/modules/contrib/taxonomy/synonyms/help/synonyms_behavior_implementation.html @@ -1,27 +1,29 @@ -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. +

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

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

By implementing I mean to provide integration between your storage 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. +

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 implementations in synonyms/synonyms_provider_{property,field}/includes/*SynonymsBehavior.class.inc files.

-Creating a new implementation pretty much consists of 2 steps: +

Just one last question before we dive into technical details: is your storage eventually comes from a field? In case it does, you might benefit from reading this page as it narrates about implementing behaviors based on fields. You can leverage some abstractions from synonyms_provider_field submodule to make your life easier.

+ +

In case your storage is not field-based or you just happen to be Chuck Norris, read on! 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. +
  5. Implementing behavior interface for a particular storage type.
  6. +
  7. Notifying Synonyms module about your new behavior implementation, that is... for what behavior and what entity type and bundle you have implemented it.
-Now let us see each of the steps in further details. +

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

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 of the plugin definition. Read the documentation for that interface and write a PHP class that implements this interface for your particular storage. 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: +

For the purposes of such notification we have the following hook 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. +

Implementing the hook is highly straight forward, you will just inform the Synonyms module about what behavior implementations your module has to offer for a given behavior, entity type, and bundle. For more details, refer to synonyms.api.php file.

diff --git a/sites/all/modules/contrib/taxonomy/synonyms/help/synonyms_behavior_implementation_field_based.html b/sites/all/modules/contrib/taxonomy/synonyms/help/synonyms_behavior_implementation_field_based.html new file mode 100644 index 00000000..1ce1367c --- /dev/null +++ b/sites/all/modules/contrib/taxonomy/synonyms/help/synonyms_behavior_implementation_field_based.html @@ -0,0 +1,32 @@ +

If you are looking into implementing a behavior for storage that is eventually field-based, then you are on the right page! Since it is quite likely that many behavior implementations will be field-based, we have walked an extra mile to facilitate this particular case.

+ +

First of all, there is synonyms_provider_field submodule that:

+ + + +

Apparently, on this page we will focus on the 2nd item from the list above. If you implement a behavior for a new field type, then:

+ + + +

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 of the plugin definition. Read the documentation for that interface and write a PHP class that implements this interface for your particular field type. We cannot give more precise instructions about this step, because it all depends on the interface of the behavior.

+ +

Notifying synonyms_provider_field submodule about your new implementation

+ +

For the purposes of such notification we have the following hooks in synonyms_provider_field submodule:

+ + + +

Implementing either of the 2 hooks is highly straight forward, you will just inform the submodule about what field type is exposed to behavior implementation through what PHP class for a given behavior. Then the synonyms_provider_field submodule will analyze what fields are attached to what entity types and will convert this data into the format expected by the core Synonyms module. For more details, refer to synonyms_provider_field.api.php file.

diff --git a/sites/all/modules/contrib/taxonomy/synonyms/help/synonyms_behaviors.html b/sites/all/modules/contrib/taxonomy/synonyms/help/synonyms_behaviors.html index 42306be1..7af054cf 100644 --- a/sites/all/modules/contrib/taxonomy/synonyms/help/synonyms_behaviors.html +++ b/sites/all/modules/contrib/taxonomy/synonyms/help/synonyms_behaviors.html @@ -1,40 +1,41 @@ -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. +

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

Before I continue further with technical details, let me set certain framework on what synonyms behavior is and where is the line between a behavior and its implementation. Behavior is ultimately an interface that pursuits to deliver some end-user useful functionality. For the sake of example, let's consider the following behavior: whenever an email is sent to a user, our example behavior will swap his/her username in the 1st line of email body with a randomly picked up synonym of that user. This is behavior, because it narrates about behavioral pattern and is abstract to specifics of synonyms storage. Moreover, it sounds reasonable to be declared as a separate behavior (instead of using some pre-existing) because website admin may want to specify from what exact synonyms we can pick up a random nick name upon email being sent.

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

On the other hand, if you are looking into providing synonyms data to behaviors, i.e. "implementing" behaviors for some specific storage facility, then you are on a wrong page. You better head off to implementing behaviors documentation.

+ +

Now that we have cleared the theoretical details, let's get busy with hands on experience. In technical terms synonyms behaviors are just cTools plugins. Synonyms module defines its own plugin type: behavior, which is owned by synonyms module. Throughout 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, it is also worthwhile to check out its documentation.

+ +

Your plugin implementation may (or must) have the following keys:

-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:
    +
    (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. &$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.
    3. $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.
    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).
    +
    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 a specific storage. 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 behavior implementations asking to "extract synonyms from the storage". 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 storage 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, then your interface may extend the interface of that 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:
      +
      (string) Name of a PHP function that will be invoked to notify your behavior that it has just been enabled on a new storage. 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. +
      7. $behavior_implementation: (array) Array of behavior implementation. It will contain various information specific to the storage on which your behavior have just been enabled. Also, behavior implementation, since it is enabled, will contain settings of your behavior which has provided website administrator. You can look up structure of this array in the comments of synonyms_behavior_get() function.
      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:
        +
        (string) Name of a PHP function that will be invoked to notify your behavior that it has just been disabled on a storage. 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. +
        7. $behavior_implementation: (array) Array of behavior implementation. It will contain various information specific to the storage on which your behavior have just been disabled. Also, behavior implementation will contain settings of your behavior which has provided website administrator. You can look up structure of this array in the comments of synonyms_behavior_get() function.
        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. +
        9. 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 storage types. Also, make sure your interface extends the necessary interfaces from other behaviors (if your behavior depends on the functionality of other behaviors).
        10. +
        11. 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. For example, in our dummy "let me swap username with his synonym upon email being sent" case, this would materialize into implementing hook_mail_alter() where you'd have to process email body and swap all occurrences of username with a randomly picked synonym. You will definitely need synonyms_behavior_get() as it allows to load enabled behavior implementations for a given behavior.
        12. +
        13. Probably you will want to implement your freshly created behavior for at least one storage type. Otherwise your behavior won't have any synonyms to work with and therefore will be useless. Read about implementing a behavior for new storage types here.
        diff --git a/sites/all/modules/contrib/taxonomy/synonyms/includes/EntityReferenceSynonymsBehavior.class.inc b/sites/all/modules/contrib/taxonomy/synonyms/includes/EntityReferenceSynonymsBehavior.class.inc deleted file mode 100644 index d1a1f3db..00000000 --- a/sites/all/modules/contrib/taxonomy/synonyms/includes/EntityReferenceSynonymsBehavior.class.inc +++ /dev/null @@ -1,84 +0,0 @@ - $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/SynonymsBehavior.interface.inc b/sites/all/modules/contrib/taxonomy/synonyms/includes/SynonymsBehavior.interface.inc index 2c30bca3..58f2e4ef 100644 --- a/sites/all/modules/contrib/taxonomy/synonyms/includes/SynonymsBehavior.interface.inc +++ b/sites/all/modules/contrib/taxonomy/synonyms/includes/SynonymsBehavior.interface.inc @@ -11,114 +11,61 @@ * All synonyms behaviors must extend this interface. */ interface SynonymsBehavior { -} - -/** - * Interface of "synonyms" behavior. - * - * The most basic synonyms behavior. - */ -interface SynonymsSynonymsBehavior extends SynonymsBehavior { /** - * Extract synonyms from a field attached to an entity. + * Extract synonyms from an entity within a specific behavior implementation. * - * 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 + * Entity from which to extract synonyms * * @return array - * Array of synonyms extracted from $items + * Array of synonyms extracted from $entity */ - public function extractSynonyms($items, $field, $instance, $entity, $entity_type); + public function extractSynonyms($entity); /** - * Add an entity as a synonym into a field of another entity. + * Add an entity as a synonym into 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 merged 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 of 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. + * 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 merged entity into + * the trunk entity). You should update $trunk_entity in such a way that it + * holds $synonym_entity as a synonym (it all depends on how data is stored in + * your behavior implementation, but probably you will store entity label or + * its ID as you cannot literally store an entity inside of another entity). + * If entity of type $synonym_entity_type cannot be converted into a format + * expected by your behavior implementation, just do nothing. * - * @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 $trunk_entity + * Entity into which another one should be added as synonym * @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 */ - public function mergeEntityAsSynonym($items, $field, $instance, $synonym_entity, $synonym_entity_type); + public function mergeEntityAsSynonym($trunk_entity, $synonym_entity, $synonym_entity_type); /** - * Hash a field item that is enabled as synonym. - * - * Your hash function must return such hash that for 2 items that yield the - * same synonyms their hash must be the same. There is no limit on minimal or - * maximum hash length, but keep it reasonable, something below 512 symbols. - * Also, your hash function should strive to minimize hash collisions, i.e. - * when 2 different items yield the same hash. - * - * @param array $item - * Field item whose hash is requested - * @param array $field - * Field from which the $item comes from - * @param array $instance - * Instance from which the $item comes from - * - * @return string - * Hash of the provided $item - */ - public function synonymItemHash($item, $field, $instance); - - /** - * Look up entities by their synonyms within a provided field. + * Look up entities by their synonyms within a behavior implementation. * * You are provided with a SQL condition that you should apply to the storage - * of synonyms within the provided field. And then return result: what - * entities match by the provided condition through what synonyms. + * of synonyms within the provided behavior implementation. And then return + * result: what entities match by the provided condition through what + * synonyms. * * @param QueryConditionInterface $condition - * Condition that defines what to search for. It may contain a placeholder - * of AbstractSynonymsSynonymsBehavior::COLUMN_PLACEHOLDER which you should - * replace by the column name where the synonyms data for your field is - * stored in plain text. For it to do, you may extend the - * AbstractSynonymsSynonymsBehavior class and then just invoke the - * AbstractSynonymsSynonymsBehavior->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 + * Condition that defines what to search for. Apart from normal SQL + * conditions as known in Drupal, it may contain the following placeholders: + * - AbstractSynonymsBehavior::COLUMN_SYNONYM_PLACEHOLDER: to denote + * synonyms column which you should replace with the actual column name + * where the synonyms data for your provider is stored in plain text. + * - AbstractSynonymsBehavior::COLUMN_ENTITY_ID_PLACEHOLDER: to denote + * column that holds entity ID. You are supposed to replace this + * placeholder with actual column name that holds entity ID in your case. + * For ease of work with these placeholders, you may extend the + * AbstractSynonymsBehavior class and then just invoke the + * AbstractSynonymsBehavior->synonymsFindProcessCondition() method, so you + * won't have to worry much about it * * @return Traversable * Traversable result set of found synonyms and entity IDs to which those @@ -128,47 +75,86 @@ interface SynonymsSynonymsBehavior extends SynonymsBehavior { * provided condition * - entity_id: (int) ID of the entity to which the found synonym belongs */ - public function synonymsFind(QueryConditionInterface $condition, $field, $instance); + public function synonymsFind(QueryConditionInterface $condition); + + /** + * Collect info on features pipe during invocation of hook_features_export(). + * + * If your synonyms provider depends on some other features components, this + * method should return them. + * + * @return array + * Array of features pipe as per hook_features_export() specification + */ + public function featuresExportPipe(); } /** - * Exception thrown by implementations of SynonymsSynonymsBehavior interface. + * Exception thrown by implementations of SynonymsBehavior interface. */ -class SynonymsSynonymsBehaviorException extends Exception {} +class SynonymsBehaviorException extends Exception {} /** - * Starting point for implementing SynonymsSynonymsBehavior interface. + * Starting point for implementing SynonymsBehavior interface. */ -abstract class AbstractSynonymsSynonymsBehavior implements SynonymsSynonymsBehavior { +abstract class AbstractSynonymsBehavior implements SynonymsBehavior { /** * Constant which denotes placeholder of a synonym column. * * @var string */ - const COLUMN_PLACEHOLDER = '***COLUMN***'; + const COLUMN_SYNONYM_PLACEHOLDER = '***COLUMN***'; + + /** + * Constant which denotes placeholder of an entity ID column. + * + * @var string + */ + const COLUMN_ENTITY_ID_PLACEHOLDER = '***ENTITY_ID***'; + + /** + * Behavior implementation on which this class was initialized. + * + * @var array + */ + protected $behavior_implementation; + + public function __construct($behavior_implementation) { + $this->behavior_implementation = $behavior_implementation; + } + + public function featuresExportPipe() { + return array(); + } /** * Process condition in 'synonymsFind' method. * * Process condition in 'synonymsFind' method replacing all references of - * synonym column with the real name of that column. + * synonym and entity ID columns with the real names of those columns. * * @param QueryConditionInterface $condition * Condition that should be processed - * @param string $column + * @param string $column_synonym * Real name of the synonym column + * @param string $column_entity_id + * Real name of the entity ID column */ - protected function synonymsFindProcessCondition(QueryConditionInterface $condition, $column) { + protected function synonymsFindProcessCondition(QueryConditionInterface $condition, $column_synonym, $column_entity_id) { $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); + $this->synonymsFindProcessCondition($v['field'], $column_synonym, $column_entity_id); } else { - $v['field'] = str_replace(self::COLUMN_PLACEHOLDER, $column, $v['field']); + $replace = array( + self::COLUMN_SYNONYM_PLACEHOLDER => $column_synonym, + self::COLUMN_ENTITY_ID_PLACEHOLDER => $column_entity_id, + ); + $v['field'] = str_replace(array_keys($replace), array_values($replace), $v['field']); } } } @@ -178,11 +164,11 @@ abstract class AbstractSynonymsSynonymsBehavior implements SynonymsSynonymsBehav /** * Interface of the autocomplete synonyms behavior. */ -interface AutocompleteSynonymsBehavior extends SynonymsSynonymsBehavior { +interface AutocompleteSynonymsBehavior extends SynonymsBehavior { } /** * Interface of the synonyms friendly select behavior. */ -interface SelectSynonymsBehavior extends SynonymsSynonymsBehavior { +interface SelectSynonymsBehavior extends SynonymsBehavior { } diff --git a/sites/all/modules/contrib/taxonomy/synonyms/includes/TextSynonymsBehavior.class.inc b/sites/all/modules/contrib/taxonomy/synonyms/includes/TextSynonymsBehavior.class.inc deleted file mode 100644 index 96472e1c..00000000 --- a/sites/all/modules/contrib/taxonomy/synonyms/includes/TextSynonymsBehavior.class.inc +++ /dev/null @@ -1,68 +0,0 @@ - $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/plugins/arguments/term_synonyms.inc b/sites/all/modules/contrib/taxonomy/synonyms/plugins/arguments/term_synonyms.inc index bc3e1d81..5943842b 100644 --- a/sites/all/modules/contrib/taxonomy/synonyms/plugins/arguments/term_synonyms.inc +++ b/sites/all/modules/contrib/taxonomy/synonyms/plugins/arguments/term_synonyms.inc @@ -5,16 +5,18 @@ * Plugin to provide a synonyms-friendly argument handler for a Taxonomy term. */ -$plugin = array( - 'title' => 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', -); +if (module_exists('taxonomy')) { + $plugin = array( + 'title' => 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. @@ -66,12 +68,12 @@ function synonyms_term_synonyms_context($arg = NULL, $conf = NULL, $empty = FALS foreach ($vocabularies as $vocabulary) { $condition = db_and(); if ($conf['transform']) { - $condition->where("REPLACE(" . AbstractSynonymsSynonymsBehavior::COLUMN_PLACEHOLDER . ", ' ', '-') = :argument", array( + $condition->where("REPLACE(" . AbstractSynonymsBehavior::COLUMN_SYNONYM_PLACEHOLDER . ", ' ', '-') = :argument", array( ':argument' => $arg, )); } else { - $condition->condition(AbstractSynonymsSynonymsBehavior::COLUMN_PLACEHOLDER, $arg); + $condition->condition(AbstractSynonymsBehavior::COLUMN_SYNONYM_PLACEHOLDER, $arg); } $rows = synonyms_synonyms_find($condition, 'taxonomy_term', $vocabulary->machine_name); if (!empty($rows)) { diff --git a/sites/all/modules/contrib/taxonomy/synonyms/plugins/behavior/autocomplete.inc b/sites/all/modules/contrib/taxonomy/synonyms/plugins/behavior/autocomplete.inc index 6d62b8aa..761376b6 100644 --- a/sites/all/modules/contrib/taxonomy/synonyms/plugins/behavior/autocomplete.inc +++ b/sites/all/modules/contrib/taxonomy/synonyms/plugins/behavior/autocomplete.inc @@ -23,8 +23,8 @@ function synonyms_behavior_autocomplete_settings_form($form, &$form_state, $sett $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:
        • @synonym to denote value of the synonym
        • @term to denote term name
        • @field_name to denote lowercase label of the field from where the synonym originates
        '), + '#default_value' => isset($settings['wording']) ? $settings['wording'] : '@synonym is a synonym of @entity', + '#description' => t('Specify with what wording the synonyms should be suggested in the autocomplete feature. You may use:
        • @synonym to denote value of the synonym
        • @entity to denote entity name
        • @field_name to denote lowercase label of the field from where the synonym originates
        '), '#required' => TRUE, ); diff --git a/sites/all/modules/contrib/taxonomy/synonyms/plugins/behavior/select.inc b/sites/all/modules/contrib/taxonomy/synonyms/plugins/behavior/select.inc index e10b6d1d..a30d4df3 100644 --- a/sites/all/modules/contrib/taxonomy/synonyms/plugins/behavior/select.inc +++ b/sites/all/modules/contrib/taxonomy/synonyms/plugins/behavior/select.inc @@ -24,7 +24,7 @@ function synonyms_behavior_select_settings_form($form, &$form_state, $settings) '#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:
        • @synonym to denote value of the synonym
        • @term to denote term name
        • @field_name to denote lowercase label of the field from where the synonym originates
        '), + '#description' => t('Specify with what wording the synonyms should be placed in the select form element. You may use:
        • @synonym to denote value of the synonym
        • @entity to denote entity name
        • @field_name to denote lowercase label of the field from where the synonym originates
        '), '#required' => TRUE, ); diff --git a/sites/all/modules/contrib/taxonomy/synonyms/plugins/behavior/synonyms.inc b/sites/all/modules/contrib/taxonomy/synonyms/plugins/behavior/synonyms.inc deleted file mode 100644 index b7c44f7f..00000000 --- a/sites/all/modules/contrib/taxonomy/synonyms/plugins/behavior/synonyms.inc +++ /dev/null @@ -1,12 +0,0 @@ - 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 d81e355b..dc8b6bcd 100644 --- a/sites/all/modules/contrib/taxonomy/synonyms/synonyms.api.php +++ b/sites/all/modules/contrib/taxonomy/synonyms/synonyms.api.php @@ -6,124 +6,170 @@ */ /** - * Hook to collect info about available synonyms behavior implementations. + * Collect info about available synonyms behavior implementations. * - * Hook to collect info about what PHP classes implement provided synonyms - * behavior for different field types. + * If your module ships a synonyms behavior implementation you probably want to + * implement this hook. However, exercise caution, if your synonyms behavior + * implementation is a field-based one, you might be better off implementing + * hook_synonyms_field_behavior_implementation_info(). * + * @param string $entity_type + * Entity type whose synonyms behavior implementations are requested + * @param string $bundle + * Bundle name whose synonyms behavior implementations are requested * @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 + * Behavior name whose implementations are requested * * @return array - * 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 + * Array of information about synonyms behavior implementations your module + * exposes. Each sub array will represent a single synonyms behavior + * implementation and should have the following structure: + * - provider: (string) machine name of your synonyms behavior implementation. + * Prefix it with your module name to make sure no name collision happens. + * Also, provider must be unique within the namespace of behavior, entity + * type and bundle. Basically, this is what distinguishes one behavior + * implementation from another + * - label: (string) Human friendly translated name of your synonyms behavior + * implementation + * - class: (string) Name of PHP class that implements synonyms behavior + * interface, which is stated in synonyms behavior definition. This class + * will do all the synonyms work. This hook serves pure declarative function + * to map entity types, bundles with their synonym behavior implementations + * whereas real "synonyms-related" work is implemented in your class */ -function hook_synonyms_behavior_implementation_info($behavior) { - switch ($behavior) { - case 'autocomplete': - return array( - 'my-field-type' => 'MyFieldTypeAutocompleteSynonymsBehavior', - ); - break; +function hook_synonyms_behavior_implementation_info($entity_type, $bundle, $behavior) { + $providers = array(); + + switch ($entity_type) { + case 'entity_type_i_want': + switch ($bundle) { + case 'bundle_i_want': + switch ($behavior) { + case 'behavior_i_want': + $providers[] = array( + 'provider' => 'my_module_synonyms_behavior_implementation_machine_name', + 'label' => t('This is human friendly name of my synonyms behavior implementation. Put something meaningful here'), + 'class' => 'MySynonymsSynonymsBehavior', + ); + break; + } + break; + } - case 'another-behavior': - return array( - 'my-field-type-or-yet-another-field-type' => 'MyFieldTypeAnotherBehaviorSynonymsBehavior', - ); break; } - return array(); + return $providers; } /** - * Hook to alter info about available synonyms behavior implementations. + * Example of synonyms behavior implementation 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 + * You are encouraged to extend AbstractSynonymsBehavior class as that one + * contains a few heuristic that make your implementation easier. */ -function hook_synonyms_behavior_implementation_info_alter(&$info, $behavior) { - switch ($behavior) { - case 'the-behavior-i-want': - $info['the-field-type-i-want'] = 'MyFieldTypeAutocompleteSynonymsBehavior'; - break; - } -} +class MySynonymsSynonymsBehavior extends AbstractSynonymsBehavior implements AutocompleteSynonymsBehavior { -/** - * 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. + /** + * Extract synonyms from an entity within a specific behavior implementation. + * + * @param object $entity + * Entity from which to extract synonyms + * + * @return array + * Array of synonyms extracted from $entity + */ + public function extractSynonyms($entity) { $synonyms = array(); - foreach ($items as $item) { - $synonyms[] = $item['foo']; - } + + // Do something with $entity in order to extract synonyms from it. Add all + // those synonyms into your $synonyms array. + return $synonyms; } - 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( - 'foo' => $label, - )); + /** + * Add an entity as a synonym into 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 merged entity + * into the trunk entity). You should update $trunk_entity in such a way that + * it holds $synonym_entity as a synonym (it all depends on how data is stored + * in your behavior implementation, but probably you will store entity label + * or its ID as you cannot literaly store an entity inside of another entity). + * If entity of type $synonym_entity_type cannot be converted into a format + * expected by your behavior implementation, just do nothing. + * + * @param object $trunk_entity + * Entity into which another one should be added as synonym + * @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 + */ + public function mergeEntityAsSynonym($trunk_entity, $synonym_entity, $synonym_entity_type) { + // If you can add $synonym_entity into $trunk_entity, then do so. + // For example: + $trunk_entity->synonym_storage[] = $synonym_entity; } - 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']; + /** + * Look up entities by their synonyms within a behavior implementation. + * + * You are provided with a SQL condition that you should apply to the storage + * of synonyms within the provided behavior implementation. And then return + * result: what entities match by the provided condition through what + * synonyms. + * + * @param QueryConditionInterface $condition + * Condition that defines what to search for. Apart from normal SQL + * conditions as known in Drupal, it may contain the following placeholders: + * - AbstractSynonymsBehavior::COLUMN_SYNONYM_PLACEHOLDER: to denote + * synonyms column which you should replace with the actual column name + * where the synonyms data for your provider is stored in plain text. + * - AbstractSynonymsBehavior::COLUMN_ENTITY_ID_PLACEHOLDER: to denote + * column that holds entity ID. You are supposed to replace this placeholder + * with actual column name that holds entity ID in your case. + * For ease of work with these placeholders, you may extend the + * AbstractSynonymsBehavior class and then just invoke the + * AbstractSynonymsBehavior->synonymsFindProcessCondition() method, so you + * won't have to worry much about it + * + * @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) { + // Here, as an example, we'll query an imaginary table where your module + // supposedly keeps synonyms. We'll also use helpful + // AbstractSynonymsBehavior::synonymsFindProcessCondition() to normalize + // $condition argument. + $query = db_select('my_synonyms_storage_table', 'table'); + $query->addField('table', 'entity_id', 'entity_id'); + $query->addField('table', 'synonym', 'synonym'); + $this->synonymsFindProcessCondition($condition, 'table.synonym', 'table.entity_id'); + $query->condition($condition); + return $query->execute(); } - 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(); + /** + * Collect info on features pipe during invocation of hook_features_export(). + * + * If your synonyms provider depends on some other features components, this + * method should return them. + * + * @return array + * Array of features pipe as per hook_features_export() specification + */ + public function featuresExportPipe() { + $pipe = parent::featuresExportPipe(); + // Here you can add any additional features components your provider + // depends on. + return $pipe; } } diff --git a/sites/all/modules/contrib/taxonomy/synonyms/synonyms.features.inc b/sites/all/modules/contrib/taxonomy/synonyms/synonyms.features.inc new file mode 100644 index 00000000..f2481251 --- /dev/null +++ b/sites/all/modules/contrib/taxonomy/synonyms/synonyms.features.inc @@ -0,0 +1,112 @@ +featuresExportPipe()); + } + + $export['dependencies'][] = 'synonyms'; + $export['dependencies'] = drupal_map_assoc(array_unique($export['dependencies'])); + return $pipe; +} + +/** + * Implements hook_features_export_options(). + */ +function synonyms_features_export_options() { + $options = array(); + + foreach (synonyms_behavior_get_all_enabled() as $behavior_implementation) { + $key = array( + $behavior_implementation['entity_type'], + $behavior_implementation['bundle'], + $behavior_implementation['provider'], + $behavior_implementation['behavior'], + ); + if ($behavior_implementation['entity_type'] == $behavior_implementation['bundle']) { + $label = t('@entity_type @provider @behavior', array( + '@entity_type' => $behavior_implementation['entity_type'], + '@provider' => $behavior_implementation['label'], + '@behavior' => $behavior_implementation['behavior'], + )); + } + else { + $label = t('@entity_type @bundle @provider @behavior', array( + '@entity_type' => $behavior_implementation['entity_type'], + '@bundle' => $behavior_implementation['bundle'], + '@provider' => $behavior_implementation['label'], + '@behavior' => $behavior_implementation['behavior'], + )); + } + $options[implode(':', $key)] = $label; + } + + return $options; +} + +/** + * Implements hook_features_export_render(). + */ +function synonyms_features_export_render($module_name, $data, $export = NULL) { + $code = array(); + $code[] = ' $synonyms = array();'; + foreach ($data as $name) { + list($entity_type, $bundle, $provider, $behavior) = explode(':', $name); + $behavior_implementation = synonyms_behavior_get_all_enabled($entity_type, $bundle, $provider, $behavior); + $behavior_implementation = reset($behavior_implementation); + if (is_array($behavior_implementation)) { + $behavior_implementation = array_intersect_key($behavior_implementation, drupal_map_assoc(array( + 'entity_type', 'bundle', 'provider', 'settings', 'behavior', + ))); + } + $code[] = " \$synonyms['{$name}'] = " . features_var_export($behavior_implementation, ' ') .";"; + } + $code[] = " return \$synonyms;"; + $code = implode("\n", $code); + return array('default_synonyms' => $code); +} + +/** + * Implements hook_features_revert(). + */ +function synonyms_features_revert($module_name) { + $synonyms = module_invoke($module_name, 'default_synonyms'); + foreach ($synonyms as $v) { + synonyms_behavior_implementation_save($v); + } +} + +/** + * Implements hook_features_rebuild(). + */ +function synonyms_features_rebuild($module_name) { + $synonyms = module_invoke($module_name, 'default_synonyms'); + foreach ($synonyms as $v) { + synonyms_behavior_implementation_save($v); + } +} diff --git a/sites/all/modules/contrib/taxonomy/synonyms/synonyms.info b/sites/all/modules/contrib/taxonomy/synonyms/synonyms.info index de760b3c..14cacff2 100644 --- a/sites/all/modules/contrib/taxonomy/synonyms/synonyms.info +++ b/sites/all/modules/contrib/taxonomy/synonyms/synonyms.info @@ -1,9 +1,12 @@ name = Synonyms -description = "Provides synonyms feature for working with Drupal Taxonomy" -package = Taxonomy +description = "Provides synonyms feature for Drupal entities." +package = Synonyms core = 7.x -dependencies[] = taxonomy dependencies[] = ctools +dependencies[] = entity +configure = admin/structure/synonyms + +test_dependencies[] = entityreference:entityreference files[] = synonyms.test @@ -15,10 +18,12 @@ 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 +files[] = views/synonyms_views_handler_field_synonyms.inc +files[] = views/synonyms_views_handler_filter_entityreference_synonyms.inc -; Information added by Drupal.org packaging script on 2015-12-02 -version = "7.x-1.4" +; Information added by Drupal.org packaging script on 2016-05-07 +version = "7.x-1.5" core = "7.x" project = "synonyms" -datestamp = "1449079740" +datestamp = "1462586641" diff --git a/sites/all/modules/contrib/taxonomy/synonyms/synonyms.install b/sites/all/modules/contrib/taxonomy/synonyms/synonyms.install index dcfd5e6c..e43c62ac 100644 --- a/sites/all/modules/contrib/taxonomy/synonyms/synonyms.install +++ b/sites/all/modules/contrib/taxonomy/synonyms/synonyms.install @@ -12,13 +12,25 @@ 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.', + 'description' => 'Stores synonyms settings for all the entities and providers. Only enabled synonyms behavior implementations 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', + 'entity_type' => array( + 'description' => 'Entity type whose behavior implementation is stored in this row.', + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + ), + 'bundle' => array( + 'description' => 'Bundle name whose behavior implementation is stored in this row.', + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + ), + 'provider' => array( + 'description' => 'Provider name whose behavior implementation is stored in this row.', + 'type' => 'varchar', + 'length' => 255, 'not null' => TRUE, - 'unsigned' => TRUE, ), 'behavior' => array( 'description' => 'Name of the synonyms behavior (ctools plugin), whose settings are stored in this row.', @@ -34,16 +46,11 @@ function synonyms_schema() { ), ), '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'), + // We build 2 different indexes on the same column set because there are + // 2 different functions that may query this table and the columns they + // filter on may vary. + 'behavior_implementation' => array('behavior', 'entity_type', 'bundle', 'provider'), + 'all_enabled' => array('entity_type', 'bundle', 'provider', 'behavior'), ), ); @@ -64,6 +71,33 @@ function synonyms_uninstall() { } } +/** + * Implements hook_requirements(). + */ +function synonyms_requirements($phase) { + $requirements = array(); + switch ($phase) { + case 'update': + // Synonyms has changed its dependencies. It used to be Taxonomy and now + // it is Entity module. We make sure Entity is enabled otherwise we halt + // the update for Synonyms. + // TODO: remove this requirement check at some point, when it becomes + // obsolete. + $t = get_t(); + $requirements['synonyms_entity_dependency'] = array( + 'title' => $t('Synonyms depends on Entity'), + 'description' => $t('Synonyms module depends on Entity module since 4-Mar-2016. At the same time it no longer depends on Taxonomy module while does integrate with the latter. If you do not have installed/enabled Entity module, do so before running updates on Synonyms module. Until Entity module is enabled and updates are executed, Synonyms module performance may degrade and even trigger PHP errors. See @url for more info.', array( + '@url' => 'https://www.drupal.org/node/1194802', + )), + 'value' => module_exists('entity') ? $t('Passed') : $t('Failed'), + 'severity' => module_exists('entity') ? REQUIREMENT_INFO : REQUIREMENT_ERROR, + ); + break; + } + + return $requirements; +} + /** * Implements hook_update_N(). * @@ -188,3 +222,92 @@ function synonyms_update_7102() { variable_del($row->name); } } + +/** + * Expanding Synonyms module to work with all entity types. + * + * Namely, the following happens: + * - general "synonyms" behavior gets removed as too ambiguous. + * - alter schema of synonyms table + * - "select" and "autocomplete" behaviors undergo change in settings ("@term" + * placeholder is replaced with "@entity" one) + * - taxonomy term reference widgets get renamed to their new names + * - all enabled behavior implementations are renamed to their new names in + * order to support field- and property-based synonyms implementations + */ +function synonyms_update_7103() { + db_delete('synonyms_settings') + ->condition('behavior', 'synonyms') + ->execute(); + + db_drop_index('synonyms_settings', 'behavior'); + db_drop_unique_key('synonyms_settings', 'instance_behavior'); + + db_add_field('synonyms_settings', 'entity_type', array( + 'description' => 'Entity type whose behavior implementation is stored in this row.', + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '', + )); + db_add_field('synonyms_settings', 'bundle', array( + 'description' => 'Bundle name whose behavior implementation is stored in this row.', + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '', + )); + db_add_field('synonyms_settings', 'provider', array( + 'description' => 'Provider name whose behavior implementation is stored in this row.', + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '', + )); + + db_query('UPDATE {synonyms_settings} s INNER JOIN {field_config_instance} i ON i.id = s.instance_id SET s.entity_type = i.entity_type, s.bundle = i.bundle, s.provider = i.field_name'); + + db_drop_field('synonyms_settings', 'instance_id'); + + db_add_unique_key('synonyms_settings', 'behavior_implementation', array('behavior', 'entity_type', 'bundle', 'provider')); + db_add_unique_key('synonyms_settings', 'all_enabled', array('entity_type', 'bundle', 'provider', 'behavior')); + + module_enable(array('synonyms_provider_field')); + + $alter_behaviors = array('select', 'autocomplete'); + foreach (synonyms_behavior_get_all_enabled() as $behavior_implementation) { + db_delete('synonyms_settings') + ->condition('provider', $behavior_implementation['provider']) + ->execute(); + $behavior_implementation['provider'] = synonyms_provider_field_provider_name(field_info_field($behavior_implementation['provider'])); + if (in_array($behavior_implementation['behavior'], $alter_behaviors)) { + $behavior_implementation['settings']['wording'] = str_replace('@term', '@entity', $behavior_implementation['settings']['wording']); + } + synonyms_behavior_implementation_save($behavior_implementation); + } + + // Keys are the old widget names, whereas corresponding values are the new + // widget names. + $migrate_map = array( + 'synonyms_select' => 'synonyms_select_taxonomy_term', + 'synonyms_autocomplete' => 'synonyms_autocomplete_taxonomy_term', + ); + foreach (field_info_field_map() as $field_name => $field_info) { + if ($field_info['type'] == 'taxonomy_term_reference') { + foreach ($field_info['bundles'] as $entity_type => $bundles) { + foreach ($bundles as $bundle) { + $instance = field_read_instance($entity_type, $field_name, $bundle); + if (in_array($instance['widget']['type'], array_keys($migrate_map))) { + $instance['widget']['type'] = $migrate_map[$instance['widget']['type']]; + + if ($instance['widget']['type'] == 'synonyms_autocomplete_taxonomy_term' && $instance['widget']['settings']['synonyms_autocomplete_path'] == 'synonyms/autocomplete') { + $instance['widget']['settings']['synonyms_autocomplete_path'] = 'synonyms/autocomplete-taxonomy-term'; + } + + field_update_instance($instance); + } + } + } + } + } +} diff --git a/sites/all/modules/contrib/taxonomy/synonyms/synonyms.module b/sites/all/modules/contrib/taxonomy/synonyms/synonyms.module index fe53208c..b322d608 100644 --- a/sites/all/modules/contrib/taxonomy/synonyms/synonyms.module +++ b/sites/all/modules/contrib/taxonomy/synonyms/synonyms.module @@ -2,7 +2,7 @@ /** * @file - * Provide synonyms feature for Drupal Taxonomy. + * Provide synonyms feature for Drupal entities. */ /** @@ -11,18 +11,62 @@ function synonyms_menu() { $items = array(); - $items['synonyms/autocomplete/%/%/%'] = array( + $items['admin/structure/synonyms'] = array( + 'title' => 'Synonyms', + 'description' => 'Manage synonyms settings for all entity types.', + 'page callback' => 'synonyms_settings_overview', + 'access arguments' => array('administer synonyms'), + 'file' => 'synonyms.pages.inc', + 'type' => MENU_NORMAL_ITEM, + ); + + $items['admin/structure/synonyms/%synonyms_entity_type/%synonyms_bundle'] = array( + 'title' => 'Synonyms settings', + 'title callback' => 'synonyms_settings_title', + 'title arguments' => array(3, 4), + 'description' => 'Manage synonyms settings.', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('synonyms_settings_form', 3, 4), + 'access arguments' => array('administer synonyms'), + 'load arguments' => array(3), + 'file' => 'synonyms.pages.inc', + ); + + if (module_exists('taxonomy')) { + $items['synonyms/autocomplete-taxonomy-term/%/%/%'] = array( + 'title' => 'Autocomplete Synonyms', + 'page callback' => 'synonyms_autocomplete_taxonomy_term', + 'page arguments' => array(2, 3, 4), + 'access arguments' => array('access content'), + 'file' => 'synonyms.pages.inc', + 'type' => MENU_CALLBACK, + ); + } + + $items['synonyms/autocomplete-entity/%/%/%'] = array( 'title' => 'Autocomplete Synonyms', - 'page callback' => 'synonyms_autocomplete', + 'page callback' => 'synonyms_autocomplete_entity', 'page arguments' => array(2, 3, 4), - 'access arguments' => array('access content'), + 'access callback' => 'entityreference_autocomplete_access_callback', + 'access arguments' => array('tags', 2, 3, 4), 'file' => 'synonyms.pages.inc', 'type' => MENU_CALLBACK, ); - return $items; } +/** + * Implements hook_permission(). + */ +function synonyms_permission() { + return array( + 'administer synonyms' => array( + 'title' => t('Administer synonyms'), + 'description' => t('Administer synonyms of all entity types.'), + ), + ); +} + /** * Implements hook_ctools_plugin_type(). */ @@ -81,58 +125,31 @@ function synonyms_theme() { */ function synonyms_entity_property_info() { $info = array(); - $properties = &$info['taxonomy_term']['properties']; - $properties['synonyms'] = array( - 'label' => t('Synonyms'), - 'description' => t('Synonyms of entity.'), - 'type' => 'list', - 'getter callback' => 'synonyms_get_sanitized', - 'computed' => TRUE, - 'sanitized' => TRUE, - 'raw getter callback' => 'synonyms_get_raw', - ); - return $info; -} -/** - * Implements hook_field_delete_instance(). - */ -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); - } -} - -/** - * Implements hook_synonyms_behavior_implementation_info(). - */ -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', + foreach (entity_get_info() as $entity_type => $entity_info) { + $entity_type = synonyms_entity_type_load($entity_type); + if ($entity_type) { + $info[$entity_type]['properties']['synonyms'] = array( + 'label' => t('Synonyms'), + 'description' => t('Synonyms of entity.'), + 'type' => 'list', + 'getter callback' => 'synonyms_get_sanitized', + 'computed' => TRUE, + 'sanitized' => TRUE, + 'raw getter callback' => 'synonyms_get_raw', ); - break; + } } - return array(); + + return $info; } /** * Implements hook_form_FORM_ID_alter(). */ function synonyms_form_taxonomy_form_vocabulary_alter(&$form, &$form_state) { + // TODO: remove this function after a few releases of Synonyms module, since + // now the module oversees synonyms of all entities, not only taxonomy terms. if (isset($form_state['confirm_delete']) && $form_state['confirm_delete']) { return; } @@ -145,99 +162,14 @@ function synonyms_form_taxonomy_form_vocabulary_alter(&$form, &$form_state) { '#type' => 'fieldset', '#title' => t('Synonyms'), '#collapsible' => TRUE, - '#tree' => TRUE, ); - $behaviors = synonyms_behaviors(); - $bundle = field_extract_bundle('taxonomy_term', $form['#vocabulary']); - - $form['synonyms']['behaviors'] = array( - '#theme' => 'synonyms_behaviors_settings', - '#id' => 'synonyms-behaviors-settings-wrapper', + $form['synonyms']['link'] = array( + '#markup' => t('You can configure synonyms of @vocabulary following this link.', array( + '@vocabulary' => $form['#vocabulary']->name, + '@url' => url('admin/structure/synonyms/taxonomy_term/' . $form['#vocabulary']->machine_name, array('query' => drupal_get_destination())), + )), ); - - 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['#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']; } /** @@ -245,12 +177,12 @@ function synonyms_behaviors_settings_form_ajax($form, &$form_state) { */ function synonyms_field_widget_info() { return array( - 'synonyms_autocomplete' => array( - 'label' => t('Synonyms friendly autocomplete term widget'), + 'synonyms_autocomplete_taxonomy_term' => array( + 'label' => t('Synonyms friendly autocomplete'), 'field types' => array('taxonomy_term_reference'), 'settings' => array( 'size' => 60, - 'synonyms_autocomplete_path' => 'synonyms/autocomplete', + 'synonyms_autocomplete_path' => 'synonyms/autocomplete-taxonomy-term', 'suggestion_size' => 10, 'suggest_only_unique' => FALSE, 'auto_creation' => 1, @@ -259,7 +191,20 @@ function synonyms_field_widget_info() { 'multiple values' => FIELD_BEHAVIOR_CUSTOM, ), ), - 'synonyms_select' => array( + 'synonyms_autocomplete_entity' => array( + 'label' => t('Synonyms friendly autocomplete'), + 'field types' => array('entityreference'), + 'settings' => array( + 'size' => 60, + 'synonyms_autocomplete_path' => 'synonyms/autocomplete-entity', + 'suggestion_size' => 10, + 'suggest_only_unique' => FALSE, + ), + 'behaviors' => array( + 'multiple values' => FIELD_BEHAVIOR_CUSTOM, + ), + ), + 'synonyms_select_taxonomy_term' => array( 'label' => t('Synonyms friendly select list'), 'field types' => array('taxonomy_term_reference'), 'settings' => array( @@ -269,6 +214,14 @@ function synonyms_field_widget_info() { 'multiple values' => FIELD_BEHAVIOR_CUSTOM, ), ), + 'synonyms_select_entity' => array( + 'label' => t('Synonyms friendly select list'), + 'field types' => array('entityreference'), + 'settings' => array(), + 'behaviors' => array( + 'multiple values' => FIELD_BEHAVIOR_CUSTOM, + ), + ), ); } @@ -282,13 +235,16 @@ function synonyms_field_widget_settings_form($field, $instance) { $form = array(); 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'], - ); + case 'synonyms_autocomplete_taxonomy_term': + case 'synonyms_autocomplete_entity': + if ($widget['type'] == 'synonyms_autocomplete_taxonomy_term') { + $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', @@ -307,7 +263,7 @@ function synonyms_field_widget_settings_form($field, $instance) { ); break; - case 'synonyms_select': + case 'synonyms_select_taxonomy_term': $form['sort'] = array( '#type' => 'radios', '#title' => t('Sort'), @@ -328,23 +284,39 @@ 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) { - $default_value = array(); - foreach ($items as $item) { - $default_value[] = $item['tid']; - } + $default_value = synonyms_select_default_value($field, $instance, $items); switch ($instance['widget']['type']) { - case 'synonyms_autocomplete': - $tags = taxonomy_term_load_multiple($default_value); + case 'synonyms_autocomplete_taxonomy_term': + case 'synonyms_autocomplete_entity': + switch ($instance['widget']['type']) { + case 'synonyms_autocomplete_taxonomy_term': + $default_value_string = taxonomy_implode_tags(taxonomy_term_load_multiple($default_value)); + $element_validate = array('taxonomy_autocomplete_validate', 'synonyms_autocomplete_taxonomy_term_validate'); + break; + + case 'synonyms_autocomplete_entity': + $default_value_string = array(); + $entity = isset($element['#entity']) ? $element['#entity'] : NULL; + $handler = entityreference_get_selection_handler($field, $instance, $instance['entity_type'], $entity); + + $target_entities = entity_load($field['settings']['target_type'], $default_value); + foreach ($target_entities as $target_entity_id => $target_entity) { + $default_value_string[] = synonyms_autocomplete_escape($handler->getLabel($target_entity)); + } + $default_value_string = drupal_implode_tags($default_value_string); + + $element_validate = array('synonyms_autocomplete_entity_validate'); + break; + } $element += array( '#type' => 'textfield', - '#default_value' => taxonomy_implode_tags($tags), + '#default_value' => $default_value_string, '#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'], + '#element_validate' => $element_validate, '#attached' => array( 'js' => array( drupal_get_path('module', 'synonyms') . '/js/synonyms-autocomplete.js' => array(), @@ -354,9 +326,12 @@ function synonyms_field_widget_form(&$form, &$form_state, $field, $instance, $la 'class' => array('synonyms-autocomplete'), ), ); + if (isset($instance['widget']['settings']['auto_creation'])) { + $element['#auto_creation'] = $instance['widget']['settings']['auto_creation']; + } break; - case 'synonyms_select': + case 'synonyms_select_taxonomy_term': $multiple = $field['cardinality'] > 1 || $field['cardinality'] == FIELD_CARDINALITY_UNLIMITED; $options = array(); @@ -367,10 +342,10 @@ function synonyms_field_widget_form(&$form, &$form_state, $field, $instance, $la 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); + $options[] = synonyms_select_option_entity($term, 'taxonomy_term', NULL, NULL, array('depth')); foreach ($behavior_implementations as $implementation) { - foreach (synonyms_extract_synonyms($term, $implementation) as $synonym) { - $options[] = synonyms_select_option($term, $synonym, $implementation); + foreach ($implementation['object']->extractSynonyms($term) as $synonym) { + $options[] = synonyms_select_option_entity($term, 'taxonomy_term', $synonym, $implementation, array('depth')); } } } @@ -379,22 +354,32 @@ function synonyms_field_widget_form(&$form, &$form_state, $field, $instance, $la case 'name': // TODO: is there any way to leverage DB for the sorting routine? - $options = synonyms_select_sort_name_options_recursive($vocabulary, $tree['parent']); + $options = synonyms_select_taxonomy_term_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'), + '#element_validate' => array('synonyms_select_validate', 'synonyms_select_form_to_storage'), + '#empty_option' => t('- None -'), + ); + break; + + case 'synonyms_select_entity': + $multiple = $field['cardinality'] > 1 || $field['cardinality'] == FIELD_CARDINALITY_UNLIMITED; + + $element += array( + '#type' => 'select', + '#multiple' => $multiple, + '#options' => synonyms_select_entity_options($field, $instance, $element['#entity']), + '#default_value' => $default_value, + '#element_validate' => array('synonyms_select_validate', 'synonyms_select_form_to_storage'), + '#empty_option' => t('- None -'), ); break; } @@ -409,18 +394,45 @@ function synonyms_field_widget_error($element, $error, $form, &$form_state) { form_error($element, $error['message']); } +/** + * Implements hook_features_api(). + */ +function synonyms_features_api() { + return array( + 'synonyms' => array( + 'name' => t('Synonyms'), + 'file' => drupal_get_path('module', 'synonyms') . '/synonyms.features.inc', + 'default_hook' => 'default_synonyms', + 'feature_source' => TRUE, + ), + ); +} + +/** + * Implements hook_modules_disabled(). + */ +function synonyms_modules_disabled($modules) { + foreach ($modules as $module) { + db_delete('synonyms_settings') + ->condition('provider', db_like($module) . '%', 'LIKE') + ->execute(); + } +} + /** * Form element validate handler. * * Handle validation for taxonomy term synonym-friendly autocomplete element. */ -function synonyms_autocomplete_validate($element, &$form_state) { +function synonyms_autocomplete_taxonomy_term_validate($element, &$form_state) { // After taxonomy_autocomplete_validate() has finished its job any terms it // didn't find have been set for autocreation. We need to: // (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']); + $tids = array(); + $field = field_widget_field($element, $form_state); foreach ($value as $delta => $term) { if ($term['tid'] == 'autocreate') { @@ -428,7 +440,7 @@ function synonyms_autocomplete_validate($element, &$form_state) { 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); + $synonyms = $behavior_implementation['object']->synonymsFind(db_and()->condition(AbstractSynonymsBehavior::COLUMN_SYNONYM_PLACEHOLDER, $term['name'])); foreach ($synonyms as $synonym) { $synonym_tid = $synonym->entity_id; break(2); @@ -436,21 +448,124 @@ function synonyms_autocomplete_validate($element, &$form_state) { } } - if ($synonym_tid != 0) { + if ($synonym_tid != 0 && !in_array($synonym_tid, $tids)) { $value[$delta]['tid'] = $synonym_tid; + $tids[] = $synonym_tid; } elseif (!$element['#auto_creation']) { unset($value[$delta]); } } + else { + $tids[] = $term['tid']; + } } $value = array_values($value); form_set_value($element, $value, $form_state); } +/** + * Form element validate handler. + * + * Validate entity reference synonyms friendly autocomplete element. + */ +function synonyms_autocomplete_entity_validate($element, &$form_state) { + $input = drupal_map_assoc(drupal_explode_tags(drupal_strtolower($element['#value']))); + + $value = array(); + if (!empty($input)) { + $field = field_info_field($element['#field_name']); + $instance = field_info_instance($element['#entity_type'], $field['field_name'], $element['#bundle']); + $handler = entityreference_get_selection_handler($field, $instance); + $matches = $handler->getReferencableEntities($input, 'IN'); + + foreach ($matches as $bundle => $entity_ids) { + foreach ($entity_ids as $entity_id => $label) { + $value[] = $entity_id; + unset($input[drupal_strtolower($label)]); + } + } + + if (!empty($input)) { + $behavior_implementations = synonyms_behavior_get('autocomplete', $field['settings']['target_type'], synonyms_field_target_bundles($field), TRUE); + foreach ($behavior_implementations as $implementation) { + $condition = db_and(); + $condition->condition(AbstractSynonymsBehavior::COLUMN_SYNONYM_PLACEHOLDER, $input, 'IN'); + foreach ($implementation['object']->synonymsFind($condition) as $synonym) { + $value[] = $synonym->entity_id; + unset($input[drupal_strtolower($synonym->synonym)]); + if (empty($input)) { + break(2); + } + } + } + } + } + + $tmp = array_unique($value); + $value = array(); + foreach ($tmp as $target_id) { + $value[] = array('target_id' => $target_id); + } + + form_set_value($element, $value, $form_state); +} + +/** + * Try finding an entity by its name or synonym. + * + * @param string $entity_type + * What entity type is being searched + * @param string $name + * The look up keyword (the supposed name or synonym) + * @param string $bundle + * Optionally limit the search within a specific bundle name of the provided + * entity type + * + * @return int + * ID of the looked up entity. If such entity was not found, then 0 is + * returned + */ +function synonyms_get_entity_by_synonym($entity_type, $name, $bundle = NULL) { + $name = trim($name); + $entity_info = entity_get_info($entity_type); + + // This is somewhat hacky, but it's the best we can do: user.module does not + // declare 'label' entity key on 'user' entity type, while there is clearly + // one: the 'name' column. In fact, entityreference.module does about the same + // thing in EntityReference_SelectionHandler_Generic_user class. + if ($entity_type == 'user') { + $entity_info['entity keys']['label'] = 'name'; + } + + if (isset($entity_info['entity keys']['label'])) { + $efq = new EntityFieldQuery(); + $efq->entityCondition('entity_type', $entity_type); + if ($bundle) { + $efq->entityCondition('bundle', $bundle); + } + $efq->propertyCondition($entity_info['entity keys']['label'], $name); + $result = $efq->execute(); + if (isset($result[$entity_type])) { + $result = array_keys($result[$entity_type]); + return reset($result); + } + } + + $synonyms = synonyms_synonyms_find(db_and()->condition(AbstractSynonymsBehavior::COLUMN_SYNONYM_PLACEHOLDER, $name), $entity_type, $bundle); + if (!empty($synonyms)) { + return reset($synonyms)->entity_id; + } + return 0; +} + /** * Try to find a term by its name or synonym. * + * You are advised to use the more general function + * synonyms_get_entity_by_synonym() unless you really need $parent input + * argument from this function for additional filtering by Taxonomy hierarchy. + * * @param string $name * The string to be searched for its {taxonomy_term_data}.tid * @param object $vocabulary @@ -465,6 +580,9 @@ function synonyms_autocomplete_validate($element, &$form_state) { * found term, otherwise returns 0 */ function synonyms_get_term_by_synonym($name, $vocabulary, $parent = 0) { + if (!module_exists('taxonomy')) { + return 0; + } $name = trim($name); $terms = taxonomy_get_term_by_name($name, $vocabulary->machine_name); @@ -481,7 +599,7 @@ function synonyms_get_term_by_synonym($name, $vocabulary, $parent = 0) { // 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); + $synonyms = synonyms_synonyms_find(db_and()->condition(AbstractSynonymsBehavior::COLUMN_SYNONYM_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 @@ -515,6 +633,9 @@ function synonyms_get_term_by_synonym($name, $vocabulary, $parent = 0) { * otherwise it creates a new term and returns its {taxonomy_term_data}.tid */ function synonyms_add_term_by_synonym($name, $vocabulary, $parent = 0) { + if (!module_exists('taxonomy')) { + return 0; + } $tid = synonyms_get_term_by_synonym($name, $vocabulary, $parent); if ($tid) { // We found some term, returning its tid. @@ -540,35 +661,39 @@ function synonyms_add_term_by_synonym($name, $vocabulary, $parent = 0) { } /** - * Retrieve a list of sanitized synonyms of a taxonomy term. + * Retrieve list of sanitized synonyms of an entity. * - * @param $item object - * Fully loaded taxonomy term + * @param $entity object + * Fully loaded entity * * @return array - * List of sanitized synonyms of a taxonomy term + * List of sanitized synonyms of an entity */ -function synonyms_get_sanitized($item) { - $synonyms = array(); - foreach (synonyms_get_term_synonyms($item) as $synonym) { - $synonyms[] = $synonym['safe_value']; - } - return $synonyms; +function synonyms_get_sanitized($entity, array $options, $name, $entity_type, &$context) { + return array_map('check_plain', synonyms_get_raw($entity, $options, $name, $entity_type, $context)); } /** - * Retrieve a list of raw synonyms of a taxonomy term. + * Retrieve list of raw synonyms of an entity. * - * @param $item object - * Fully loaded taxonomy term + * @param $entity object + * Fully loaded entity * * @return array - * List of raw synonyms of a taxonomy term + * List of raw synonyms of an entity */ -function synonyms_get_raw($item) { +function synonyms_get_raw($entity, array $options, $name, $entity_type, &$context) { $synonyms = array(); - foreach (synonyms_get_term_synonyms($item) as $synonym) { - $synonyms[] = $synonym['value']; + $bundle = entity_extract_ids($entity_type, $entity); + $bundle = $bundle[2]; + + $behavior_implementations = synonyms_behavior_get_all_enabled($entity_type, $bundle); + $providers = array(); + foreach ($behavior_implementations as $implementation) { + if (!in_array($implementation['provider'], $providers)) { + $synonyms = array_merge($synonyms, $implementation['object']->extractSynonyms($entity)); + $providers[] = $implementation['provider']; + } } return $synonyms; } @@ -592,55 +717,23 @@ function synonyms_get_raw($item) { * @deprecated */ function synonyms_get_term_synonyms($term) { + if (!module_exists('taxonomy')) { + return array(); + } $synonyms = array(); $vocabulary = taxonomy_vocabulary_load($term->vid); $bundle = field_extract_bundle('taxonomy_term', $vocabulary); - $behavior_implementations = synonyms_behavior_get('synonyms', 'taxonomy_term', $bundle, TRUE); + $behavior_implementations = synonyms_behavior_get_all_enabled('taxonomy_term', $bundle); foreach ($behavior_implementations as $implementation) { - foreach (synonyms_extract_synonyms($term, $implementation) as $synonym) { - $synonyms[] = array( - 'value' => $synonym, - 'safe_value' => check_plain($synonym), - ); - } + foreach ($implementation['object']->extractSynonyms($term) 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; } @@ -650,19 +743,22 @@ function synonyms_extract_synonyms($entity, $behavior_implementation) { * @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: + * AbstractSynonymsBehavior::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%'") + * ->where("LOWER(REPLACE(" . AbstractSynonymsBehavior::COLUMN_PLACEHOLDER . ", ' ', '-')) LIKE :synonym", array( + * ':synonym' => '%' . db_like($some-var) . '%' + * )) * 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 + * @param string|array $bundle + * Optionally specify among synonyms of what bundle(-s) to search. You can + * specify here a string - bundle name to search within or an array of bundles + * within which to search * * @return array * Array of found synonyms and entity IDs to which those belong. Each element @@ -671,12 +767,12 @@ function synonyms_extract_synonyms($entity, $behavior_implementation) { * $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) { +function synonyms_synonyms_find(QueryConditionInterface $condition, $entity_type, $bundle = NULL) { $rows = array(); - $behavior_implementations = synonyms_behavior_get('synonyms', $entity_type, $bundle, TRUE); + $behavior_implementations = synonyms_behavior_get_all_enabled($entity_type, $bundle); foreach ($behavior_implementations as $behavior_implementation) { - foreach (synonyms_synonyms_find_behavior($condition, $behavior_implementation) as $row) { + foreach ($behavior_implementation['object']->synonymsFind($condition) as $row) { $rows[] = $row; } } @@ -684,55 +780,6 @@ function synonyms_synonyms_find(QueryConditionInterface $condition, $entity_type 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. * @@ -757,57 +804,26 @@ function synonyms_synonyms_find_behavior(QueryConditionInterface $condition, $be * * @return bool * Whether synonym has been successfully added + * + * TODO: This should be shifted into Term Merge module. */ function synonyms_add_entity_as_synonym($trunk_entity, $trunk_entity_type, $field, $synonym_entity, $synonym_entity_type) { - if ($trunk_entity_type != 'taxonomy_term') { - // Currently synonyms module only operates on taxonomy terms. - return FALSE; - } - $bundle = entity_extract_ids($trunk_entity_type, $trunk_entity); $bundle = $bundle[2]; - $behavior_implementations = synonyms_behavior_get('synonyms', $trunk_entity_type, $bundle, TRUE); + // TODO: this somehow must be incorporated into synonyms_provider_field + // submodule. + $behavior_implementations = synonyms_behavior_get_all_enabled($trunk_entity_type, $bundle, synonyms_provider_field_provider_name(field_info_field($field))); - $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. + if (empty($behavior_implementations)) { + // $field either doesn't exist in the $trunk_entity or it does not have any + // enabled behavior. return FALSE; } - $items = field_get_items($trunk_entity_type, $trunk_entity, $field['field_name']); - $items = is_array($items) ? $items : array(); + $behavior_implementation = reset($behavior_implementations); + $behavior_implementation['object']->mergeEntityAsSynonym($trunk_entity, $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(). - taxonomy_term_save($trunk_entity); + entity_save($trunk_entity_type, $trunk_entity); return TRUE; } @@ -829,11 +845,19 @@ function synonyms_add_entity_as_synonym($trunk_entity, $trunk_entity_type, $fiel * @deprecated */ function synonyms_synonyms_fields($vocabulary) { + // TODO: remove this ugly function as soon as possible. It has got hacky since + // now not only fields may be providers of synonyms. + if (!module_exists('taxonomy')) { + return array(); + } $fields = array(); $bundle = field_extract_bundle('taxonomy_term', $vocabulary); - $behavior_implementations = synonyms_behavior_get('synonyms', 'taxonomy_term', $bundle, TRUE); + $behavior_implementations = synonyms_behavior_get_all_enabled('taxonomy_term', $bundle); foreach ($behavior_implementations as $v) { - $fields[] = $v['field_name']; + $field_name = synonyms_provider_field_field_name($v['provider']); + if (field_info_field($field_name)) { + $fields[] = $field_name; + } } return $fields; } @@ -855,72 +879,124 @@ function synonyms_views_api() { * 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) + * Behavior implementations of what entity type should be looked up + * @param string|array $bundle + * Behavior implementations of what bundle(-s) should be looked up. If you + * want to look up only a single bundle, provide a string. If you want to + * look up multiple bundles at a time, provide an array of bundles. Empty + * array here would mean to include all bundles that are known within provided + * $entity_type * @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: + * Array of loaded existing synonyms behavior implementations. 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 + * - provider: (string) Machine name of this synonyms behavior implementation + * - label: (string) Human friendly name of this synonyms behavior + * implementation + * - class: (string) Name of PHP class that implements behavior interface + * - 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 + * - object: (SynonymsBehavior) If the synonyms behavior implementation is + * enabled, this property will contain a fully initialized object that + * corresponds to this behavior implementation. This object is ready for + * use: to query for synonyms or invoke any other methods behavior interface + * declares + * - module: (string) Name of the module that provides this synonyms behavior + * implementation */ -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); +function synonyms_behavior_get($behavior, $entity_type, $bundle, $only_enabled = FALSE) { + $behavior_implementations = array(); + $enabled_behavior_implementations = array(); - 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'); + $query = db_select('synonyms_settings', 's'); + $query->fields('s'); + $query->condition('behavior', $behavior); + $query->condition('entity_type', $entity_type); + $query->condition('bundle', synonyms_bundle_normalize($entity_type, $bundle)); $result = $query->execute(); - $result = $result->fetchAllAssoc('instance_id', PDO::FETCH_ASSOC); + foreach ($result as $row) { + $row = (array) $row; + if ($only_enabled) { + $behavior_implementations[] = $row; + } + else { + $enabled_behavior_implementations[$row['bundle']][$row['provider']] = $row; + } + } - return synonyms_behavior_settings_unpack($result); + if (!$only_enabled) { + foreach (synonyms_bundle_normalize($entity_type, $bundle) as $bundle_name) { + foreach (synonyms_behavior_implementation_info($entity_type, $bundle_name, $behavior) as $provider_info) { + if (isset($enabled_behavior_implementations[$bundle_name][$provider_info['provider']])) { + $provider_info['settings_serialized'] = $enabled_behavior_implementations[$bundle_name][$provider_info['provider']]['settings_serialized']; + } + $behavior_implementations[] = $provider_info; + } + } + } + return synonyms_behavior_settings_unpack($behavior_implementations); } /** - * Retrieve information about all ctools plugins of type 'synonyms behavior'. + * Load all enabled behavior implementations on an entity_type and a bundle. + * + * This is useful when you want to do some operation on all enabled behavior + * implementations for a specific entity_type and a bundle. + * + * @param string $entity_type + * Optionally filter by entity type whose enabled behavior implementations + * should be loaded + * @param string|array $bundle + * Optionally filter by bundle whose enabled behavior implementations should + * be loaded. You can supply here string - single bundle name or an array of + * bundle names. Empty array implies all existing bundle names for a provided + * $entity_type + * @param string $provider + * Optional filter to only return enabled synonyms behavior implementations of + * a specific provider + * @param string $behavior + * Optional filter to only return enabled synonyms behavior implementations of + * a specific behavior + * + * @return array + * Array of enabled behavior implementations for a provided entity type and a + * bundle. Return structure of this function is identical to the return + * structure of synonyms_behavior_get() + */ +function synonyms_behavior_get_all_enabled($entity_type = NULL, $bundle = array(), $provider = NULL, $behavior = NULL) { + $query = db_select('synonyms_settings', 's'); + $query->fields('s'); + if ($entity_type) { + $query->condition('entity_type', $entity_type); + $query->condition('bundle', synonyms_bundle_normalize($entity_type, $bundle)); + } + if ($provider) { + $query->condition('provider', $provider); + } + if ($behavior) { + $query->condition('behavior', $behavior); + } + + $result = $query->execute(); + $behavior_implementations = array(); + foreach ($result as $row) { + $behavior_implementations[] = (array) $row; + } + return synonyms_behavior_settings_unpack($behavior_implementations); +} + +/** + * Retrieve information about all cTools plugins of type 'synonyms behavior'. * * @return array * Array of information on all available synonyms behavior plugins @@ -931,52 +1007,56 @@ function synonyms_behaviors() { } /** - * 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. + * Collect info on available synonyms behavior implementations. * + * @param string $entity_type + * Entity type whose available synonyms behavior implementations to collect + * @param string $bundle + * Bundle whose available synonyms behavior implementations to collect * @param string $behavior - * What specific behavior is queried. Supply here keys from the return of - * synonyms_behaviors() function + * Name of behavior whose available synonyms behavior implementations to + * collect * * @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 + * Array of available synonyms behavior implementations. Each synonym behavior + * implementation will be an array with the following structure: + * - provider: (string) Machine name of the synonyms behavior implementation + * - label: (string) Human name of the synonyms behavior implementation + * - class: (string) Name of PHP class that implements behavior interface + * which is stated in cTools behavior plugin definition + * - entity_type: (string) Entity type that corresponds to this synonyms + * behavior implementation + * - bundle: (string) Bundle that corresponds to this synonyms behavior + * implementation + * - behavior: (string) Name of behavior that corresponds to this synonyms + * behavior implementation + * - module: (string) Name of the module that provides this synonyms behavior + * implementation */ -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; +function synonyms_behavior_implementation_info($entity_type, $bundle, $behavior) { + $providers = array(); + foreach (module_implements('synonyms_behavior_implementation_info') as $module) { + foreach (module_invoke($module, 'synonyms_behavior_implementation_info', $entity_type, $bundle, $behavior) as $provider) { + $provider['entity_type'] = $entity_type; + $provider['behavior'] = $behavior; + $provider['bundle'] = $bundle; + $provider['module'] = $module; + $providers[$provider['provider']] = $provider; + } + } + return $providers; } /** - * Determine what PHP class implements specific behavior for specific field. + * Execute unpacking on the just loaded synonyms behavior implementations. * - * @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 + * @param array $behavior_implementations + * Array of the just loaded behavior_implementations. Each sub array should + * contain the following keys: + * - entity_type: (string) Entity type that corresponds to this behavior + * implementation + * - bundle: (string) Bundle that corresponds to this behavior implementation + * - provider: (string) Machine name of this synonyms behavior implementation * - behavior: (string) name of the synonyms behavior to which these settings * apply * - settings_serialized: (string) serialized content of the settings @@ -984,93 +1064,100 @@ function synonyms_behavior_implementation_class($behavior, $field) { * @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; +function synonyms_behavior_settings_unpack($behavior_implementations) { + // Array of previously queried synonyms provider info. + $cache = array(); + + foreach ($behavior_implementations as &$behavior_implementation) { + if (!isset($cache[$behavior_implementation['behavior']][$behavior_implementation['entity_type']][$behavior_implementation['bundle']])) { + $cache[$behavior_implementation['behavior']][$behavior_implementation['entity_type']][$behavior_implementation['bundle']] = synonyms_behavior_implementation_info($behavior_implementation['entity_type'], $behavior_implementation['bundle'], $behavior_implementation['behavior']); + } + + // Behavior implementation info may be not available in some rare extreme + // cases. For example, when a field instance is being deleted. + if (isset($cache[$behavior_implementation['behavior']][$behavior_implementation['entity_type']][$behavior_implementation['bundle']][$behavior_implementation['provider']])) { + $behavior_implementation += $cache[$behavior_implementation['behavior']][$behavior_implementation['entity_type']][$behavior_implementation['bundle']][$behavior_implementation['provider']]; + } + if (isset($behavior_implementation['settings_serialized'])) { + $behavior_implementation['settings'] = unserialize($behavior_implementation['settings_serialized']); + if (isset($behavior_implementation['class'])) { + $class = $behavior_implementation['class']; + $behavior_implementation['object'] = new $class($behavior_implementation); + } + } } - return $settings; + unset($behavior_implementation); + + return $behavior_implementations; } /** - * Save the provided synonyms behavior settings into the database. + * Save the provided synonyms behavior implementation 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 + * @param array $behavior_implementation + * Behavior implementation array, such as one from synonyms_behavior_get() or + * alike */ -function synonyms_behavior_settings_save($settings) { - if (!isset($settings['settings'])) { - $settings['settings'] = array(); +function synonyms_behavior_implementation_save($behavior_implementation) { + if (!isset($behavior_implementation['settings'])) { + $behavior_implementation['settings'] = array(); } - $settings['settings_serialized'] = serialize($settings['settings']); + $behavior_implementation['settings_serialized'] = serialize($behavior_implementation['settings']); $result = db_merge('synonyms_settings') ->key(array( - 'instance_id' => $settings['instance_id'], - 'behavior' => $settings['behavior'], + 'entity_type' => $behavior_implementation['entity_type'], + 'bundle' => $behavior_implementation['bundle'], + 'provider' => $behavior_implementation['provider'], + 'behavior' => $behavior_implementation['behavior'], )) ->fields(array( - 'instance_id' => $settings['instance_id'], - 'behavior' => $settings['behavior'], - 'settings_serialized' => $settings['settings_serialized'], + 'entity_type' => $behavior_implementation['entity_type'], + 'bundle' => $behavior_implementation['bundle'], + 'provider' => $behavior_implementation['provider'], + 'behavior' => $behavior_implementation['behavior'], + 'settings_serialized' => $behavior_implementation['settings_serialized'], )) ->execute(); switch ($result) { case MergeQuery::STATUS_INSERT: $behavior_definition = synonyms_behaviors(); - $behavior_definition = $behavior_definition[$settings['behavior']]; + $behavior_definition = $behavior_definition[$behavior_implementation['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'])); + $enabled_callback($behavior_definition, $behavior_implementation); } break; } } /** - * Delete settings for specific behavior and field instance. + * Delete behavior implementation from database. * - * @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 + * @param array $behavior_implementation + * Behavior implementation array, such as one from synonyms_behavior_get() or + * alike */ -function synonyms_behavior_settings_delete($instance_id, $behavior) { +function synonyms_behavior_implementation_delete($behavior_implementation) { $behavior_definition = synonyms_behaviors(); - $behavior_definition = $behavior_definition[$behavior]; + $behavior_definition = $behavior_definition[$behavior_implementation['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); + foreach (synonyms_behavior_get($behavior_implementation['behavior'], $behavior_implementation['entity_type'], $behavior_implementation['bundle'], TRUE) as $enabled_behavior_implementation) { + if ($enabled_behavior_implementation['provider'] == $behavior_implementation['provider']) { + $disabled_callback($behavior_definition, $behavior_implementation); + break; + } + } } db_delete('synonyms_settings') - ->condition('instance_id', $instance_id) - ->condition('behavior', $behavior) + ->condition('provider', $behavior_implementation['provider']) + ->condition('entity_type', $behavior_implementation['entity_type']) + ->condition('bundle', $behavior_implementation['bundle']) + ->condition('behavior', $behavior_implementation['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. * @@ -1079,6 +1166,21 @@ function synonyms_instance_id_load($instance_id) { * expected by Field module. */ function synonyms_select_form_to_storage($element, &$form_state) { + $form_state_value = array(); + foreach (drupal_array_get_nested_value($form_state['values'], $element['#parents']) as $entity_id) { + $form_state_value[] = array($element['#columns'][0] => $entity_id); + } + + form_set_value($element, $form_state_value, $form_state); +} + +/** + * Element validate handler. + * + * Convert selected synonyms into their terms and save the updated data in the + * value of the form element. + */ +function synonyms_select_validate($element, &$form_state) { $value = array(); if ($element['#multiple']) { $value = $element['#value']; @@ -1088,26 +1190,22 @@ function synonyms_select_form_to_storage($element, &$form_state) { } foreach ($value as $k => $v) { - // For the cases when a synonym was selected and not a term option, we + // For the cases when a synonym was selected and not an entity option, we // process the selected values stripping everything that goes after // semicolon. if (!is_numeric($v)) { - $tid = explode(':', $v); - $value[$k] = $tid[0]; + $entity_id = explode(':', $v); + $value[$k] = $entity_id[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. + // The user also might have selected multiple times the same entity, given + // that an entity can be represented by more than 1 option (an entity 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); + form_set_value($element, $value, $form_state); } /** @@ -1141,37 +1239,102 @@ function synonyms_taxonomy_term_is_child_of($tid, $parent_tid) { } /** - * Format an option for select form element. + * Format an option for entity reference select form element. * - * @param object $term - * Fully loaded taxonomy term which is represented by this option + * @param object $entity + * Fully loaded entity which is represented by this option + * @param string $entity_type + * Entity type of the $entity object * @param string $synonym - * If the provided term is represented in this option by a synonym, then + * If the provided entity 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 + * @param array $options + * Array of additional settings or options that may influence execution of + * this function. Currently supported options are: + * - depth: Whether to prefix wording of option labels with depth of the + * entity. This will work only for taxonomy term entities, as they are the + * only ones that have notion of depth * * @return object - * An option for select form element + * An option for entity reference 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; +function synonyms_select_option_entity($entity, $entity_type, $synonym = NULL, $behavior_implementation = NULL, $options = array()) { + $entity_id = entity_extract_ids($entity_type, $entity); + $entity_id = $entity_id[0]; + $key = $synonym ? $entity_id . ':' . drupal_html_class($synonym) : $entity_id; + $wording = entity_label($entity_type, $entity); 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']), + '@entity' => entity_label($behavior_implementation['entity_type'], $entity), + '@field_name' => drupal_strtolower($behavior_implementation['label']), )); } + + if (in_array('depth', $options) && $entity_type == 'taxonomy_term') { + $depth = count(taxonomy_get_parents_all($entity_id)) - 1; + $wording = str_repeat('-', $depth) . $wording; + } + return (object) array( - 'option' => array($key => str_repeat('-', $term->depth) . $wording), + 'option' => array($key => $wording), ); } /** - * Supportive function to build options array with sorting by name logic. + * Construct options array for entity reference synonyms friendly select list. + * + * @param array $field + * Field definition array of entityreference type for which to construct the + * options + * @param array $instance + * Field instance definition array that corresponds to $field + * @param object $entity + * If entity is known for which the options should be generated, provide it + * here. It is safe to omit this parameter. Frankly, I do not understand well + * why or how it is used. It is just directly passed into entityreference + * selection handler class + * + * @return array + * Options array that can be plugged in directly into any #select form element + */ +function synonyms_select_entity_options($field, $instance, $entity = NULL) { + $options = entityreference_get_selection_handler($field, $instance, $instance['entity_type'], $entity)->getReferencableEntities(); + $synonyms_options = array(); + + $target_entity_info = entity_get_info($field['settings']['target_type']); + $entity_ids = array(); + foreach ($options as $bundle_entity_ids) { + $entity_ids = array_merge($entity_ids, array_keys($bundle_entity_ids)); + } + $entities = entity_load($field['settings']['target_type'], $entity_ids); + foreach ($options as $bundle => $bundle_entity_ids) { + $synonyms_options[$target_entity_info['bundles'][$bundle]['label']] = array(); + + $behavior_implementations = synonyms_behavior_get('select', $field['settings']['target_type'], $bundle, TRUE); + + foreach ($bundle_entity_ids as $entity_id => $v) { + $entity = $entities[$entity_id]; + $synonyms_options[$target_entity_info['bundles'][$bundle]['label']][] = synonyms_select_option_entity($entity, $field['settings']['target_type']); + foreach ($behavior_implementations as $behavior_implementation) { + foreach ($behavior_implementation['object']->extractSynonyms($entity) as $synonym) { + $synonyms_options[$target_entity_info['bundles'][$bundle]['label']][] = synonyms_select_option_entity($entity, $field['settings']['target_type'], $synonym, $behavior_implementation); + } + } + } + usort($synonyms_options[$target_entity_info['bundles'][$bundle]['label']], 'synonyms_select_sort_name'); + } + + if (count($synonyms_options) == 1) { + $synonyms_options = reset($synonyms_options); + } + return $synonyms_options; +} + +/** + * Supportive function to build taxonomy term options array sorted by name. * * 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 @@ -1194,10 +1357,10 @@ function synonyms_select_option($term, $synonym = NULL, $behavior_implementation * 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) { +function synonyms_select_taxonomy_term_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()); + $behavior_implementations = &drupal_static(__FUNCTION__, array()); $bundle = field_extract_bundle('taxonomy_term', $vocabulary); if (!isset($behavior_implementations[$bundle])) { @@ -1209,10 +1372,10 @@ function synonyms_select_sort_name_options_recursive($vocabulary, $parent = 0, $ $options = array(); foreach ($terms as $term) { $term->depth = $depth; - $options[] = synonyms_select_option($term); + $options[] = synonyms_select_option_entity($term, 'taxonomy_term', NULL, NULL, array('depth')); foreach ($behavior_implementations[$bundle] as $implementation) { - foreach (synonyms_extract_synonyms($term, $implementation) as $synonym) { - $options[] = synonyms_select_option($term, $synonym, $implementation); + foreach ($implementation['object']->extractSynonyms($term) as $synonym) { + $options[] = synonyms_select_option_entity($term, 'taxonomy_term', $synonym, $implementation, array('depth')); } } } @@ -1228,7 +1391,7 @@ function synonyms_select_sort_name_options_recursive($vocabulary, $parent = 0, $ $tid = array_keys($v->option); $tid = $tid[0]; if (is_numeric($tid)) { - $nested_options = synonyms_select_sort_name_options_recursive($vocabulary, $tid, $depth + 1); + $nested_options = synonyms_select_taxonomy_term_sort_name_options_recursive($vocabulary, $tid, $depth + 1); $options = array_merge(array_slice($options, 0, $i), $nested_options, array_slice($options, $i)); } } @@ -1244,3 +1407,160 @@ function synonyms_select_sort_name_options_recursive($vocabulary, $parent = 0, $ function synonyms_select_sort_name($a, $b) { return strcasecmp(reset($a->option), reset($b->option)); } + +/** + * Test if entity type is applicable for having synonyms. + * + * @param string $entity_type + * Entity type to test + * + * @return bool|string + * Whether the provided entity type is applicable for having synonyms. If it + * is applicable, the $entity_type input argument will be returned. Otherwise + * FALSE is returned + */ +function synonyms_entity_type_load($entity_type) { + if (module_exists('synonyms_provider_property')) { + // If this submodule is enabled, we get into infinite recursion. Moreover, + // it is very likely all entity types will have at least 1 property on them, + // so the synonyms_provider_property module will get at least 1 synonym for + // every entity type. So it is quite safe assumption to say we accept about + // any entity type. + return $entity_type; + } + $bundles = synonyms_bundle_normalize($entity_type, array()); + foreach (synonyms_behaviors() as $behavior => $behavior_definition) { + foreach ($bundles as $bundle) { + $behavior_implementations = synonyms_behavior_implementation_info($entity_type, $bundle, $behavior); + if (!empty($behavior_implementations)) { + return $entity_type; + } + } + } + return FALSE; +} + +/** + * Test if provided entity type and bundle are applicable for having synonyms. + * + * @param string $bundle + * Bundle name to test for ability to have synonyms + * @param string $entity_type + * Entity type to test for ability to have synonyms + * + * @return bool|string + * Whether the provided entity type and bundle may have synonyms. If they do, + * the $bundle input argument is returned. Otherwise FALSE is returned + */ +function synonyms_bundle_load($bundle, $entity_type) { + foreach (synonyms_behaviors() as $behavior => $behavior_definition) { + $behavior_implementations = synonyms_behavior_implementation_info($entity_type, $bundle, $behavior); + if (!empty($behavior_implementations)) { + return $bundle; + } + } + return FALSE; +} + +/** + * Menu title callback function for synonyms settings page. + * + * @param string $entity_type + * Entity type whose synonyms settings are managed + * @param string $bundle + * Bundle whose synonyms settings are managed + * + * @return string + * Title of the synonyms settings page + */ +function synonyms_settings_title($entity_type, $bundle) { + $entity_info = entity_get_info($entity_type); + $entity_label = isset($entity_info['plural label']) ? $entity_info['plural label'] : $entity_info['label']; + if ($entity_type == $bundle) { + return t('Synonyms settings of @entity_type', array( + '@entity_type' => $entity_label, + )); + } + return t('Synonyms settings of @bundle @entity_type', array( + '@bundle' => $entity_info['bundles'][$bundle]['label'], + '@entity_type' => $entity_label, + )); +} + +/** + * Escape string to safely use in autocomplete text field as default value. + * + * @param string $value + * String to be escaped + * + * @return string + * Escaped string $value + */ +function synonyms_autocomplete_escape($value) { + // Commas or quotes must be wrapped in quotes. + if (strpos($value, ',') !== FALSE || strpos($value, '"') !== FALSE) { + $value = '"' . str_replace('"', '""', $value) . '"'; + } + return $value; +} + +/** + * Determine a list of target bundles of an entityreference field. + * + * @param array $field + * Field definition array. This field must be of type 'entityreference' + * + * @return array|null + * List of target bundles per the settings of provided field or NULL if the + * provided field does not expose any limitations on target bundles + */ +function synonyms_field_target_bundles($field) { + $target_bundles = isset($field['settings']['handler_settings']['target_bundles']) ? array_values($field['settings']['handler_settings']['target_bundles']) : array(); + if (empty($target_bundles)) { + $target_bundles = NULL; + } + return $target_bundles; +} + +/** + * Normalize bundle argument. + * + * @param string $entity_type + * Entity type to define scope of bundles + * @param string|array $bundle + * Either a single bundle name or an array of bundle names. Empty array + * implies all known bundles for $entity_type + * + * @return array + * Normalized array bundle names + */ +function synonyms_bundle_normalize($entity_type, $bundle) { + $bundle = (array) $bundle; + if (empty($bundle)) { + $bundle = array_keys(field_info_bundles($entity_type)); + } + return $bundle; +} + +/** + * Extract default value for a select widget of Synonyms module. + * + * @param array $field + * Field definition array whose default value should be extracted + * @param array $instance + * Field instance definition array whose default value should be extracted + * @param array $items + * Array of items that should compose default value + * + * @return array + * Array of default value that can be plugged in into a select widget + */ +function synonyms_select_default_value($field, $instance, $items) { + $default_value = array(); + $column = array_keys($field['columns']); + $column = reset($column); + foreach ($items as $item) { + $default_value[] = $item[$column]; + } + return $default_value; +} diff --git a/sites/all/modules/contrib/taxonomy/synonyms/synonyms.pages.inc b/sites/all/modules/contrib/taxonomy/synonyms/synonyms.pages.inc index 04e6ec2c..bf72fd33 100644 --- a/sites/all/modules/contrib/taxonomy/synonyms/synonyms.pages.inc +++ b/sites/all/modules/contrib/taxonomy/synonyms/synonyms.pages.inc @@ -26,7 +26,7 @@ * autocomplete form element. Only the last term is used for autocompletion. * Defaults to '' (an empty string). */ -function synonyms_autocomplete($field_name, $entity_type, $bundle, $tags_typed = '') { +function synonyms_autocomplete_taxonomy_term($field_name, $entity_type, $bundle, $tags_typed = '') { // If the request has a '/' in the search text, then the menu system will have // split it into multiple arguments, recover the intended $tags_typed. $args = func_get_args(); @@ -56,7 +56,7 @@ 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'); + $widget = $instance['widget']['type'] == 'synonyms_autocomplete_taxonomy_term' ? $instance['widget']['settings'] : field_info_widget_settings('synonyms_autocomplete_taxonomy_term'); // How many suggestions maximum we are able to output. $max_suggestions = $widget['suggestion_size']; @@ -81,7 +81,14 @@ function synonyms_autocomplete($field_name, $entity_type, $bundle, $tags_typed = } } - $term_matches = array(); + // Array of found suggestions. Each subarray of this array will represent a + // single suggestion entry. + // - tid: (int) tid of the suggested term + // - name: (string) name of the suggested term + // - synonym: (string) optional synonym string that matched this entry + // - behavior_implementation: (array) optional behavior implementation that + // provided the synonym + $tags_return = array(); if ($tag_last != '') { // Part of the criteria for the query come from the field's own settings. $vocabularies = array(); @@ -91,14 +98,6 @@ 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 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. $query = db_select('taxonomy_term_data', 't'); $query->addTag('translatable'); @@ -116,9 +115,7 @@ function synonyms_autocomplete($field_name, $entity_type, $bundle, $tags_typed = ->range(0, $max_suggestions) ->execute(); foreach ($result as $v) { - $v = (array) $v; - $v['wording'] = check_plain($v['name']); - $tags_return[] = $v; + $tags_return[] = (array) $v; } // Now we go vocabulary by vocabulary looking through synonym fields. @@ -129,27 +126,27 @@ function synonyms_autocomplete($field_name, $entity_type, $bundle, $tags_typed = $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'); + $condition->condition(AbstractSynonymsBehavior::COLUMN_SYNONYM_PLACEHOLDER, '%' . db_like($tag_last) . '%', 'LIKE'); if (!empty($tags_typed_tids)) { - $condition->condition('entity_id', $tags_typed_tids, 'NOT IN'); + $condition->condition(AbstractSynonymsBehavior::COLUMN_ENTITY_ID_PLACEHOLDER, $tags_typed_tids, 'NOT IN'); } if ($suggest_only_unique && !empty($tags_return)) { $tmp = array(); foreach ($tags_return as $tag_return) { $tmp[] = $tag_return['tid']; } - $condition->condition('entity_id', $tmp, 'NOT IN'); + $condition->condition(AbstractSynonymsBehavior::COLUMN_ENTITY_ID_PLACEHOLDER, $tmp, 'NOT IN'); } $new_tids = array(); - foreach (synonyms_synonyms_find_behavior($condition, $implementation) as $synonym) { + foreach ($implementation['object']->synonymsFind($condition) 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, + 'behavior_implementation' => $implementation, ); $new_tids[] = $synonym->entity_id; } @@ -168,36 +165,201 @@ function synonyms_autocomplete($field_name, $entity_type, $bundle, $tags_typed = $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']), - )); } } } - $prefix = empty($tags_typed) ? '' : drupal_implode_tags($tags_typed) . ', '; - if (count($tags_return) > $max_suggestions) { $tags_return = array_slice($tags_return, 0, $max_suggestions); } + } + $prefix = empty($tags_typed) ? '' : drupal_implode_tags($tags_typed) . ', '; + drupal_json_output(synonyms_autocomplete_format($tags_return, $prefix)); +} - // Now formatting the results. - foreach ($tags_return as $info) { - $n = $info['name']; - // Term names containing commas or quotes must be wrapped in quotes. - if (strpos($info['name'], ',') !== FALSE || strpos($info['name'], '"') !== FALSE) { - $n = '"' . str_replace('"', '""', $info['name']) . '"'; - } - while (isset($term_matches[$prefix . $n])) { - $n .= ' '; - } - $term_matches[$prefix . $n] = $info['wording']; +/** + * Page callback: Outputs JSON for entity autocomplete suggestions. + * + * This callback outputs entity name suggestions in response to Ajax requests + * made by the synonyms autocomplete widget for entity reference fields. The + * output is a JSON object of plain-text entity suggestions, keyed by the + * user-entered value with the completed entity name appended. Entity names + * containing commas are wrapped in quotes. The search is made with + * consideration of synonyms. + * + * @param string $field_name + * The name of the entity reference field. + * @param string $entity_type + * Entity type to which the supplied $field_name is attached to + * @param string $bundle + * Bundle name to which the supplied $field_name is attached to + * @param string $tags_typed + * (optional) A comma-separated list of entity names entered in the + * autocomplete form element. Only the last term is used for autocompletion. + * Defaults to '' (an empty string). + */ +function synonyms_autocomplete_entity($field_name, $entity_type, $bundle, $tags_typed = '') { + // If the request has a '/' in the search text, then the menu system will have + // split it into multiple arguments, recover the intended $tags_typed. + $args = func_get_args(); + // Shift off the $field_name argument. + array_shift($args); + // Shift off the $entity_type argument. + array_shift($args); + // Shift off the $bundle argument. + array_shift($args); + $tags_typed = implode('/', $args); + + if (!($field = field_info_field($field_name)) || $field['type'] != 'entityreference') { + print t('Entity reference field @field_name not found.', array('@field_name' => $field_name)); + exit; + } + + if (!($instance = field_info_instance($entity_type, $field['field_name'], $bundle))) { + // Error string. The JavaScript handler will realize this is not JSON and + // will display it as debugging information. + print t('There was not found an instance of @field_name in @entity_type.', array( + '@field_name' => $field_name, + '@entity_type' => $entity_type, + )); + exit; + } + + $widget = $instance['widget']['type'] == 'synonyms_autocomplete_entity' ? $instance['widget']['settings'] : field_info_widget_settings('synonyms_autocomplete_entity'); + + // How many suggestions maximum we are able to output. + $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 = $widget['suggest_only_unique']; + + $tags_typed = drupal_explode_tags($tags_typed); + $tag_last = drupal_strtolower(array_pop($tags_typed)); + $prefix = count($tags_typed) ? drupal_implode_tags($tags_typed) . ', ' : ''; + + $handler = entityreference_get_selection_handler($field, $instance, $entity_type, NULL); + + $tags_typed_entity_ids = array(); + if (!empty($tags_typed)) { + foreach ($handler->getReferencableEntities($tags_typed, 'IN') as $target_entity_ids) { + $tags_typed_entity_ids = array_merge($tags_typed_entity_ids, array_keys($target_entity_ids)); } } - drupal_json_output($term_matches); + + $matches = array(); + if ($tag_last) { + foreach ($handler->getReferencableEntities($tag_last) as $target_entity_ids) { + foreach (array_diff_key($target_entity_ids, drupal_map_assoc($tags_typed_entity_ids)) as $target_id => $label) { + $matches[] = array( + 'target_id' => $target_id, + 'name' => $label, + ); + if (count($matches) == $max_suggestions) { + break (2); + } + } + } + + if (count($matches) < $max_suggestions) { + $behavior_implementations = synonyms_behavior_get('autocomplete', $field['settings']['target_type'], synonyms_field_target_bundles($field), TRUE); + foreach ($behavior_implementations as $implementation) { + $condition = db_and(); + $condition->condition(AbstractSynonymsBehavior::COLUMN_SYNONYM_PLACEHOLDER, '%' . db_like($tag_last) . '%', 'LIKE'); + + if (!empty($tags_typed_entity_ids)) { + $condition->condition(AbstractSynonymsBehavior::COLUMN_ENTITY_ID_PLACEHOLDER, $tags_typed_entity_ids, 'NOT IN'); + } + if ($suggest_only_unique && !empty($matches)) { + $tmp = array(); + foreach ($matches as $match) { + $tmp[] = $match['target_id']; + } + $condition->condition(AbstractSynonymsBehavior::COLUMN_ENTITY_ID_PLACEHOLDER, $tmp, 'NOT IN'); + } + + $new_target_ids = array(); + foreach ($implementation['object']->synonymsFind($condition) as $synonym) { + if (!$suggest_only_unique || !in_array($synonym->entity_id, $new_target_ids)) { + $matches[] = array( + 'target_id' => $synonym->entity_id, + 'synonym' => $synonym->synonym, + 'behavior_implementation' => $implementation, + ); + $new_target_ids[] = $synonym->entity_id; + if (count($matches) == $max_suggestions) { + break (2); + } + } + } + } + } + + $synonym_entities = array(); + foreach ($matches as $match) { + if (!isset($match['wording']) && isset($match['synonym'])) { + $synonym_entities[] = $match['target_id']; + } + } + if (!empty($synonym_entities)) { + $synonym_entities = entity_load($field['settings']['target_type'], $synonym_entities); + foreach ($matches as $k => $match) { + if (!isset($match['name']) && isset($match['synonym'])) { + if (entity_access('view', $field['settings']['target_type'], $synonym_entities[$match['target_id']])) { + $matches[$k]['name'] = entity_label($field['settings']['target_type'], $synonym_entities[$match['target_id']]); + } + else { + unset($matches[$k]); + } + } + } + $matches = array_values($matches); + } + } + + drupal_json_output(synonyms_autocomplete_format($matches, $prefix)); +} + +/** + * Supportive function to format autocomplete suggestions. + * + * @param array $matches + * Array of matched entries. It should follow this structure: + * - name: (string) String to be inserted into autocomplete textfield if user + * chooses this autocomplete entry + * - synonym: (string) If this entry is matched through a synonym, put that + * synonym here + * - behavior_implementation: (array) If this entry is matched through a + * synonym, put here the behavior implementation array that provided this + * match + * @param string $prefix + * Any prefix to be appended to 'name' property of $matches array when + * inserting into the autocomplete textfield. Normally it is the already + * entered entries in the textfield + * + * @return array + * Array of formatted autocomplete response entries ready to be returned to + * the autocomplete JavaScript + */ +function synonyms_autocomplete_format($matches, $prefix) { + $output = array(); + + foreach ($matches as $match) { + $n = synonyms_autocomplete_escape($match['name']); + while (isset($output[$prefix . $n])) { + $n .= ' '; + } + $wording = check_plain($match['name']); + if (isset($match['synonym'])) { + $wording = format_string(filter_xss($match['behavior_implementation']['settings']['wording']), array( + '@entity' => $match['name'], + '@synonym' => $match['synonym'], + '@field_name' => drupal_strtolower($match['behavior_implementation']['label']), + )); + } + $output[$prefix . $n] = $wording; + } + return $output; } /** @@ -211,7 +373,7 @@ function theme_synonyms_behaviors_settings($variables) { $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.'), + 'empty' => t('Seems like there are no fields for which synonyms functionality available. Try adding a text field to get started.'), ); $instance_ids = array(); @@ -238,3 +400,144 @@ function theme_synonyms_behaviors_settings($variables) { return '
        ' . theme('table', $table) . drupal_render_children($element) . '
        '; } + +/** + * Page menu callback for managing Synonyms settings of entity types. + */ +function synonyms_settings_overview() { + $output = array(); + + $output['table'] = array( + '#theme' => 'table', + '#header' => array(t('Entity type'), t('Bundle'), t('Manage')), + '#rows' => array(), + ); + + foreach (entity_get_info() as $entity_type => $entity_info) { + if (synonyms_entity_type_load($entity_type)) { + foreach ($entity_info['bundles'] as $bundle => $bundle_info) { + $output['table']['#rows'][] = array( + $entity_info['label'], + $bundle == $entity_type ? '' : $bundle_info['label'], + l(t('Edit'), 'admin/structure/synonyms/' . $entity_type . '/' . $bundle), + ); + } + } + } + + return $output; +} + +/** + * Synonyms settings form for a specific entity type and bundle name. + * + * @param string $entity_type + * Entity type for which to generate synonyms settings form + * @param string $bundle + * Bundle name for which to generate synonyms settings form + */ +function synonyms_settings_form($form, &$form_state, $entity_type, $bundle) { + $form['settings'] = array( + '#tree' => TRUE, + '#theme' => 'synonyms_behaviors_settings', + '#id' => 'synonyms-behaviors-settings-wrapper', + '#entity_type' => $entity_type, + '#bundle' => $bundle, + ); + + $behaviors = synonyms_behaviors(); + + foreach ($behaviors as $behavior => $behavior_info) { + $form['settings'][$behavior] = array( + '#title' => $behavior_info['title'], + ); + + $behavior_implementations = synonyms_behavior_get($behavior, $entity_type, $bundle); + + foreach ($behavior_implementations as $implementation) { + $form['settings'][$behavior][$implementation['provider']]['#title'] = $implementation['label']; + + if (isset($form_state['values']['settings'][$behavior][$implementation['provider']])) { + $behavior_settings = (bool) $form_state['values']['settings'][$behavior][$implementation['provider']]['enabled']; + } + else { + $behavior_settings = isset($implementation['settings']); + } + + if ($behavior_settings) { + if (isset($form_state['values']['settings'][$behavior][$implementation['provider']]['settings'])) { + $behavior_settings = $form_state['values']['settings'][$behavior][$implementation['provider']]['settings']; + } + elseif (isset($implementation['settings'])) { + $behavior_settings = $implementation['settings']; + } + else { + $behavior_settings = array(); + } + } + + $form['settings'][$behavior][$implementation['provider']]['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['settings'][$behavior][$implementation['provider']]['enabled']['#ajax'] = array( + 'callback' => 'synonyms_settings_form_ajax', + 'wrapper' => $form['settings']['#id'], + ); + + if ($behavior_settings !== FALSE) { + $form['settings'][$behavior][$implementation['provider']]['settings'] = $settings_form($form, $form_state, $behavior_settings); + } + } + } + } + + $form['actions'] = array( + '#type' => '#actions', + ); + + $form['actions']['submit'] = array( + '#type' => 'submit', + '#value' => t('Save'), + ); + + return $form; +} + +/** + * Submit handler for 'synonyms_settings_form' form. + * + * Store synonyms behavior settings. + */ +function synonyms_settings_form_submit($form, &$form_state) { + foreach ($form_state['values']['settings'] as $behavior => $settings) { + foreach ($settings as $provider => $behavior_settings) { + $behavior_implementation = array( + 'entity_type' => $form['settings']['#entity_type'], + 'bundle' => $form['settings']['#bundle'], + 'provider' => $provider, + 'behavior' => $behavior, + 'settings' => isset($behavior_settings['settings']) ? $behavior_settings['settings'] : NULL, + ); + if ($behavior_settings['enabled']) { + synonyms_behavior_implementation_save($behavior_implementation); + } + else { + synonyms_behavior_implementation_delete($behavior_implementation); + } + } + } + drupal_set_message(t('Synonyms settings have been successfully saved.')); + $form_state['redirect'] = array('admin/structure/synonyms'); +} + +/** + * Ajax callback function for synonyms settings form. + */ +function synonyms_settings_form_ajax($form, &$form_state) { + return $form['settings']; +} diff --git a/sites/all/modules/contrib/taxonomy/synonyms/synonyms.test b/sites/all/modules/contrib/taxonomy/synonyms/synonyms.test index 296929a2..bb53e7b2 100644 --- a/sites/all/modules/contrib/taxonomy/synonyms/synonyms.test +++ b/sites/all/modules/contrib/taxonomy/synonyms/synonyms.test @@ -59,24 +59,24 @@ abstract class SynonymsWebTestCase extends DrupalWebTestCase { ); /** - * Name of a synonyms behavior that is being tested. - * - * @var string - */ - protected $behavior; - - /** - * Settings for the behavior that is being tested. + * Behavior implementation that is being tested. * * @var array */ - protected $behavior_settings = array(); + protected $behavior_implementation = array( + 'provider' => NULL, + 'entity_type' => 'taxonomy_term', + 'bundle' => NULL, + 'behavior' => NULL, + 'settings' => array(), + ); /** * SetUp method. */ public function setUp($modules = array()) { - $modules[] = 'synonyms'; + array_unshift($modules, 'synonyms_provider_field'); + array_unshift($modules, 'synonyms'); parent::setUp($modules); $this->admin = $this->drupalCreateUser(array( @@ -97,23 +97,36 @@ abstract class SynonymsWebTestCase extends DrupalWebTestCase { $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; + if (!isset($this->fields['enabled']['instance']['bundle'])) { + $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; + if (!isset($this->fields['disabled']['instance']['bundle'])) { + $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, - )); + if (!$this->behavior_implementation['bundle']) { + $this->behavior_implementation['bundle'] = $this->fields['enabled']['instance']['bundle']; + } + if (!$this->behavior_implementation['provider']) { + $this->behavior_implementation['provider'] = synonyms_provider_field_provider_name($this->fields['enabled']['field']); + } + + synonyms_behavior_implementation_save($this->behavior_implementation); + foreach (synonyms_behavior_get($this->behavior_implementation['behavior'], $this->behavior_implementation['entity_type'], $this->behavior_implementation['bundle'], TRUE) as $behavior_implementation) { + if ($behavior_implementation['provider'] == $this->behavior_implementation['provider']) { + $this->behavior_implementation = $behavior_implementation; + break; + } + } } /** @@ -140,19 +153,25 @@ abstract class SynonymsWebTestCase extends DrupalWebTestCase { */ class SynonymsSynonymsWebTestCase extends SynonymsWebTestCase { - protected $behavior = 'synonyms'; - /** * GetInfo method. */ public static function getInfo() { return array( - 'name' => 'Taxonomy synonyms', - 'description' => 'Ensure that the general "synonyms" behavior works correctly.', + 'name' => 'Synonyms', + 'description' => 'Ensure that the general synonyms functionality works correctly.', 'group' => 'Synonyms', ); } + /** + * SetUp method. + */ + public function setUp($modules = array()) { + $this->behavior_implementation['behavior'] = 'autocomplete'; + parent::setUp($modules); + } + /** * Test the functionality of synonyms. */ @@ -234,14 +253,23 @@ class SynonymsSynonymsWebTestCase extends SynonymsWebTestCase { ); taxonomy_term_save($term); + // We enable the same provider in another synonym behavior too and test that + // we only get unique synonyms, i.e. synonyms do not get doubled for being + // sourced from the same provider twice (once per each behavior). + $behavior_implementation = $this->behavior_implementation; + $behavior_implementation['behavior'] = 'select'; + synonyms_behavior_implementation_save($behavior_implementation); + // Testing the 'synonyms' property of 'taxonomy_term' entity. - $synonyms = synonyms_get_sanitized($no_synonyms_term); + $synonyms = entity_metadata_wrapper('taxonomy_term', $no_synonyms_term)->synonyms->value(); $this->assertTrue(empty($synonyms), 'Successfully retrieved synonyms_get_sanitized() for a term without synonyms.'); - $synonyms = synonyms_get_sanitized($one_synonym_term); + $synonyms = entity_metadata_wrapper('taxonomy_term', $one_synonym_term)->synonyms->value(); $this->assertTrue(count($synonyms) == 1 && $synonyms[0] == $synonym1, 'Successfully retrieved synonyms_get_sanitized() for a term with a single synonym.'); - $synonyms = synonyms_get_sanitized($two_synonyms_term); + $synonyms = entity_metadata_wrapper('taxonomy_term', $two_synonyms_term)->synonyms->value(); $this->assertTrue(count($synonyms) == 2 && $synonyms[0] == $synonym1 && $synonyms[1] == $synonym2, 'Successfully retrieved synonyms_get_sanitized() for a term with 2 synonyms.'); + synonyms_behavior_implementation_delete($behavior_implementation); + // Testing the function synonyms_get_term_by_synonym(). $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.'); @@ -270,19 +298,38 @@ class SynonymsSynonymsWebTestCase extends SynonymsWebTestCase { $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); $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).'); + + // Testing the function synonyms_get_entity_by_synonym(). + $another_vocabulary = (object) array( + 'name' => $this->randomName(), + 'machine_name' => 'another_bundle', + ); + taxonomy_vocabulary_save($another_vocabulary); + + $entity_id = synonyms_get_entity_by_synonym('taxonomy_term', $this->randomName()); + $this->assertEqual($entity_id, 0, 'synonyms_get_entity_by_synonym() function returns 0 when fails to look up any entity.'); + + $entity_id = synonyms_get_entity_by_synonym('taxonomy_term', drupal_strtoupper($term_parent->name)); + $this->assertEqual($entity_id, $term_parent->tid, 'synonyms_get_entity_by_synonym() function returns entity ID when a name of an existing entity is supplised.'); + + $entity_id = synonyms_get_entity_by_synonym('taxonomy_term', drupal_strtoupper($term_parent->name), $another_vocabulary->machine_name); + $this->assertEqual($entity_id, 0, 'synonyms_get_entity_by_synonym() returns 0 if an existing entity name is provided, but the search is conducted within another bundle.'); + + $entity_id = synonyms_get_entity_by_synonym('taxonomy_term', drupal_strtolower($synonym2)); + $this->assertEqual($entity_id, $two_synonyms_term->tid, 'synonyms_get_entity_by_synonym() returns entity ID when a synonym of that entity is supplied.'); + + $entity_id = synonyms_get_entity_by_synonym('taxonomy_term', drupal_strtolower($parent_synonym), $another_vocabulary->machine_name); + $this->assertEqual($entity_id, 0, 'synonyms_get_entity_by_synonym() returns 0 if a synonym of an existing entity is supplied, but the search is conducted within another bundle.'); + + $entity_id = synonyms_get_entity_by_synonym('taxonomy_term', $term_parent->{$this->fields['disabled']['field']['field_name']}[LANGUAGE_NONE][0]['value']); + $this->assertEqual($entity_id, 0, 'synonyms_get_entity_by_synonym() returns 0 if a non-synonym field value is supplied.'); } } /** * Test "Synonyms friendly autocomplete" widget of Synonyms module. */ -class AutocompleteSynonymsWebTestCase extends SynonymsWebTestCase { - - protected $behavior = 'autocomplete'; - - protected $behavior_settings = array( - 'wording' => '@synonym @field_name @term', - ); +abstract class AbstractAutocompleteSynonymsWebTestCase extends SynonymsWebTestCase { /** * Array of fully loaded taxonomy term entities to be used in this test. @@ -311,60 +358,306 @@ class AutocompleteSynonymsWebTestCase extends SynonymsWebTestCase { * * @var array */ - protected $term_reference_field = array(); + protected $reference_field = array(); /** - * GetInfo method. + * Field instance definition array of the field that will be attached to + * $this->entity_type with synonyms friendly autocomplete widget. + * + * @var array */ - public static function getInfo() { - return array( - 'name' => 'Taxonomy synonyms autocomplete', - 'description' => 'Ensure that the "synonym friendly autocomplete" widget works correctly with taxonomy terms.', - 'group' => 'Synonyms', - ); - } + protected $reference_instance = array(); /** * SetUp method. */ public function setUp($modules = array()) { + $this->behavior_implementation['behavior'] = 'autocomplete'; + $this->behavior_implementation['settings'] = array( + 'wording' => '@synonym @field_name @entity', + ); + $modules[] = 'synonyms_provider_property'; parent::setUp($modules); + // We'll also enable the ID property to be source of synonyms so we can + // extra test multiple providers at the same time. + $behavior_implementation = $this->behavior_implementation; + $entity_info = entity_get_info($behavior_implementation['entity_type']); + $behavior_implementation['provider'] = synonyms_provider_property_provider_name($entity_info['entity keys']['id']); + synonyms_behavior_implementation_save($behavior_implementation); + // Creating a test content type. $this->drupalPost('admin/structure/types/add', array( 'name' => 'Synonyms Test Content', 'type' => $this->bundle, ), 'Save content type'); - - $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); - - $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. + $this->createTerms(); + } + + /** + * Test autocomplete menu path. + * + * Feed all known "buggy" input to synonym friendly autocomplete menu path, + * in order to test its performance. + */ + public function testAutocompleteMenuPath() { + $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.'); + + $this->assertAutocompleteMenuPath(drupal_strtoupper(drupal_substr($this->entityLabel($this->terms['term1']), 1, -1)), array( + $this->entityLabel($this->terms['term1']) => $this->entityLabel($this->terms['term1']), + $this->entityLabel($this->terms['term1_longer_name']) => $this->entityLabel($this->terms['term1_longer_name']), + ), 'Submitting a name similar to 2 existing term names yields both terms included in the autocomplete response.'); + + $this->assertAutocompleteMenuPath($this->entityLabel($this->terms['term1']) . ', ' . drupal_strtoupper(drupal_substr($this->entityLabel($this->terms['term1']), 1, -1)), array( + $this->entityLabel($this->terms['term1']) . ', ' . $this->entityLabel($this->terms['term1_longer_name']) => $this->entityLabel($this->terms['term1_longer_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->entityLabel($this->terms['no_synonyms']), 1, -1)), array( + $this->entityLabel($this->terms['no_synonyms']) => $this->entityLabel($this->terms['no_synonyms']), + ), 'Submitting a name similar to one existing term name into autocomplete path yields that term included.'); + + $this->assertAutocompleteMenuPath(drupal_strtolower($this->entityLabel($this->terms['no_synonyms'])) . ', ' . drupal_strtoupper(drupal_substr($this->entityLabel($this->terms['no_synonyms']), 1, -1)), array(), 'Submitting the same term over again into autocomplete path yields no results.'); + + $this->assertAutocompleteMenuPath($this->entityLabel($this->terms['one_synonym']) . ', ' . $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.'); + + foreach (array('no_synonyms', 'one_synonym', 'two_synonyms') as $k) { + $this->assertAutocompleteMenuPath(drupal_strtolower(drupal_substr($this->entityLabel($this->terms[$k]), 1, -1)), array( + $this->entityLabel($this->terms[$k]) => $this->entityLabel($this->terms[$k]), + ), 'Submitting a name similar to ' . $k . ' term into autocomplete path yields the term included.'); + + $synonyms = field_get_items($this->behavior_implementation['entity_type'], $this->terms[$k], $this->fields['enabled']['field']['field_name']); + if (is_array($synonyms)) { + foreach ($synonyms as $delta => $item) { + $this->assertAutocompleteMenuPath(drupal_strtolower(drupal_substr($item['value'], 1, -1)), array( + $this->entityLabel($this->terms[$k]) => $this->synonymAutocompleteResult($this->terms[$k], $item['value']), + ), 'Submitting a name similar to synonym#' . $delta . ' of the term ' . $k . ' into autocomplete path yields the term included.'); + } + } + } + + $this->assertAutocompleteMenuPath('one_term_name_another_synonym_', array( + $this->entityLabel($this->terms['name_another_synonym']) => $this->entityLabel($this->terms['name_another_synonym']), + $this->entityLabel($this->terms['synonym_another_name']) => $this->synonymAutocompleteResult($this->terms['synonym_another_name'], $this->terms['synonym_another_name']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE][0]['value']), + ), '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. + $behavior_implementation = $this->behavior_implementation; + $behavior_implementation['provider'] = synonyms_provider_field_provider_name($this->fields['disabled']['field']); + synonyms_behavior_implementation_save($behavior_implementation); + $this->terms['one_synonym']->{$this->fields['disabled']['field']['field_name']} = $this->terms['one_synonym']->{$this->fields['enabled']['field']['field_name']}; + entity_save($this->behavior_implementation['entity_type'], $this->terms['one_synonym']); + $this->assertAutocompleteMenuPath($this->terms['one_synonym']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE][0]['value'], array( + $this->entityLabel($this->terms['one_synonym']) => $this->synonymAutocompleteResult($this->terms['one_synonym'], $this->terms['one_synonym']->{$this->fields['disabled']['field']['field_name']}[LANGUAGE_NONE][0]['value'], $behavior_implementation), + $this->entityLabel($this->terms['one_synonym']) . ' ' => $this->synonymAutocompleteResult($this->terms['one_synonym'], $this->terms['one_synonym']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE][0]['value']), + ), 'Autocomplete works correctly when more than 1 field participates in the autocomplete behavior.'); + synonyms_behavior_implementation_delete($behavior_implementation); + + // Submit a name that is similar to one of our terms and is similar to a + // a term from another vocabulary, which should not participate in the + // autocomplete. We do this trick in different flavors, all with the idea + // to make sure the bundles that should not participate in the field values + // are not suggested by the autocomplete menu path. The different flavors + // are: + // - non valid term similar to valid term + // - non valid term similar to valid synonym + // - non valid synonym similar to valid term + // - non valid synonym similar to valid synonym + $instance = field_create_instance(array( + 'field_name' => $this->fields['enabled']['field']['field_name'], + 'entity_type' => $this->fields['enabled']['instance']['entity_type'], + 'bundle' => 'another_vocabulary', + )); + $instance = field_info_instance($instance['entity_type'], $instance['field_name'], $instance['bundle']); + $behavior_implementation = $this->behavior_implementation; + $behavior_implementation['bundle'] = $instance['bundle']; + synonyms_behavior_implementation_save($behavior_implementation); + + $this->assertAutocompleteMenuPath($this->entityLabel($this->terms['no_synonyms']), array( + $this->entityLabel($this->terms['no_synonyms']) => $this->entityLabel($this->terms['no_synonyms']), + ), 'Submitting term name similar to term from another bundle does not include the term from another bundle.'); + + $this->assertAutocompleteMenuPath($this->terms['one_synonym']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE][0]['value'], array( + $this->entityLabel($this->terms['one_synonym']) => $this->synonymAutocompleteResult($this->terms['one_synonym'], $this->terms['one_synonym']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE][0]['value']), + ), 'Submitting synonym similar to term from another bundle does not include the term from another bundle.'); + + $this->assertAutocompleteMenuPath($this->entityLabel($this->terms['no_synonyms']), array( + $this->entityLabel($this->terms['no_synonyms']) => $this->entityLabel($this->terms['no_synonyms']), + ), 'Submitting term name similar to a synonym of a term from another bundle does not include the term from another bundle.'); + + $this->assertAutocompleteMenuPath($this->terms['one_synonym']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE][0]['value'], array( + $this->entityLabel($this->terms['one_synonym']) => $this->synonymAutocompleteResult($this->terms['one_synonym'], $this->terms['one_synonym']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE][0]['value']), + ), 'Submitting synonym similar to a synonym of a term from another bundle does not include the term from another bundle.'); + } + + /** + * Test autocomplete text field validation. + * + * In particular, this test does the following: + * - test submitting a synonym into the text field (that should be converted + * into its entity) + * - test submitting the same entity name twice (only one reference should be + * saved) + * - test submitting entity name and one of its synonyms (only one reference + * should be saved) + * - test submitting 2 different synonyms of the same entity (only one + * reference should be saved) + */ + public function testAutocompleteTextField() { + $this->drupalPost('node/add/synonyms-test-content', array( + 'title' => $this->randomName(), + $this->reference_field['field_name'] . '[' . LANGUAGE_NONE . ']' => $this->terms['one_synonym']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE][0]['value'], + ), 'Save'); + $this->assertText($this->entityLabel($this->terms['one_synonym']), 'Submitting a synonym into autocomplete text field results into term being saved.'); + + $this->drupalPost('node/add/synonyms-test-content', array( + 'title' => $this->randomName(), + $this->reference_field['field_name'] . '[' . LANGUAGE_NONE . ']' => drupal_implode_tags(array($this->entityLabel($this->terms['one_synonym']), $this->entityLabel($this->terms['one_synonym']))), + ), 'Save'); + $this->assertUniqueText($this->entityLabel($this->terms['one_synonym']), 'Submitting the same term name twice into autocomplete text field results in saving the term only once in the field.'); + + $this->drupalPost('node/add/synonyms-test-content', array( + 'title' => $this->randomName(), + $this->reference_field['field_name'] . '[' . LANGUAGE_NONE . ']' => drupal_implode_tags(array($this->entityLabel($this->terms['one_synonym']), $this->terms['one_synonym']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE][0]['value'])), + ), 'Save'); + $this->assertUniqueText($this->entityLabel($this->terms['one_synonym']), 'Submitting term name and one of its synonyms results in saving the term only once in the field.'); + + $this->drupalPost('node/add/synonyms-test-content', array( + 'title' => $this->randomName(), + $this->reference_field['field_name'] . '[' . LANGUAGE_NONE . ']' => drupal_implode_tags(array($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][1]['value'])), + ), 'Save'); + $this->assertUniqueText($this->entityLabel($this->terms['two_synonyms']), 'Submitting 2 different synonyms of the same term results in saving the term only once in the field.'); + } + + /** + * Test 'Suggestions Size' setting of synonyms-friendly autocomplete widget. + */ + public function testWidgetSettingsSuggestionSize() { + $suggestion_size = 1; + $this->reference_instance['widget']['settings']['suggestion_size'] = $suggestion_size; + field_update_instance($this->reference_instance); + + // If size was bigger than 1, we'd get suggested 2 terms: 'term1' and + // 'term1_longer_name'. + $this->assertAutocompleteMenuPath($this->entityLabel($this->terms['term1']), array( + $this->entityLabel($this->terms['term1']) => $this->entityLabel($this->terms['term1']), + ), 'Suggestions Size option is respected in autocomplete widget for entity suggestion entries.'); + + $this->assertAutocompleteMenuPath($this->entityLabel($this->terms['name_similar_synonym']), array( + $this->entityLabel($this->terms['name_similar_synonym']) => $this->entityLabel($this->terms['name_similar_synonym']), + ), 'Suggestions Size option is respected in autocomplete widget for entity and synonym suggestion entries.'); + + $this->assertAutocompleteMenuPath(drupal_substr($this->terms['similar_synonyms']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE][0]['value'], 0, 8), array( + $this->entityLabel($this->terms['similar_synonyms']) => $this->synonymAutocompleteResult($this->terms['similar_synonyms'], $this->terms['similar_synonyms']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE][0]['value']), + ), 'Suggestions Size option is respected in autocomplete widget for synonyms suggestion entries.'); + + $this->assertAutocompleteMenuPath('one_term_name_another_synonym_', array( + $this->entityLabel($this->terms['name_another_synonym'])=> $this->entityLabel($this->terms['name_another_synonym']), + ), 'Suggestions Size option is respected in autocomplete widget for the case when there is match by entity name and by synonyms; and preference is given to the match by entity name.'); + } + + /** + * Test 'Suggest only one entry per term' setting of autocomplete widget. + */ + public function testWidgetSettingsSuggestOnlyUnique() { + // Testing disabled "Suggest only one entry per term" setting. + $this->assertAutocompleteMenuPath($this->entityLabel($this->terms['name_similar_synonym']), array( + $this->entityLabel($this->terms['name_similar_synonym']) => $this->entityLabel($this->terms['name_similar_synonym']), + $this->entityLabel($this->terms['name_similar_synonym']) . ' ' => $this->synonymAutocompleteResult($this->terms['name_similar_synonym'], $this->terms['name_similar_synonym']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE][0]['value']), + ), 'Both term and its synonym are shown when "Suggest only one entry per term" is off.'); + + $this->assertAutocompleteMenuPath(drupal_substr($this->terms['similar_synonyms']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE][0]['value'], 0, 8), array( + $this->entityLabel($this->terms['similar_synonyms']) => $this->synonymAutocompleteResult($this->terms['similar_synonyms'], $this->terms['similar_synonyms']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE][0]['value']), + $this->entityLabel($this->terms['similar_synonyms']). ' ' => $this->synonymAutocompleteResult($this->terms['similar_synonyms'], $this->terms['similar_synonyms']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE][1]['value']), + ), 'Multiple synonyms are shown when "Suggest only one entry per term" is off.'); + + // Testing enabled "Suggest only one entry per term" setting. + $this->reference_instance['widget']['settings']['suggest_only_unique'] = TRUE; + field_update_instance($this->reference_instance); + + $this->assertAutocompleteMenuPath($this->entityLabel($this->terms['name_similar_synonym']), array( + $this->entityLabel($this->terms['name_similar_synonym']) => $this->entityLabel($this->terms['name_similar_synonym']), + ), 'Only term is shown and synonym is not shown when "Suggest only one entry per term" is on.'); + + $this->assertAutocompleteMenuPath(drupal_substr($this->terms['similar_synonyms']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE][0]['value'], 0, 8), array( + $this->entityLabel($this->terms['similar_synonyms']) => $this->synonymAutocompleteResult($this->terms['similar_synonyms'], $this->terms['similar_synonyms']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE][0]['value']), + ), 'Only single synonym is shown when "Suggest only one entry per term" is on.'); + } + + /** + * Assert output of synonym friendly autocomplete path. + * + * @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($input, $standard, $message) { + $response = $this->drupalGet($this->reference_instance['widget']['settings']['synonyms_autocomplete_path'] . '/' . $this->reference_field['field_name'] . '/' . $this->entity_type . '/' . $this->bundle . '/' . $input); + if (!$response) { + $this->fail($message, 'Autocomplete Menu Path'); + return; + } + $response = (array) json_decode($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, $message, 'Autocomplete Menu Path'); + } + + /** + * Return expected autocomplete menu path result. + * + * The result is prepared as if the entity was found by the supplied synonym. + * + * @param object $entity + * Fully loaded entity for which the result is generated. + * @param string $synonym + * Synonym by which the entity was hit in the search + * @param array $behavior_implementation + * Behavior implementation array which the $synonym originates from. If + * omitted, standard $this->behavior_implementation is considered + * + * @return string + * Formatted autocomplete result + */ + protected function synonymAutocompleteResult($entity, $synonym, $behavior_implementation = NULL) { + if (!$behavior_implementation) { + $behavior_implementation = $this->behavior_implementation; + } + $provider = synonyms_behavior_implementation_info($behavior_implementation['entity_type'], $behavior_implementation['bundle'], $behavior_implementation['behavior']); + $provider = $provider[$behavior_implementation['provider']]; + return format_string($behavior_implementation['settings']['wording'], array( + '@synonym' => $synonym, + '@entity' => entity_label($behavior_implementation['entity_type'], $entity), + '@field_name' => drupal_strtolower($provider['label']), + )); + } + + /** + * Supportive method to retrieve label of a provided entity. + * + * @param object $entity + * Entity, whose label should be returned + * + * @return string + * Label of the provided entity + */ + protected function entityLabel($entity) { + return entity_label($this->behavior_implementation['entity_type'], $entity); + } + + /** + * Method to initiate all necessary terms for testing. + */ + protected function createTerms() { $name = $this->randomName(); $term = (object) array( @@ -383,7 +676,7 @@ class AutocompleteSynonymsWebTestCase extends SynonymsWebTestCase { $term = (object) array( 'vid' => $this->vocabulary->vid, - 'name'=> $name, + 'name' => $name, $this->fields['disabled']['field']['field_name'] => array( LANGUAGE_NONE => array( array('value' => $this->randomName()), @@ -507,21 +800,102 @@ class AutocompleteSynonymsWebTestCase extends SynonymsWebTestCase { ); taxonomy_term_save($term); $this->terms['synonym_another_name'] = $term; + + $another_vocabulary = (object) array( + 'name' => $this->randomName(), + 'machine_name' => 'another_vocabulary', + ); + taxonomy_vocabulary_save($another_vocabulary); + + $term_similar_term = (object) array( + 'name' => $this->terms['no_synonyms']->name, + 'vid' => $another_vocabulary->vid, + ); + taxonomy_term_save($term_similar_term); + + $term_similar_synonym = (object) array( + 'name' => $this->terms['one_synonym']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE][0]['value'], + 'vid' => $another_vocabulary->vid, + ); + taxonomy_term_save($term_similar_synonym); + + $synonym_similar_term = (object) array( + 'name' => $this->randomName(), + 'vid' => $another_vocabulary->vid, + $this->fields['enabled']['field']['field_name'] => array(LANGUAGE_NONE => array( + $this->terms['no_synonyms']->name, + )), + ); + taxonomy_term_save($synonym_similar_term); + + $synonym_similar_synonym = (object) array( + 'name' => $this->randomName(), + 'vid' => $another_vocabulary->vid, + $this->fields['enabled']['field']['field_name'] => array(LANGUAGE_NONE => array( + $this->terms['one_synonym']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE][0]['value'], + )), + ); + taxonomy_term_save($synonym_similar_synonym); + } +} + +/** + * Test synonyms friendly autocomplete widget for taxonomy term reference field. + */ +class TaxonomyTermReferenceAutocompleteSynonymsWebTestCase extends AbstractAutocompleteSynonymsWebTestCase { + + /** + * GetInfo method. + */ + public static function getInfo() { + return array( + 'name' => 'Taxonomy synonyms autocomplete', + 'description' => 'Ensure that the "synonym friendly autocomplete" widget works correctly with taxonomy term reference field type.', + 'group' => 'Synonyms', + ); + } + + public function setUp($modules = array()) { + parent::setUp($modules); + + $this->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->reference_field = field_create_field($this->reference_field); + + $this->reference_instance = array( + 'field_name' => $this->reference_field['field_name'], + 'entity_type' => 'node', + 'bundle' => $this->bundle, + 'label' => 'Synonym Terms Autcomplete', + 'widget' => array( + 'type' => 'synonyms_autocomplete_taxonomy_term', + ), + ); + $this->reference_instance['widget']['settings'] = field_info_widget_settings($this->reference_instance['widget']['type']); + $this->reference_instance = field_create_instance($this->reference_instance); } /** * Test auto-creation functionality. * * Test the auto-creation functionality of the synonym friendly autocomplete - * widget type. Along the way it tests whether synonyms, submitted into the - * widget's textfield are converted into the terms, synonyms of which they - * are. + * widget type for taxonomy term reference field type. */ public function testAutoCreation() { // Trying enabled auto creation. - $this->drupalPost('admin/structure/types/manage/synonyms-test-content/fields/synonyms_term_enabled', array( - 'instance[widget][settings][auto_creation]' => TRUE, - ), 'Save settings'); + $this->reference_instance['widget']['settings']['auto_creation'] = TRUE; + field_update_instance($this->reference_instance); $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( @@ -535,9 +909,8 @@ class AutocompleteSynonymsWebTestCase extends SynonymsWebTestCase { $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/synonyms_term_enabled', array( - 'instance[widget][settings][auto_creation]' => FALSE, - ), 'Save settings'); + $this->reference_instance['widget']['settings']['auto_creation'] = FALSE; + field_update_instance($this->reference_instance); $new_term_name = $this->terms['term1']->{$this->fields['disabled']['field']['field_name']}[LANGUAGE_NONE][0]['value']; $this->drupalPost('node/add/synonyms-test-content', array( @@ -550,185 +923,59 @@ class AutocompleteSynonymsWebTestCase extends SynonymsWebTestCase { $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.'); } +} + +/** + * Test synonyms friendly autocomplete widget for entity reference field type. + */ +class EntityReferenceAutocompleteSynonymsWebTestCase extends AbstractAutocompleteSynonymsWebTestCase { /** - * Test autocomplete menu path. - * - * Feed all known "buggy" input to synonym friendly autocomplete menu path, - * in order to test its performance. + * GetInfo method. */ - public function testAutocompleteMenuPath() { - $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.'); - - $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.'); - - foreach (array('no_synonyms', 'one_synonym', 'two_synonyms') as $k) { - $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', $this->terms[$k], $this->fields['enabled']['field']['field_name']); - if (is_array($synonyms)) { - foreach ($synonyms as $delta => $item) { - $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.'); - } - } - } - - $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.'); + public static function getInfo() { + return array( + 'name' => 'Entity reference synonyms autocomplete', + 'description' => 'Ensure that the "synonym friendly autocomplete" widget works correctly with entityreference field type.', + 'group' => 'Synonyms', + ); } - /** - * Test 'Suggestions Size' setting of synonyms-friendly autocomplete widget. - */ - public function testWidgetSettingsSuggestionSize() { - $suggestion_size = 1; - $this->drupalPost('admin/structure/types/manage/synonyms-test-content/fields/synonyms_term_enabled', array( - 'instance[widget][settings][suggestion_size]' => $suggestion_size, - ), 'Save settings'); + public function setUp($modules = array()) { + $modules[] = 'entityreference'; + parent::setUp($modules); - // If size was bigger than 1, we'd get suggested 2 terms: 'term1' and - // 'term1_longer_name'. - $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.'); + $this->reference_field = array( + 'type' => 'entityreference', + 'field_name' => 'synonyms_term_enabled', + 'cardinality' => FIELD_CARDINALITY_UNLIMITED, + 'settings' => array( + 'target_type' => 'taxonomy_term', + 'handler_settings' => array( + 'target_bundles' => drupal_map_assoc(array($this->fields['enabled']['instance']['bundle'])), + ), + ), + ); + $this->reference_field = field_create_field($this->reference_field); - $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.'); - - $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.'); - - $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. - */ - public function testWidgetSettingsSuggestOnlyUnique() { - // Testing disabled "Suggest only one entry per term" setting. - $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.'); - - $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/synonyms_term_enabled', array( - 'instance[widget][settings][suggest_only_unique]' => TRUE, - ), 'Save settings'); - - $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.'); - - $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 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($input, $standard, $message) { - $response = $this->drupalGet('synonyms/autocomplete/' . $this->term_reference_field['field_name'] . '/' . $this->entity_type . '/' . $this->bundle . '/' . $input); - if (!$response) { - $this->fail($message, 'Autocomplete Menu Path'); - return; - } - $response = (array) json_decode($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, $message, 'Autocomplete Menu Path'); - } - - /** - * Return expected autocomplete menu path result. - * - * The result is prepared as if the term was found by the supplied synonym. - * - * @param object $term - * 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, $instance) { - return format_string($this->behavior_settings['wording'], array( - '@synonym' => $synonym, - '@term' => $term->name, - '@field_name' => drupal_strtolower($instance['label']), - )); + $this->reference_instance = array( + 'field_name' => $this->reference_field['field_name'], + 'entity_type' => 'node', + 'bundle' => $this->bundle, + 'label' => 'Synonym Entity Autcomplete', + 'widget' => array( + 'type' => 'synonyms_autocomplete_entity', + ), + ); + $this->reference_instance['widget']['settings'] = field_info_widget_settings($this->reference_instance['widget']['type']); + $this->reference_instance = field_create_instance($this->reference_instance); } } /** * Test "Synonyms friendly select" widget of Synonyms module. */ -class SelectSynonymsWebTestCase extends SynonymsWebTestCase { - - protected $behavior = 'select'; - - protected $behavior_settings = array( - 'wording' => '@synonym @term @field_name', - ); +abstract class AbstractSelectSynonymsWebTestCase extends SynonymsWebTestCase { /** * Array of fully loaded taxonomy term entities to be used in this test. @@ -738,14 +985,14 @@ class SelectSynonymsWebTestCase extends SynonymsWebTestCase { protected $terms = array(); /** - * Entity type to which a term reference field with tested widget is attached. + * Entity type to which a field with tested widget is attached. * * @var string */ protected $entity_type = 'node'; /** - * Bundle to which a term reference field with tested widget is attached. + * Bundle to which a field with tested widget is attached. * * @var string */ @@ -757,17 +1004,22 @@ class SelectSynonymsWebTestCase extends SynonymsWebTestCase { * * @var array */ - protected $term_reference_field = array(); + protected $reference_field = array(); - public static function getInfo() { - return array( - 'name' => 'Synonyms friendly select', - 'description' => 'Ensure that the "synonym friendly select" widget works correctly with taxonomy terms.', - 'group' => 'Synonyms', - ); - } + /** + * Field instance definition array of the field that will be attached to + * $this->entity_type with synonyms friendly select widget. + * + * @var array + */ + protected $reference_instance = array(); public function setUp($modules = array()) { + $this->behavior_implementation['behavior'] = 'select'; + $this->behavior_implementation['settings'] = array( + 'wording' => '@synonym @entity @field_name', + ); + parent::setUp($modules); // Creating a test content type. @@ -776,32 +1028,120 @@ class SelectSynonymsWebTestCase extends SynonymsWebTestCase { 'type' => $this->bundle, ), 'Save content type'); - $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); + $this->createTerms(); + } - $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); + /** + * Test sorting options of the widget. + */ + abstract public function testWidgetSorting(); + /** + * Test main functionality of the widget. + */ + abstract public function testWidget(); + + /** + * 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: + * - entity: (object) Entity this option represents + * - synonym: (string) If the option comes from a synonym, 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 = '') { + $multiple = $this->reference_field['cardinality'] > 1 || $this->reference_field['cardinality'] == FIELD_CARDINALITY_UNLIMITED; + + $element = array( + '#options' => array(), + '#value' => $multiple ? array() : 'nothing', + ); + + if (!$multiple) { + $element['#options'][''] = t('- None -'); + } + + foreach ($options as $v) { + if (!isset($v['synonym'])) { + $v['synonym'] = NULL; + } + $key = $this->synonymSelectKey($v['entity'], $v['synonym']); + $label = entity_label($this->behavior_implementation['entity_type'], $v['entity']); + if ($v['synonym']) { + $provider = synonyms_behavior_implementation_info($this->behavior_implementation['entity_type'], $this->behavior_implementation['bundle'], $this->behavior_implementation['behavior']); + $provider = $provider[$this->behavior_implementation['provider']]; + + $label = format_string($this->behavior_implementation['settings']['wording'], array( + '@synonym' => $v['synonym'], + '@entity'=> $label, + '@field_name' => $provider['label'], + )); + } + + if (isset($v['selected']) && $v['selected']) { + if ($multiple) { + $element['#value'][] = $key; + } + else { + $element['#value'] = $key; + } + } + + $element['#options'][$key] = $this->synonymsSelectOptionPrefix($v['entity'], $v['synonym']) . $label; + } + $this->assertRaw('>' . form_select_options($element) . '', $message, 'Synonyms friendly select'); + } + + /** + * Generate necessary prefix before the main wording of the label in select. + * + * It may be useful to insert some kind of hierarchy indention, for example. + * + * @param object $entity + * Fully loaded entity object whose prefix is requested in the option label + * of the synonyms friendly select widget + * @param string $synonym + * If this option comes from a synonym, that synonym will be supplied here + * + * @return string + * Any suitable prefix before the main wording of the option for this + * particular option + */ + protected function synonymsSelectOptionPrefix($entity, $synonym = NULL) { + return ''; + } + + /** + * Form a key for the option of a synonyms friendly select. + * + * @param object $entity + * Fully loaded entity 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 synonymSelectKey($entity, $synonym = NULL) { + $entity_id = entity_extract_ids($this->behavior_implementation['entity_type'], $entity); + $key = $entity_id[0]; + if ($synonym) { + $key .= ':' . drupal_html_class($synonym); + } + return $key; + } + + /** + * Method to initiate all necessary terms for testing. + */ + protected function createTerms() { $this->terms['parent_term'] = (object) array( 'vid' => $this->vocabulary->vid, 'name' => 'Parent Term', @@ -839,10 +1179,51 @@ class SelectSynonymsWebTestCase extends SynonymsWebTestCase { ); taxonomy_term_save($this->terms['normal_term']); } +} + +/** + * Test synonyms friendly select widget for taxonomy term reference field. + */ +class TaxonomyTermReferenceSelectSynonymsWebTestCase extends AbstractSelectSynonymsWebTestCase { + + public static function getInfo() { + return array( + 'name' => 'Taxonomy synonyms select', + 'description' => 'Ensure that the "synonym friendly select" widget works correctly with taxonomy term reference field.', + 'group' => 'Synonyms', + ); + } + + public function setUp($modules = array()) { + parent::setUp($modules); + + $this->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->reference_field = field_create_field($this->reference_field); + + $this->reference_instance = array( + 'field_name' => $this->reference_field['field_name'], + 'entity_type' => 'node', + 'bundle' => $this->bundle, + 'label' => 'Synonym Terms Select', + 'widget' => array( + 'type' => 'synonyms_select_taxonomy_term', + ), + ); + $this->reference_instance = field_create_instance($this->reference_instance); + } - /** - * Test sorting options of the widget. - */ public function testWidgetSorting() { $cardinality = array( 1 => 1, @@ -855,49 +1236,48 @@ class SelectSynonymsWebTestCase extends SynonymsWebTestCase { 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); + $this->reference_field['cardinality'] = $cardinality_k; + field_update_field($this->reference_field); - $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); + $this->reference_instance['required'] = $required_k; + $this->reference_instance['widget']['settings']['sort'] = 'weight'; + field_update_instance($this->reference_instance); $this->terms['parent_term']->weight = 0; taxonomy_term_save($this->terms['parent_term']); $options = array(); $options[] = array( - 'term' => $this->terms['normal_term'], + 'entity' => $this->terms['normal_term'], ); $options[] = array( - 'term' => $this->terms['normal_term'], + 'entity' => $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'], + 'entity' => $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'], + 'entity' => $this->terms['parent_term'], ); $options[] = array( - 'term' => $this->terms['parent_term'], + 'entity' => $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'], + 'entity' => $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'], + 'entity' => $this->terms['child_term'], ); $options[] = array( - 'term' => $this->terms['child_term'], + 'entity' => $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'], + 'entity' => $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'); @@ -907,77 +1287,76 @@ class SelectSynonymsWebTestCase extends SynonymsWebTestCase { taxonomy_term_save($this->terms['parent_term']); $options = array(); $options[] = array( - 'term' => $this->terms['parent_term'], + 'entity' => $this->terms['parent_term'], ); $options[] = array( - 'term' => $this->terms['parent_term'], + 'entity' => $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'], + 'entity' => $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'], + 'entity' => $this->terms['child_term'], ); $options[] = array( - 'term' => $this->terms['child_term'], + 'entity' => $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'], + 'entity' => $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'], + 'entity' => $this->terms['normal_term'], ); $options[] = array( - 'term' => $this->terms['normal_term'], + 'entity' => $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'], + 'entity' => $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); + $this->reference_instance['widget']['settings']['sort'] = 'name'; + field_update_instance($this->reference_instance); $options = array(); $options[] = array( - 'term' => $this->terms['normal_term'], + 'entity' => $this->terms['normal_term'], ); $options[] = array( - 'term' => $this->terms['normal_term'], + 'entity' => $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'], + 'entity' => $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'], + 'entity' => $this->terms['parent_term'], ); $options[] = array( - 'term' => $this->terms['child_term'], + 'entity' => $this->terms['child_term'], ); $options[] = array( - 'term' => $this->terms['child_term'], + 'entity' => $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'], + 'entity' => $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'], + 'entity' => $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'], + 'entity' => $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'); @@ -986,14 +1365,11 @@ class SelectSynonymsWebTestCase extends SynonymsWebTestCase { } } - /** - * 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->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, @@ -1009,50 +1385,50 @@ class SelectSynonymsWebTestCase extends SynonymsWebTestCase { $options = array(); $options[] = array( - 'term' => $this->terms['normal_term'], + 'entity' => $this->terms['normal_term'], 'selected' => TRUE, ); $options[] = array( - 'term' => $this->terms['normal_term'], + 'entity' => $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'], + 'entity' => $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'], + 'entity' => $this->terms['parent_term'], 'selected' => TRUE, ); $options[] = array( - 'term' => $this->terms['parent_term'], + 'entity' => $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'], + 'entity' => $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'], + 'entity' => $this->terms['child_term'], 'selected' => TRUE, ); $options[] = array( - 'term' => $this->terms['child_term'], + 'entity' => $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'], + 'entity' => $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); + $this->reference_field['cardinality'] = 2; + field_update_field($this->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->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, @@ -1062,7 +1438,7 @@ class SelectSynonymsWebTestCase extends SynonymsWebTestCase { $this->drupalPost('node/add/synonyms-test-content', array( 'title' => $name, - $this->term_reference_field['field_name'] . '[' . LANGUAGE_NONE . '][]' => array( + $this->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']), @@ -1073,12 +1449,12 @@ class SelectSynonymsWebTestCase extends SynonymsWebTestCase { $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); + $this->reference_field['cardinality'] = 1; + field_update_field($this->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']), + $this->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); @@ -1086,44 +1462,44 @@ class SelectSynonymsWebTestCase extends SynonymsWebTestCase { $options = array(); $options[] = array( - 'term' => $this->terms['normal_term'], + 'entity' => $this->terms['normal_term'], ); $options[] = array( - 'term' => $this->terms['normal_term'], + 'entity' => $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'], + 'entity' => $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'], + 'entity' => $this->terms['parent_term'], 'selected' => TRUE, ); $options[] = array( - 'term' => $this->terms['parent_term'], + 'entity' => $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'], + 'entity' => $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'], + 'entity' => $this->terms['child_term'], ); $options[] = array( - 'term' => $this->terms['child_term'], + 'entity' => $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'], + 'entity' => $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, + $this->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.'); @@ -1131,973 +1507,405 @@ class SelectSynonymsWebTestCase extends SynonymsWebTestCase { $options = array(); $options[] = array( - 'term' => $this->terms['normal_term'], + 'entity' => $this->terms['normal_term'], ); $options[] = array( - 'term' => $this->terms['normal_term'], + 'entity' => $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'], + 'entity' => $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'], + 'entity' => $this->terms['parent_term'], ); $options[] = array( - 'term' => $this->terms['parent_term'], + 'entity' => $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'], + 'entity' => $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'], + 'entity' => $this->terms['child_term'], 'selected' => TRUE, ); $options[] = array( - 'term' => $this->terms['child_term'], + 'entity' => $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'], + 'entity' => $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'); - } - - /** - * Form a key for the option of a synonyms friendly select. - * - * @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 synonymSelectKey($term, $synonym = NULL) { - $key = $term->tid; - if ($synonym) { - $key .= ':' . drupal_html_class($synonym); - } - return $key; + protected function synonymsSelectOptionPrefix($entity, $synonym = NULL) { + $depth = count(taxonomy_get_parents_all($entity->tid)) - 1; + return str_repeat('-', $depth) . parent::synonymsSelectOptionPrefix($entity, $synonym); } } /** - * Base class for all tests that test Synonyms behavior implementation classes. + * Test synonyms friendly select widget for entity reference field type. */ -abstract class AbstractSynonymsBehaviorWebTestCase extends SynonymsWebTestCase { +class EntityReferenceSelectSynonymsWebTestCase extends AbstractSelectSynonymsWebTestCase { - 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); - } - - - /** - * 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 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 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->found_synonyms = $v['found_synonyms']; - $terms[] = $term; - } - - $return = $behavior_implementation->synonymsFind($condition, $this->fields['enabled']['field'], $this->fields['enabled']['instance']); - - $rows = array(); - foreach ($return as $row) { - $rows[] = $row; - } - - $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 TextSynonymsBehavior class. - */ -class TextSynonymsBehaviorWebTestCase extends AbstractSynonymsBehaviorWebTestCase { - - /** - * GetInfo method. - */ public static function getInfo() { return array( - 'name' => 'TextSynonymsBehavior', - 'description' => 'Ensure that the synonyms module extracts synonyms from text and number fields correctly.', + 'name' => 'Entity reference synonyms select', + 'description' => 'Ensure that the "synonym friendly select" widget works correctly with entity reference field.', 'group' => 'Synonyms', ); } - /** - * Test synonyms extraction for 'text' field type. - */ - public function testText() { - // Testing synonymsExtract(). - $this->assertSynonymsExtract(array(), array(), 'on empty field.'); - - $synonym = $this->randomName(); - $this->assertSynonymsExtract(array( - LANGUAGE_NONE => array( - 0 => array('value' => $synonym), - ), - ), array($synonym), 'on a field that holds one value.'); - - // Testing mergeEntityAsSynonym() method. - $node = (object) array( - 'title' => $this->randomName(), - 'type' => 'page', - ); - 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 TaxonomySynonymsBehavior class. - */ -class TaxonomySynonymsBehaviorWebTestCase extends AbstractSynonymsBehaviorWebTestCase { - - /** - * Taxonomy vocabulary object terms. - * - * Terms of this vocabulary are synonyms of the main vocabulary terms. - * - * @var object - */ - protected $vocabularySource; - - /** - * GetInfo method. - */ - public static function getInfo() { - return array( - 'name' => 'TaxonomySynonymsBehavior', - 'description' => 'Ensure that the synonyms module extracts synonyms from taxonomy term reference fields correctly.', - 'group' => 'Synonyms', - ); - } - - /** - * SetUp method. - */ - 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', - ); - taxonomy_vocabulary_save($this->vocabularySource); - } - - /** - * Test synonyms extraction for 'taxonomy_term_reference' field type. - */ - public function testTaxonomyTermReference() { - // Testing synonymsExtract(). - $this->assertSynonymsExtract(array(), array(), 'on empty field.'); - - $synonym_term = $this->createSynonymTerm(); - $this->assertSynonymsExtract(array( - LANGUAGE_NONE => array( - 0 => array( - 'tid' => $synonym_term->tid, - ), - ), - ), array($synonym_term->name), 'on a field that holds one value.'); - - // 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'); - - $meta_data = array(); - $meta_data[] = array( - 'items' => array( - LANGUAGE_NONE => array( - array('tid' => $this->createSynonymTerm()->tid), - ), - ), - '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), - ), - ), - 'found_synonyms' => array(), - ); - $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'); - - $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, '%' . 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"); - } - - /** - * Supportive function. - * - * Create a taxonomy term in the synonyms source vocabulary with the specified - * name. - * - * @param string $name - * Name of the term to be created. If nothing is supplied a random string - * is used - * - * @return object - * Fully loaded taxonomy term object of the just created term - */ - protected function createSynonymTerm($name = NULL) { - if (is_null($name)) { - $name = $this->randomName(); - } - $synonym_term = (object) array( - 'name' => $name, - 'vid' => $this->vocabularySource->vid, - ); - taxonomy_term_save($synonym_term); - return $synonym_term; - } -} - -/** - * Test EntityReferenceSynonymsBehavior class. - */ -class EntityReferenceSynonymsBehaviorWebTestCase extends AbstractSynonymsBehaviorWebTestCase { - - /** - * GetInfo method. - */ - public static function getInfo() { - return array( - 'name' => 'EntityReferenceSynonymsBehavior', - 'description' => 'Ensure that the synonyms module extracts synonyms from entity reference fields correctly.', - 'group' => 'Synonyms', - ); - } - - /** - * SetUp method. - */ public function setUp($modules = array()) { $modules[] = 'entityreference'; + parent::setUp($modules); - $this->fields['enabled']['field'] = array( - 'field_name' => 'reference', - 'cardinality' => FIELD_CARDINALITY_UNLIMITED, + $this->reference_field = array( 'type' => 'entityreference', - // For the sake of experiment we use entityreference field that references - // nodes of a Drupal standard type to make things easier. + 'field_name' => 'synonyms_term', + 'cardinality' => FIELD_CARDINALITY_UNLIMITED, 'settings' => array( - 'target_type' => 'node', - 'handler' => 'base', - 'handler_settings' => array( - 'target_bundles' => array('page' => 'page'), - 'sort' => array('type' => 'none'), - ), + 'target_type' => 'taxonomy_term', + 'handler_settings' => array(), ), ); + $this->reference_field = field_create_field($this->reference_field); + $this->reference_instance = array( + 'field_name' => $this->reference_field['field_name'], + 'entity_type' => 'node', + 'bundle' => $this->bundle, + 'label' => 'Synonym Terms Select', + 'widget' => array( + 'type' => 'synonyms_select_entity', + ), + ); + $this->reference_instance = field_create_instance($this->reference_instance); + } + + public function testWidgetSorting() { + $cardinality = array( + 1 => 1, + FIELD_CARDINALITY_UNLIMITED => 'unlimited', + ); + $required = array( + TRUE => 'required', + FALSE => 'not required', + ); + + foreach ($cardinality as $cardinality_k => $cardinality_v) { + foreach ($required as $required_k => $required_v) { + $this->reference_field['cardinality'] = $cardinality_k; + field_update_field($this->reference_field); + + $this->reference_instance['required'] = $required_k; + field_update_instance($this->reference_instance); + + $options = array(); + $options[] = array( + 'entity' => $this->terms['child_term'], + ); + $options[] = array( + 'entity' => $this->terms['child_term'], + 'synonym' => $this->terms['child_term']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE][1]['value'], + ); + $options[] = array( + 'entity' => $this->terms['child_term'], + 'synonym' => $this->terms['child_term']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE][0]['value'], + ); + $options[] = array( + 'entity' => $this->terms['normal_term'], + ); + $options[] = array( + 'entity' => $this->terms['normal_term'], + 'synonym' => $this->terms['normal_term']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE][0]['value'], + ); + $options[] = array( + 'entity' => $this->terms['normal_term'], + 'synonym' => $this->terms['normal_term']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE][1]['value'], + ); + $options[] = array( + 'entity' => $this->terms['parent_term'], + ); + $options[] = array( + 'entity' => $this->terms['parent_term'], + 'synonym' => $this->terms['parent_term']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE][0]['value'], + ); + $options[] = array( + 'entity' => $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); + } + } + } + + public function testWidget() { + $name = $this->randomName(); + $this->drupalPost('node/add/synonyms-test-content', array( + 'title' => $name, + $this->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'); + + $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( + 'entity' => $this->terms['child_term'], + 'selected' => TRUE, + ); + $options[] = array( + 'entity' => $this->terms['child_term'], + 'synonym' => $this->terms['child_term']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE][1]['value'], + ); + $options[] = array( + 'entity' => $this->terms['child_term'], + 'synonym' => $this->terms['child_term']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE][0]['value'], + ); + $options[] = array( + 'entity' => $this->terms['normal_term'], + 'selected' => TRUE, + ); + $options[] = array( + 'entity' => $this->terms['normal_term'], + 'synonym' => $this->terms['normal_term']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE][0]['value'], + ); + $options[] = array( + 'entity' => $this->terms['normal_term'], + 'synonym' => $this->terms['normal_term']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE][1]['value'], + ); + $options[] = array( + 'entity' => $this->terms['parent_term'], + 'selected' => TRUE, + ); + $options[] = array( + 'entity' => $this->terms['parent_term'], + 'synonym' => $this->terms['parent_term']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE][0]['value'], + ); + $options[] = array( + 'entity' => $this->terms['parent_term'], + 'synonym' => $this->terms['parent_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->reference_field['cardinality'] = 2; + field_update_field($this->reference_field); + $name = $this->randomName(); + $this->drupalPost('node/add/synonyms-test-content', array( + 'title' => $name, + $this->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->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->reference_field['cardinality'] = 1; + field_update_field($this->reference_field); + $name = $this->randomName(); + $this->drupalPost('node/add/synonyms-test-content', array( + 'title' => $name, + $this->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( + 'entity' => $this->terms['child_term'], + ); + $options[] = array( + 'entity' => $this->terms['child_term'], + 'synonym' => $this->terms['child_term']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE][1]['value'], + ); + $options[] = array( + 'entity' => $this->terms['child_term'], + 'synonym' => $this->terms['child_term']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE][0]['value'], + ); + $options[] = array( + 'entity' => $this->terms['normal_term'], + ); + $options[] = array( + 'entity' => $this->terms['normal_term'], + 'synonym' => $this->terms['normal_term']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE][0]['value'], + ); + $options[] = array( + 'entity' => $this->terms['normal_term'], + 'synonym' => $this->terms['normal_term']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE][1]['value'], + ); + $options[] = array( + 'entity' => $this->terms['parent_term'], + 'selected' => TRUE, + ); + $options[] = array( + 'entity' => $this->terms['parent_term'], + 'synonym' => $this->terms['parent_term']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE][0]['value'], + ); + $options[] = array( + 'entity' => $this->terms['parent_term'], + 'synonym' => $this->terms['parent_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->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( + 'entity' => $this->terms['child_term'], + 'selected' => TRUE, + ); + $options[] = array( + 'entity' => $this->terms['child_term'], + 'synonym' => $this->terms['child_term']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE][1]['value'], + ); + $options[] = array( + 'entity' => $this->terms['child_term'], + 'synonym' => $this->terms['child_term']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE][0]['value'], + ); + $options[] = array( + 'entity' => $this->terms['normal_term'], + ); + $options[] = array( + 'entity' => $this->terms['normal_term'], + 'synonym' => $this->terms['normal_term']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE][0]['value'], + ); + $options[] = array( + 'entity' => $this->terms['normal_term'], + 'synonym' => $this->terms['normal_term']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE][1]['value'], + ); + $options[] = array( + 'entity' => $this->terms['parent_term'], + ); + $options[] = array( + 'entity' => $this->terms['parent_term'], + 'synonym' => $this->terms['parent_term']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE][0]['value'], + ); + $options[] = array( + 'entity' => $this->terms['parent_term'], + 'synonym' => $this->terms['parent_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.'); + } +} + +/** + * Test 'term_synonyms' cTools argument plugin implementation. + */ +class TaxonomyTermArgumentSynonymsWebTestCase extends SynonymsWebTestCase { + + /** + * GetInfo method. + */ + public static function getInfo() { + return array( + 'name' => 'Term synonyms cTools argument plugin', + 'description' => 'Ensure that synonyms friendly taxonomy terms cTools argument plugin works correctly.', + 'group' => 'Synonyms', + ); + } + + /** + * SetUp method. + */ + public function setUp($modules = array()) { + $this->behavior_implementation['behavior'] = 'autocomplete'; + $this->behavior_implementation['settings'] = array( + 'wording' => '@synonym', + ); parent::setUp($modules); } /** - * Test synonyms extraction for 'entityreference' field type. + * Test the term synonym argument. */ - public function testEntityReference() { - // Testing synonymsExtract(). - $this->assertSynonymsExtract(array(), array(), 'on empty field.'); - - $synonym_entity = $this->createNode(); - $this->assertSynonymsExtract(array( - LANGUAGE_NONE => array( - 0 => array( - 'target_id' => entity_id('node', $synonym_entity), - ), - ), - ), array(entity_label('node', $synonym_entity)), 'on a field that holds one value.'); - - // 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'); - - $meta_data = array(); - $meta_data[] = array( - 'items' => array(), - 'found_synonyms' => array(), + public function testTermSynonymArgument() { + $term = (object) array( + 'name' => 'Term name', + 'vid' => $this->vocabulary->vid, + $this->fields['enabled']['field']['field_name'] => array(LANGUAGE_NONE => array( + array('value' => 'Synonym of term'), + )), ); - $this->assertSynonymsFind($meta_data, db_and()->condition(AbstractSynonymsSynonymsBehavior::COLUMN_PLACEHOLDER, $this->randomName()), 'on a field without values'); + taxonomy_term_save($term); - $meta_data = array(); - $meta_data[] = array( - 'items' => array( - LANGUAGE_NONE => array( - array('target_id' => entity_id('node', $this->createNode())), - ), - ), - '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'); + ctools_include('context'); - $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)), + $argument = array( + 'name' => 'term_synonyms', + 'vids' => array(), + 'transform' => FALSE, + 'identifier' => 'Just Testing', + 'keyword' => 'term', + 'id' => 0, ); - $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'); + $result = ctools_context_get_context_from_argument($argument, $this->randomName()); + $this->assertNull($result, 'Term synonym does not look up anything when random string is supplied.'); - $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( - LANGUAGE_NONE => array( - array('target_id' => entity_id('node', $this->createNode())), - array('target_id' => entity_id('node', $this->createNode())), - ), - ), - 'found_synonyms' => array(), - ); - $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'); + $result = ctools_context_get_context_from_argument($argument, drupal_strtolower($term->name)); + $this->assertEqual($result->data->tid, $term->tid, 'Term synonym argument correctly looks up the term by its name.'); - $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%'); + $result = ctools_context_get_context_from_argument($argument, drupal_strtolower($term->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE][0]['value'])); + $this->assertEqual($result->data->tid, $term->tid, 'Term synonym argument correctly looks up the term by its synonym.'); - $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%'); + $argument['transform'] = TRUE; + $result = ctools_context_get_context_from_argument($argument, str_replace(' ', '-', $term->name)); + $this->assertEqual($result->data->tid, $term->tid, 'Term synonym argument correctly looks up the term by its name, if the spaces are repaced with dashes.'); - $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'); + $result = ctools_context_get_context_from_argument($argument, str_replace(' ', '-', $term->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE][0]['value'])); + $this->assertEqual($result->data->tid, $term->tid, 'Term synonym argument correctly looks up the term by its synonym, if the spaces are repaced with dashes.'); - $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)), + $argument['transform'] = FALSE; + $another_vocabulary = (object) array( + 'name' => $this->randomName(), + 'machine_name' => 'another_vocabulary', ); - $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%'); + taxonomy_vocabulary_save($another_vocabulary); + $argument['vids'][$another_vocabulary->vid] = $another_vocabulary->vid; + $result = ctools_context_get_context_from_argument($argument, drupal_strtolower($term->name)); + $this->assertNull($result, 'Term synonym argument does not look up anything when term name is supplied, but the search is limited to another vocabulary.'); - $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"); - } - - /** - * Supportive function. - * - * Create an entity of necessary entity type (in our test it's node). - * - * @param string $label - * Label to use for the entity that is about to be created - * @param string $bundle - * Bundle to use for the entity that is about to be created - * - * @return object - * Fully loaded entity object of the just created entity - */ - protected function createNode($label = NULL, $bundle = 'page') { - if (is_null($label)) { - $label = $this->randomName(); - } - $entity = (object) array( - 'type' => $bundle, - 'title' => $label, - ); - node_save($entity); - return $entity; + $result = ctools_context_get_context_from_argument($argument, drupal_strtolower($term->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE][0]['value'])); + $this->assertNull($result, 'Term synonym argument does not look up anything when term synonym is supplied, but the search is limited to another vocabulary.'); } } diff --git a/sites/all/modules/contrib/taxonomy/synonyms/synonyms_commerce/includes/CommerceProductReferenceSynonymsBehavior.class.inc b/sites/all/modules/contrib/taxonomy/synonyms/synonyms_commerce/includes/CommerceProductReferenceSynonymsBehavior.class.inc new file mode 100644 index 00000000..db5e47b2 --- /dev/null +++ b/sites/all/modules/contrib/taxonomy/synonyms/synonyms_commerce/includes/CommerceProductReferenceSynonymsBehavior.class.inc @@ -0,0 +1,66 @@ +entityItems($entity) as $item) { + $product_ids[] = $item['product_id']; + } + $entities = commerce_product_load_multiple($product_ids); + foreach ($entities as $entity) { + $synonyms[] = entity_label('commerce_product', $entity); + } + + return $synonyms; + } + + public function mergeEntityAsSynonym($trunk_entity, $synonym_entity, $synonym_entity_type) { + if ('commerce_product' != $synonym_entity_type) { + return; + } + + $items = $this->entityItems($trunk_entity); + $synonym_entity_id = entity_extract_ids($synonym_entity_type, $synonym_entity); + $items[] = array( + 'product_id' => $synonym_entity_id[0], + ); + $trunk_entity->{$this->field['field_name']}[LANGUAGE_NONE] = $this->uniqueItems($items, array('product_id')); + } + + public function synonymsFind(QueryConditionInterface $condition) { + if ($this->field['storage']['type'] != 'field_sql_storage') { + throw new SynonymsBehaviorException(t('Not supported storage engine %type in @method() method.', array( + '%type' => $this->field['storage']['type'], + '@method' => __METHOD__, + ))); + } + $table = array_keys($this->field['storage']['details']['sql'][FIELD_LOAD_CURRENT]); + $table = reset($table); + $column = $this->field['storage']['details']['sql'][FIELD_LOAD_CURRENT][$table]['product_id']; + + $query = db_select($table, 'field'); + + $product_entity_type_info = entity_get_info('commerce_product'); + + $product_entity_alias = $query->innerJoin($product_entity_type_info['base table'], 'product', 'field.' . $column . ' = %alias.' . $product_entity_type_info['entity keys']['id']); + $query->addField($product_entity_alias, $product_entity_type_info['entity keys']['label'], 'synonym'); + $query->fields('field', array('entity_id')); + $query->condition('field.entity_type', $this->instance['entity_type']); + $query->condition('field.bundle', $this->instance['bundle']); + + $this->synonymsFindProcessCondition($condition, $product_entity_alias . '.' . $product_entity_type_info['entity keys']['label'], 'field.entity_id'); + $query->condition($condition); + return $query->execute(); + } +} diff --git a/sites/all/modules/contrib/taxonomy/synonyms/synonyms_commerce/synonyms_commerce.info b/sites/all/modules/contrib/taxonomy/synonyms/synonyms_commerce/synonyms_commerce.info new file mode 100644 index 00000000..30f7970f --- /dev/null +++ b/sites/all/modules/contrib/taxonomy/synonyms/synonyms_commerce/synonyms_commerce.info @@ -0,0 +1,19 @@ +name = Synonyms Commerce +description = "Provides synonyms integration with Commerce." +package = Synonyms +core = 7.x +dependencies[] = synonyms_provider_field +dependencies[] = commerce_product_reference + +test_dependencies[] = commerce:commerce_product_ui + +files[] = synonyms_commerce.test + +files[] = includes/CommerceProductReferenceSynonymsBehavior.class.inc + +; Information added by Drupal.org packaging script on 2016-05-07 +version = "7.x-1.5" +core = "7.x" +project = "synonyms" +datestamp = "1462586641" + diff --git a/sites/all/modules/contrib/taxonomy/synonyms/synonyms_commerce/synonyms_commerce.module b/sites/all/modules/contrib/taxonomy/synonyms/synonyms_commerce/synonyms_commerce.module new file mode 100644 index 00000000..c5c7307c --- /dev/null +++ b/sites/all/modules/contrib/taxonomy/synonyms/synonyms_commerce/synonyms_commerce.module @@ -0,0 +1,247 @@ + 'Synonyms-friendly product autocomplete widget', + 'page callback' => 'synonyms_commerce_autocomplete', + 'access callback' => TRUE, + 'type' => MENU_CALLBACK, + 'file' => 'synonyms_commerce.pages.inc', + ); + + return $items; +} + +/** + * Implements hook_field_widget_info(). + */ +function synonyms_commerce_field_widget_info() { + return array( + 'synonyms_commerce_autocomplete' => array( + 'label' => t('Synonyms friendly autocomplete'), + 'field types' => array('commerce_product_reference'), + 'settings' => array( + 'size' => 60, + 'synonyms_autocomplete_path' => 'synonyms-commerce/autocomplete', + 'suggestion_size' => 10, + 'suggest_only_unique' => FALSE, + ), + 'behaviors' => array( + 'multiple values' => FIELD_BEHAVIOR_CUSTOM, + ), + ), + 'synonyms_commerce_select' => array( + 'label' => t('Synonyms friendly select list'), + 'field types' => array('commerce_product_reference'), + 'settings' => array(), + 'behaviors' => array( + 'multiple values' => FIELD_BEHAVIOR_CUSTOM, + ), + ), + ); +} + +/** + * Implements hook_field_widget_settings_form(). + */ +function synonyms_commerce_field_widget_settings_form($field, $instance) { + $widget = $instance['widget']; + $settings = $widget['settings'] + field_info_widget_settings($widget['type']); + + $form = array(); + + switch ($widget['type']) { + case 'synonyms_commerce_autocomplete': + $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 product'), + '#description' => t('If you want to include only product name or a single synonym, suggesting a particular product, while disregarding all ongoing ones, please, tick this checkbox on.'), + '#default_value' => $settings['suggest_only_unique'], + ); + break; + } + + return $form; +} + +/** + * Implements hook_field_widget_form(). + */ +function synonyms_commerce_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) { + $default_value = synonyms_select_default_value($field, $instance, $items); + switch ($instance['widget']['type']) { + case 'synonyms_commerce_autocomplete': + $default_value_string = array(); + foreach (commerce_product_load_multiple($default_value) as $product) { + $default_value_string[] = entity_label('commerce_product', $product); + } + $default_value_string = drupal_implode_tags($default_value_string); + + $element += array( + '#type' => 'textfield', + '#default_value' => $default_value_string, + '#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('synonyms_commerce_autocomplete_validate'), + '#attached' => array( + 'js' => array( + drupal_get_path('module', 'synonyms') . '/js/synonyms-autocomplete.js' => array(), + ), + ), + '#attributes' => array( + 'class' => array('synonyms-autocomplete'), + ), + ); + if (isset($instance['widget']['settings']['auto_creation'])) { + $element['#auto_creation'] = $instance['widget']['settings']['auto_creation']; + } + break; + + case 'synonyms_commerce_select': + $multiple = $field['cardinality'] > 1 || $field['cardinality'] == FIELD_CARDINALITY_UNLIMITED; + + $options = module_invoke('commerce_product_reference', 'options_list', $field, $instance); + $tmp = reset($options); + if (is_array($tmp)) { + // These options have optgroups. + foreach ($options as $k => $v) { + $options[$k] = synonyms_commerce_product_options_expand($v); + } + } + else { + $options = synonyms_commerce_product_options_expand($options); + } + + $element += array( + '#type' => 'select', + '#empty_option' => t('- None -'), + '#options' => $options, + '#multiple' => $multiple, + '#element_validate' => array('synonyms_select_validate', 'synonyms_select_form_to_storage'), + '#default_value' => $default_value, + ); + break; + } + return $element; +} + +/** + * Implements hook_synonyms_provider_field_behavior_implementation_info(). + */ +function synonyms_commerce_synonyms_provider_field_behavior_implementation_info($behavior) { + switch ($behavior) { + case 'select': + case 'autocomplete': + return array( + 'commerce_product_reference' => 'CommerceProductReferenceSynonymsBehavior', + ); + break; + } + return array(); +} + +/** + * Expand the options for commerce product select widget with synonyms. + * + * @param array $options + * Array of commerce product reference widget options that should be expanded + * with synonyms + * + * @return array + * Expanded with synonyms version of the provided $options + */ +function synonyms_commerce_product_options_expand($options) { + $synonyms_options = array(); + + $behavior_implementations = array(); + + foreach (commerce_product_load_multiple(array_keys($options)) as $product) { + if (!isset($behavior_implementations[$product->type])) { + $behavior_implementations[$product->type] = synonyms_behavior_get('select', 'commerce_product', $product->type, TRUE); + } + $synonyms_options[] = synonyms_select_option_entity($product, 'commerce_product'); + + foreach ($behavior_implementations[$product->type] as $behavior_implementation) { + foreach ($behavior_implementation['object']->extractSynonyms($product) as $synonym) { + $synonyms_options[] = synonyms_select_option_entity($product, $behavior_implementation['entity_type'], $synonym, $behavior_implementation); + } + } + } + usort($synonyms_options , 'synonyms_select_sort_name'); + return $synonyms_options; +} + +/** + * Element validate for commerce product synonyms friendly autocomplete widget. + */ +function synonyms_commerce_autocomplete_validate($element, &$form_state) { + $input = drupal_map_assoc(drupal_explode_tags(drupal_strtolower($element['#value']))); + + $value = array(); + + $field = field_info_field($element['#field_name']); + $instance = field_info_instance($element['#entity_type'], $field['field_name'], $element['#bundle']); + + if (!empty($input)) { + $target_bundles = synonyms_bundle_normalize('commerce_product', array_filter($instance['settings']['referenceable_types'])); + + $efq = new EntityFieldQuery(); + $efq->entityCondition('entity_type', 'commerce_product'); + $efq->entityCondition('bundle', $target_bundles); + $efq->propertyCondition('title', $input, 'IN'); + $result = $efq->execute(); + if (isset($result['commerce_product'])) { + foreach (commerce_product_load_multiple(array_keys($result['commerce_product'])) as $product) { + $label = drupal_strtolower(entity_label('commerce_product', $product)); + unset($input[$label]); + $entity_id = entity_extract_ids('commerce_product', $product); + $value[] = $entity_id[0]; + } + } + + if (!empty($input)) { + $behavior_implementations = synonyms_behavior_get('autocomplete', 'commerce_product', $target_bundles, TRUE); + foreach ($behavior_implementations as $implementation) { + $condition = db_and(); + $condition->condition(AbstractSynonymsBehavior::COLUMN_SYNONYM_PLACEHOLDER, $input, 'IN'); + foreach ($implementation['object']->synonymsFind($condition) as $synonym) { + $value[] = $synonym->entity_id; + unset($input[drupal_strtolower($synonym->synonym)]); + if (empty($input)) { + break(2); + } + } + } + } + } + + $tmp = array_unique($value); + $value = array(); + $column = array_keys($field['columns']); + $column = reset($column); + foreach ($tmp as $target_id) { + $value[] = array($column => $target_id); + } + + form_set_value($element, $value, $form_state); +} diff --git a/sites/all/modules/contrib/taxonomy/synonyms/synonyms_commerce/synonyms_commerce.pages.inc b/sites/all/modules/contrib/taxonomy/synonyms/synonyms_commerce/synonyms_commerce.pages.inc new file mode 100644 index 00000000..41edc42a --- /dev/null +++ b/sites/all/modules/contrib/taxonomy/synonyms/synonyms_commerce/synonyms_commerce.pages.inc @@ -0,0 +1,128 @@ + $field_name)); + exit; + } + + if (!($instance = field_info_instance($entity_type, $field['field_name'], $bundle))) { + // Error string. The JavaScript handler will realize this is not JSON and + // will display it as debugging information. + print t('There was not found an instance of @field_name in @entity_type.', array( + '@field_name' => $field_name, + '@entity_type' => $entity_type, + )); + exit; + } + + module_load_include('inc', 'synonyms', 'synonyms.pages'); + + $widget = $instance['widget']['type'] == 'synonyms_commerce_autocomplete' ? $instance['widget']['settings'] : field_info_widget_settings('synonyms_commerce_autocomplete'); + + // How many suggestions maximum we are able to output. + $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 = $widget['suggest_only_unique']; + + $tags_typed = drupal_explode_tags($tags_typed); + $tag_last = drupal_strtolower(array_pop($tags_typed)); + $prefix = count($tags_typed) ? drupal_implode_tags($tags_typed) . ', ' : ''; + + $matches = array(); + + if ($tag_last) { + $tags_typed_entity_ids = array(); + + if (!empty($tags_typed)) { + foreach (commerce_product_load_multiple(array(), array('title' => $tags_typed)) as $product) { + $product_ids = entity_extract_ids('commerce_product', $product); + $tags_typed_entity_ids[] = $product_ids[0]; + } + } + + $new_target_ids = array(); + + foreach (commerce_product_match_products($field, $instance, $tag_last, 'contains', array(), $max_suggestions) as $product) { + $product = commerce_product_load_by_sku($product['sku']); + $product_id = entity_extract_ids('commerce_product', $product); + $product_id = $product_id[0]; + if (!in_array($product_id, $tags_typed_entity_ids)) { + $matches[] = array( + 'name' => entity_label('commerce_product', $product), + ); + $new_target_ids[] = $product_id; + } + } + + if (count($matches) < $max_suggestions) { + $target_bundles = synonyms_bundle_normalize('commerce_product', array_filter($instance['settings']['referenceable_types'])); + + $behavior_implementations = synonyms_behavior_get('autocomplete', 'commerce_product', $target_bundles, TRUE); + foreach ($behavior_implementations as $implementation) { + $condition = db_and(); + $condition->condition(AbstractSynonymsBehavior::COLUMN_SYNONYM_PLACEHOLDER, '%' . db_like($tag_last) . '%', 'LIKE'); + + if (!empty($tags_typed_entity_ids)) { + $condition->condition(AbstractSynonymsBehavior::COLUMN_ENTITY_ID_PLACEHOLDER, $tags_typed_entity_ids, 'NOT IN'); + } + if ($suggest_only_unique && !empty($new_target_ids)) { + $condition->condition(AbstractSynonymsBehavior::COLUMN_ENTITY_ID_PLACEHOLDER, $new_target_ids, 'NOT IN'); + } + + foreach ($implementation['object']->synonymsFind($condition) as $synonym) { + if (!$suggest_only_unique || !in_array($synonym->entity_id, $new_target_ids)) { + $matches[] = array( + 'target_id' => $synonym->entity_id, + 'synonym' => $synonym->synonym, + 'behavior_implementation' => $implementation, + ); + $new_target_ids[] = $synonym->entity_id; + if (count($matches) == $max_suggestions) { + break (2); + } + } + } + } + } + + $synonym_entities = array(); + foreach ($matches as $match) { + if (!isset($match['wording']) && isset($match['synonym'])) { + $synonym_entities[] = $match['target_id']; + } + } + if (!empty($synonym_entities)) { + $synonym_entities = commerce_product_load_multiple($synonym_entities); + foreach ($matches as $k => $match) { + if (!isset($match['name']) && isset($match['synonym'])) { + $matches[$k]['name'] = entity_label('commerce_product', $synonym_entities[$match['target_id']]); + } + } + $matches = array_values($matches); + } + } + + drupal_json_output(synonyms_autocomplete_format($matches, $prefix)); +} diff --git a/sites/all/modules/contrib/taxonomy/synonyms/synonyms_commerce/synonyms_commerce.test b/sites/all/modules/contrib/taxonomy/synonyms/synonyms_commerce/synonyms_commerce.test new file mode 100644 index 00000000..c577fe23 --- /dev/null +++ b/sites/all/modules/contrib/taxonomy/synonyms/synonyms_commerce/synonyms_commerce.test @@ -0,0 +1,849 @@ + 'Commerce product reference synonyms autocomplete', + 'description' => 'Ensure that the "synonym friendly autocomplete" widget works correctly with commerce product reference field type.', + 'group' => 'Synonyms', + ); + } + + public function setUp($modules = array()) { + $modules[] = 'commerce_product_reference'; + $modules[] = 'commerce_product_ui'; + $modules[] = 'synonyms_commerce'; + + $this->fields['enabled']['instance']['entity_type'] = 'commerce_product'; + $this->fields['enabled']['instance']['bundle'] = 'product'; + $this->fields['disabled']['instance']['entity_type'] = 'commerce_product'; + $this->fields['disabled']['instance']['bundle'] = 'product'; + + $this->behavior_implementation['entity_type'] = $this->fields['enabled']['instance']['entity_type']; + + parent::setUp($modules); + + $this->reference_field = array( + 'type' => 'commerce_product_reference', + 'field_name' => 'synonyms_commerce', + 'cardinality' => FIELD_CARDINALITY_UNLIMITED, + 'settings' => array(), + ); + $this->reference_field = field_create_field($this->reference_field); + + $this->reference_instance = array( + 'field_name' => $this->reference_field['field_name'], + 'entity_type' => $this->entity_type, + 'bundle' => $this->bundle, + 'label' => 'Synonym Commerce Autocomplete', + 'settings' => array( + 'referenceable_types' => drupal_map_assoc(array('product')), + ), + 'widget' => array( + 'type' => 'synonyms_commerce_autocomplete', + ), + ); + $this->reference_instance = field_create_instance($this->reference_instance); + $this->reference_instance = field_info_instance($this->reference_instance['entity_type'], $this->reference_instance['field_name'], $this->reference_instance['bundle']); + } + + protected function createTerms() { + $name = $this->randomName(); + + $product = (object) array( + 'type' => 'product', + 'title' => $name, + 'sku' => 'a' . drupal_strtolower($this->randomName()), + $this->fields['disabled']['field']['field_name'] => array( + LANGUAGE_NONE => array( + array('value' => $this->randomName()), + ), + ), + ); + commerce_product_save($product); + $this->terms['term1'] = $product; + + $name .= $this->randomName(); + + $product = (object) array( + 'type' => 'product', + 'title' => $name, + 'sku' => 'z' . drupal_strtolower($this->randomName()), + $this->fields['disabled']['field']['field_name'] => array( + LANGUAGE_NONE => array( + array('value' => $this->randomName()), + ), + ), + ); + commerce_product_save($product); + $this->terms['term1_longer_name'] = $product; + + $product = (object) array( + 'type' => 'product', + 'title' => $this->randomName(), + 'sku' => drupal_strtolower($this->randomName()), + $this->fields['disabled']['field']['field_name'] => array( + LANGUAGE_NONE => array( + array('value' => $this->randomName()), + ), + ), + ); + commerce_product_save($product); + $this->terms['no_synonyms'] = $product; + + $product = (object) array( + 'type' => 'product', + 'title' => $this->randomName(), + 'sku' => drupal_strtolower($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()), + ), + ), + ); + commerce_product_save($product); + $this->terms['one_synonym'] = $product; + + $product = (object) array( + 'type' => 'product', + 'title' => $this->randomName(), + 'sku' => drupal_strtolower($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()), + ), + ), + ); + commerce_product_save($product); + $this->terms['two_synonyms'] = $product; + + $name = $this->randomName(); + $product = (object) array( + 'type' => 'product', + 'title' => $name, + 'sku' => drupal_strtolower($this->randomName()), + $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()), + ), + ), + ); + commerce_product_save($product); + $this->terms['name_similar_synonym'] = $product; + + $name = 'similar_synonyms_'; + $product = (object) array( + 'type' => 'product', + 'title' => $this->randomName(), + 'sku' => drupal_strtolower($this->randomName()), + $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()), + ), + ), + ); + commerce_product_save($product); + $this->terms['similar_synonyms'] = $product; + + $name = 'one_term_name_another_synonym_'; + + $product = (object) array( + 'type' => 'product', + 'title' => $name . $this->randomName(), + 'sku' => drupal_strtolower($this->randomName()), + $this->fields['disabled']['field']['field_name'] => array( + LANGUAGE_NONE => array( + array('value' => $this->randomName()), + ), + ), + ); + commerce_product_save($product); + $this->terms['name_another_synonym'] = $product; + + $product = (object) array( + 'type' => 'product', + 'title' => $this->randomName(), + 'sku' => drupal_strtolower($this->randomName()), + $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()), + ), + ), + ); + commerce_product_save($product); + $this->terms['synonym_another_name'] = $product; + + $another_product_type = commerce_product_ui_product_type_new(); + $another_product_type['type'] = 'another_vocabulary'; + $another_product_type['name'] = $this->randomName(); + commerce_product_ui_product_type_save($another_product_type); + + $product_similar_product = (object) array( + 'type' => $another_product_type['type'], + 'title' => $this->entityLabel($this->terms['no_synonyms']), + 'sku' => drupal_strtolower($this->randomName()), + ); + commerce_product_save($product_similar_product); + + $product_similar_synonym = (object) array( + 'type' => $another_product_type['type'], + 'title' => $this->terms['one_synonym']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE][0]['value'], + 'sku' => drupal_strtolower($this->randomName()), + ); + commerce_product_save($product_similar_synonym); + + $synonym_similar_product = (object) array( + 'type' => $another_product_type['type'], + 'title' => $this->randomName(), + 'sku' => drupal_strtolower($this->randomName()), + $this->fields['enabled']['field']['field_name'] => array(LANGUAGE_NONE => array( + $this->entityLabel($this->terms['no_synonyms']), + )), + ); + commerce_product_save($synonym_similar_product); + + $synonym_similar_synonym = (object) array( + 'type' => $another_product_type['type'], + 'title' => $this->randomName(), + 'sku' => drupal_strtolower($this->randomName()), + $this->fields['enabled']['field']['field_name'] => array(LANGUAGE_NONE => array( + $this->terms['one_synonym']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE][0]['value'], + )), + ); + commerce_product_save($synonym_similar_synonym); + } +} + + +/** + * Test synonyms-friendly select widget for commerce product reference field. + */ +class CommerceProductReferenceSelectSynonymsWebTestCase extends AbstractSelectSynonymsWebTestCase { + + /** + * Array of products on which the testing is held. + * + * @var array + */ + protected $products = array(); + + public static function getInfo() { + return array( + 'name' => 'Commerce product reference synonyms select', + 'description' => 'Ensure that the "synonym friendly select" widget works correctly with commerce product reference field.', + 'group' => 'Synonyms', + ); + } + + public function setUp($modules = array()) { + $modules[] = 'commerce_product_reference'; + $modules[] = 'synonyms_commerce'; + + $this->fields['enabled']['instance']['entity_type'] = 'commerce_product'; + $this->fields['enabled']['instance']['bundle'] = 'product'; + $this->fields['disabled']['instance']['entity_type'] = 'commerce_product'; + $this->fields['disabled']['instance']['bundle'] = 'product'; + + $this->behavior_implementation['entity_type'] = $this->fields['enabled']['instance']['entity_type']; + + parent::setUp($modules); + + $this->reference_field = array( + 'type' => 'commerce_product_reference', + 'field_name' => 'synonyms_commerce', + 'cardinality' => FIELD_CARDINALITY_UNLIMITED, + 'settings' => array(), + ); + $this->reference_field = field_create_field($this->reference_field); + + $this->reference_instance = array( + 'field_name' => $this->reference_field['field_name'], + 'entity_type' => $this->entity_type, + 'bundle' => $this->bundle, + 'label' => 'Synonym Commerce Select', + 'widget' => array( + 'type' => 'synonyms_commerce_select', + ), + ); + $this->reference_instance = field_create_instance($this->reference_instance); + } + + public function testWidgetSorting() { + $cardinality = array( + 1 => 1, + FIELD_CARDINALITY_UNLIMITED => 'unlimited', + ); + $required = array( + TRUE => 'required', + FALSE => 'not required', + ); + + foreach ($cardinality as $cardinality_k => $cardinality_v) { + foreach ($required as $required_k => $required_v) { + $this->reference_field['cardinality'] = $cardinality_k; + field_update_field($this->reference_field); + + $this->reference_instance['required'] = $required_k; + field_update_instance($this->reference_instance); + + $options = array(); + $options[] = array( + 'entity' => $this->terms['product3'], + 'synonym' => $this->terms['product3']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE][0]['value'], + ); + $options[] = array( + 'entity' => $this->terms['product3'], + 'synonym' => $this->terms['product3']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE][1]['value'], + ); + $options[] = array( + 'entity' => $this->terms['product1'], + ); + $options[] = array( + 'entity' => $this->terms['product1'], + 'synonym' => $this->terms['product1']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE][0]['value'], + ); + $options[] = array( + 'entity' => $this->terms['product1'], + 'synonym' => $this->terms['product1']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE][1]['value'], + ); + $options[] = array( + 'entity' => $this->terms['product2'], + ); + $options[] = array( + 'entity' => $this->terms['product2'], + 'synonym' => $this->terms['product2']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE][1]['value'], + ); + $options[] = array( + 'entity' => $this->terms['product2'], + 'synonym' => $this->terms['product2']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE][0]['value'], + ); + $options[] = array( + 'entity' => $this->terms['product3'], + ); + + $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); + } + } + } + + public function testWidget() { + $name = $this->randomName(); + $this->drupalPost('node/add/synonyms-test-content', array( + 'title' => $name, + $this->reference_field['field_name'] . '[' . LANGUAGE_NONE . '][]' => array( + $this->synonymSelectKey($this->terms['product1'], $this->terms['product1']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE][0]['value']), + $this->terms['product2']->product_id, + $this->terms['product3']->product_id, + $this->synonymSelectKey($this->terms['product3'], $this->terms['product3']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE][0]['value']), + ), + ), 'Save'); + + $node = $this->drupalGetNodeByTitle($name); + $this->drupalGet('node/' . $node->nid); + $this->assertText($this->terms['product1']->title, 'Product is saved when its synonym is submitted through synonyms friendly select for the unlimited cardinality.'); + $this->assertText($this->terms['product2']->title, 'Product is saved when it is submitted through synonyms friendly select for the unlimited cardinality.'); + $this->assertText($this->terms['product3']->title, 'Product is saved only once when the term and its synonym are submitted through synonyms friendly select for the unlimited cardinality.'); + + $options = array(); + $options[] = array( + 'entity' => $this->terms['product3'], + 'synonym' => $this->terms['product3']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE][0]['value'], + ); + $options[] = array( + 'entity' => $this->terms['product3'], + 'synonym' => $this->terms['product3']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE][1]['value'], + ); + $options[] = array( + 'entity' => $this->terms['product1'], + 'selected' => TRUE, + ); + $options[] = array( + 'entity' => $this->terms['product1'], + 'synonym' => $this->terms['product1']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE][0]['value'], + ); + $options[] = array( + 'entity' => $this->terms['product1'], + 'synonym' => $this->terms['product1']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE][1]['value'], + ); + $options[] = array( + 'entity' => $this->terms['product2'], + 'selected' => TRUE, + ); + $options[] = array( + 'entity' => $this->terms['product2'], + 'synonym' => $this->terms['product2']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE][1]['value'], + ); + $options[] = array( + 'entity' => $this->terms['product2'], + 'synonym' => $this->terms['product2']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE][0]['value'], + ); + $options[] = array( + 'entity' => $this->terms['product3'], + 'selected' => TRUE, + ); + $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->reference_field['cardinality'] = 2; + field_update_field($this->reference_field); + $name = $this->randomName(); + $this->drupalPost('node/add/synonyms-test-content', array( + 'title' => $name, + $this->reference_field['field_name'] . '[' . LANGUAGE_NONE . '][]' => array( + $this->synonymSelectKey($this->terms['product1'], $this->terms['product1']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE][0]['value']), + $this->terms['product2']->product_id, + $this->terms['product3']->product_id, + ), + ), '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 products, results in a form error.'); + + $this->drupalPost('node/add/synonyms-test-content', array( + 'title' => $name, + $this->reference_field['field_name'] . '[' . LANGUAGE_NONE . '][]' => array( + $this->synonymSelectKey($this->terms['product1'], $this->terms['product1']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE][0]['value']), + $this->terms['product2']->product_id, + $this->synonymSelectKey($this->terms['product2'], $this->terms['product2']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE][0]['value']), + ), + ), 'Save'); + $node = $this->drupalGetNodeByTitle($name); + $this->drupalGet('node/' . $node->nid); + $this->assertText($this->terms['product1']->title, 'Submitting 3 entries into a field with cardinality of 2, that refer to only 2 products, results in form getting submitted. Product #1 is saved.'); + $this->assertText($this->terms['product2']->title, 'Product #2 is saved.'); + + $this->reference_field['cardinality'] = 1; + field_update_field($this->reference_field); + // We need to invoke this one. Without it some caching within Entity + // metadata wrappers interferes. See https://www.drupal.org/node/2717019 for + // details. + entity_flush_caches(); + + $name = $this->randomName(); + $this->drupalPost('node/add/synonyms-test-content', array( + 'title' => $name, + $this->reference_field['field_name'] . '[' . LANGUAGE_NONE . ']' => $this->synonymSelectKey($this->terms['product1'], $this->terms['product1']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE][0]['value']), + ), 'Save'); + $node = $this->drupalGetNodeByTitle($name); + + $this->drupalGet('node/' . $node->nid); + $this->assertText($this->terms['product1']->title, 'Product is saved when its synonym is submitted through synonyms friendly select for the cardinality of 1.'); + + $options = array(); + $options[] = array( + 'entity' => $this->terms['product3'], + 'synonym' => $this->terms['product3']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE][0]['value'], + ); + $options[] = array( + 'entity' => $this->terms['product3'], + 'synonym' => $this->terms['product3']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE][1]['value'], + ); + $options[] = array( + 'entity' => $this->terms['product1'], + 'selected' => TRUE, + ); + $options[] = array( + 'entity' => $this->terms['product1'], + 'synonym' => $this->terms['product1']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE][0]['value'], + ); + $options[] = array( + 'entity' => $this->terms['product1'], + 'synonym' => $this->terms['product1']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE][1]['value'], + ); + $options[] = array( + 'entity' => $this->terms['product2'], + ); + $options[] = array( + 'entity' => $this->terms['product2'], + 'synonym' => $this->terms['product2']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE][1]['value'], + ); + $options[] = array( + 'entity' => $this->terms['product2'], + 'synonym' => $this->terms['product2']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE][0]['value'], + ); + $options[] = array( + 'entity' => $this->terms['product3'], + ); + $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->reference_field['field_name'] . '[' . LANGUAGE_NONE . ']' => $this->terms['product2']->product_id, + ), 'Save'); + $this->drupalGet('node/' . $node->nid); + $this->assertNoText($this->terms['product1']->title, 'After updating entity the old product is removed.'); + $this->assertText($this->terms['product2']->title, 'Product is saved when it is submitted through synonyms friendly select for the cardinality of 1.'); + + $options = array(); + $options[] = array( + 'entity' => $this->terms['product3'], + 'synonym' => $this->terms['product3']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE][0]['value'], + ); + $options[] = array( + 'entity' => $this->terms['product3'], + 'synonym' => $this->terms['product3']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE][1]['value'], + ); + $options[] = array( + 'entity' => $this->terms['product1'], + ); + $options[] = array( + 'entity' => $this->terms['product1'], + 'synonym' => $this->terms['product1']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE][0]['value'], + ); + $options[] = array( + 'entity' => $this->terms['product1'], + 'synonym' => $this->terms['product1']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE][1]['value'], + ); + $options[] = array( + 'entity' => $this->terms['product2'], + 'selected' => TRUE, + ); + $options[] = array( + 'entity' => $this->terms['product2'], + 'synonym' => $this->terms['product2']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE][1]['value'], + ); + $options[] = array( + 'entity' => $this->terms['product2'], + 'synonym' => $this->terms['product2']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE][0]['value'], + ); + $options[] = array( + 'entity' => $this->terms['product3'], + ); + $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.'); + } + + protected function createTerms() { + $this->terms['product1'] = (object) array( + 'type' => $this->behavior_implementation['bundle'], + 'title' => 'Product 1', + 'sku' => drupal_strtolower($this->randomName()), + $this->fields['enabled']['field']['field_name'] => array( + LANGUAGE_NONE => array( + array('value' => 'Product 1A' . $this->randomName()), + array('value' => 'Product 1Z' . $this->randomName()), + ), + ), + ); + commerce_product_save($this->terms['product1']); + + $this->terms['product2'] = (object) array( + 'type' => $this->behavior_implementation['bundle'], + 'title' => 'Product 2', + 'sku' => drupal_strtolower($this->randomName()), + $this->fields['enabled']['field']['field_name'] => array( + LANGUAGE_NONE => array( + array('value' => 'Product 2Z' . $this->randomName()), + array('value' => 'Product 2A' . $this->randomName()), + ), + ), + ); + commerce_product_save($this->terms['product2']); + + $this->terms['product3'] = (object) array( + 'type' => $this->behavior_implementation['bundle'], + 'title' => 'Product 3', + 'sku' => drupal_strtolower($this->randomName()), + $this->fields['enabled']['field']['field_name'] => array( + LANGUAGE_NONE => array( + array('value' => 'Another Product 3A' . $this->randomName()), + array('value' => 'Another Product 3Z' . $this->randomName()), + ), + ), + ); + commerce_product_save($this->terms['product3']); + } +} + +/** + * Test CommerceProductReferenceSynonymsBehavior class. + */ +class CommerceProductReferenceSynonymsBehaviorWebTestCase extends AbstractSynonymsProviderFieldWebTestCase { + + /** + * GetInfo method. + */ + public static function getInfo() { + return array( + 'name' => 'CommerceProductReferenceSynonymsBehavior', + 'description' => 'Ensure that the synonyms module extracts synonyms from commerce product reference fields correctly.', + 'group' => 'Synonyms', + ); + } + + /** + * SetUp method. + */ + public function setUp($modules = array()) { + $modules[] = 'commerce_product_reference'; + $modules[] = 'commerce_product_ui'; + $modules[] = 'synonyms_commerce'; + + $this->fields['enabled']['field'] = array( + 'field_name' => 'reference', + 'cardinality' => FIELD_CARDINALITY_UNLIMITED, + 'type' => 'commerce_product_reference', + ); + + parent::setUp($modules); + } + + /** + * Test synonyms extraction for 'commerce_product_reference' field type. + */ + public function testCommerceProductReference() { + // Testing synonymsExtract(). + $this->assertSynonymsExtract(array(), array(), 'on empty field.'); + + $synonym_entity = $this->createProduct(); + $this->assertSynonymsExtract(array( + LANGUAGE_NONE => array( + 0 => array( + 'product_id' => entity_id('commerce_product', $synonym_entity), + ), + ), + ), array(entity_label('commerce_product', $synonym_entity)), 'on a field that holds one value.'); + + // Testing mergeEntityAsSynonym() method. + $product = $this->createProduct(); + $this->assertMergeEntityAsSynonym(array(), $product, 'commerce_product', array(array('product_id' => $product->product_id)), 'on a product.'); + + // Testing synonymFind() method. + $this->assertSynonymsFind(array(), db_and()->condition(AbstractSynonymsBehavior::COLUMN_SYNONYM_PLACEHOLDER, $this->randomName()), 'on empty field'); + + $meta_data = array(); + $meta_data[] = array( + 'items' => array(), + 'found_synonyms' => array(), + ); + $this->assertSynonymsFind($meta_data, db_and()->condition(AbstractSynonymsBehavior::COLUMN_SYNONYM_PLACEHOLDER, $this->randomName()), 'on a field without values'); + + $meta_data = array(); + $meta_data[] = array( + 'items' => array( + LANGUAGE_NONE => array( + array('product_id' => entity_id('commerce_product', $this->createProduct())), + ), + ), + 'found_synonyms' => array(), + ); + $this->assertSynonymsFind($meta_data, db_and()->condition(AbstractSynonymsBehavior::COLUMN_SYNONYM_PLACEHOLDER, $this->randomName()), 'on a field with a value but searching for another string'); + + $meta_data = array(); + $synonym_entity = $this->createProduct(); + $meta_data[] = array( + 'items' => array( + LANGUAGE_NONE => array( + array('product_id' => entity_id('commerce_product', $synonym_entity)), + ), + ), + 'found_synonyms' => array(entity_label('commerce_product', $synonym_entity)), + ); + $this->assertSynonymsFind($meta_data, db_and()->condition(AbstractSynonymsBehavior::COLUMN_SYNONYM_PLACEHOLDER, entity_label('commerce_product', $synonym_entity)), 'on a field with a single value searching for that string'); + + $meta_data = array(); + $synonym_entity = $this->createProduct(); + $meta_data[] = array( + 'items' => array( + LANGUAGE_NONE => array( + array('product_id' => entity_id('commerce_product', $synonym_entity)), + array('product_id' => entity_id('commerce_product', $this->createProduct())), + ), + ), + 'found_synonyms' => array(entity_label('commerce_product', $synonym_entity)), + ); + $this->assertSynonymsFind($meta_data, db_and()->condition(AbstractSynonymsBehavior::COLUMN_SYNONYM_PLACEHOLDER, entity_label('commerce_product', $synonym_entity)), 'on a field with 2 values searching for one of those 2 values'); + + $meta_data = array(); + $synonym_entity = $this->createProduct(); + $meta_data[] = array( + 'items' => array( + LANGUAGE_NONE => array( + array('product_id' => entity_id('commerce_product', $synonym_entity)), + array('product_id' => entity_id('commerce_product', $this->createProduct())), + ), + ), + 'found_synonyms' => array(entity_label('commerce_product', $synonym_entity)), + ); + $meta_data[] = array( + 'items' => array( + LANGUAGE_NONE => array( + array('product_id' => entity_id('commerce_product', $this->createProduct())), + array('product_id' => entity_id('commerce_product', $this->createProduct())), + ), + ), + 'found_synonyms' => array(), + ); + $this->assertSynonymsFind($meta_data, db_and()->condition(AbstractSynonymsBehavior::COLUMN_SYNONYM_PLACEHOLDER, entity_label('commerce_product', $synonym_entity)), 'on 2 fields with 2 values each searching for one of those values'); + + $meta_data = array(); + $synonym_entity = $this->createProduct(); + $meta_data[] = array( + 'items' => array( + LANGUAGE_NONE => array( + array('product_id' => entity_id('commerce_product', $synonym_entity)), + ), + ), + 'found_synonyms' => array(entity_label('commerce_product', $synonym_entity)), + ); + $this->assertSynonymsFind($meta_data, db_and()->condition(AbstractSynonymsBehavior::COLUMN_SYNONYM_PLACEHOLDER, '%' . db_like(drupal_substr(entity_label('commerce_product', $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->createProduct($tag . $this->randomName()); + $synonym_entity2 = $this->createProduct($tag . $this->randomName()); + $meta_data[] = array( + 'items' => array( + LANGUAGE_NONE => array( + array('product_id' => entity_id('commerce_product', $synonym_entity1)), + array('product_id' => entity_id('commerce_product', $synonym_entity2)), + ), + ), + 'found_synonyms' => array( + entity_label('commerce_product', $synonym_entity1), + entity_label('commerce_product', $synonym_entity2), + ), + ); + $this->assertSynonymsFind($meta_data, db_and()->condition(AbstractSynonymsBehavior::COLUMN_SYNONYM_PLACEHOLDER, db_like($tag) . '%', 'LIKE'), 'on a field with 2 similar values searching a string like %both values%'); + + $meta_data = array(); + $synonym_entity1 = $this->createProduct(); + $synonym_entity2 = $this->createProduct(); + $meta_data[] = array( + 'items' => array( + LANGUAGE_NONE => array( + array('product_id' => entity_id('commerce_product', $synonym_entity1)), + array('product_id' => entity_id('commerce_product', $synonym_entity2)), + ), + ), + 'found_synonyms' => array( + entity_label('commerce_product', $synonym_entity1), + entity_label('commerce_product', $synonym_entity2), + ), + ); + $condition = db_or() + ->condition(AbstractSynonymsBehavior::COLUMN_SYNONYM_PLACEHOLDER, entity_label('commerce_product', $synonym_entity1)) + ->condition(AbstractSynonymsBehavior::COLUMN_SYNONYM_PLACEHOLDER, entity_label('commerce_product', $synonym_entity2)); + $this->assertSynonymsFind($meta_data, $condition, 'on a field with 2 values searching for value1 or value2'); + + $meta_data = array(); + $synonym_entity = $this->createProduct(); + $meta_data[] = array( + 'items' => array( + LANGUAGE_NONE => array( + array('product_id' => entity_id('commerce_product', $synonym_entity)), + array('product_id' => entity_id('commerce_product', $this->createProduct())), + ), + ), + 'found_synonyms' => array(entity_label('commerce_product', $synonym_entity)), + ); + $condition = db_and() + ->condition(AbstractSynonymsBehavior::COLUMN_SYNONYM_PLACEHOLDER, entity_label('commerce_product', $synonym_entity)) + ->condition(AbstractSynonymsBehavior::COLUMN_SYNONYM_PLACEHOLDER, '%' . db_like(drupal_substr(entity_label('commerce_product', $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->createProduct(); + $synonym_entity2 = $this->createProduct(); + $meta_data[] = array( + 'items' => array( + LANGUAGE_NONE => array( + array('product_id' => entity_id('commerce_product', $synonym_entity1)), + array('product_id' => entity_id('commerce_product', $synonym_entity2)), + ), + ), + 'found_synonyms' => array( + entity_label('commerce_product', $synonym_entity1), + entity_label('commerce_product', $synonym_entity2), + ), + ); + $condition = db_or() + ->condition(AbstractSynonymsBehavior::COLUMN_SYNONYM_PLACEHOLDER, entity_label('commerce_product', $synonym_entity1)) + ->condition(db_and()->condition(AbstractSynonymsBehavior::COLUMN_SYNONYM_PLACEHOLDER, entity_label('commerce_product', $synonym_entity2))->condition(AbstractSynonymsBehavior::COLUMN_SYNONYM_PLACEHOLDER, '%' . db_like(drupal_substr(entity_label('commerce_product', $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->createProduct($this->randomName() . ' ' . $this->randomName() . ' ' . $this->randomName()); + $synonym_entity2 = $this->createProduct(str_replace(' ', '-', entity_label('commerce_product', $synonym_entity1))); + $meta_data[] = array( + 'items' => array( + LANGUAGE_NONE => array( + array('product_id' => entity_id('commerce_product', $synonym_entity1)), + array('product_id' => entity_id('commerce_product', $synonym_entity2)), + ), + ), + 'found_synonyms' => array( + entity_label('commerce_product', $synonym_entity1), + entity_label('commerce_product', $synonym_entity2), + ), + ); + $condition = db_and() + ->where("REPLACE(" . AbstractSynonymsBehavior::COLUMN_SYNONYM_PLACEHOLDER . ", ' ', '-') = :synonym", array( + ':synonym' => entity_label('commerce_product', $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"); + } + + /** + * Supportive function. + * + * Create an entity of necessary entity type (in our test it's commerce + * product). + * + * @param string $label + * Label to use for the entity that is about to be created + * @param string $bundle + * Bundle to use for the entity that is about to be created + * + * @return object + * Fully loaded entity object of the just created entity + */ + protected function createProduct($label = NULL, $bundle = 'product') { + if (is_null($label)) { + $label = $this->randomName(); + } + $entity = commerce_product_new($bundle); + $entity->title = $label; + $entity->sku = drupal_html_id($label); + commerce_product_save($entity); + return $entity; + } +} diff --git a/sites/all/modules/contrib/taxonomy/synonyms/synonyms_provider_field/includes/AbstractFieldSynonymsBehavior.class.inc b/sites/all/modules/contrib/taxonomy/synonyms/synonyms_provider_field/includes/AbstractFieldSynonymsBehavior.class.inc new file mode 100644 index 00000000..65688aa3 --- /dev/null +++ b/sites/all/modules/contrib/taxonomy/synonyms/synonyms_provider_field/includes/AbstractFieldSynonymsBehavior.class.inc @@ -0,0 +1,81 @@ +field = field_info_field(synonyms_provider_field_field_name($behavior_implementation['provider'])); + $this->instance = field_info_instance($behavior_implementation['entity_type'], $this->field['field_name'], $behavior_implementation['bundle']); + } + + public function featuresExportPipe() { + $pipe = parent::featuresExportPipe(); + + // We include the Features component for the underlying field on which this + // synonyms provider is built. + $pipe['field'][] = implode('-', array($this->behavior_implementation['entity_type'], $this->behavior_implementation['bundle'], $this->field['field_name'])); + return $pipe; + } + + /** + * Retrieve items of the underlying field in this behavior implementation. + * + * @param object $entity + * Entity whose items should be retrieved + * + * @return array + * Array of items that provided entity has in the field on which behavior + * implementation is set up + */ + protected function entityItems($entity) { + $items = field_get_items($this->instance['entity_type'], $entity, $this->field['field_name']); + return is_array($items) ? $items : array(); + } + + /** + * Filter $items only to contain unique values. + * + * @param array $items + * Array of field items that should be filtered to contain only unique + * values + * @param array $unique_index + * Array of column names that define uniqueness for an item + * + * @return array + * Only unique items from the provided $items array + */ + protected function uniqueItems($items, $unique_index) { + $index = array(); + foreach ($items as $item) { + $item_index = array(); + foreach ($unique_index as $column) { + $item_index[] = is_scalar($column) ? $column : serialize($column); + } + $index[serialize($item_index)] = $item; + } + return array_values($index); + } +} diff --git a/sites/all/modules/contrib/taxonomy/synonyms/synonyms_provider_field/includes/EntityReferenceSynonymsBehavior.class.inc b/sites/all/modules/contrib/taxonomy/synonyms/synonyms_provider_field/includes/EntityReferenceSynonymsBehavior.class.inc new file mode 100644 index 00000000..299383b0 --- /dev/null +++ b/sites/all/modules/contrib/taxonomy/synonyms/synonyms_provider_field/includes/EntityReferenceSynonymsBehavior.class.inc @@ -0,0 +1,86 @@ +entityItems($entity) as $item) { + $target_tids[] = $item['target_id']; + } + $entities = entity_load($this->field['settings']['target_type'], $target_tids); + foreach ($entities as $entity) { + $synonyms[] = entity_label($this->field['settings']['target_type'], $entity); + } + + return $synonyms; + } + + public function mergeEntityAsSynonym($trunk_entity, $synonym_entity, $synonym_entity_type) { + // Firstly validating that this entity reference is able to reference to + // that type of entity. + $expected_synonym_entity_type = $this->field['settings']['target_type']; + + $items = $this->entityItems($trunk_entity); + + if ($expected_synonym_entity_type != $synonym_entity_type) { + return; + } + $synonym_entity_id = entity_extract_ids($synonym_entity_type, $synonym_entity); + $synonym_entity_id = $synonym_entity_id[0]; + $items[] = array( + 'target_id' => $synonym_entity_id, + ); + $trunk_entity->{$this->field['field_name']}[LANGUAGE_NONE] = $this->uniqueItems($items, array('target_id')); + } + + public function synonymsFind(QueryConditionInterface $condition) { + if ($this->field['storage']['type'] != 'field_sql_storage') { + throw new SynonymsBehaviorException(t('Not supported storage engine %type in @method() method.', array( + '%type' => $this->field['storage']['type'], + '@method' => __METHOD__, + ))); + } + $table = array_keys($this->field['storage']['details']['sql'][FIELD_LOAD_CURRENT]); + $table = reset($table); + $column = $this->field['storage']['details']['sql'][FIELD_LOAD_CURRENT][$table]['target_id']; + + $query = db_select($table, 'field'); + + $target_entity_type_info = entity_get_info($this->field['settings']['target_type']); + if (!isset($target_entity_type_info['base table']) || !$target_entity_type_info['base table']) { + throw new SynonymsBehaviorException(t('Target entity type %entity_type is not stored in database.', array( + '%entity_type' => $this->field['settings']['target_type'], + ))); + } + if (!isset($target_entity_type_info['entity keys']['id'])) { + throw new SynonymsBehaviorException(t('Target entity type %entity_type does not declare primary key.', array( + '%entity_type' => $this->field['settings']['target_type'], + ))); + } + if (!isset($target_entity_type_info['entity keys']['label'])) { + throw new SynonymsBehaviorException(t('Target entity type %entity_type does not declare label column.', array( + '%entity_type' => $this->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', $this->instance['entity_type']); + $query->condition('field.bundle', $this->instance['bundle']); + + $this->synonymsFindProcessCondition($condition, $target_entity_alias . '.' . $target_entity_type_info['entity keys']['label'], 'field.entity_id'); + $query->condition($condition); + return $query->execute(); + } +} diff --git a/sites/all/modules/contrib/taxonomy/synonyms/includes/TaxonomySynonymsBehavior.class.inc b/sites/all/modules/contrib/taxonomy/synonyms/synonyms_provider_field/includes/TaxonomySynonymsBehavior.class.inc similarity index 60% rename from sites/all/modules/contrib/taxonomy/synonyms/includes/TaxonomySynonymsBehavior.class.inc rename to sites/all/modules/contrib/taxonomy/synonyms/synonyms_provider_field/includes/TaxonomySynonymsBehavior.class.inc index 6e58a6c4..8a3c5380 100644 --- a/sites/all/modules/contrib/taxonomy/synonyms/includes/TaxonomySynonymsBehavior.class.inc +++ b/sites/all/modules/contrib/taxonomy/synonyms/synonyms_provider_field/includes/TaxonomySynonymsBehavior.class.inc @@ -8,13 +8,13 @@ /** * Definition of TaxonomySynonymsBehavior class. */ -class TaxonomySynonymsBehavior extends AbstractSynonymsSynonymsBehavior implements SynonymsSynonymsBehavior, AutocompleteSynonymsBehavior, SelectSynonymsBehavior { +class TaxonomySynonymsBehavior extends AbstractFieldSynonymsBehavior implements AutocompleteSynonymsBehavior, SelectSynonymsBehavior { - public function extractSynonyms($items, $field, $instance, $entity, $entity_type) { + public function extractSynonyms($entity) { $synonyms = array(); $terms = array(); - foreach ($items as $item) { + foreach ($this->entityItems($entity) as $item) { $terms[] = $item['tid']; } $terms = taxonomy_term_load_multiple($terms); @@ -24,16 +24,19 @@ class TaxonomySynonymsBehavior extends AbstractSynonymsSynonymsBehavior implemen return $synonyms; } - public function mergeEntityAsSynonym($items, $field, $instance, $synonym_entity, $synonym_entity_type) { + public function mergeEntityAsSynonym($trunk_entity, $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(); + return; } + + $items = $this->entityItems($trunk_entity); + // Checking that $field is configured to reference the vocabulary of // $synonym_entity term. $is_allowed = FALSE; - foreach ($field['settings']['allowed_values'] as $setting) { + foreach ($this->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. @@ -53,35 +56,33 @@ class TaxonomySynonymsBehavior extends AbstractSynonymsSynonymsBehavior implemen if (!$is_allowed) { // Synonym term is from a vocabulary that is not expected by this field, // or under unexpected parent. - return array(); + return; } - return array(array( + $items[] = array( 'tid' => $synonym_entity->tid, - )); + ); + $trunk_entity->{$this->field['field_name']}[LANGUAGE_NONE] = $this->uniqueItems($items, array('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'], + public function synonymsFind(QueryConditionInterface $condition) { + if ($this->field['storage']['type'] != 'field_sql_storage') { + throw new SynonymsBehaviorException(t('Not supported storage engine %type in @method() method.', array( + '%type' => $this->field['storage']['type'], + '@method' => __METHOD__, ))); } - $table = array_keys($field['storage']['details']['sql'][FIELD_LOAD_CURRENT]); + $table = array_keys($this->field['storage']['details']['sql'][FIELD_LOAD_CURRENT]); $table = reset($table); - $column = $field['storage']['details']['sql'][FIELD_LOAD_CURRENT][$table]['tid']; + $column = $this->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']); + $query->condition('field.entity_type', $this->instance['entity_type']); + $query->condition('field.bundle', $this->instance['bundle']); - $this->synonymsFindProcessCondition($condition, $term_alias . '.name'); + $this->synonymsFindProcessCondition($condition, $term_alias . '.name', 'field.entity_id'); $query->condition($condition); return $query->execute(); } diff --git a/sites/all/modules/contrib/taxonomy/synonyms/synonyms_provider_field/includes/TextSynonymsBehavior.class.inc b/sites/all/modules/contrib/taxonomy/synonyms/synonyms_provider_field/includes/TextSynonymsBehavior.class.inc new file mode 100644 index 00000000..0c34546d --- /dev/null +++ b/sites/all/modules/contrib/taxonomy/synonyms/synonyms_provider_field/includes/TextSynonymsBehavior.class.inc @@ -0,0 +1,69 @@ +entityItems($entity) as $item) { + $synonyms[] = $item['value']; + } + + return $synonyms; + } + + public function mergeEntityAsSynonym($trunk_entity, $synonym_entity, $synonym_entity_type) { + $synonym = entity_label($synonym_entity_type, $synonym_entity); + + switch ($this->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; + } + break; + + } + $items = $this->entityItems($trunk_entity); + $items[] = array( + 'value' => $synonym, + ); + $trunk_entity->{$this->field['field_name']}[LANGUAGE_NONE] = $this->uniqueItems($items, array('value')); + } + + public function synonymsFind(QueryConditionInterface $condition) { + if ($this->field['storage']['type'] != 'field_sql_storage') { + throw new SynonymsBehaviorException(t('Not supported storage engine %type in @method() method.', array( + '%type' => $this->field['storage']['type'], + '@method' => __METHOD__, + ))); + } + $table = array_keys($this->field['storage']['details']['sql'][FIELD_LOAD_CURRENT]); + $table = reset($table); + $column = $this->field['storage']['details']['sql'][FIELD_LOAD_CURRENT][$table]['value']; + + $this->synonymsFindProcessCondition($condition, $column, 'entity_id'); + + $query = db_select($table); + $query->fields($table, array('entity_id')); + $query->addField($table, $column, 'synonym'); + return $query->condition($condition) + ->condition('entity_type', $this->instance['entity_type']) + ->condition('bundle', $this->instance['bundle']) + ->execute(); + } + +} diff --git a/sites/all/modules/contrib/taxonomy/synonyms/synonyms_provider_field/synonyms_provider_field.api.php b/sites/all/modules/contrib/taxonomy/synonyms/synonyms_provider_field/synonyms_provider_field.api.php new file mode 100644 index 00000000..10fa462c --- /dev/null +++ b/sites/all/modules/contrib/taxonomy/synonyms/synonyms_provider_field/synonyms_provider_field.api.php @@ -0,0 +1,72 @@ + 'MyFieldTypeAutocompleteSynonymsBehavior', + ); + break; + + case 'another-behavior': + return array( + 'my-field-type-or-yet-another-field-type' => 'MyFieldTypeAnotherBehaviorSynonymsBehavior', + ); + break; + } + + return array(); +} + +/** + * Alter info about available field-based synonyms behavior implementations. + * + * This hook is invoked right after + * hook_synonyms_field_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 $field_providers + * Array of information about existing field-based synonyms behavior + * implementations that was collected from modules + * @param string $behavior + * Name of the behavior for which the field-based synonyms behavior + * implementations are being generated + */ +function hook_synonyms_provider_field_behavior_implementation_info_alter(&$field_providers, $behavior) { + switch ($behavior) { + case 'the-behavior-i-want': + $field_providers['the-field-type-i-want'] = 'MyFieldTypeAutocompleteSynonymsBehavior'; + break; + } +} diff --git a/sites/all/modules/contrib/taxonomy/synonyms/synonyms_provider_field/synonyms_provider_field.info b/sites/all/modules/contrib/taxonomy/synonyms/synonyms_provider_field/synonyms_provider_field.info new file mode 100644 index 00000000..72767c8a --- /dev/null +++ b/sites/all/modules/contrib/taxonomy/synonyms/synonyms_provider_field/synonyms_provider_field.info @@ -0,0 +1,20 @@ +name = Field Synonyms Provider +description = "Provides synonyms from entity fields." +package = Synonyms +core = 7.x +dependencies[] = synonyms +dependencies[] = field + +files[] = includes/AbstractFieldSynonymsBehavior.class.inc +files[] = includes/EntityReferenceSynonymsBehavior.class.inc +files[] = includes/TaxonomySynonymsBehavior.class.inc +files[] = includes/TextSynonymsBehavior.class.inc + +files[] = synonyms_provider_field.test + +; Information added by Drupal.org packaging script on 2016-05-07 +version = "7.x-1.5" +core = "7.x" +project = "synonyms" +datestamp = "1462586641" + diff --git a/sites/all/modules/contrib/taxonomy/synonyms/synonyms_provider_field/synonyms_provider_field.module b/sites/all/modules/contrib/taxonomy/synonyms/synonyms_provider_field/synonyms_provider_field.module new file mode 100644 index 00000000..02bfa443 --- /dev/null +++ b/sites/all/modules/contrib/taxonomy/synonyms/synonyms_provider_field/synonyms_provider_field.module @@ -0,0 +1,97 @@ + synonyms_provider_field_provider_name($field), + 'label' => $instance['label'], + 'class' => $field_providers[$field['type']], + ); + } + } + + return $providers; +} + +/** + * Implements hook_synonyms_provider_field_behavior_implementation_info(). + */ +function synonyms_provider_field_synonyms_provider_field_behavior_implementation_info($behavior) { + switch ($behavior) { + case 'autocomplete': + case 'select': + return array( + 'number_integer' => 'TextSynonymsBehavior', + 'number_decimal' => 'TextSynonymsBehavior', + 'number_float' => 'TextSynonymsBehavior', + 'text' => 'TextSynonymsBehavior', + 'taxonomy_term_reference' => 'TaxonomySynonymsBehavior', + 'entityreference' => 'EntityReferenceSynonymsBehavior', + ); + break; + } + return array(); +} + +/** + * Construct synonyms provider name out of underlying field. + * + * This function is inverse of synonyms_provider_field_field_name(). + * + * @param array $field + * Field definition array whose provider name should be constructed + * + * @return string + * Provider name that corresponds to the $field + * + * @see synonyms_provider_field_field_name() + */ +function synonyms_provider_field_provider_name($field) { + return 'synonyms_provider_field_' . $field['field_name']; +} + +/** + * Reconstruct field name from the name of its provider. + * + * This function is inverse of synonyms_provider_field_provider_name(). + * + * @param string $provider + * Name of the provider whose underlying field should be reconstructed + * + * @return string + * Field name that corresponds to the $provider + * + * @see synonyms_provider_field_provider_name() + */ +function synonyms_provider_field_field_name($provider) { + return drupal_substr($provider, drupal_strlen('synonyms_provider_field_')); +} diff --git a/sites/all/modules/contrib/taxonomy/synonyms/synonyms_provider_field/synonyms_provider_field.test b/sites/all/modules/contrib/taxonomy/synonyms/synonyms_provider_field/synonyms_provider_field.test new file mode 100644 index 00000000..9786615f --- /dev/null +++ b/sites/all/modules/contrib/taxonomy/synonyms/synonyms_provider_field/synonyms_provider_field.test @@ -0,0 +1,873 @@ +behavior_implementation['behavior']) { + $this->behavior_implementation['behavior'] = 'autocomplete'; + } + parent::setUp($modules); + } + + /** + * 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 = '') { + $term = (object) array( + 'name' => $this->randomName(), + 'vid' => $this->vocabulary->vid, + ); + $term->{$this->fields['enabled']['field']['field_name']} = $items; + taxonomy_term_save($term); + $synonyms = $this->behavior_implementation['object']->extractSynonyms($term); + $this->assertTrue(count(array_intersect($standard, $synonyms)) == count($standard), $this->behavior_implementation['class'] . '::extractSynonyms() passed: ' . $message); + // Cleaning up. + taxonomy_term_delete($term->tid); + } + + /** + * Test mergeEntityAsSynonym method. + * + * @param array $items + * Items array to place into the field on which synonyms provider is based + * @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 = '') { + $message = $this->behavior_implementation['class'] . '::mergeEntityAsSynonym() passed: ' . $message; + + $term = (object) array( + 'name' => $this->randomName(), + 'vid' => $this->vocabulary->vid, + ); + $term->{$this->fields['enabled']['field']['field_name']} = $items; + taxonomy_term_save($term); + + $this->behavior_implementation['object']->mergeEntityAsSynonym($term, $synonym_entity, $synonym_entity_type); + $merged_items = $term->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE]; + foreach ($standard as $k => $v) { + if (count(array_intersect($standard[$k], $merged_items[$k])) != count($standard[$k])) { + $this->fail($message); + return; + } + } + $this->pass($message); + + taxonomy_term_delete($term->tid); + } + + + /** + * 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 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 element of 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 assertSynonymsFind($meta_data, QueryConditionInterface $condition, $message = '') { + $message = $this->behavior_implementation['class'] . '::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->found_synonyms = $v['found_synonyms']; + $terms[] = $term; + } + + $return = $this->behavior_implementation['object']->synonymsFind($condition); + + $rows = array(); + foreach ($return as $row) { + $rows[] = $row; + } + + $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 TextSynonymsBehavior class. + */ +class TextSynonymsBehaviorWebTestCase extends AbstractSynonymsProviderFieldWebTestCase { + + /** + * GetInfo method. + */ + public static function getInfo() { + return array( + 'name' => 'TextSynonymsBehavior', + 'description' => 'Ensure that the synonyms module extracts synonyms from text and number fields correctly.', + 'group' => 'Synonyms', + ); + } + + /** + * Test synonyms extraction for 'text' field type. + */ + public function testText() { + // Testing synonymsExtract(). + $this->assertSynonymsExtract(array(), array(), 'on empty field.'); + + $synonym = $this->randomName(); + $this->assertSynonymsExtract(array( + LANGUAGE_NONE => array( + 0 => array('value' => $synonym), + ), + ), array($synonym), 'on a field that holds one value.'); + + // Testing mergeEntityAsSynonym() method. + $node = (object) array( + 'title' => $this->randomName(), + 'type' => 'page', + ); + 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(AbstractSynonymsBehavior::COLUMN_SYNONYM_PLACEHOLDER, $this->randomName()), 'on empty field.'); + + $meta_data = array(); + $meta_data[] = array( + 'items' => array(), + 'found_synonyms' => array(), + ); + $this->assertSynonymsFind($meta_data, db_and()->condition(AbstractSynonymsBehavior::COLUMN_SYNONYM_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(AbstractSynonymsBehavior::COLUMN_SYNONYM_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(AbstractSynonymsBehavior::COLUMN_SYNONYM_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(AbstractSynonymsBehavior::COLUMN_SYNONYM_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(AbstractSynonymsBehavior::COLUMN_SYNONYM_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(AbstractSynonymsBehavior::COLUMN_SYNONYM_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(AbstractSynonymsBehavior::COLUMN_SYNONYM_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(AbstractSynonymsBehavior::COLUMN_SYNONYM_PLACEHOLDER, $synonym1)->condition(AbstractSynonymsBehavior::COLUMN_SYNONYM_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()->condition(AbstractSynonymsBehavior::COLUMN_SYNONYM_PLACEHOLDER, $synonym)->condition(AbstractSynonymsBehavior::COLUMN_SYNONYM_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(AbstractSynonymsBehavior::COLUMN_SYNONYM_PLACEHOLDER, $synonym1); + $condition->condition(db_and()->condition(AbstractSynonymsBehavior::COLUMN_SYNONYM_PLACEHOLDER, $synonym2)->condition(AbstractSynonymsBehavior::COLUMN_SYNONYM_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(" . AbstractSynonymsBehavior::COLUMN_SYNONYM_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 TaxonomySynonymsBehavior class. + */ +class TaxonomySynonymsBehaviorWebTestCase extends AbstractSynonymsProviderFieldWebTestCase { + + /** + * Taxonomy vocabulary object terms. + * + * Terms of this vocabulary are synonyms of the main vocabulary terms. + * + * @var object + */ + protected $vocabularySource; + + /** + * GetInfo method. + */ + public static function getInfo() { + return array( + 'name' => 'TaxonomySynonymsBehavior', + 'description' => 'Ensure that the synonyms module extracts synonyms from taxonomy term reference fields correctly.', + 'group' => 'Synonyms', + ); + } + + /** + * SetUp method. + */ + 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', + ); + taxonomy_vocabulary_save($this->vocabularySource); + } + + /** + * Test synonyms extraction for 'taxonomy_term_reference' field type. + */ + public function testTaxonomyTermReference() { + // Testing synonymsExtract(). + $this->assertSynonymsExtract(array(), array(), 'on empty field.'); + + $synonym_term = $this->createSynonymTerm(); + $this->assertSynonymsExtract(array( + LANGUAGE_NONE => array( + 0 => array( + 'tid' => $synonym_term->tid, + ), + ), + ), array($synonym_term->name), 'on a field that holds one value.'); + + // 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(AbstractSynonymsBehavior::COLUMN_SYNONYM_PLACEHOLDER, $this->randomName()), 'on empty field'); + + $meta_data = array(); + $meta_data[] = array( + 'items' => array(), + 'found_synonyms' => array(), + ); + $this->assertSynonymsFind($meta_data, db_and()->condition(AbstractSynonymsBehavior::COLUMN_SYNONYM_PLACEHOLDER, $this->randomName()), 'on a field without values'); + + $meta_data = array(); + $meta_data[] = array( + 'items' => array( + LANGUAGE_NONE => array( + array('tid' => $this->createSynonymTerm()->tid), + ), + ), + 'found_synonyms' => array(), + ); + $this->assertSynonymsFind($meta_data, db_and()->condition(AbstractSynonymsBehavior::COLUMN_SYNONYM_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(AbstractSynonymsBehavior::COLUMN_SYNONYM_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(AbstractSynonymsBehavior::COLUMN_SYNONYM_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), + ), + ), + 'found_synonyms' => array(), + ); + $this->assertSynonymsFind($meta_data, db_and()->condition(AbstractSynonymsBehavior::COLUMN_SYNONYM_PLACEHOLDER, $synonym_term->name), 'on 2 fields with 2 values each searching for one of those values'); + + $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(AbstractSynonymsBehavior::COLUMN_SYNONYM_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(AbstractSynonymsBehavior::COLUMN_SYNONYM_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(AbstractSynonymsBehavior::COLUMN_SYNONYM_PLACEHOLDER, $synonym_term1->name)->condition(AbstractSynonymsBehavior::COLUMN_SYNONYM_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(AbstractSynonymsBehavior::COLUMN_SYNONYM_PLACEHOLDER, $synonym_term->name)->condition(AbstractSynonymsBehavior::COLUMN_SYNONYM_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(AbstractSynonymsBehavior::COLUMN_SYNONYM_PLACEHOLDER, $synonym_term1->name); + $condition->condition(db_and() + ->condition(AbstractSynonymsBehavior::COLUMN_SYNONYM_PLACEHOLDER, $synonym_term2->name) + ->condition(AbstractSynonymsBehavior::COLUMN_SYNONYM_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(" . AbstractSynonymsBehavior::COLUMN_SYNONYM_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"); + } + + /** + * Supportive function. + * + * Create a taxonomy term in the synonyms source vocabulary with the specified + * name. + * + * @param string $name + * Name of the term to be created. If nothing is supplied a random string + * is used + * + * @return object + * Fully loaded taxonomy term object of the just created term + */ + protected function createSynonymTerm($name = NULL) { + if (is_null($name)) { + $name = $this->randomName(); + } + $synonym_term = (object) array( + 'name' => $name, + 'vid' => $this->vocabularySource->vid, + ); + taxonomy_term_save($synonym_term); + return $synonym_term; + } +} + +/** + * Test EntityReferenceSynonymsBehavior class. + */ +class EntityReferenceSynonymsBehaviorWebTestCase extends AbstractSynonymsProviderFieldWebTestCase { + + /** + * GetInfo method. + */ + public static function getInfo() { + return array( + 'name' => 'EntityReferenceSynonymsBehavior', + 'description' => 'Ensure that the synonyms module extracts synonyms from entity reference fields correctly.', + 'group' => 'Synonyms', + ); + } + + /** + * SetUp method. + */ + public function setUp($modules = array()) { + $modules[] = 'entityreference'; + + $this->fields['enabled']['field'] = array( + 'field_name' => 'reference', + 'cardinality' => FIELD_CARDINALITY_UNLIMITED, + 'type' => 'entityreference', + // For the sake of experiment we use entityreference field that references + // nodes of a Drupal standard type to make things easier. + 'settings' => array( + 'target_type' => 'node', + 'handler' => 'base', + 'handler_settings' => array( + 'target_bundles' => array('page' => 'page'), + 'sort' => array('type' => 'none'), + ), + ), + ); + + parent::setUp($modules); + } + + /** + * Test synonyms extraction for 'entityreference' field type. + */ + public function testEntityReference() { + // Testing synonymsExtract(). + $this->assertSynonymsExtract(array(), array(), 'on empty field.'); + + $synonym_entity = $this->createNode(); + $this->assertSynonymsExtract(array( + LANGUAGE_NONE => array( + 0 => array( + 'target_id' => entity_id('node', $synonym_entity), + ), + ), + ), array(entity_label('node', $synonym_entity)), 'on a field that holds one value.'); + + // 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(AbstractSynonymsBehavior::COLUMN_SYNONYM_PLACEHOLDER, $this->randomName()), 'on empty field'); + + $meta_data = array(); + $meta_data[] = array( + 'items' => array(), + 'found_synonyms' => array(), + ); + $this->assertSynonymsFind($meta_data, db_and()->condition(AbstractSynonymsBehavior::COLUMN_SYNONYM_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())), + ), + ), + 'found_synonyms' => array(), + ); + $this->assertSynonymsFind($meta_data, db_and()->condition(AbstractSynonymsBehavior::COLUMN_SYNONYM_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(AbstractSynonymsBehavior::COLUMN_SYNONYM_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(AbstractSynonymsBehavior::COLUMN_SYNONYM_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( + LANGUAGE_NONE => array( + array('target_id' => entity_id('node', $this->createNode())), + array('target_id' => entity_id('node', $this->createNode())), + ), + ), + 'found_synonyms' => array(), + ); + $this->assertSynonymsFind($meta_data, db_and()->condition(AbstractSynonymsBehavior::COLUMN_SYNONYM_PLACEHOLDER, entity_label('node', $synonym_entity)), 'on 2 fields with 2 values each searching for one of those values'); + + $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(AbstractSynonymsBehavior::COLUMN_SYNONYM_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(AbstractSynonymsBehavior::COLUMN_SYNONYM_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(AbstractSynonymsBehavior::COLUMN_SYNONYM_PLACEHOLDER, entity_label('node', $synonym_entity1)) + ->condition(AbstractSynonymsBehavior::COLUMN_SYNONYM_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(AbstractSynonymsBehavior::COLUMN_SYNONYM_PLACEHOLDER, entity_label('node', $synonym_entity)) + ->condition(AbstractSynonymsBehavior::COLUMN_SYNONYM_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(AbstractSynonymsBehavior::COLUMN_SYNONYM_PLACEHOLDER, entity_label('node', $synonym_entity1)) + ->condition(db_and()->condition(AbstractSynonymsBehavior::COLUMN_SYNONYM_PLACEHOLDER, entity_label('node', $synonym_entity2))->condition(AbstractSynonymsBehavior::COLUMN_SYNONYM_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(" . AbstractSynonymsBehavior::COLUMN_SYNONYM_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"); + } + + /** + * Supportive function. + * + * Create an entity of necessary entity type (in our test it's node). + * + * @param string $label + * Label to use for the entity that is about to be created + * @param string $bundle + * Bundle to use for the entity that is about to be created + * + * @return object + * Fully loaded entity object of the just created entity + */ + protected function createNode($label = NULL, $bundle = 'page') { + if (is_null($label)) { + $label = $this->randomName(); + } + $entity = (object) array( + 'type' => $bundle, + 'title' => $label, + ); + node_save($entity); + return $entity; + } +} diff --git a/sites/all/modules/contrib/taxonomy/synonyms/synonyms_provider_property/includes/AbstractPropertySynonymsBehavior.class.inc b/sites/all/modules/contrib/taxonomy/synonyms/synonyms_provider_property/includes/AbstractPropertySynonymsBehavior.class.inc new file mode 100644 index 00000000..40fbb647 --- /dev/null +++ b/sites/all/modules/contrib/taxonomy/synonyms/synonyms_provider_property/includes/AbstractPropertySynonymsBehavior.class.inc @@ -0,0 +1,56 @@ +property = synonyms_provider_property_name($this->behavior_implementation['provider']); + $this->entity_info = entity_get_info($this->behavior_implementation['entity_type']); + } + + public function extractSynonyms($entity) { + $synonyms = array(); + + if (isset($entity->{$this->property}) && $entity->{$this->property}) { + $synonyms[] = $entity->{$this->property}; + } + return $synonyms; + } + + public function mergeEntityAsSynonym($trunk_entity, $synonym_entity, $synonym_entity_type) { + // TODO: what to do ??? + } + + public function synonymsFind(QueryConditionInterface $condition) { + $query = db_select($this->entity_info['base table'], 'base'); + $query->addField('base', $this->entity_info['entity keys']['id'], 'entity_id'); + $query->addField('base', $this->property, 'synonym'); + + $this->synonymsFindProcessCondition($condition, 'base.' . $this->property, 'base.' . $this->entity_info['entity keys']['id']); + $query->condition($condition); + return $query->execute(); + } +} diff --git a/sites/all/modules/contrib/taxonomy/synonyms/synonyms_provider_property/includes/PropertySynonymsBehavior.class.inc b/sites/all/modules/contrib/taxonomy/synonyms/synonyms_provider_property/includes/PropertySynonymsBehavior.class.inc new file mode 100644 index 00000000..58ddb336 --- /dev/null +++ b/sites/all/modules/contrib/taxonomy/synonyms/synonyms_provider_property/includes/PropertySynonymsBehavior.class.inc @@ -0,0 +1,12 @@ + $column) { + if (in_array($column, $entity_info['schema_fields_sql']['base table'])) { + $providers[] = array( + 'provider' => synonyms_provider_property_provider_name($column), + 'label' => $entity_key, + 'class' => $class, + ); + + unset($properties[$column]); + } + } + + foreach ($properties as $property => $property_info) { + if (isset($property_info['schema field']) && $property_info['schema field']) { + $providers[] = array( + 'provider' => synonyms_provider_property_provider_name($property_info['schema field']), + 'label' => $property_info['label'], + 'class' => $class, + ); + } + } + } + break; + } + + return $providers; +} + +/** + * Construct synonyms provider name out of underlying property. + * + * This function is inverse of synonyms_provider_property_name(). + * + * @param string $property + * Entity property name whose provider name should be constructed + * + * @return string + * Provider name that corresponds to the $property + * + * @see synonyms_provider_property_name() + */ +function synonyms_provider_property_provider_name($property) { + return 'synonyms_provider_property_' . $property; +} + +/** + * Reconstruct entity property name from the name of its provider. + * + * This function is inverse of synonyms_provider_property_provider_name(). + * + * @param string $provider + * Name of the provider whose underlying entity property should be + * reconstructed + * + * @return string + * Entity property name that corresponds to the $provider + * + * @see synonyms_provider_property_provider_name() + */ +function synonyms_provider_property_name($provider) { + return drupal_substr($provider, drupal_strlen('synonyms_provider_property_')); +} diff --git a/sites/all/modules/contrib/taxonomy/synonyms/synonyms_provider_property/synonyms_provider_property.test b/sites/all/modules/contrib/taxonomy/synonyms/synonyms_provider_property/synonyms_provider_property.test new file mode 100644 index 00000000..5ae04fdb --- /dev/null +++ b/sites/all/modules/contrib/taxonomy/synonyms/synonyms_provider_property/synonyms_provider_property.test @@ -0,0 +1,112 @@ + 'PropertySynonymsBehavior', + 'description' => 'Ensure that the synonyms module extracts synonyms from entity properties correctly.', + 'group' => 'Synonyms', + ); + } + + public function setUp($modules = array()) { + $this->behavior_implementation['behavior'] = 'autocomplete'; + $this->behavior_implementation['provider'] = 'whatever'; + + array_unshift($modules, 'synonyms_provider_property'); + parent::setUp($modules); + + // We can't initialize this before parent::setUp() because we depend on + // synonyms_provider_property_provider_name() function. + synonyms_behavior_implementation_delete($this->behavior_implementation); + $this->behavior_implementation['provider'] = synonyms_provider_property_provider_name('tid'); + synonyms_behavior_implementation_save($this->behavior_implementation); + foreach (synonyms_behavior_get($this->behavior_implementation['behavior'], $this->behavior_implementation['entity_type'], $this->behavior_implementation['bundle'], TRUE) as $behavior_implementation) { + if ($behavior_implementation['provider'] == $this->behavior_implementation['provider']) { + $this->behavior_implementation = $behavior_implementation; + break; + } + } + } + + /** + * Test property-based synonyms provider. + */ + public function testProperty() { + $term = (object) array( + 'vid' => $this->vocabulary->vid, + 'name' => $this->randomName(), + ); + taxonomy_term_save($term); + + $term2 = (object) array( + 'vid' => $this->vocabulary->vid, + 'name' => $this->randomName(), + ); + taxonomy_term_save($term2); + + // Test extractSynonyms() method. + $synonyms = $this->behavior_implementation['object']->extractSynonyms($term); + $this->assertIdentical($synonyms, array($term->tid), $this->behavior_implementation['class'] . '::extractSynonyms() passed.'); + + // Test synonymsFind() method. + $condition = db_and()->condition(AbstractSynonymsBehavior::COLUMN_SYNONYM_PLACEHOLDER, 0); + $this->assertSynonymsFind($condition, array(), 'on non-existing synonym'); + + $condition = db_and()->condition(AbstractSynonymsBehavior::COLUMN_SYNONYM_PLACEHOLDER, $term->tid); + $this->assertSynonymsFind($condition, array(array('entity_id' => $term->tid, 'synonym' => $term->tid)), 'on a synonym'); + + $condition = db_and() + ->condition(AbstractSynonymsBehavior::COLUMN_SYNONYM_PLACEHOLDER, $term->tid) + ->condition(AbstractSynonymsBehavior::COLUMN_SYNONYM_PLACEHOLDER, $term2->tid); + $this->assertSynonymsFind($condition, array(), 'on a synonym AND on another synonym'); + + $condition = db_or() + ->condition(AbstractSynonymsBehavior::COLUMN_SYNONYM_PLACEHOLDER, $term->tid) + ->condition(AbstractSynonymsBehavior::COLUMN_SYNONYM_PLACEHOLDER, $term2->tid); + $this->assertSynonymsFind($condition, array(array('entity_id' => $term->tid, 'synonym' => $term->tid), array('entity_id' => $term2->tid, 'synonym' => $term2->tid)), 'on a synonym OR another synonym'); + } + + /** + * Supportive assert method. + * + * Assert results of synonymsFind() method. + * + * @param QueryConditionInterface $condition + * Query condition to be passed on into synonymsFind() method + * @param array $standard + * Expected result + * @param string $message + * Optional additional assert message + */ + protected function assertSynonymsFind(QueryConditionInterface $condition, $standard, $message = '') { + $map_standard = array(); + foreach ($standard as $v) { + $map_standard[$v['entity_id']] = $v['synonym']; + } + + $message = $this->behavior_implementation['class'] . '::synonymsFind() ' . $message; + $result = $this->behavior_implementation['object']->synonymsFind($condition); + $count = 0; + foreach ($result as $row) { + $count++; + if (!isset($map_standard[$row->entity_id]) || $map_standard[$row->entity_id] != $row->synonym) { + $this->fail($message); + return; + } + } + $this->assertEqual(count($map_standard), $count, $message); + } +} diff --git a/sites/all/modules/contrib/taxonomy/synonyms/synonyms_search/includes/SearchCommerceProductReferenceSynonymsBehavior.class.inc b/sites/all/modules/contrib/taxonomy/synonyms/synonyms_search/includes/SearchCommerceProductReferenceSynonymsBehavior.class.inc new file mode 100644 index 00000000..9747980a --- /dev/null +++ b/sites/all/modules/contrib/taxonomy/synonyms/synonyms_search/includes/SearchCommerceProductReferenceSynonymsBehavior.class.inc @@ -0,0 +1,12 @@ +original, $behavior_implementation)); + $current_search_synonyms = array_merge($current_search_synonyms, $behavior_implementation['object']->extractSynonyms($term)); + $previous_search_synonyms = array_merge($previous_search_synonyms, $behavior_implementation['object']->extractSynonyms($term->original)); } $diff = array_diff($current_search_synonyms, $previous_search_synonyms); if (!empty($diff) || count($current_search_synonyms) != count($previous_search_synonyms)) { @@ -51,11 +51,13 @@ function synonyms_search_taxonomy_term_update($term) { 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. + // We go a field by field looking for taxonomy term reference or entity + // reference of taxonomy term type and if that vocabulary has enabled search + // synonyms, we add them to the search index. + // TODO: implement this through foreign keys information. See + // term_merge_fields_with_foreign_key() function. $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) { @@ -63,6 +65,14 @@ function synonyms_search_node_update_index($node) { } } } + if ($field_info['type'] == 'entityreference' && $field_info['settings']['target_type'] == 'taxonomy_term') { + $terms = field_get_items('node', $node, $instance['field_name']); + if (is_array($terms) && !empty($terms)) { + foreach ($terms as $v) { + $output[] = $v['target_id']; + } + } + } } if (!empty($output)) { @@ -72,7 +82,7 @@ function synonyms_search_node_update_index($node) { $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)); + $output = array_merge($output, $implementation['object']->extractSynonyms($term)); } } } @@ -90,15 +100,15 @@ function synonyms_search_term_update_index($term) { $synonyms = array(); foreach ($behavior_implementations as $implementation) { - $synonyms = array_merge($synonyms, synonyms_extract_synonyms($term, $implementation)); + $synonyms = array_merge($synonyms, $implementation['object']->extractSynonyms($term)); } return implode(', ', $synonyms); } /** - * Implements hook_synonyms_behavior_implementation_info(). + * Implements hook_synonyms_provider_field_behavior_implementation_info(). */ -function synonyms_search_synonyms_behavior_implementation_info($behavior) { +function synonyms_search_synonyms_provider_field_behavior_implementation_info($behavior) { switch ($behavior) { case 'search': return array( @@ -108,6 +118,7 @@ function synonyms_search_synonyms_behavior_implementation_info($behavior) { 'text' => 'SearchTextSynonymsBehavior', 'taxonomy_term_reference' => 'SearchTaxonomySynonymsBehavior', 'entityreference' => 'SearchEntityReferenceSynonymsBehavior', + 'commerce_product_reference' => 'SearchCommerceProductReferenceSynonymsBehavior', ); break; } 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 index b6876f31..12ff36d3 100644 --- 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 @@ -20,9 +20,9 @@ function synonyms_search_reindex_nodes_by_terms($tids) { // that would imply a big amount of SQL queries on some websites. $found_nids = array(); foreach (field_info_field_map() as $field_name => $v) { + // TODO: explore possibility of using foreign keys instead of hard coding + // fields types in here. 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') @@ -31,6 +31,18 @@ function synonyms_search_reindex_nodes_by_terms($tids) { $found_nids = array_merge($found_nids, array_keys($result['node'])); } } + if ($v['type'] == 'entityreference' && isset($v['bundles']['node'])) { + $field = field_info_field($field_name); + if ($field['settings']['target_type'] == 'taxonomy_term') { + $query = new EntityFieldQuery(); + $result = $query->entityCondition('entity_type', 'node') + ->fieldCondition($field_name, 'target_id', $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') 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 index 5477d9a7..d7fb2f7e 100644 --- a/sites/all/modules/contrib/taxonomy/synonyms/synonyms_search/synonyms_search.test +++ b/sites/all/modules/contrib/taxonomy/synonyms/synonyms_search/synonyms_search.test @@ -10,8 +10,6 @@ */ abstract class AbstractSearchSynonymsWebTestCase extends SynonymsWebTestCase { - protected $behavior = 'search'; - /** * What search type is being tested. * @@ -30,7 +28,9 @@ abstract class AbstractSearchSynonymsWebTestCase extends SynonymsWebTestCase { * SetUp method. */ public function setUp($modules = array()) { - $modules[] = 'synonyms_search'; + $this->behavior_implementation['behavior'] = 'search'; + + array_unshift($modules, 'synonyms_search'); parent::setUp($modules); // Create a few terms and synonyms. @@ -121,6 +121,7 @@ class NodeSearchSynonymsWebTestCase extends AbstractSearchSynonymsWebTestCase { * SetUp method. */ public function setUp($modules = array()) { + $modules[] = 'entityreference'; parent::setUp($modules); // Creating a test content type. @@ -128,7 +129,18 @@ class NodeSearchSynonymsWebTestCase extends AbstractSearchSynonymsWebTestCase { 'name' => 'Synonyms Test Content', 'type' => 'synonyms_test_content', ), 'Save content type'); + } + /** + * 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() { // Attaching term reference field to the new content type. $field = array( 'type' => 'taxonomy_term_reference', @@ -150,24 +162,10 @@ class NodeSearchSynonymsWebTestCase extends AbstractSearchSynonymsWebTestCase { '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', @@ -217,7 +215,99 @@ class NodeSearchSynonymsWebTestCase extends AbstractSearchSynonymsWebTestCase { // 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); + synonyms_behavior_implementation_delete($this->behavior_implementation); + $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.'); + } + } + } + } + + /** + * Test searching nodes by a term synonym when referenced by entity reference. + * + * This test pretty much does the same thing as the testSearchTermSynonym() + * with the only different that the terms are referenced through entity + * reference field type. + */ + public function testSearchTermSynonymEntityReference() { + // Attaching entity reference field to the new content type. + $field = array( + 'type' => 'entityreference', + 'field_name' => 'synonyms_term_enabled', + 'cardinality' => FIELD_CARDINALITY_UNLIMITED, + 'settings' => array( + 'target_type' => 'taxonomy_term', + 'handler_settings' => array( + 'target_bundles' => array($this->vocabulary->machine_name), + ), + ), + ); + $field = field_create_field($field); + + $instance = array( + 'field_name' => $field['field_name'], + 'entity_type' => 'node', + 'bundle' => 'synonyms_test_content', + 'label' => 'Synonym Terms', + ); + + field_create_instance($instance); + + // 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('target_id' => $this->terms['no_synonyms']->tid), + array('target_id' => $this->terms['one_synonym']->tid), + array('target_id' => $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_implementation_delete($this->behavior_implementation); $this->cronRun(); foreach ($this->terms as $k => $term) { $items = field_get_items('taxonomy_term', $term, $this->fields['enabled']['field']['field_name']); @@ -326,7 +416,7 @@ class TermSearchSynonymsWebTestCase extends AbstractSearchSynonymsWebTestCase { // 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); + synonyms_behavior_implementation_delete($this->behavior_implementation); $this->cronRun(); foreach ($this->terms as $k => $term) { $items = field_get_items('taxonomy_term', $term, $this->fields['enabled']['field']['field_name']); 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 eaf80164..c86922de 100644 --- a/sites/all/modules/contrib/taxonomy/synonyms/views/synonyms.views.inc +++ b/sites/all/modules/contrib/taxonomy/synonyms/views/synonyms.views.inc @@ -5,29 +5,73 @@ * Views integration of Synonyms module. */ +/** + * Implements hook_views_data(). + */ +function synonyms_views_data() { + $data = array(); + + foreach (entity_get_info() as $entity_type => $entity_info) { + $wrapper = entity_metadata_wrapper($entity_type); + $property_info = $wrapper->getPropertyInfo(); + if (isset($property_info['synonyms']) && isset($entity_info['base table']) && $entity_info['base table']) { + $data[$entity_info['base table']]['synonyms'] = array( + 'title' => t('All synonyms'), + 'help' => t('All synonyms of @entity_type', array( + '@entity_type' => $entity_info['label'], + )), + 'field' => array( + 'handler' => 'synonyms_views_handler_field_synonyms', + 'real field' => $entity_info['entity keys']['id'], + 'click sortable' => FALSE, + 'synonyms entity type' => $entity_type, + ), + ); + } + } + + return $data; +} + /** * Implements 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'; + if (module_exists('taxonomy')) { + // 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'; + switch ($field['type']) { + case 'taxonomy_term_reference': + // Add synonyms friendly filters. + 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'; + } } - } + break; + + case 'entityreference': + foreach ($field['storage']['details']['sql'] as $table) { + $target_id_column = reset($table); + $target_id_column = $target_id_column['target_id']; + $table = array_keys($table); + $table = $table[0]; + if (isset($result[$table][$target_id_column]['filter'])) { + $result[$table][$target_id_column]['filter']['handler'] = 'synonyms_views_handler_filter_entityreference_synonyms'; + } + } + break; } } diff --git a/sites/all/modules/contrib/taxonomy/synonyms/views/synonyms_views_handler_field_synonyms.inc b/sites/all/modules/contrib/taxonomy/synonyms/views/synonyms_views_handler_field_synonyms.inc new file mode 100644 index 00000000..793b054a --- /dev/null +++ b/sites/all/modules/contrib/taxonomy/synonyms/views/synonyms_views_handler_field_synonyms.inc @@ -0,0 +1,102 @@ + 'ul', + ); + + $options['separator'] = array( + 'default' => '', + 'translatable' => TRUE, + ); + + return $options; + } + + function options_form(&$form, &$form_state) { + parent::options_form($form, $form_state); + + $form['list'] = array( + '#type' => 'radios', + '#title' => t('Display type'), + '#options' => array( + 'ul' => t('Unordered list'), + 'ol' => t('Ordered list'), + 'separator' => t('Simple separator'), + ), + '#default_value' => $this->options['list'], + ); + + $form['separator'] = array( + '#type' => 'textfield', + '#title' => t('Separator'), + '#default_value' => $this->options['separator'], + '#dependency' => array( + 'radio:options[list]' => array('separator'), + ), + ); + } + + function pre_render(&$values) { + // Load all the queried entities in a single batch. + $entity_ids = array(); + foreach ($values as $value) { + $entity_ids[] = $value->{$this->definition['real field']}; + } + $entity_ids = array_unique($entity_ids); + if (!empty($entity_ids)) { + $entities = entity_load($this->definition['synonyms entity type'], $entity_ids); + foreach ($values as &$value) { + $value->synonyms_entity = $entities[$value->{$this->definition['real field']}]; + unset($value); + } + } + parent::pre_render($values); + } + + function get_value($values, $field = NULL) { + $property = $this->field; + $wrapper = entity_metadata_wrapper($this->definition['synonyms entity type'], $values->synonyms_entity); + $synonyms = $wrapper->$property->value(array('sanitize' => TRUE)); + + if (empty($synonyms)) { + $synonyms = ''; + } + else { + switch ($this->options['list']) { + case 'ol': + case 'ul': + $synonyms = theme('item_list', array( + 'type' => $this->options['list'], + 'items' => $synonyms, + )); + break; + + case 'separator': + $synonyms = implode($this->options['separator'], $synonyms); + break; + } + } + + return $synonyms; + } + + function sanitize_value($value, $type = NULL) { + if (is_null($type)) { + $type = 'xss_admin'; + } + return parent::sanitize_value($value, $type); + } +} diff --git a/sites/all/modules/contrib/taxonomy/synonyms/views/synonyms_views_handler_filter_entityreference_synonyms.inc b/sites/all/modules/contrib/taxonomy/synonyms/views/synonyms_views_handler_filter_entityreference_synonyms.inc new file mode 100644 index 00000000..2b27019a --- /dev/null +++ b/sites/all/modules/contrib/taxonomy/synonyms/views/synonyms_views_handler_filter_entityreference_synonyms.inc @@ -0,0 +1,144 @@ + 'numeric', + ); + + return $options; + } + + function extra_options_form(&$form, &$form_state) { + parent::extra_options_form($form, $form_state); + + $form['type'] = array( + '#type' => 'radios', + '#title' => t('Type'), + '#options' => array( + 'numeric' => t('Numeric'), + 'synonyms_autocomplete' => t('Synonyms friendly autocomplete'), + 'synonyms_select' => t('Synonyms friendly select list'), + ), + '#default_value' => $this->options['type'], + ); + } + + function has_extra_options() { + return TRUE; + } + + function operators() { + $operators = parent::operators(); + switch ($this->options['type']) { + case 'synonyms_autocomplete': + case 'synonyms_select': + // Only "equals" and "not equals" make sense, other operators are rather + // funky when it comes to IDs and not simple integers. + $operators = array_intersect_key($operators, drupal_map_assoc(array( + '=', '!=', + ))); + break; + } + return $operators; + } + + function value_form(&$form, &$form_state) { + parent::value_form($form, $form_state); + + if (isset($form['value']['#type'])) { + $element = &$form['value']; + } + elseif (isset($form['value']['value']['#type'])) { + $element = &$form['value']['value']; + } + + if (isset($element)) { + $field = field_info_field($this->definition['field_name']); + $entity_type = array_keys($field['bundles']); + $entity_type = reset($entity_type); + $bundle = reset($field['bundles'][$entity_type]); + $instance = field_info_instance($entity_type, $field['field_name'], $bundle); + + switch ($this->options['type']) { + case 'synonyms_autocomplete': + $widget = field_info_widget_settings('synonyms_autocomplete_entity'); + $element['#autocomplete_path'] = $widget['synonyms_autocomplete_path'] . '/' . $field['field_name'] . '/' . $instance['entity_type'] . '/' . $instance['bundle']; + $element['#attached']['js'][drupal_get_path('module', 'synonyms') . '/js/synonyms-autocomplete.js'] = array(); + $element['#attributes']['class'][] = 'synonyms-autocomplete'; + break; + + case 'synonyms_select': + $element['#type'] = 'select'; + $element['#options'] = synonyms_select_entity_options($field, $instance); + if (!$this->is_exposed()) { + $element['#empty_option'] = t('- None -'); + } + $element['#element_validate'][] = 'synonyms_select_validate'; + $element['#element_validate'][] = 'synonyms_select_views_entityreference_filter_validate'; + unset($element['#size']); + break; + } + } + } + + function query() { + switch ($this->options['type']) { + case 'synonyms_autocomplete': + $field = field_info_field($this->definition['field_name']); + $this->value['value'] = synonyms_get_entity_by_synonym($field['settings']['target_type'], $this->value['value'], synonyms_field_target_bundles($field)); + break; + } + parent::query(); + } + + function admin_summary() { + if ($this->is_a_group()) { + return t('grouped'); + } + if (!empty($this->options['exposed'])) { + return t('exposed'); + } + + switch ($this->options['type']) { + case 'numeric': + case 'synonyms_autocomplete': + return parent::admin_summary(); + break; + + case 'synonyms_select': + $field = field_info_field($this->definition['field_name']); + $entity = entity_load($field['settings']['target_type'], array($this->value['value'])); + $entity = reset($entity); + if (is_object($entity)) { + $label = entity_label($field['settings']['target_type'], $entity); + $options = $this->operator_options('short'); + return check_plain($options[$this->operator]) . ' ' . check_plain($label); + } + break; + } + } +} + +/** + * Form element validate handler. + * + * Simply convert select value from an array (as mostly operated within Field + * module) to a single value. + */ +function synonyms_select_views_entityreference_filter_validate($element, &$form_state) { + $value = drupal_array_get_nested_value($form_state['values'], $element['#parents']); + $value = reset($value); + form_set_value($element, $value, $form_state); +} 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 index 4fafd5dd..bc3c18f3 100644 --- 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 @@ -13,97 +13,162 @@ class synonyms_views_handler_filter_term_tid extends views_handler_filter_term_n parent::extra_options_form($form, $form_state); $form['type']['#options']['synonyms_autocomplete'] = t('Synonyms friendly autocomplete'); + $form['type']['#options']['synonyms_select'] = t('Synonyms friendly select'); } function value_form(&$form, &$form_state) { - $restore_value = $this->options['type'] == 'synonyms_autocomplete'; - if ($restore_value) { - $this->options['type'] = 'textfield'; + $vocabulary = taxonomy_vocabulary_machine_name_load($this->options['vocabulary']); + if (empty($vocabulary) && $this->options['limit']) { + $form['markup'] = array( + '#markup' => '
        ' . t('An invalid vocabulary is selected. Please change it in the options.') . '
        ', + ); + return; } - 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; + switch ($this->options['type']) { + case 'synonyms_autocomplete': + $tags = array(); + if ($this->value) { + $result = taxonomy_term_load_multiple($this->value); + foreach ($result as $entity_term) { + $tags[] = entity_label('taxonomy_term', $entity_term); + } } - } + $tags = drupal_implode_tags($tags); - 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]; - } + $info = $this->synonyms_field_instance(); - // We just grab the first instance of this field within the determined - // entity type. - $bundle = $field['bundles'][$field_entity_type][0]; + if ($info['instance']['widget']['type'] == 'synonyms_autocomplete_taxonomy_term') { + $widget = $info['instance']['widget']['settings']; + } + else { + $widget = field_info_widget_settings('synonyms_autocomplete_taxonomy_term'); + } + $autocomplete_path = $widget['synonyms_autocomplete_path']; + $size = $widget['size']; - $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'] = array( + '#title' => $this->options['limit'] ? t('Select terms from vocabulary @voc', array('@voc' => $vocabulary->name)) : t('Select terms'), + '#type' => 'textfield', + '#default_value' => $tags, + '#autocomplete_path' => $autocomplete_path . '/' . $this->definition['field_name'] . '/' . $info['instance']['entity_type'] . '/' . $info['instance']['bundle'], + '#size' => $size, + '#auto_creation' => FALSE, + '#attributes' => array('class' => array('synonyms-autocomplete')), + '#attached' => array('js' => array( + drupal_get_path('module', 'synonyms') . '/js/synonyms-autocomplete.js' => array(), + )), + ); + break; - $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'; + case 'synonyms_select': + $info = $this->synonyms_field_instance(); + + $options = array(); + $widget = $info['instance']['widget']['type'] == 'synonyms_select_taxonomy_term' ? $info['instance']['widget']['settings'] : field_info_widget_settings('synonyms_select_taxonomy_term'); + foreach ($info['field']['settings']['allowed_values'] as $tree) { + if ($vocabulary = taxonomy_vocabulary_machine_name_load($tree['vocabulary'])) { + switch ($widget['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_entity($term, 'taxonomy_term', NULL, NULL, array('depth')); + foreach ($behavior_implementations as $implementation) { + foreach ($implementation['object']->extractSynonyms($term) as $synonym) { + $options[] = synonyms_select_option_entity($term, 'taxonomy_term', $synonym, $implementation, array('depth')); + } + } + } + } + break; + + case 'name': + // TODO: is there any way to leverage DB for the sorting routine? + $options = synonyms_select_taxonomy_term_sort_name_options_recursive($vocabulary, $tree['parent']); + break; + } + } + } + + $form['value'] = array( + '#type' => 'select', + '#title' => $this->options['limit'] ? t('Select terms from vocabulary @voc', array('@voc' => $vocabulary->name)) : t('Select terms'), + '#multiple' => TRUE, + '#options' => $options, + '#size' => min(9, count($options)), + '#default_value' => (array) $this->value, + '#element_validate' => array('synonyms_select_validate'), + ); + break; + + default: + parent::value_form($form, $form_state); + break; } } 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); + switch ($this->options['type']) { + case '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); + if ($tids) { + $form_state['values']['options']['value'] = $tids; + } + break; + + case 'synonyms_select': + break; + + default: + parent::value_validate($form, $form_state); + break; } } 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; - } + switch ($this->options['type']) { + case '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); + $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); + if ($tids) { + $this->validated_exposed_input = $tids; + } + break; + + case 'synonyms_select': + if (empty($this->options['exposed'])) { + return; + } + if (empty($this->options['expose']['identifier'])) { + return; + } + + $all_key = array_search('All', $form_state['values'][$this->options['expose']['identifier']]); + if ($all_key !== FALSE) { + unset($form_state['values'][$this->options['expose']['identifier']][$all_key]); + } + + if (!empty($form_state['values'][$this->options['expose']['identifier']])) { + return; + } + $this->validated_exposed_input = $form_state['values'][$this->options['expose']['identifier']]; + break; + + default: + parent::exposed_validate($form, $form_state); + break; } } @@ -154,9 +219,9 @@ class synonyms_views_handler_filter_term_tid extends views_handler_filter_term_n if (!empty($missing)) { $condition = db_or(); foreach ($missing as $tag => $v) { - $condition->condition(AbstractSynonymsSynonymsBehavior::COLUMN_PLACEHOLDER, $tag); + $condition->condition(AbstractSynonymsBehavior::COLUMN_SYNONYM_PLACEHOLDER, $tag); } - $synonyms = synonyms_synonyms_find_behavior($condition, $behavior_implementation); + $synonyms = $behavior_implementation['object']->synonymsFind($condition); foreach ($synonyms as $synonym) { $synonym->synonym = drupal_strtolower($synonym->synonym); unset($missing[$synonym->synonym]); @@ -171,4 +236,49 @@ class synonyms_views_handler_filter_term_tid extends views_handler_filter_term_n return $tids; } + + /** + * Collect info about field and instance that correspond to this filter. + * + * @return array + * Array with the following structure: + * - field: (array) Field definition array that corresponds to this filter + * - instance: (array) Field instance definition array that corresponds to + * this filter + */ + protected function synonyms_field_instance() { + $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); + + return array( + 'field' => $field, + 'instance' => $instance, + ); + } } 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 18fcb795..989abeed 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 @@ -57,18 +57,18 @@ class synonyms_views_plugin_argument_validate_taxonomy_term extends views_plugin if (empty($vocabularies)) { // 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()); + $bundles = synonyms_bundle_normalize('taxonomy_term', $vocabularies); } foreach ($bundles as $bundle) { $condition = db_and(); if ($transform) { - $condition->where("REPLACE(" . AbstractSynonymsSynonymsBehavior::COLUMN_PLACEHOLDER . ", ' ', '-') = :argument", array( + $condition->where("REPLACE(" . AbstractSynonymsBehavior::COLUMN_SYNONYM_PLACEHOLDER . ", ' ', '-') = :argument", array( ':argument' => $argument, )); } else { - $condition->condition(AbstractSynonymsSynonymsBehavior::COLUMN_PLACEHOLDER, $argument); + $condition->condition(AbstractSynonymsBehavior::COLUMN_SYNONYM_PLACEHOLDER, $argument); } $synonyms = synonyms_synonyms_find($condition, 'taxonomy_term', $bundle); if (!empty($synonyms)) {