updated synonyms to 1.3
This commit is contained in:
parent
86c993f638
commit
8d24211bf5
@ -3,11 +3,15 @@
|
||||
|
||||
The Synonyms module extends the Drupal core Taxonomy features. Currently
|
||||
the module provides this additional functionality:
|
||||
* support of synonyms through Field API. Any field, for which synonyms extractor
|
||||
is created, attached to a term can be enabled as source of synonyms.
|
||||
* synonym-friendly autocomplete widget for taxonomy_term_reference fields
|
||||
* integration with Drupal search functionality enabling searching content by
|
||||
synonyms of the terms that the content references
|
||||
* support of synonyms through Field API. Any field, for which synonyms behavior
|
||||
exists, can be enabled as source of synonyms.
|
||||
* synonyms-friendly autocomplete and select widgets for taxonomy_term_reference
|
||||
fields
|
||||
* integration with Drupal search functionality through Synonyms Search
|
||||
submodule. It enables searching content by synonyms of the terms that the
|
||||
content references. Synonyms Search submodule also integrates with Term
|
||||
Search contributed module in a fashion that allows your terms to be found by
|
||||
their synonyms.
|
||||
* integration with Search API. If you include synonyms of a term into your
|
||||
Search API search index, your clients will be able to find content with
|
||||
search keywords that contain synonyms and not actual names of terms.
|
||||
@ -16,11 +20,11 @@ the module provides this additional functionality:
|
||||
|
||||
The Synonyms module requires the following modules:
|
||||
* Taxonomy module
|
||||
* Text module
|
||||
* CTools module
|
||||
|
||||
-- SYNONYMS EXTRACTORS, SUPPORTED FIELD TYPES --
|
||||
-- SYNONYMS BEHAVIOR, SUPPORTED FIELD TYPES --
|
||||
|
||||
Module ships with ability to extract synonyms from the following field types:
|
||||
Module ships with ability to use the following field types as synonyms:
|
||||
* Text
|
||||
* Taxonomy Term Reference
|
||||
* Entity Reference
|
||||
@ -28,12 +32,38 @@ Module ships with ability to extract synonyms from the following field types:
|
||||
* Float
|
||||
* Decimal
|
||||
|
||||
If you want to implement your own synonyms extractor that would enable support
|
||||
for any other field type, please, refer to synonyms.api.php file for
|
||||
instructions on how to do it, or file an issue against Synonyms module. We will
|
||||
try to implement support for your field type too. If you have written your
|
||||
synonyms extractor, please share by opening an issue, and it will be included
|
||||
into this module.
|
||||
If you want to implement your own synonyms behavior that would enable support
|
||||
for any other field type, refer to synonyms.api.php file for instructions on how
|
||||
to do it, or file an issue against Synonyms module. We will try to implement
|
||||
support for your field type too. If you have written your synonyms behavior
|
||||
implementation, please share by opening an issue, and it will be included into
|
||||
this module.
|
||||
|
||||
-- GRANULATION WITHIN SYNONYMS BEHAVIOR --
|
||||
|
||||
In order to achieve greater flexibility, this module introduced additional
|
||||
granularity into what "synonyms" mean. Then you can enable different synonyms
|
||||
behaviors for different fields. For example, field "Typos" can be part of
|
||||
autocomplete behavior, while field "Other spellings" can be part of search
|
||||
integration behavior. Currently the following synonym behaviors are recognized
|
||||
(other modules actually can extend this list):
|
||||
* General synonym - normally we suggest to enable this behavior for all fields
|
||||
that have enabled at least one another behavior. In technical words, this
|
||||
behavior is responsible for including content of the field into term synonyms
|
||||
and also enables ability to add entities as synonym into this field.
|
||||
* Autocomplete - whether content of this field should participate in
|
||||
autocomplete suggestions. This module ships an autocomplete synonyms friendly
|
||||
widget and its autocomplete suggestions will be filled in with the content of
|
||||
the fields that have enabled this behavior.
|
||||
* Select - whether content of this field should be included in the synonyms
|
||||
friendly select widget.
|
||||
* Search integration (requires Synonyms Search enabled) - allows your content to
|
||||
be found by synonyms of the terms it references. Your nodes will be found by
|
||||
all synonyms that have this behavior enabled.
|
||||
|
||||
Therefore, on the vocabulary edit page you will see a table, where rows are
|
||||
fields that can become synonyms and columns are these "synonym behaviors" and
|
||||
you decide what synonym behaviors to activate on what fields.
|
||||
|
||||
-- INSTALLATION --
|
||||
|
||||
@ -42,15 +72,14 @@ into this module.
|
||||
-- CONFIGURATION --
|
||||
|
||||
* The module itself does not provide any configuration as of the moment.
|
||||
Although during creation/editing of a Taxonomy vocabulary you will be able
|
||||
to enable/disable for that particular vocabulary the additional functionality
|
||||
this module provides, you will find additional fieldset at the bottom of
|
||||
vocabulary edit page.
|
||||
Although during editing of a Taxonomy vocabulary you will be specify for that
|
||||
particular vocabulary the additional functionality this module provides, you
|
||||
will find additional fieldset at the bottom of the vocabulary edit page.
|
||||
|
||||
-- FUTURE DEVELOPMENT --
|
||||
|
||||
* If you are interested into converting this module from synonyms for Taxonomy
|
||||
terms into synonyms for any entity types, please go to this issue
|
||||
terms into synonyms for any entity type, please go to this issue
|
||||
http://drupal.org/node/1194802 and leave a comment. Once we see some demand for
|
||||
this great feature and the Synonyms module gets a little more mature, we will
|
||||
try to make it happen.
|
||||
|
@ -0,0 +1,14 @@
|
||||
[advanced help settings]
|
||||
line break = TRUE
|
||||
|
||||
[synonyms]
|
||||
title = Synonyms
|
||||
|
||||
[synonyms_behaviors]
|
||||
title = Creating a new synonyms behavior
|
||||
parent = synonyms
|
||||
weight = -1
|
||||
|
||||
[synonyms_behavior_implementation]
|
||||
title = Creating a new behavior implementation
|
||||
parent = synonyms
|
@ -0,0 +1,79 @@
|
||||
The Synonyms module provides various functionality targeted at handling synonymous (similar) spelling of the same name.
|
||||
|
||||
Currently the module only provides synonyms for Taxonomy Terms, though there are plans to extend it and to support synonyms for all entity types. There are few key concepts tightly integrated into Synonyms module that are worth knowing for the end user. They are:
|
||||
<dt>Field independency</dt>
|
||||
<dd>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.</dd>
|
||||
<dt>Synonyms behaviors</dt>
|
||||
<dd>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.</dd>
|
||||
<dt>Synonyms behavior implementations</dt>
|
||||
<dd>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.</dd>
|
||||
|
||||
Let's cover each of these important concepts in details.
|
||||
|
||||
<h2>Field Independency</h2>
|
||||
|
||||
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:
|
||||
<ul>
|
||||
<li>Text</li>
|
||||
<li>Term reference</li>
|
||||
<li>Entity reference</li>
|
||||
<li>Number</li>
|
||||
<li>Decimal</li>
|
||||
<li>Float</li>
|
||||
</ul>
|
||||
|
||||
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.
|
||||
|
||||
<h2>Synonyms behaviors</h2>
|
||||
|
||||
Synonyms behaviors are some useful for the end user features that leverage the synonyms data. Synonyms module ships with the following behaviors:
|
||||
<ul>
|
||||
<li>Synonyms: just the basic behavior which allows to extract synonyms from a field and to search for a synonym within a field. Normally we advice to enable this behavior for all fields that one way or another are seen as source of synonyms. This behavior has many various goodies, such as support for synonym-friendly Views contextual filter and panels argument, adding another entity as synonym, etc.</li>
|
||||
<li>Autocomplete: allows synonyms to participate in the synonyms friendly autocomplete functionality.</li>
|
||||
<li>Select: allows synonyms to participate in the synonyms friendly select.</li>
|
||||
<li>Search: behavior that integrates synonyms with Search module, i.e. your nodes can be found not only by names of the terms they reference, but also by the synonyms of those terms. It also integrates with <a href="https://www.drupal.org/project/term_search">Term Search</a> module in the same manner: when searching for terms you can also find them by their synonyms.</li>
|
||||
</ul>
|
||||
|
||||
Similarly, as with field independency, other modules can introduce their own behaviors. If you are interested in introducing your own behavior, refer to <a href="&topic:synonyms/synonyms_behaviors&">synonyms behaviors</a> page.
|
||||
|
||||
<h2>Behavior Implementations</h2>
|
||||
|
||||
Behavior implementations connect behaviors to field types. Synonyms module ships the following set of behavior implementations:
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Field type \ Behavior</th>
|
||||
<th>General synonyms</th>
|
||||
<th>Autocomplete</th>
|
||||
<th>Select</th>
|
||||
<th>Search</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><b>Text</b></td>
|
||||
<td>implemented</td>
|
||||
<td>implemented</td>
|
||||
<td>implemented</td>
|
||||
<td>implemented</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Term reference</b></td>
|
||||
<td>implemented</td>
|
||||
<td>implemented</td>
|
||||
<td>implemented</td>
|
||||
<td>implemented</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Entity reference</b></td>
|
||||
<td>implemented</td>
|
||||
<td>implemented</td>
|
||||
<td>implemented</td>
|
||||
<td>implemented</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
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 <a href="&topic:synonyms/synonyms_behavior_implementation&">writing custom behavior implementation</a>.
|
@ -0,0 +1,27 @@
|
||||
At this point you must possess good technical knowledge about what synonyms behaviors are. In this article we will show how you can implement an arbitrary behavior for an arbitrary field type.
|
||||
|
||||
By implementing I mean to provide integration between a field type (between how that field type stores and encodes synonyms data in the database) and a synonym behavior (how that behavior requires to work with synonyms data). It must sound a bit too baked, but the ongoing paragraphs should shed more light onto it.
|
||||
|
||||
Throughout writing your own synonyms behavior implementation you can always look into Synonyms module source code to get better understanding. You will find the behavior implementatios in <em>synonyms/includes/*SynonymsBehavior.class.inc</em> files.
|
||||
|
||||
Creating a new implementation pretty much consists of 2 steps:
|
||||
<ol>
|
||||
<li>Implementing behavior interface for a particular field type.</li>
|
||||
<li>Notifying Synonyms module about your new PHP class and what field types and what behavior that PHP class is responsible for.</li>
|
||||
</ol>
|
||||
|
||||
Now let us see each of the steps in further details.
|
||||
|
||||
<h2>Implementing behavior interface</h2>
|
||||
|
||||
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.
|
||||
|
||||
<h2>Notifying Synonyms module about your new implementation</h2>
|
||||
|
||||
For the purposes of such notification we have 2 hooks in Synonyms module:
|
||||
<ul>
|
||||
<li>hook_synonyms_behavior_implementation_info() to collect info from modules about existing behavior implementations</li>
|
||||
<li>hook_synonyms_behavior_implementation_info_alter() to alter info about existing behavior implementations, for example, if you want to overwrite behavior implementation introduced in another module.</li>
|
||||
</ul>
|
||||
|
||||
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.
|
@ -0,0 +1,40 @@
|
||||
If you made it up to here, then we expect you to already have decent understanding about what a synonyms behavior is. This page will try to explain how you could create a new one.
|
||||
|
||||
In technical terms synonyms heaviors are just cTools plugins. Synonyms module defines its own plugin type: <em>behavior</em>, which is owned by <em>synonyms</em> module.
|
||||
|
||||
Throught writing your own synonyms behavior you can always look into Synonyms module behaviors to get better understanding. You will find the plugins in <em>synonyms/plugins/behavior/*.inc</em> files. If you have never worked before with cTools plugins, if is also worthwhile to check out its <a href="&topic:ctools/plugins-implementing&">documentation</a>.
|
||||
|
||||
Your plugin implementation may (or must) have the following keys:
|
||||
<dt>title</dt>
|
||||
<dd><b>Required</b> (string) Human-friendly translated title of your behavior.</dd>
|
||||
<dt>description</dt>
|
||||
<dd>(string) Human-friendly translated description of your behavior. Include a couple of words about what exactly it does and what end user functionality has.</dd>
|
||||
<dt>settings form callback</dt>
|
||||
<dd>(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: <ol>
|
||||
<li><b>$form</b>: (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.</li>
|
||||
<li><b>&$form_state</b>: (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.</li>
|
||||
<li><b>$settings</b>: (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.</li>
|
||||
</ol>
|
||||
Based on these input arguments, your settings form callback function should generate form elements array and return it.</dd>
|
||||
<dt>interface</dt>
|
||||
<dd><b>Required</b>: (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: <em>NameOfYourBehaviorHereSynonymsBehavior</em>. Also, your interface must extend the starting point of all synonyms behaviors - the <em>SynonymsBehavior</em> 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 <em>SynonymsSynonymsBehavior</em> in the case of "synonyms" behavior).</dd>
|
||||
<dt>enabled callback</dt>
|
||||
<dd>(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:<ol>
|
||||
<li><b>$behavior_definition</b>: (array) Array of behavior definition. Basically it is your cTools plugin definition.</li>
|
||||
<li><b>$settings</b>: (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.</li>
|
||||
<li><b>$instance</b>: (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.</li>
|
||||
</ol></dd>
|
||||
<dt>disabled callback</dt>
|
||||
<dd>(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: <ol>
|
||||
<li><b>$behavior_definition</b>: (array) Array of behavior definition. Basically it is your cTools plugin definition.</li>
|
||||
<li><b>$behavior_implementation</b>: (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.</li>
|
||||
<li><b>$instance</b>: (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.</li>
|
||||
</ol></dd>
|
||||
|
||||
Having said the above, let us wrap up on how you should create a new behavior:
|
||||
<ol>
|
||||
<li>Think through what you want your behavior to do and what interface will be required to support your behavior.</li>
|
||||
<li>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).</li>
|
||||
<li>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.</li>
|
||||
<li>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 <a href="&topic:synonyms/synonyms_behavior_implementation&">here</a>.</li>
|
||||
</ol>
|
@ -1,116 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Define interface required for extracting synonyms from field types.
|
||||
*/
|
||||
|
||||
abstract class AbstractSynonymsExtractor {
|
||||
|
||||
/**
|
||||
* Return array of supported field types for synonyms extraction.
|
||||
*
|
||||
* @return array
|
||||
* Array of Field API field types from which this class is able to extract
|
||||
* synonyms
|
||||
*/
|
||||
public static function fieldTypesSupported() {
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract synonyms from a field attached to an entity.
|
||||
*
|
||||
* We try to pass as many info about context as possible, however, normally
|
||||
* you will only need $items to extract the synonyms.
|
||||
*
|
||||
* @param array $items
|
||||
* Array of items
|
||||
* @param array $field
|
||||
* Array of field definition according to Field API
|
||||
* @param array $instance
|
||||
* Array of instance definition according to Field API
|
||||
* @param object $entity
|
||||
* Fully loaded entity object to which the $field and $instance with $item
|
||||
* values is attached to
|
||||
* @param string $entity_type
|
||||
* Type of the entity $entity according to Field API definition of entity
|
||||
* types
|
||||
*
|
||||
* @return array
|
||||
* Array of synonyms extracted from $items
|
||||
*/
|
||||
public static function synonymsExtract($items, $field, $instance, $entity, $entity_type) {
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow you to hook in during autocomplete suggestions generation.
|
||||
*
|
||||
* Allow you to include entities for autocomplete suggestion that are possible
|
||||
* candidates based on your field as a source of synonyms. This method is
|
||||
* void, however, you have to alter and add your condition to $query
|
||||
* parameter.
|
||||
*
|
||||
* @param string $tag
|
||||
* What user has typed in into autocomplete widget. Normally you would
|
||||
* run LIKE '%$tag%' on your column
|
||||
* @param EntityFieldQuery $query
|
||||
* EntityFieldQuery object where you should put your conditions to
|
||||
* @param array $field
|
||||
* Array of field definition according to Field API
|
||||
* @param array $instance
|
||||
* Array of instance definition according to Field API
|
||||
*/
|
||||
public static function processEntityFieldQuery($tag, EntityFieldQuery $query, $field, $instance) {}
|
||||
|
||||
/**
|
||||
* Add an entity as a synonym into a field of another entity.
|
||||
*
|
||||
* Basically this method should be called when you want to add some entity
|
||||
* as a synonym to another entity (for example when you merge one entity
|
||||
* into another and besides merging want to add synonym of the merging
|
||||
* entity into the trunk entity). You should extract synonym value (according
|
||||
* to what value is expected in this field) and return it. We try to provide
|
||||
* you with as much 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.
|
||||
*
|
||||
* @param array $items
|
||||
* Array items that already exist in the field into which new synonyms is to
|
||||
* be added
|
||||
* @param array $field
|
||||
* Field array definition according to Field API of the field into which new
|
||||
* synonym is to be added
|
||||
* @param array $instance
|
||||
* Instance array definition according to Field API of the instance into
|
||||
* which new synonym is to be added
|
||||
* @param object $synonym_entity
|
||||
* Fully loaded entity object which has to be added as synonym
|
||||
* @param string $synonym_entity_type
|
||||
* Entity type of $synonym_entity
|
||||
*
|
||||
* @return array
|
||||
* Array of extra items to be merged into the items that already exist in
|
||||
* field values
|
||||
*/
|
||||
public static function mergeEntityAsSynonym($items, $field, $instance, $synonym_entity, $synonym_entity_type) {
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Supportive method.
|
||||
*
|
||||
* Set such a condition on $query that it will always yield no results. Should
|
||||
* be called from $this->processEntityFieldQuery() when for whatever reason
|
||||
* the object can't alter $query to include matched synonyms. As a fallback it
|
||||
* should call this method to make sure it filtered everything out.
|
||||
*
|
||||
* @param EntityFieldQuery $query
|
||||
* Query object passed to $this->processEntityFieldQuery() method
|
||||
*/
|
||||
protected static function emptyResultsCondition(EntityFieldQuery $query) {
|
||||
$query->range(0, 0);
|
||||
}
|
||||
}
|
@ -0,0 +1,84 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Enables Entity Reference field type to be source of synonyms.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Definition of EntityReferenceSynonymsBehavior class.
|
||||
*/
|
||||
class EntityReferenceSynonymsBehavior extends AbstractSynonymsSynonymsBehavior implements SynonymsSynonymsBehavior, AutocompleteSynonymsBehavior, SelectSynonymsBehavior {
|
||||
|
||||
public function extractSynonyms($items, $field, $instance, $entity, $entity_type) {
|
||||
$synonyms = array();
|
||||
|
||||
$target_tids = array();
|
||||
foreach ($items as $item) {
|
||||
$target_tids[] = $item['target_id'];
|
||||
}
|
||||
$entities = entity_load($field['settings']['target_type'], $target_tids);
|
||||
foreach ($entities as $entity) {
|
||||
$synonyms[] = entity_label($field['settings']['target_type'], $entity);
|
||||
}
|
||||
|
||||
return $synonyms;
|
||||
}
|
||||
|
||||
public function mergeEntityAsSynonym($items, $field, $instance, $synonym_entity, $synonym_entity_type) {
|
||||
// Firstly validating that this entity reference is able to reference to
|
||||
// that type of entity.
|
||||
$expected_synonym_entity_type = $field['settings']['target_type'];
|
||||
if ($expected_synonym_entity_type != $synonym_entity_type) {
|
||||
return array();
|
||||
}
|
||||
$synonym_entity_id = entity_id($synonym_entity_type, $synonym_entity);
|
||||
return array(array(
|
||||
'target_id' => $synonym_entity_id,
|
||||
));
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
@ -1,70 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Enables Entity Reference field type to be source of synonyms.
|
||||
*/
|
||||
|
||||
class EntityReferenceSynonymsExtractor extends AbstractSynonymsExtractor {
|
||||
|
||||
public static function fieldTypesSupported() {
|
||||
return array('entityreference');
|
||||
}
|
||||
|
||||
public static function synonymsExtract($items, $field, $instance, $entity, $entity_type) {
|
||||
$synonyms = array();
|
||||
|
||||
// For speading up loading all the entities at once.
|
||||
$target_tids = array();
|
||||
foreach ($items as $item) {
|
||||
$target_tids[] = $item['target_id'];
|
||||
}
|
||||
$entities = entity_load($field['settings']['target_type'], $target_tids);
|
||||
foreach ($entities as $entity) {
|
||||
$synonyms[] = entity_label($field['settings']['target_type'], $entity);
|
||||
}
|
||||
|
||||
return $synonyms;
|
||||
}
|
||||
|
||||
public static function processEntityFieldQuery($tag, EntityFieldQuery $query, $field, $instance) {
|
||||
// Unfortunately EntityFieldQuery does not currently support INNER JOINing
|
||||
// referenced entities via any field type.
|
||||
// Thus, we use an ugly solution -- going through all entities that exist
|
||||
// in such entity type trying to see if there is a match by entity's label.
|
||||
$efq = new EntityFieldQuery();
|
||||
$efq->entityCondition('entity_type', $field['settings']['target_type']);
|
||||
// Additionally we have to figure out which column in the entity table
|
||||
// represents entity label.
|
||||
$entity_info = entity_get_info($field['settings']['target_type']);
|
||||
if (!isset($entity_info['entity keys']['label'])) {
|
||||
// We can't get any matches if we do not know what column to query
|
||||
// against. So we add a condition to $query which will 100% yield empty
|
||||
// results.
|
||||
self::emptyResultsCondition($query);
|
||||
return;
|
||||
}
|
||||
$efq->propertyCondition($entity_info['entity keys']['label'], '%' . $tag . '%', 'LIKE');
|
||||
$result = $efq->execute();
|
||||
|
||||
if (!isset($result[$field['settings']['target_type']]) || !is_array($result[$field['settings']['target_type']])) {
|
||||
self::emptyResultsCondition($query);
|
||||
return;
|
||||
}
|
||||
$result = $result[$field['settings']['target_type']];
|
||||
$query->fieldCondition($field, 'target_id', array_keys($result));
|
||||
}
|
||||
|
||||
public static function mergeEntityAsSynonym($items, $field, $instance, $synonym_entity, $synonym_entity_type) {
|
||||
// Firstly validating that this entity reference is able to reference to
|
||||
// that type of entity.
|
||||
$expected_synonym_entity_type = $field['settings']['target_type'];
|
||||
if ($expected_synonym_entity_type != $synonym_entity_type) {
|
||||
return array();
|
||||
}
|
||||
$synonym_entity_id = entity_id($synonym_entity_type, $synonym_entity);
|
||||
return array(array(
|
||||
'target_id' => $synonym_entity_id,
|
||||
));
|
||||
}
|
||||
}
|
@ -0,0 +1,188 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Interfaces of synonyms behaviors that are shipped with Synonyms module.
|
||||
*/
|
||||
|
||||
/**
|
||||
* General interface of a synonyms behavior.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* We try to pass as many info about context as possible, however, normally
|
||||
* you will only need $items to extract the synonyms.
|
||||
*
|
||||
* @param array $items
|
||||
* Array of items
|
||||
* @param array $field
|
||||
* Array of field definition according to Field API
|
||||
* @param array $instance
|
||||
* Array of instance definition according to Field API
|
||||
* @param object $entity
|
||||
* Fully loaded entity object to which the $field and $instance with $item
|
||||
* values is attached to
|
||||
* @param string $entity_type
|
||||
* Type of the entity $entity according to Field API definition of entity
|
||||
* types
|
||||
*
|
||||
* @return array
|
||||
* Array of synonyms extracted from $items
|
||||
*/
|
||||
public function extractSynonyms($items, $field, $instance, $entity, $entity_type);
|
||||
|
||||
/**
|
||||
* Add an entity as a synonym into a field of another entity.
|
||||
*
|
||||
* Basically this method should be called when you want to add some entity
|
||||
* as a synonym to another entity (for example when you merge one entity
|
||||
* into another and besides merging want to add synonym of the 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.
|
||||
*
|
||||
* @param array $items
|
||||
* Array items that already exist in the field into which new synonyms is to
|
||||
* be added
|
||||
* @param array $field
|
||||
* Field array definition according to Field API of the field into which new
|
||||
* synonym is to be added
|
||||
* @param array $instance
|
||||
* Instance array definition according to Field API of the instance into
|
||||
* which new synonym is to be added
|
||||
* @param object $synonym_entity
|
||||
* Fully loaded entity object which has to be added as synonym
|
||||
* @param string $synonym_entity_type
|
||||
* Entity type of $synonym_entity
|
||||
*
|
||||
* @return array
|
||||
* Array of extra items to be merged into the items that already exist in
|
||||
* field values
|
||||
*/
|
||||
public function mergeEntityAsSynonym($items, $field, $instance, $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.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* @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
|
||||
*
|
||||
* @return Traversable
|
||||
* Traversable result set of found synonyms and entity IDs to which those
|
||||
* belong. Each element in the result set should be an object and will have
|
||||
* the following structure:
|
||||
* - synonym: (string) Synonym that was found and which satisfies the
|
||||
* provided condition
|
||||
* - entity_id: (int) ID of the entity to which the found synonym belongs
|
||||
*/
|
||||
public function synonymsFind(QueryConditionInterface $condition, $field, $instance);
|
||||
}
|
||||
|
||||
/**
|
||||
* Exception thrown by implementations of SynonymsSynonymsBehavior interface.
|
||||
*/
|
||||
class SynonymsSynonymsBehaviorException extends Exception {}
|
||||
|
||||
/**
|
||||
* Starting point for implementing SynonymsSynonymsBehavior interface.
|
||||
*/
|
||||
abstract class AbstractSynonymsSynonymsBehavior implements SynonymsSynonymsBehavior {
|
||||
|
||||
/**
|
||||
* Constant which denotes placeholder of a synonym column.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const COLUMN_PLACEHOLDER = '***COLUMN***';
|
||||
|
||||
/**
|
||||
* Process condition in 'synonymsFind' method.
|
||||
*
|
||||
* Process condition in 'synonymsFind' method replacing all references of
|
||||
* synonym column with the real name of that column.
|
||||
*
|
||||
* @param QueryConditionInterface $condition
|
||||
* Condition that should be processed
|
||||
* @param string $column
|
||||
* Real name of the synonym column
|
||||
*/
|
||||
protected function synonymsFindProcessCondition(QueryConditionInterface $condition, $column) {
|
||||
$condition_array = &$condition->conditions();
|
||||
foreach ($condition_array as &$v) {
|
||||
if (is_array($v) && isset($v['field'])) {
|
||||
if ($v['field'] instanceof QueryConditionInterface) {
|
||||
// Recursively process this condition too.
|
||||
$this->synonymsFindProcessCondition($v['field'], $column);
|
||||
}
|
||||
else {
|
||||
$v['field'] = str_replace(self::COLUMN_PLACEHOLDER, $column, $v['field']);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface of the autocomplete synonyms behavior.
|
||||
*/
|
||||
interface AutocompleteSynonymsBehavior extends SynonymsSynonymsBehavior {
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface of the synonyms friendly select behavior.
|
||||
*/
|
||||
interface SelectSynonymsBehavior extends SynonymsSynonymsBehavior {
|
||||
}
|
@ -1,48 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Default Synonyms Extractor class that ships together with the Synonym module.
|
||||
*/
|
||||
|
||||
class SynonymsSynonymsExtractor extends AbstractSynonymsExtractor {
|
||||
|
||||
public static function fieldTypesSupported() {
|
||||
return array('text', 'number_integer', 'number_float', 'number_decimal');
|
||||
}
|
||||
|
||||
public static function synonymsExtract($items, $field, $instance, $entity, $entity_type) {
|
||||
$synonyms = array();
|
||||
|
||||
foreach ($items as $item) {
|
||||
$synonyms[] = $item['value'];
|
||||
}
|
||||
|
||||
return $synonyms;
|
||||
}
|
||||
|
||||
public static function processEntityFieldQuery($tag, EntityFieldQuery $query, $field, $instance) {
|
||||
$query->fieldCondition($field, 'value', '%' . $tag . '%', 'LIKE');
|
||||
}
|
||||
|
||||
public static function mergeEntityAsSynonym($items, $field, $instance, $synonym_entity, $synonym_entity_type) {
|
||||
$synonym = entity_label($synonym_entity_type, $synonym_entity);
|
||||
switch ($field['type']) {
|
||||
case 'text':
|
||||
break;
|
||||
|
||||
// We add synonyms for numbers only if $synonym is a number.
|
||||
case 'number_integer':
|
||||
case 'number_float':
|
||||
case 'number_decimal':
|
||||
if (!is_numeric($synonym)) {
|
||||
return array();
|
||||
}
|
||||
break;
|
||||
|
||||
}
|
||||
return array(array(
|
||||
'value' => $synonym,
|
||||
));
|
||||
}
|
||||
}
|
@ -0,0 +1,88 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Enables Taxonomy Term Reference field type to be source of synonyms.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Definition of TaxonomySynonymsBehavior class.
|
||||
*/
|
||||
class TaxonomySynonymsBehavior extends AbstractSynonymsSynonymsBehavior implements SynonymsSynonymsBehavior, AutocompleteSynonymsBehavior, SelectSynonymsBehavior {
|
||||
|
||||
public function extractSynonyms($items, $field, $instance, $entity, $entity_type) {
|
||||
$synonyms = array();
|
||||
|
||||
$terms = array();
|
||||
foreach ($items as $item) {
|
||||
$terms[] = $item['tid'];
|
||||
}
|
||||
$terms = taxonomy_term_load_multiple($terms);
|
||||
foreach ($terms as $term) {
|
||||
$synonyms[] = entity_label('taxonomy_term', $term);
|
||||
}
|
||||
return $synonyms;
|
||||
}
|
||||
|
||||
public function mergeEntityAsSynonym($items, $field, $instance, $synonym_entity, $synonym_entity_type) {
|
||||
// Taxonomy term reference supports only referencing of entity types
|
||||
// 'taxonomy_term'.. duh.
|
||||
if ($synonym_entity_type != 'taxonomy_term') {
|
||||
return array();
|
||||
}
|
||||
// Checking that $field is configured to reference the vocabulary of
|
||||
// $synonym_entity term.
|
||||
$is_allowed = FALSE;
|
||||
foreach ($field['settings']['allowed_values'] as $setting) {
|
||||
if ($synonym_entity->vocabulary_machine_name == $setting['vocabulary']) {
|
||||
if ($setting['parent'] == 0) {
|
||||
// No need to check parent property as there is no limitation on it.
|
||||
$is_allowed = TRUE;
|
||||
break;
|
||||
}
|
||||
else {
|
||||
foreach (taxonomy_get_parents_all($synonym_entity->tid) as $parent) {
|
||||
if ($parent->tid == $setting['parent']) {
|
||||
$is_allowed = TRUE;
|
||||
break(2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!$is_allowed) {
|
||||
// Synonym term is from a vocabulary that is not expected by this field,
|
||||
// or under unexpected parent.
|
||||
return array();
|
||||
}
|
||||
return array(array(
|
||||
'tid' => $synonym_entity->tid,
|
||||
));
|
||||
}
|
||||
|
||||
public function synonymItemHash($item, $field, $instance) {
|
||||
return $item['tid'];
|
||||
}
|
||||
|
||||
public function synonymsFind(QueryConditionInterface $condition, $field, $instance) {
|
||||
if ($field['storage']['type'] != 'field_sql_storage') {
|
||||
throw new SynonymsSynonymsBehaviorException(t('Not supported storage engine %type in synonymsFind() method.', array(
|
||||
'%type' => $field['storage']['type'],
|
||||
)));
|
||||
}
|
||||
$table = array_keys($field['storage']['details']['sql'][FIELD_LOAD_CURRENT]);
|
||||
$table = reset($table);
|
||||
$column = $field['storage']['details']['sql'][FIELD_LOAD_CURRENT][$table]['tid'];
|
||||
|
||||
$query = db_select($table, 'field');
|
||||
$term_alias = $query->innerJoin('taxonomy_term_data', 'term', 'field.' . $column . ' = term.tid');
|
||||
$query->addField($term_alias, 'name', 'synonym');
|
||||
$query->fields('field', array('entity_id'));
|
||||
$query->condition('field.entity_type', $instance['entity_type']);
|
||||
$query->condition('field.bundle', $instance['bundle']);
|
||||
|
||||
$this->synonymsFindProcessCondition($condition, $term_alias . '.name');
|
||||
$query->condition($condition);
|
||||
return $query->execute();
|
||||
}
|
||||
}
|
@ -1,94 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Enables Taxonomy Term Reference field types to be source of synonyms.
|
||||
*/
|
||||
|
||||
class TaxonomySynonymsExtractor extends AbstractSynonymsExtractor {
|
||||
|
||||
public static function fieldTypesSupported() {
|
||||
return array('taxonomy_term_reference');
|
||||
}
|
||||
|
||||
public static function synonymsExtract($items, $field, $instance, $entity, $entity_type) {
|
||||
$synonyms = array();
|
||||
|
||||
$terms = array();
|
||||
foreach ($items as $item) {
|
||||
$terms[] = $item['tid'];
|
||||
}
|
||||
$terms = taxonomy_term_load_multiple($terms);
|
||||
foreach ($terms as $term) {
|
||||
$synonyms[] = entity_label('taxonomy_term', $term);
|
||||
}
|
||||
return $synonyms;
|
||||
}
|
||||
|
||||
public static function processEntityFieldQuery($tag, EntityFieldQuery $query, $field, $instance) {
|
||||
// Unfortunately EntityFieldQuery does not currently support INNER JOINing
|
||||
// term entity that is referenced via taxonomy_term_reference field type.
|
||||
// Thus, we use an ugly solution -- going through all terms that exist in
|
||||
// vocabulary and trying to see if there is a match by term's name.
|
||||
$tids = array();
|
||||
|
||||
foreach ($field['settings']['allowed_values'] as $settings) {
|
||||
$efd = new EntityFieldQuery();
|
||||
$efd->entityCondition('bundle', $settings['vocabulary'])
|
||||
->entityCondition('entity_type', 'taxonomy_term')
|
||||
->propertyCondition('name', '%' . $tag . '%', 'LIKE');
|
||||
$result = $efd->execute();
|
||||
if (isset($result['taxonomy_term'])) {
|
||||
foreach ($result['taxonomy_term'] as $tid) {
|
||||
$tids[] = $tid->tid;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Now we have tids of terms from the referenced vocabulary which names
|
||||
// LIKE %$tag%, suggested are the terms that refer to any of these $tids.
|
||||
if (empty($tids)) {
|
||||
// No possible suggestions were found. We have to make sure $query yields
|
||||
// no results.
|
||||
self::emptyResultsCondition($query);
|
||||
return;
|
||||
}
|
||||
$query->fieldCondition($field, 'tid', $tids);
|
||||
}
|
||||
|
||||
public static function mergeEntityAsSynonym($items, $field, $instance, $synonym_entity, $synonym_entity_type) {
|
||||
// Taxonomy term reference supports only referencing of entity types
|
||||
// 'taxonomy_term'.. duh.
|
||||
if ($synonym_entity_type != 'taxonomy_term') {
|
||||
return array();
|
||||
}
|
||||
// Checking that $field is configured to reference the vocabulary of
|
||||
// $synonym_entity term.
|
||||
$is_allowed = FALSE;
|
||||
foreach ($field['settings']['allowed_values'] as $setting) {
|
||||
if ($synonym_entity->vocabulary_machine_name == $setting['vocabulary']) {
|
||||
if ($setting['parent'] == 0) {
|
||||
// No need to check parent property as there is no limitation on it.
|
||||
$is_allowed = TRUE;
|
||||
break;
|
||||
}
|
||||
else {
|
||||
foreach (taxonomy_get_parents_all($synonym_entity->tid) as $parent) {
|
||||
if ($parent->tid == $setting['parent']) {
|
||||
$is_allowed = TRUE;
|
||||
break(2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!$is_allowed) {
|
||||
// Synonym term is from a vocabulary that is not expected by this field,
|
||||
// or under unexpected parent.
|
||||
return array();
|
||||
}
|
||||
return array(array(
|
||||
'tid' => $synonym_entity->tid,
|
||||
));
|
||||
}
|
||||
}
|
@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Enables text and number field types to be source of synonyms.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Definition of TextSynonymsBehavior class.
|
||||
*/
|
||||
class TextSynonymsBehavior extends AbstractSynonymsSynonymsBehavior implements SynonymsSynonymsBehavior, AutocompleteSynonymsBehavior, SelectSynonymsBehavior {
|
||||
|
||||
public function extractSynonyms($items, $field, $instance, $entity, $entity_type) {
|
||||
$synonyms = array();
|
||||
|
||||
foreach ($items as $item) {
|
||||
$synonyms[] = $item['value'];
|
||||
}
|
||||
|
||||
return $synonyms;
|
||||
}
|
||||
|
||||
public function mergeEntityAsSynonym($items, $field, $instance, $synonym_entity, $synonym_entity_type) {
|
||||
$synonym = entity_label($synonym_entity_type, $synonym_entity);
|
||||
switch ($field['type']) {
|
||||
case 'text':
|
||||
break;
|
||||
|
||||
// We add synonyms for numbers only if $synonym is a number.
|
||||
case 'number_integer':
|
||||
case 'number_float':
|
||||
case 'number_decimal':
|
||||
if (!is_numeric($synonym)) {
|
||||
return array();
|
||||
}
|
||||
break;
|
||||
|
||||
}
|
||||
return array(array(
|
||||
'value' => $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();
|
||||
}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
(function ($) {
|
||||
|
||||
/**
|
||||
* Fix the autocomplete core undesired behavior.
|
||||
*
|
||||
* The core autocomplete only allows 1 entry per suggestion, i.e. you can't have
|
||||
* 2 suggestion entries suggest the same key. Synonyms module very well needs
|
||||
* such ability, since multiple synonyms may point to the same entity. In order
|
||||
* to bypass this limitation Synonyms module pads the suggestion entries with
|
||||
* extra spaces on the right until it finds a "free" spot. This JavaScript
|
||||
* right-trims the entries in order to cancel out the effect.
|
||||
*/
|
||||
Drupal.behaviors.synonymsAutocompleteWidget = {
|
||||
attach: function (context, settings) {
|
||||
$('input.form-autocomplete.synonyms-autocomplete', context).once('synonyms-autocomplete', function () {
|
||||
$(this).bind('autocompleteSelect', function() {
|
||||
var value = $(this).val();
|
||||
value = value.replace(/\s+$/, '');
|
||||
$(this).val(value);
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
})(jQuery);
|
@ -0,0 +1,147 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* 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',
|
||||
);
|
||||
|
||||
/**
|
||||
* Discover if this argument gives us the term we crave.
|
||||
*/
|
||||
function synonyms_term_synonyms_context($arg = NULL, $conf = NULL, $empty = FALSE) {
|
||||
// If unset it wants a generic, unfilled context.
|
||||
if ($empty) {
|
||||
return ctools_context_create_empty('entity:taxonomy_term');
|
||||
}
|
||||
|
||||
$conf['vids'] = is_array($conf['vids']) ? array_filter($conf['vids']) : array();
|
||||
|
||||
if (is_object($arg)) {
|
||||
$term = $arg;
|
||||
|
||||
if (!empty($conf['vids']) && empty($conf['vids'][$term->vid])) {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if ($conf['transform']) {
|
||||
$tids = db_select('taxonomy_term_data', 't')
|
||||
->fields('t', array('tid'))
|
||||
->where("REPLACE(t.name, ' ', '-') = :argument", array(
|
||||
':argument' => $arg,
|
||||
));
|
||||
if (!empty($conf['vids'])) {
|
||||
$tids->condition('t.vid', $conf['vids']);
|
||||
}
|
||||
$tids = $tids->execute()->fetchCol();
|
||||
$terms = taxonomy_term_load_multiple($tids);
|
||||
}
|
||||
else {
|
||||
$terms = taxonomy_get_term_by_name($arg);
|
||||
}
|
||||
|
||||
if (!empty($conf['vids'])) {
|
||||
foreach ($terms as $k => $term) {
|
||||
if (!isset($conf['vids'][$term->vid])) {
|
||||
unset($terms[$k]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($terms)) {
|
||||
// We couldn't find the term by name, so we will look it up now by
|
||||
// synonyms.
|
||||
$vocabularies = taxonomy_vocabulary_load_multiple(empty($conf['vids']) ? FALSE : $conf['vids']);
|
||||
foreach ($vocabularies as $vocabulary) {
|
||||
$condition = db_and();
|
||||
if ($conf['transform']) {
|
||||
$condition->where("REPLACE(" . AbstractSynonymsSynonymsBehavior::COLUMN_PLACEHOLDER . ", ' ', '-') = :argument", array(
|
||||
':argument' => $arg,
|
||||
));
|
||||
}
|
||||
else {
|
||||
$condition->condition(AbstractSynonymsSynonymsBehavior::COLUMN_PLACEHOLDER, $arg);
|
||||
}
|
||||
$rows = synonyms_synonyms_find($condition, 'taxonomy_term', $vocabulary->machine_name);
|
||||
if (!empty($rows)) {
|
||||
// We have found a match, no need to search further.
|
||||
$terms[] = taxonomy_term_load($rows[0]->entity_id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($terms)) {
|
||||
return NULL;
|
||||
}
|
||||
$term = array_shift($terms);
|
||||
}
|
||||
|
||||
$context = ctools_context_create('entity:taxonomy_term', $term);
|
||||
$context->original_argument = $arg;
|
||||
return $context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Settings form for the argument.
|
||||
*/
|
||||
function synonyms_term_synonyms_settings_form(&$form, &$form_state, $conf) {
|
||||
$vocabularies = taxonomy_get_vocabularies();
|
||||
$options = array();
|
||||
foreach ($vocabularies as $vid => $vocab) {
|
||||
$options[$vid] = $vocab->name;
|
||||
}
|
||||
$form['settings']['vids'] = array(
|
||||
'#title' => t('Limit to these vocabularies'),
|
||||
'#type' => 'checkboxes',
|
||||
'#options' => $options,
|
||||
'#default_value' => !empty($conf['vids']) ? $conf['vids'] : array(),
|
||||
'#description' => t('If no vocabularies are checked, terms from all vocabularies will be accepted.'),
|
||||
);
|
||||
|
||||
$form['settings']['breadcrumb'] = array(
|
||||
'#title' => t('Inject hierarchy into breadcrumb trail'),
|
||||
'#type' => 'checkbox',
|
||||
'#default_value' => !empty($conf['breadcrumb']),
|
||||
'#description' => t('If checked, taxonomy term parents will appear in the breadcrumb trail.'),
|
||||
);
|
||||
|
||||
$form['settings']['transform'] = array(
|
||||
'#title' => t('Transform dashes in URL to spaces in term name filter values'),
|
||||
'#type' => 'checkbox',
|
||||
'#default_value' => !empty($conf['transform']),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Form fragment to get an argument to convert a placeholder for preview.
|
||||
*/
|
||||
function synonyms_term_synonyms_ctools_argument_placeholder($conf) {
|
||||
return array(
|
||||
'#type' => 'textfield',
|
||||
'#description' => t('Enter a taxonomy term name.'),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inject the breadcrumb trail if necessary.
|
||||
*/
|
||||
function synonyms_term_synonyms_breadcrumb($conf, $context) {
|
||||
// Outsource the real implementation of breadcrumb to terms argument plugin.
|
||||
$plugin = ctools_get_plugins('ctools', 'arguments', 'term');
|
||||
$function = ctools_plugin_get_function($plugin, 'breadcrumb');
|
||||
if ($function) {
|
||||
call_user_func_array($function, func_get_args());
|
||||
}
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Plugin definition for autocomplete synonyms behavior.
|
||||
*/
|
||||
|
||||
$plugin = array(
|
||||
'title' => t('Autocomplete'),
|
||||
'description' => t('Synonyms friendly autocomplete'),
|
||||
'settings form callback' => 'synonyms_behavior_autocomplete_settings_form',
|
||||
'interface' => 'AutocompleteSynonymsBehavior',
|
||||
);
|
||||
|
||||
/**
|
||||
* Settings form for autocomplete behavior.
|
||||
*/
|
||||
function synonyms_behavior_autocomplete_settings_form($form, &$form_state, $settings) {
|
||||
static $is_first_time = TRUE;
|
||||
|
||||
$element = array();
|
||||
|
||||
$element['wording'] = array(
|
||||
'#type' => 'textfield',
|
||||
'#title' => t('Autocomplete wording'),
|
||||
'#default_value' => isset($settings['wording']) ? $settings['wording'] : '@synonym is a synonym of @term',
|
||||
'#description' => t('Specify with what wording the synonyms should be suggested in the autocomplete feature. You may use: <ul><li><em>@synonym</em> to denote value of the synonym</li><li><em>@term</em> to denote term name</li><li><em>@field_name</em> to denote lowercase label of the field from where the synonym originates</li></ul>'),
|
||||
'#required' => TRUE,
|
||||
);
|
||||
|
||||
if (!$is_first_time) {
|
||||
// Remove the description, if the element is created more than once on the
|
||||
// same form. Otherwise the whole form looks too clumsy.
|
||||
unset($element['wording']['#description']);
|
||||
}
|
||||
|
||||
$is_first_time = FALSE;
|
||||
|
||||
return $element;
|
||||
}
|
||||
|
@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Plugin definition for synonyms friendly select behavior.
|
||||
*/
|
||||
|
||||
$plugin = array(
|
||||
'title' => t('Select'),
|
||||
'description' => t('Synonyms friendly select'),
|
||||
'settings form callback' => 'synonyms_behavior_select_settings_form',
|
||||
'interface' => 'SelectSynonymsBehavior',
|
||||
);
|
||||
|
||||
/**
|
||||
* Settings form for select behavior.
|
||||
*/
|
||||
function synonyms_behavior_select_settings_form($form, &$form_state, $settings) {
|
||||
static $is_first_time = TRUE;
|
||||
|
||||
$element = array();
|
||||
|
||||
$element['wording'] = array(
|
||||
'#type' => 'textfield',
|
||||
'#title' => t('Select wording'),
|
||||
'#default_value' => isset($settings['wording']) ? $settings['wording'] : '@synonym',
|
||||
'#description' => t('Specify with what wording the synonyms should be placed in the select form element. You may use: <ul><li><em>@synonym</em> to denote value of the synonym</li><li><em>@term</em> to denote term name</li><li><em>@field_name</em> to denote lowercase label of the field from where the synonym originates</li></ul>'),
|
||||
'#required' => TRUE,
|
||||
);
|
||||
|
||||
if (!$is_first_time) {
|
||||
// Remove the description, if the element is created more than once on the
|
||||
// same form. Otherwise the whole form looks too clumsy.
|
||||
unset($element['wording']['#description']);
|
||||
}
|
||||
|
||||
$is_first_time = FALSE;
|
||||
|
||||
return $element;
|
||||
}
|
||||
|
@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Plugin definition for most general synonyms behavior.
|
||||
*/
|
||||
|
||||
$plugin = array(
|
||||
'title' => 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',
|
||||
);
|
@ -6,149 +6,124 @@
|
||||
*/
|
||||
|
||||
/**
|
||||
* Provide Synonyms module with names of synonyms extractor classes.
|
||||
* Hook to collect info about available synonyms behavior implementations.
|
||||
*
|
||||
* Provide Synonyms module with names of classes that are able to extract
|
||||
* synonyms from fields. Each of the provided classes should extend
|
||||
* AbstractSynonymsExtractor base class.
|
||||
* Hook to collect info about what PHP classes implement provided synonyms
|
||||
* behavior for different field types.
|
||||
*
|
||||
* @param string $behavior
|
||||
* Name of a synonyms behavior. This string will always be among the keys
|
||||
* of the return of synonyms_behaviors(), i.e. name of a ctools plugin
|
||||
*
|
||||
* @return array
|
||||
* Array of strings, where each value is a name of synonyms extractor class
|
||||
* Array of information about what synonyms behavior implementations your
|
||||
* module supplies. The return array must contain field types as keys, whereas
|
||||
* corresponding values should be names of PHP classes that implement the
|
||||
* provided behavior for that field type. Read more about how to implement a
|
||||
* specific behavior in the advanced help of this module. In a few words: you
|
||||
* will have to implement an interface that is defined in the behavior
|
||||
* definition. Do not forget to make sure your PHP class is visible to Drupal
|
||||
* auto discovery mechanism
|
||||
*/
|
||||
function hook_synonyms_extractor_info() {
|
||||
return array(
|
||||
// Please see below the definition of ApiSynonymsSynonymsExtractor class
|
||||
// for your reference.
|
||||
'ApiSynonymsSynonymsExtractor',
|
||||
);
|
||||
function hook_synonyms_behavior_implementation_info($behavior) {
|
||||
switch ($behavior) {
|
||||
case 'autocomplete':
|
||||
return array(
|
||||
'my-field-type' => 'MyFieldTypeAutocompleteSynonymsBehavior',
|
||||
);
|
||||
break;
|
||||
|
||||
case 'another-behavior':
|
||||
return array(
|
||||
'my-field-type-or-yet-another-field-type' => 'MyFieldTypeAnotherBehaviorSynonymsBehavior',
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Dummy synonyms extractor class for documentation purposes.
|
||||
* Hook to alter info about available synonyms behavior implementations.
|
||||
*
|
||||
* This is a copy of SynonymsSynonymsExtractor class providing an example of
|
||||
* how to write your own synonyms extractor class. See the definition of
|
||||
* AbstractSynonymsExtractor for reference and in code comments. For more
|
||||
* complicated examples take a look at EntityReferenceSynonymsExtractor class.
|
||||
* This hook is invoked right after hook_synonyms_behavior_implementation_info()
|
||||
* and is designed to let modules overwrite implementation info from some other
|
||||
* modules. For example, if module A provides implementation for some field
|
||||
* type, but your module has a better version of that implementation, you would
|
||||
* need to implement this hook and to overwrite the implementation info.
|
||||
*
|
||||
* @param array $info
|
||||
* Array of information about existing synonyms behavior implementations that
|
||||
* was collected from modules
|
||||
* @param string $behavior
|
||||
* Name of the behavior for which the info about implementation is being
|
||||
* generated
|
||||
*/
|
||||
class ApiSynonymsSynonymsExtractor extends AbstractSynonymsExtractor {
|
||||
|
||||
/**
|
||||
* Return array of supported field types for synonyms extraction.
|
||||
*
|
||||
* @return array
|
||||
* Array of Field API field types from which this class is able to extract
|
||||
* synonyms
|
||||
*/
|
||||
static public function fieldTypesSupported() {
|
||||
return array('text', 'number_integer', 'number_float', 'number_decimal');
|
||||
function hook_synonyms_behavior_implementation_info_alter(&$info, $behavior) {
|
||||
switch ($behavior) {
|
||||
case 'the-behavior-i-want':
|
||||
$info['the-field-type-i-want'] = 'MyFieldTypeAutocompleteSynonymsBehavior';
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract synonyms from a field attached to an entity.
|
||||
*
|
||||
* We try to pass as many info about context as possible, however, normally
|
||||
* you will only need $items to extract the synonyms.
|
||||
*
|
||||
* @param array $items
|
||||
* Array of items
|
||||
* @param array $field
|
||||
* Array of field definition according to Field API
|
||||
* @param array $instance
|
||||
* Array of instance definition according to Field API
|
||||
* @param object $entity
|
||||
* Fully loaded entity object to which the $field and $instance with $item
|
||||
* values is attached to
|
||||
* @param string $entity_type
|
||||
* Type of the entity $entity according to Field API definition of entity
|
||||
* types
|
||||
*
|
||||
* @return array
|
||||
* Array of synonyms extracted from $items
|
||||
*/
|
||||
static public function synonymsExtract($items, $field, $instance, $entity, $entity_type) {
|
||||
/**
|
||||
* Example of how to implement a synonyms behavior for an arbitrary field type.
|
||||
*/
|
||||
class MyFieldTypeAutocompleteSynonymsBehavior extends AbstractSynonymsSynonymsBehavior implements AutocompleteSynonymsBehavior {
|
||||
|
||||
public function extractSynonyms($items, $field, $instance, $entity, $entity_type) {
|
||||
// Let's say our synonyms is stored in the 'foo' column of the field.
|
||||
$synonyms = array();
|
||||
|
||||
foreach ($items as $item) {
|
||||
$synonyms[] = $item['value'];
|
||||
$synonyms[] = $item['foo'];
|
||||
}
|
||||
|
||||
return $synonyms;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow you to hook in during autocomplete suggestions generation.
|
||||
*
|
||||
* Allow you to include entities for autocomplete suggestion that are possible
|
||||
* candidates based on your field as a source of synonyms. This method is
|
||||
* void, however, you have to alter and add your condition to $query
|
||||
* parameter.
|
||||
*
|
||||
* @param string $tag
|
||||
* What user has typed in into autocomplete widget. Normally you would
|
||||
* run LIKE '%$tag%' on your column
|
||||
* @param EntityFieldQuery $query
|
||||
* EntityFieldQuery object where you should add your conditions to
|
||||
* @param array $field
|
||||
* Array of field definition according to Field API, autocomplete on which
|
||||
* is fired
|
||||
* @param array $instance
|
||||
* Array of instance definition according to Field API, autocomplete on
|
||||
* which is fired
|
||||
*/
|
||||
static public function processEntityFieldQuery($tag, EntityFieldQuery $query, $field, $instance) {
|
||||
$query->fieldCondition($field, 'value', '%' . $tag . '%', 'LIKE');
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an entity as a synonym into a field of another entity.
|
||||
*
|
||||
* Basically this method should be called when you want to add some entity
|
||||
* as a synonym to another entity (for example when you merge one entity
|
||||
* into another and besides merging want to add synonym of the merging
|
||||
* entity into the trunk entity). You should extract synonym value (according
|
||||
* to what value is expected in this field) and return it. We try to provide
|
||||
* you with as much context as possible, but normally you would only need
|
||||
* $synonym_entity and $synonym_entity_type parameters. Return an empty array
|
||||
* if entity of type $synonym_entity_type cannot be converted into a format
|
||||
* expected by $field.
|
||||
*
|
||||
* @param array $items
|
||||
* Array items that already exist in the field into which new synonyms is to
|
||||
* be added
|
||||
* @param array $field
|
||||
* Field array definition according to Field API of the field into which new
|
||||
* synonym is to be added
|
||||
* @param array $instance
|
||||
* Instance array definition according to Field API of the instance into
|
||||
* which new synonym is to be added
|
||||
* @param object $synonym_entity
|
||||
* Fully loaded entity object which has to be added as synonym
|
||||
* @param string $synonym_entity_type
|
||||
* Entity type of $synonym_entity
|
||||
*
|
||||
* @return array
|
||||
* Array of extra items to be merged into the items that already exist in
|
||||
* field values
|
||||
*/
|
||||
static public function mergeEntityAsSynonym($items, $field, $instance, $synonym_entity, $synonym_entity_type) {
|
||||
$synonym = entity_label($synonym_entity_type, $synonym_entity);
|
||||
switch ($field['type']) {
|
||||
case 'text':
|
||||
break;
|
||||
|
||||
// We add synonyms for numbers only if $synonym is a number.
|
||||
case 'number_integer':
|
||||
case 'number_float':
|
||||
case 'number_decimal':
|
||||
if (!is_numeric($synonym)) {
|
||||
return array();
|
||||
}
|
||||
break;
|
||||
|
||||
}
|
||||
public function mergeEntityAsSynonym($items, $field, $instance, $synonym_entity, $synonym_entity_type) {
|
||||
// Let's say we keep the synonyms as strings and under the 'foo' column, to
|
||||
// keep it consistent with the extractSynonyms() method.
|
||||
$label = entity_label($synonym_entity_type, $synonym_entity);
|
||||
return array(array(
|
||||
'value' => $synonym,
|
||||
'foo' => $label,
|
||||
));
|
||||
}
|
||||
|
||||
public function synonymItemHash($item, $field, $instance) {
|
||||
// Since we've agreed that the column that stores data in our imaginary
|
||||
// field type is "foo". Then it suffices just to implement the hash function
|
||||
// as the value of foo column.
|
||||
return $item['foo'];
|
||||
}
|
||||
|
||||
public function synonymsFind(QueryConditionInterface $condition, $field, $instance) {
|
||||
// We only can find synonyms in SQL storage. If this field is not one, then
|
||||
// we have full right to throw an exception.
|
||||
if ($field['storage']['type'] != 'field_sql_storage') {
|
||||
throw new SynonymsSynonymsBehaviorException(t('Not supported storage engine %type in synonymsFind() method.', array(
|
||||
'%type' => $field['storage']['type'],
|
||||
)));
|
||||
}
|
||||
// Now we will figure out in what table $field is stored. We want to
|
||||
// condition the 'foo' column within that field table.
|
||||
$table = array_keys($field['storage']['details']['sql'][FIELD_LOAD_CURRENT]);
|
||||
$table = reset($table);
|
||||
$column = $field['storage']['details']['sql'][FIELD_LOAD_CURRENT][$table]['foo'];
|
||||
|
||||
// Once we know full path to the column that stores synonyms as plain text,
|
||||
// we can use a supplementary method from AbstractSynonymsSynonymsBehavior,
|
||||
// which helps us to convert a column placeholder into its real value within
|
||||
// the condition we have received from outside.
|
||||
$this->synonymsFindProcessCondition($condition, $column);
|
||||
|
||||
// Now we are all set to build a SELECT query and return its result set.
|
||||
$query = db_select($table);
|
||||
$query->fields($table, array('entity_id'));
|
||||
$query->addField($table, $column, 'synonym');
|
||||
return $query->condition($condition)
|
||||
->condition('entity_type', $instance['entity_type'])
|
||||
->condition('bundle', $instance['bundle'])
|
||||
->execute();
|
||||
}
|
||||
}
|
||||
|
1
sites/all/modules/contrib/taxonomy/synonyms/synonyms.css
Normal file
1
sites/all/modules/contrib/taxonomy/synonyms/synonyms.css
Normal file
@ -0,0 +1 @@
|
||||
.synonyms-behavior-settings .form-item .description {white-space:normal;}
|
@ -3,19 +3,22 @@ description = "Provides synonyms feature for working with Drupal Taxonomy"
|
||||
package = Taxonomy
|
||||
core = 7.x
|
||||
dependencies[] = taxonomy
|
||||
dependencies[] = text
|
||||
dependencies[] = ctools
|
||||
|
||||
files[] = synonyms.test
|
||||
files[] = includes/AbstractSynonymsExtractor.class.inc
|
||||
files[] = includes/SynonymsSynonymsExtractor.class.inc
|
||||
files[] = includes/TaxonomySynonymsExtractor.class.inc
|
||||
files[] = includes/EntityReferenceSynonymsExtractor.class.inc
|
||||
|
||||
files[] = includes/SynonymsBehavior.interface.inc
|
||||
files[] = includes/TextSynonymsBehavior.class.inc
|
||||
files[] = includes/TaxonomySynonymsBehavior.class.inc
|
||||
files[] = includes/EntityReferenceSynonymsBehavior.class.inc
|
||||
|
||||
files[] = views/synonyms.views.inc
|
||||
files[] = views/synonyms_views_plugin_argument_validate_taxonomy_term.inc
|
||||
files[] = views/synonyms_views_handler_filter_term_tid.inc
|
||||
|
||||
; Information added by Drupal.org packaging script on 2014-09-15
|
||||
version = "7.x-1.2"
|
||||
; Information added by Drupal.org packaging script on 2015-11-29
|
||||
version = "7.x-1.3"
|
||||
core = "7.x"
|
||||
project = "synonyms"
|
||||
datestamp = "1410753528"
|
||||
datestamp = "1448771941"
|
||||
|
||||
|
@ -5,13 +5,55 @@
|
||||
* Install, update, and uninstall functions for the Synonyms module.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements hook_schema().
|
||||
*/
|
||||
function synonyms_schema() {
|
||||
$schema = array();
|
||||
|
||||
$schema['synonyms_settings'] = array(
|
||||
'description' => 'Stores synonyms settings for all the entities and fields. Only enabled synonyms behaviors are included in this table.',
|
||||
'fields' => array(
|
||||
'instance_id' => array(
|
||||
'description' => 'Reference to {field_config_instance}.id of the instance, whose synonyms settings are stored in this row.',
|
||||
'type' => 'int',
|
||||
'not null' => TRUE,
|
||||
'unsigned' => TRUE,
|
||||
),
|
||||
'behavior' => array(
|
||||
'description' => 'Name of the synonyms behavior (ctools plugin), whose settings are stored in this row.',
|
||||
'type' => 'varchar',
|
||||
'length' => 255,
|
||||
'not null' => TRUE,
|
||||
),
|
||||
'settings_serialized' => array(
|
||||
'description' => 'Settings of the specified synonyms behavior for the specified field instance.',
|
||||
'type' => 'text',
|
||||
'serialize' => TRUE,
|
||||
'not null' => TRUE,
|
||||
),
|
||||
),
|
||||
'unique keys' => array(
|
||||
'instance_behavior' => array('instance_id', 'behavior'),
|
||||
),
|
||||
'foreign keys' => array(
|
||||
'field_config_instance' => array(
|
||||
'table' => 'field_config_instance',
|
||||
'columns' => array('instance_id' => 'id'),
|
||||
),
|
||||
),
|
||||
'indexes' => array(
|
||||
'behavior' => array('behavior'),
|
||||
),
|
||||
);
|
||||
|
||||
return $schema;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_uninstall().
|
||||
*/
|
||||
function synonyms_uninstall() {
|
||||
// We rely on a constant defined in the main module's file, so we include it.
|
||||
drupal_load('module', 'synonyms');
|
||||
field_delete_field(SYNONYMS_DEFAULT_FIELD_NAME);
|
||||
// Cleaning all configure variables.
|
||||
$results = db_select('variable', 'var')
|
||||
->fields('var', array('name'))
|
||||
@ -39,8 +81,108 @@ function synonyms_update_7101() {
|
||||
// Enabled synonyms now stored as field names, since the field independency
|
||||
// has been introduced. See issue http://drupal.org/node/1850748.
|
||||
drupal_load('module', 'synonyms');
|
||||
$settings['synonyms'] = $settings['synonyms'] ? array(SYNONYMS_DEFAULT_FIELD_NAME) : array();
|
||||
$settings['synonyms'] = $settings['synonyms'] ? array('synonyms_synonyms') : array();
|
||||
variable_set($var->name, $settings);
|
||||
}
|
||||
return t('Updated settings of synonyms.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Multiple adjustments in the internal structures of the module.
|
||||
*
|
||||
* Unlock the 'synonyms_synonyms' field, because Synonyms module no longer uses
|
||||
* it.
|
||||
* Create 'synonyms_settings' table.
|
||||
* Enable 'synonyms_search' module if the core Search module is enabled.
|
||||
* Enable all available behaviors on all "source of synonyms" fields with the
|
||||
* default settings.
|
||||
*/
|
||||
function synonyms_update_7102() {
|
||||
$field = field_info_field('synonyms_synonyms');
|
||||
$field['locked'] = FALSE;
|
||||
field_update_field($field);
|
||||
|
||||
db_create_table('synonyms_settings', array(
|
||||
'description' => 'Stores synonyms settings for all the entities and fields. Only enabled synonyms behaviors are included in this table.',
|
||||
'fields' => array(
|
||||
'instance_id' => array(
|
||||
'description' => 'Reference to {field_config_instance}.id of the instance, whose synonyms settings are stored in this row.',
|
||||
'type' => 'int',
|
||||
'not null' => TRUE,
|
||||
'unsigned' => TRUE,
|
||||
),
|
||||
'behavior' => array(
|
||||
'description' => 'Name of the synonyms behavior (ctools plugin), whose settings are stored in this row.',
|
||||
'type' => 'varchar',
|
||||
'length' => 255,
|
||||
'not null' => TRUE,
|
||||
),
|
||||
'settings_serialized' => array(
|
||||
'description' => 'Settings of the specified synonyms behavior for the specified field instance.',
|
||||
'type' => 'text',
|
||||
'serialize' => TRUE,
|
||||
'not null' => TRUE,
|
||||
),
|
||||
),
|
||||
'unique keys' => array(
|
||||
'instance_behavior' => array('instance_id', 'behavior'),
|
||||
),
|
||||
'foreign keys' => array(
|
||||
'field_config_instance' => array(
|
||||
'table' => 'field_config_instance',
|
||||
'columns' => array('instance_id' => 'id'),
|
||||
),
|
||||
),
|
||||
'indexes' => array(
|
||||
'behavior' => array('behavior'),
|
||||
),
|
||||
));
|
||||
|
||||
if (module_exists('search')) {
|
||||
module_enable(array('synonyms_search'));
|
||||
}
|
||||
|
||||
$vars = db_select('variable', 'v')
|
||||
->fields('v', array('name'))
|
||||
->condition('name', db_like('synonyms_settings_') . '%', 'LIKE')
|
||||
->execute();
|
||||
foreach ($vars as $row) {
|
||||
$var = variable_get($row->name);
|
||||
$vid = substr($row->name, drupal_strlen('synonyms_settings_'));
|
||||
$vocabulary = taxonomy_vocabulary_load($vid);
|
||||
if ($vocabulary) {
|
||||
$bundle = $vocabulary->machine_name;
|
||||
foreach ($var['synonyms'] as $field) {
|
||||
$instance = field_info_instance('taxonomy_term', $field, $bundle);
|
||||
foreach (synonyms_behaviors() as $behavior) {
|
||||
switch ($behavior['name']) {
|
||||
case 'synonyms':
|
||||
case 'search':
|
||||
default:
|
||||
$settings = array();
|
||||
break;
|
||||
|
||||
case 'select':
|
||||
$settings = array(
|
||||
'wording' => '@synonym',
|
||||
);
|
||||
break;
|
||||
|
||||
case 'autocomplete':
|
||||
$settings = array(
|
||||
'wording' => '@synonym, synonym of @term',
|
||||
);
|
||||
break;
|
||||
}
|
||||
$settings = array(
|
||||
'instance_id' => $instance['id'],
|
||||
'behavior' => $behavior['name'],
|
||||
'settings' => $settings,
|
||||
);
|
||||
synonyms_behavior_settings_save($settings);
|
||||
}
|
||||
}
|
||||
}
|
||||
variable_del($row->name);
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -56,18 +56,31 @@ function synonyms_autocomplete($field_name, $entity_type, $bundle, $tags_typed =
|
||||
exit;
|
||||
}
|
||||
|
||||
$widget = $instance['widget']['type'] == 'synonyms_autocomplete' ? $instance['widget']['settings'] : field_info_widget_settings('synonyms_autocomplete');
|
||||
|
||||
// How many suggestions maximum we are able to output.
|
||||
$max_suggestions = $instance['widget']['settings']['suggestion_size'];
|
||||
$max_suggestions = $widget['suggestion_size'];
|
||||
|
||||
// Whether we are allowed to suggest more than one entry per term, shall that
|
||||
// entry be either term name itself or one of its synonyms.
|
||||
$suggest_only_unique = $instance['widget']['settings']['suggest_only_unique'];
|
||||
$suggest_only_unique = $widget['suggest_only_unique'];
|
||||
|
||||
// The user enters a comma-separated list of tags. We only autocomplete the
|
||||
// last tag.
|
||||
$tags_typed = drupal_explode_tags($tags_typed);
|
||||
$tag_last = drupal_strtolower(array_pop($tags_typed));
|
||||
|
||||
$tags_typed_tids = array();
|
||||
if (!empty($tags_typed)) {
|
||||
$efq = new EntityFieldQuery();
|
||||
$efq->entityCondition('entity_type', 'taxonomy_term');
|
||||
$efq->propertyCondition('name', $tags_typed);
|
||||
$tags_typed_tids = $efq->execute();
|
||||
if (isset($tags_typed_tids['taxonomy_term'])) {
|
||||
$tags_typed_tids = array_keys($tags_typed_tids['taxonomy_term']);
|
||||
}
|
||||
}
|
||||
|
||||
$term_matches = array();
|
||||
if ($tag_last != '') {
|
||||
// Part of the criteria for the query come from the field's own settings.
|
||||
@ -79,14 +92,11 @@ function synonyms_autocomplete($field_name, $entity_type, $bundle, $tags_typed =
|
||||
$vocabularies = taxonomy_vocabulary_load_multiple(array_keys($vocabularies));
|
||||
|
||||
// Array of found suggestions. Each subarray of this array will represent
|
||||
// a single suggestion entry. The sub array must at least contain the
|
||||
// following keys:
|
||||
// tid - tid of the suggested term
|
||||
// name - name of the suggested term
|
||||
// And optionally, if the term is suggested based on its synonym, the sub
|
||||
// array should also include the additional key:
|
||||
// synonym - string representation of the synonym based on which the
|
||||
// suggested term was included.
|
||||
// a single suggestion entry. The sub array must contain the following keys:
|
||||
// - tid: (int) tid of the suggested term
|
||||
// - name: (string) name of the suggested term
|
||||
// - wording: (string) human friendly XSS escaped text of the suggestion
|
||||
// entry
|
||||
$tags_return = array();
|
||||
|
||||
// Firstly getting a list of tids that match by $term->name.
|
||||
@ -95,8 +105,8 @@ function synonyms_autocomplete($field_name, $entity_type, $bundle, $tags_typed =
|
||||
$query->addTag('term_access');
|
||||
|
||||
// Do not select already entered terms.
|
||||
if (!empty($tags_typed)) {
|
||||
$query->condition('t.name', $tags_typed, 'NOT IN');
|
||||
if (!empty($tags_typed_tids)) {
|
||||
$query->condition('t.tid', $tags_typed_tids, 'NOT IN');
|
||||
}
|
||||
// Select rows that match by term name.
|
||||
$result = $query
|
||||
@ -104,78 +114,67 @@ function synonyms_autocomplete($field_name, $entity_type, $bundle, $tags_typed =
|
||||
->condition('t.vid', array_keys($vocabularies))
|
||||
->condition('t.name', '%' . db_like($tag_last) . '%', 'LIKE')
|
||||
->range(0, $max_suggestions)
|
||||
->execute()
|
||||
->fetchAllKeyed();
|
||||
|
||||
// Converting results into another format.
|
||||
foreach ($result as $tid => $name) {
|
||||
$tags_return[] = array(
|
||||
'name' => $name,
|
||||
'tid' => $tid,
|
||||
);
|
||||
->execute();
|
||||
foreach ($result as $v) {
|
||||
$v = (array) $v;
|
||||
$v['wording'] = check_plain($v['name']);
|
||||
$tags_return[] = $v;
|
||||
}
|
||||
|
||||
$synonym_tids = array();
|
||||
// Now we go vocabulary by vocabulary looking through synonym fields.
|
||||
foreach ($vocabularies as $vocabulary) {
|
||||
// Now we go a synonym field by synonym field gathering suggestions.
|
||||
// Since officially EntityFieldQuery doesn't support OR conditions
|
||||
// we are enforced to go a field by field querying multiple times the DB.
|
||||
$bundle = field_extract_bundle('taxonomy_term', $vocabulary);
|
||||
foreach (synonyms_synonyms_fields($vocabulary) as $field) {
|
||||
$field = field_info_field($field);
|
||||
$instance = field_info_instance('taxonomy_term', $field['field_name'], $bundle);
|
||||
$query = new EntityFieldQuery();
|
||||
$query->entityCondition('entity_type', 'taxonomy_term')
|
||||
->entityCondition('bundle', $bundle);
|
||||
|
||||
// We let the class that defines this field type as a source of synonyms
|
||||
// filter out and provide its suggestions based on this field.
|
||||
$class = synonyms_extractor_info($field['type']);
|
||||
call_user_func(array($class, 'processEntityFieldQuery'), $tag_last, $query, $field, $instance);
|
||||
$behavior_implementations = synonyms_behavior_get('autocomplete', 'taxonomy_term', $bundle, TRUE);
|
||||
foreach ($behavior_implementations as $implementation) {
|
||||
$condition = db_and();
|
||||
$condition->condition(AbstractSynonymsSynonymsBehavior::COLUMN_PLACEHOLDER, '%' . db_like($tag_last) . '%', 'LIKE');
|
||||
|
||||
if (!empty($tags_typed)) {
|
||||
$query->propertyCondition('name', $tags_typed, 'NOT IN');
|
||||
if (!empty($tags_typed_tids)) {
|
||||
$condition->condition('entity_id', $tags_typed_tids, 'NOT IN');
|
||||
}
|
||||
if ($suggest_only_unique && !empty($tags_return)) {
|
||||
$tmp = array();
|
||||
foreach ($tags_return as $tag_return) {
|
||||
$tmp[] = $tag_return['tid'];
|
||||
}
|
||||
// We don't want to search among the terms already found by term name.
|
||||
$query->entityCondition('entity_id', $tmp, 'NOT IN');
|
||||
$condition->condition('entity_id', $tmp, 'NOT IN');
|
||||
}
|
||||
if ($suggest_only_unique && !empty($synonym_tids)) {
|
||||
// We also don't want to search among the terms already matched by
|
||||
// previous synonym fields.
|
||||
$query->entityCondition('entity_id', $synonym_tids, 'NOT IN');
|
||||
}
|
||||
$tmp = $query->execute();
|
||||
if (!empty($tmp)) {
|
||||
// Merging the results.
|
||||
$tmp = array_keys($tmp['taxonomy_term']);
|
||||
$synonym_tids = array_merge($synonym_tids, $tmp);
|
||||
|
||||
$new_tids = array();
|
||||
foreach (synonyms_synonyms_find_behavior($condition, $implementation) as $synonym) {
|
||||
if (!$suggest_only_unique || !in_array($synonym->entity_id, $new_tids)) {
|
||||
$tags_return[] = array(
|
||||
'tid' => $synonym->entity_id,
|
||||
'name' => '',
|
||||
'synonym' => $synonym->synonym,
|
||||
'implementation' => $implementation,
|
||||
);
|
||||
$new_tids[] = $synonym->entity_id;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($synonym_tids)) {
|
||||
foreach (taxonomy_term_load_multiple($synonym_tids) as $synonym_term) {
|
||||
$tmp = array(
|
||||
'name' => $synonym_term->name,
|
||||
'tid' => $synonym_term->tid,
|
||||
);
|
||||
// Additionally we have to find out which synonym triggered inclusion of
|
||||
// this term.
|
||||
foreach (synonyms_get_sanitized($synonym_term) as $item) {
|
||||
if (strpos(mb_strtoupper($item, 'UTF-8'), mb_strtoupper($tag_last, 'UTF-8')) !== FALSE) {
|
||||
$tags_return[] = array('synonym' => $item) + $tmp;
|
||||
if ($suggest_only_unique) {
|
||||
// We just want to output 1 single suggestion entry per term, so
|
||||
// one synonym is enough.
|
||||
break;
|
||||
}
|
||||
}
|
||||
$synonym_terms = array();
|
||||
foreach ($tags_return as $v) {
|
||||
if (isset($v['synonym'])) {
|
||||
$synonym_terms[] = $v['tid'];
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($synonym_terms)) {
|
||||
$synonym_terms = taxonomy_term_load_multiple($synonym_terms);
|
||||
foreach ($tags_return as &$v) {
|
||||
if (isset($v['synonym'])) {
|
||||
$instance = field_info_instance($v['implementation']['entity_type'], $v['implementation']['field_name'], $v['implementation']['bundle']);
|
||||
$v['name'] = $synonym_terms[$v['tid']]->name;
|
||||
$v['wording'] = format_string(filter_xss($v['implementation']['settings']['wording']), array(
|
||||
'@synonym' => $v['synonym'],
|
||||
'@term' => $v['name'],
|
||||
'@field_name' => drupal_strtolower($instance['label']),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -192,98 +191,50 @@ function synonyms_autocomplete($field_name, $entity_type, $bundle, $tags_typed =
|
||||
if (strpos($info['name'], ',') !== FALSE || strpos($info['name'], '"') !== FALSE) {
|
||||
$n = '"' . str_replace('"', '""', $info['name']) . '"';
|
||||
}
|
||||
|
||||
if (isset($info['synonym'])) {
|
||||
$display_name = t('@synonym, synonym of %term', array('@synonym' => $info['synonym'], '%term' => $info['name']));
|
||||
}
|
||||
else {
|
||||
$display_name = check_plain($info['name']);
|
||||
}
|
||||
while (isset($term_matches[$prefix . $n])) {
|
||||
$n .= ' ';
|
||||
}
|
||||
$term_matches[$prefix . $n] = $display_name;
|
||||
$term_matches[$prefix . $n] = $info['wording'];
|
||||
}
|
||||
}
|
||||
drupal_json_output($term_matches);
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark nodes that reference specific terms for search re-indexing.
|
||||
*
|
||||
* This is particularly useful, when the terms in question have been changed.
|
||||
*
|
||||
* @param $tids array
|
||||
* Array of tids of the terms
|
||||
* Default theme implementation for behavior settings form element.
|
||||
*/
|
||||
function synonyms_reindex_nodes_by_terms($tids) {
|
||||
// In order to speed up the process, we will query DB for nid's that reference
|
||||
// handled to us tids, and at the end we'll trigger their re-indexing in just
|
||||
// a single SQL query. Probably it is better to use search_touch_node(), but
|
||||
// that would imply a big amount of SQL queries on some websites.
|
||||
$found_nids = array();
|
||||
foreach (field_info_field_map() as $field_name => $v) {
|
||||
if ($v['type'] == 'taxonomy_term_reference' && isset($v['bundles']['node'])) {
|
||||
// This field is taxonomy term reference and it is attached to nodes, so
|
||||
// we will run EntityFieldQuery on it.
|
||||
$query = new EntityFieldQuery();
|
||||
$result = $query->entityCondition('entity_type', 'node')
|
||||
->fieldCondition($field_name, 'tid', $tids, 'IN')
|
||||
->execute();
|
||||
if (isset($result['node'])) {
|
||||
$found_nids = array_merge($found_nids, array_keys($result['node']));
|
||||
function theme_synonyms_behaviors_settings($variables) {
|
||||
drupal_add_css(drupal_get_path('module', 'synonyms') . '/synonyms.css');
|
||||
|
||||
$element = &$variables['element'];
|
||||
|
||||
$table = array(
|
||||
'header' => array(t('Field')),
|
||||
'rows' => array(),
|
||||
'empty' => t('Seems like there are no fields for which synonyms functionality is available. Try adding a text field to get started.'),
|
||||
);
|
||||
|
||||
$instance_ids = array();
|
||||
foreach (element_children($element) as $behavior) {
|
||||
$table['header'][] = check_plain($element[$behavior]['#title']);
|
||||
$instance_ids = array_unique(array_merge($instance_ids, element_children($element[$behavior])));
|
||||
}
|
||||
|
||||
foreach ($instance_ids as $instance_id) {
|
||||
$row = array();
|
||||
$row_title = '';
|
||||
foreach (element_children($element) as $behavior) {
|
||||
if (isset($element[$behavior][$instance_id]['#title']) && !$row_title) {
|
||||
$row_title = check_plain($element[$behavior][$instance_id]['#title']);
|
||||
}
|
||||
$row[] = array(
|
||||
'data' => isset($element[$behavior][$instance_id]) ? drupal_render($element[$behavior][$instance_id]) : t('Not implemented'),
|
||||
'class' => array('synonyms-behavior-settings', 'synonyms-behavior-settings-' . $behavior),
|
||||
);
|
||||
}
|
||||
array_unshift($row, $row_title);
|
||||
$table['rows'][] = $row;
|
||||
}
|
||||
if (!empty($found_nids)) {
|
||||
db_update('search_dataset')
|
||||
->fields(array('reindex' => REQUEST_TIME))
|
||||
->condition('type', 'node')
|
||||
->condition('sid', $found_nids, 'IN')
|
||||
->execute();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark nodes that reference terms from a vocabulary for search re-indexing.
|
||||
*
|
||||
* @param $vocabulary object
|
||||
* Fully loaded vocabulary object
|
||||
*/
|
||||
function synonyms_reindex_nodes_by_vocabulary($vocabulary) {
|
||||
$tids = db_select('taxonomy_term_data', 't')
|
||||
->fields('t', array('tid'))
|
||||
->condition('vid', $vocabulary->vid)
|
||||
->execute()
|
||||
->fetchCol();
|
||||
if (!empty($tids)) {
|
||||
synonyms_reindex_nodes_by_terms($tids);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit handler for Taxonomy vocabulary edit form.
|
||||
*
|
||||
* Store extra values attached to form in this module.
|
||||
*/
|
||||
function synonyms_taxonomy_form_vocabulary_submit($form, &$form_state) {
|
||||
$values = $form_state['values'];
|
||||
|
||||
if ($values['op'] == $form['actions']['submit']['#value']) {
|
||||
if (isset($form['#vocabulary']->vid)) {
|
||||
$vocabulary = taxonomy_vocabulary_load($form['#vocabulary']->vid);
|
||||
}
|
||||
else {
|
||||
// As a fallback, if this is a just created vocabulary, we try to pull it
|
||||
// up by the just submitted machine name.
|
||||
$vocabulary = taxonomy_vocabulary_machine_name_load($values['machine_name']);
|
||||
}
|
||||
|
||||
// Preprocessing values keeping only field names of the checked fields.
|
||||
$values['synonyms']['synonyms'] = array_values(array_filter($values['synonyms']['synonyms']));
|
||||
|
||||
$settings = synonyms_vocabulary_settings($vocabulary);
|
||||
$settings = $values['synonyms'] + $settings;
|
||||
synonyms_vocabulary_settings_save($vocabulary, $settings);
|
||||
}
|
||||
|
||||
return '<div id="' . $element['#id'] . '">' . theme('table', $table) . drupal_render_children($element) . '</div>';
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Enables Entity Reference field type for search integration.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Definition of SearchEntityReferenceSynonymsBehavior class.
|
||||
*/
|
||||
class SearchEntityReferenceSynonymsBehavior extends EntityReferenceSynonymsBehavior implements SearchSynonymsBehavior {
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Interfaces of synonyms behaviors shipped with Synonyms Search module.
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Interface of search integration synonyms behavior.
|
||||
*/
|
||||
interface SearchSynonymsBehavior extends SynonymsSynonymsBehavior {
|
||||
}
|
||||
|
@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Enables Taxonomy Term Reference field type for search integration.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Definition of SearchTaxonomySynonymsBehavior class.
|
||||
*/
|
||||
class SearchTaxonomySynonymsBehavior extends TaxonomySynonymsBehavior implements SearchSynonymsBehavior {
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Enables text and number field types for search integration.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Definition of SearchTextSynonymsBehavior class.
|
||||
*/
|
||||
class SearchTextSynonymsBehavior extends TextSynonymsBehavior implements SearchSynonymsBehavior {
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Plugin definition for synonyms search behavior.
|
||||
*/
|
||||
|
||||
$plugin = array(
|
||||
'title' => t('Search'),
|
||||
'description' => t('Integrate synonyms with Search module'),
|
||||
'interface' => 'SearchSynonymsBehavior',
|
||||
'enabled callback' => 'synonyms_search_behavior_search_enabled',
|
||||
'disabled callback' => 'synonyms_search_behavior_search_disabled',
|
||||
);
|
||||
|
||||
/**
|
||||
* Callback for when the behavior is enabled.
|
||||
*
|
||||
* Trigger re-indexing of all the nodes that reference terms from the vocabulary
|
||||
* where the change has taken place.
|
||||
*/
|
||||
function synonyms_search_behavior_search_enabled($behavior_definition, $settings, $instance) {
|
||||
module_load_include('inc', 'synonyms_search', 'synonyms_search.pages');
|
||||
synonyms_search_reindex_nodes_by_vocabulary(taxonomy_vocabulary_machine_name_load($instance['bundle']));
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for when the behavior is disabled.
|
||||
*
|
||||
* Trigger re-indexing of all the nodes that reference terms from the vocabulary
|
||||
* where the change has taken place.
|
||||
*/
|
||||
function synonyms_search_behavior_search_disabled($behavior_definition, $behavior_implementation, $instance) {
|
||||
module_load_include('inc', 'synonyms_search', 'synonyms_search.pages');
|
||||
synonyms_search_reindex_nodes_by_vocabulary(taxonomy_vocabulary_machine_name_load($instance['bundle']));
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
name = Synonyms Search
|
||||
description = "Provides synonyms integration with searching."
|
||||
package = Taxonomy
|
||||
core = 7.x
|
||||
dependencies[] = synonyms
|
||||
dependencies[] = search
|
||||
|
||||
files[] = synonyms_search.test
|
||||
|
||||
files[] = includes/SearchSynonymsBehavior.interface.inc
|
||||
files[] = includes/SearchTextSynonymsBehavior.class.inc
|
||||
files[] = includes/SearchTaxonomySynonymsBehavior.class.inc
|
||||
files[] = includes/SearchEntityReferenceSynonymsBehavior.class.inc
|
||||
|
||||
; Information added by Drupal.org packaging script on 2015-11-29
|
||||
version = "7.x-1.3"
|
||||
core = "7.x"
|
||||
project = "synonyms"
|
||||
datestamp = "1448771941"
|
||||
|
@ -0,0 +1,115 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Provides synonyms integration with searching.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements hook_ctools_plugin_directory().
|
||||
*/
|
||||
function synonyms_search_ctools_plugin_directory($owner, $plugin_type) {
|
||||
switch ($owner) {
|
||||
case 'synonyms':
|
||||
switch ($plugin_type) {
|
||||
case 'behavior':
|
||||
return 'plugins/' . $plugin_type;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_taxonomy_term_update().
|
||||
*/
|
||||
function synonyms_search_taxonomy_term_update($term) {
|
||||
// If we notice at least some change in synonyms of this term, we want to
|
||||
// trigger search re-indexing of nodes, where this term is referenced, since
|
||||
// change in term synonyms affects nodes ranking in the search.
|
||||
if (isset($term->original)) {
|
||||
$bundle = field_extract_bundle('taxonomy_term', $term);
|
||||
$behavior_implementations = synonyms_behavior_get('search', 'taxonomy_term', $bundle, TRUE);
|
||||
|
||||
$current_search_synonyms = array();
|
||||
$previous_search_synonyms = array();
|
||||
|
||||
foreach ($behavior_implementations as $behavior_implementation) {
|
||||
$current_search_synonyms = array_merge($current_search_synonyms, synonyms_extract_synonyms($term, $behavior_implementation));
|
||||
$previous_search_synonyms = array_merge($previous_search_synonyms, synonyms_extract_synonyms($term->original, $behavior_implementation));
|
||||
}
|
||||
$diff = array_diff($current_search_synonyms, $previous_search_synonyms);
|
||||
if (!empty($diff) || count($current_search_synonyms) != count($previous_search_synonyms)) {
|
||||
module_load_include('inc', 'synonyms_search', 'synonyms_search.pages');
|
||||
synonyms_search_reindex_nodes_by_terms(array($term->tid));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_node_update_index().
|
||||
*/
|
||||
function synonyms_search_node_update_index($node) {
|
||||
$output = array();
|
||||
foreach (field_info_instances('node', $node->type) as $instance) {
|
||||
// We go a field by field looking for taxonomy term reference and if that
|
||||
// vocabulary has enabled search synonyms, we add them to the search index.
|
||||
$field_info = field_info_field($instance['field_name']);
|
||||
if ($field_info['type'] == 'taxonomy_term_reference') {
|
||||
// For each term referenced in this node we have to add synonyms.
|
||||
$terms = field_get_items('node', $node, $instance['field_name']);
|
||||
if (is_array($terms) && !empty($terms)) {
|
||||
foreach ($terms as $v) {
|
||||
$output[] = $v['tid'];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($output)) {
|
||||
$terms = taxonomy_term_load_multiple($output);
|
||||
$output = array();
|
||||
foreach ($terms as $term) {
|
||||
$bundle = field_extract_bundle('taxonomy_term', $term);
|
||||
$behavior_implementations = synonyms_behavior_get('search', 'taxonomy_term', $bundle, TRUE);
|
||||
foreach ($behavior_implementations as $implementation) {
|
||||
$output = array_merge($output, synonyms_extract_synonyms($term, $implementation));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return empty($output) ? '' : '<strong>' . implode($output, ', ') . '</strong>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_term_update_index().
|
||||
*/
|
||||
function synonyms_search_term_update_index($term) {
|
||||
$bundle = taxonomy_vocabulary_load($term->vid);
|
||||
$bundle = field_extract_bundle('taxonomy_term', $bundle);
|
||||
$behavior_implementations = synonyms_behavior_get('search', 'taxonomy_term', $bundle, TRUE);
|
||||
|
||||
$synonyms = array();
|
||||
foreach ($behavior_implementations as $implementation) {
|
||||
$synonyms = array_merge($synonyms, synonyms_extract_synonyms($term, $implementation));
|
||||
}
|
||||
return implode(', ', $synonyms);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_synonyms_behavior_implementation_info().
|
||||
*/
|
||||
function synonyms_search_synonyms_behavior_implementation_info($behavior) {
|
||||
switch ($behavior) {
|
||||
case 'search':
|
||||
return array(
|
||||
'number_integer' => 'SearchTextSynonymsBehavior',
|
||||
'number_decimal' => 'SearchTextSynonymsBehavior',
|
||||
'number_float' => 'SearchTextSynonymsBehavior',
|
||||
'text' => 'SearchTextSynonymsBehavior',
|
||||
'taxonomy_term_reference' => 'SearchTaxonomySynonymsBehavior',
|
||||
'entityreference' => 'SearchEntityReferenceSynonymsBehavior',
|
||||
);
|
||||
break;
|
||||
}
|
||||
return array();
|
||||
}
|
@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Supportive functions for the module.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Mark nodes that reference specific terms for search re-indexing.
|
||||
*
|
||||
* This is particularly useful, when the terms in question have been changed.
|
||||
*
|
||||
* @param $tids array
|
||||
* Array of tids of the terms
|
||||
*/
|
||||
function synonyms_search_reindex_nodes_by_terms($tids) {
|
||||
// In order to speed up the process, we will query DB for nid's that reference
|
||||
// handled to us tids, and at the end we'll trigger their re-indexing just in
|
||||
// a single SQL query. Probably it is better to use search_touch_node(), but
|
||||
// that would imply a big amount of SQL queries on some websites.
|
||||
$found_nids = array();
|
||||
foreach (field_info_field_map() as $field_name => $v) {
|
||||
if ($v['type'] == 'taxonomy_term_reference' && isset($v['bundles']['node'])) {
|
||||
// This field is taxonomy term reference and it is attached to nodes, so
|
||||
// we will run EntityFieldQuery on it.
|
||||
$query = new EntityFieldQuery();
|
||||
$result = $query->entityCondition('entity_type', 'node')
|
||||
->fieldCondition($field_name, 'tid', $tids, 'IN')
|
||||
->execute();
|
||||
if (isset($result['node'])) {
|
||||
$found_nids = array_merge($found_nids, array_keys($result['node']));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!empty($found_nids)) {
|
||||
db_update('search_dataset')
|
||||
->fields(array('reindex' => REQUEST_TIME))
|
||||
->condition('type', 'node')
|
||||
->condition('sid', $found_nids, 'IN')
|
||||
->execute();
|
||||
}
|
||||
|
||||
// Integration with Term Search module: reset terms index too.
|
||||
if (module_exists('term_search')) {
|
||||
db_update('search_dataset')
|
||||
->fields(array('reindex' => REQUEST_TIME))
|
||||
->condition('type', 'term')
|
||||
->condition('sid', $tids)
|
||||
->execute();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark nodes that reference terms from a vocabulary for search re-indexing.
|
||||
*
|
||||
* @param $vocabulary object
|
||||
* Fully loaded vocabulary object
|
||||
*/
|
||||
function synonyms_search_reindex_nodes_by_vocabulary($vocabulary) {
|
||||
$tids = db_select('taxonomy_term_data', 't')
|
||||
->fields('t', array('tid'))
|
||||
->condition('vid', $vocabulary->vid)
|
||||
->execute()
|
||||
->fetchCol();
|
||||
if (!empty($tids)) {
|
||||
synonyms_search_reindex_nodes_by_terms($tids);
|
||||
}
|
||||
}
|
@ -0,0 +1,367 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Tests for the Synonyms Search module.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Base class for tests of Synonyms Search module.
|
||||
*/
|
||||
abstract class AbstractSearchSynonymsWebTestCase extends SynonymsWebTestCase {
|
||||
|
||||
protected $behavior = 'search';
|
||||
|
||||
/**
|
||||
* What search type is being tested.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $search_type = 'node';
|
||||
|
||||
/**
|
||||
* Array of terms that will be used for testing.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $terms = array();
|
||||
|
||||
/**
|
||||
* SetUp method.
|
||||
*/
|
||||
public function setUp($modules = array()) {
|
||||
$modules[] = 'synonyms_search';
|
||||
parent::setUp($modules);
|
||||
|
||||
// Create a few terms and synonyms.
|
||||
$term = (object) array(
|
||||
'vid' => $this->vocabulary->vid,
|
||||
'name' => $this->randomName(),
|
||||
$this->fields['disabled']['field']['field_name'] => array(
|
||||
LANGUAGE_NONE => array(
|
||||
array('value' => $this->randomName()),
|
||||
),
|
||||
),
|
||||
);
|
||||
taxonomy_term_save($term);
|
||||
$this->terms['no_synonyms'] = $term;
|
||||
|
||||
$term = (object) array(
|
||||
'vid' => $this->vocabulary->vid,
|
||||
'name' => $this->randomName(),
|
||||
$this->fields['enabled']['field']['field_name'] => array(
|
||||
LANGUAGE_NONE => array(
|
||||
array('value' => $this->randomName()),
|
||||
),
|
||||
),
|
||||
$this->fields['disabled']['field']['field_name'] => array(
|
||||
LANGUAGE_NONE => array(
|
||||
array('value' => $this->randomName()),
|
||||
),
|
||||
),
|
||||
);
|
||||
taxonomy_term_save($term);
|
||||
$this->terms['one_synonym'] = $term;
|
||||
|
||||
$term = (object) array(
|
||||
'vid' => $this->vocabulary->vid,
|
||||
'name' => $this->randomName(),
|
||||
$this->fields['enabled']['field']['field_name'] => array(
|
||||
LANGUAGE_NONE => array(
|
||||
array('value' => $this->randomName()),
|
||||
array('value' => $this->randomName()),
|
||||
),
|
||||
),
|
||||
$this->fields['disabled']['field']['field_name'] => array(
|
||||
LANGUAGE_NONE => array(
|
||||
array('value' => $this->randomName()),
|
||||
),
|
||||
),
|
||||
);
|
||||
taxonomy_term_save($term);
|
||||
$this->terms['two_synonyms'] = $term;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve search results.
|
||||
*
|
||||
* @param $keyword string
|
||||
* Keyword to supply to the search mechanism
|
||||
*
|
||||
* @return array
|
||||
* Array of HTML search results. Each element in this array is a single
|
||||
* search result represented in HTML code as Drupal search mechanism outputs
|
||||
* it
|
||||
*/
|
||||
protected function getSearchResults($keyword) {
|
||||
$response = $this->drupalGet('search/' . $this->search_type . '/' . $keyword);
|
||||
$matches = array();
|
||||
preg_match_all('#\<li[^>]+class="search-result"[^>]*\>(.*?)\</li\>#si', $response, $matches);
|
||||
return $matches[1];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test Synonyms module integration with Drupal search functionality for nodes.
|
||||
*/
|
||||
class NodeSearchSynonymsWebTestCase extends AbstractSearchSynonymsWebTestCase {
|
||||
|
||||
/**
|
||||
* GetInfo method.
|
||||
*/
|
||||
public static function getInfo() {
|
||||
return array(
|
||||
'name' => 'Synonyms node search integration',
|
||||
'description' => 'Ensure that Synonyms module correctly integrates with the Drupal search functionality.',
|
||||
'group' => 'Synonyms',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* SetUp method.
|
||||
*/
|
||||
public function setUp($modules = array()) {
|
||||
parent::setUp($modules);
|
||||
|
||||
// Creating a test content type.
|
||||
$this->drupalPost('admin/structure/types/add', array(
|
||||
'name' => 'Synonyms Test Content',
|
||||
'type' => 'synonyms_test_content',
|
||||
), 'Save content type');
|
||||
|
||||
// Attaching term reference field to the new content type.
|
||||
$field = array(
|
||||
'type' => 'taxonomy_term_reference',
|
||||
'field_name' => 'synonyms_term_enabled',
|
||||
'cardinality' => FIELD_CARDINALITY_UNLIMITED,
|
||||
'settings' => array(
|
||||
'allowed_values' => array(
|
||||
array(
|
||||
'vocabulary' => $this->vocabulary->machine_name,
|
||||
'parent' => 0,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
$field = field_create_field($field);
|
||||
|
||||
$instance = array(
|
||||
'field_name' => $field['field_name'],
|
||||
'entity_type' => 'node',
|
||||
'bundle' => 'synonyms_test_content',
|
||||
'label' => 'Synonym Terms',
|
||||
'widget' => array(
|
||||
'type' => 'synonyms_autocomplete',
|
||||
),
|
||||
);
|
||||
|
||||
field_create_instance($instance);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test searching nodes by a term synonym.
|
||||
*
|
||||
* Since logically term and its synonyms represent the same entity, the idea
|
||||
* is that searching by a term synonym should trigger all content referencing
|
||||
* that term to be included in search results. Additionally we test that when
|
||||
* a synonym is deleted/edited in a term, corresponding content is no longer
|
||||
* encountered when searched by ex-synonym.
|
||||
*/
|
||||
public function testSearchTermSynonym() {
|
||||
// Creating a node, which references all the terms we have.
|
||||
$node = (object) array(
|
||||
'type' => 'synonyms_test_content',
|
||||
'title' => $this->randomName(),
|
||||
'synonyms_term_enabled' => array(LANGUAGE_NONE => array(
|
||||
array('tid' => $this->terms['no_synonyms']->tid),
|
||||
array('tid' => $this->terms['one_synonym']->tid),
|
||||
array('tid' => $this->terms['two_synonyms']->tid),
|
||||
)),
|
||||
);
|
||||
node_save($node);
|
||||
|
||||
// Rebuilding Search index.
|
||||
$this->cronRun();
|
||||
|
||||
foreach ($this->terms as $k => $term) {
|
||||
$this->assertSearchResults($term->name, array($node), 'Searching by name of the term ' . $k);
|
||||
$items = field_get_items('taxonomy_term', $term, $this->fields['disabled']['field']['field_name']);
|
||||
if (is_array($items)) {
|
||||
foreach ($items as $delta => $item) {
|
||||
$this->assertSearchResults($item['value'], array(), 'Searching by not enabled search integration field value #' . $delta . ' of term ' . $k);
|
||||
}
|
||||
}
|
||||
|
||||
$items = field_get_items('taxonomy_term', $term, $this->fields['enabled']['field']['field_name']);
|
||||
if (is_array($items)) {
|
||||
foreach ($items as $delta => $item) {
|
||||
$this->assertSearchResults($item['value'], array($node), 'Searching by synonym #' . $delta . ' of the term ' . $k);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Removing a synonym from the term. Then asserting node got re-indexed with
|
||||
// new values of synonyms.
|
||||
$deleted_synonym = array_pop($this->terms['one_synonym']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE]);
|
||||
taxonomy_term_save($this->terms['one_synonym']);
|
||||
$this->cronRun();
|
||||
$this->assertSearchResults($deleted_synonym['value'], array(), 'Searching by recently deleted synonym of a taxonomy term yields no results.');
|
||||
|
||||
// Editing a synonym in a term. Then asserting node got re-indexed with new
|
||||
// values of synonyms.
|
||||
$ex_synonym = $this->terms['two_synonyms']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE][0]['value'];
|
||||
$this->terms['two_synonyms']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE][0]['value'] = $this->randomName();
|
||||
taxonomy_term_save($this->terms['two_synonyms']);
|
||||
$this->cronRun();
|
||||
$this->assertSearchResults($ex_synonym, array(), 'Searching by recently changed synonym of a taxonomy term yields no results.');
|
||||
|
||||
// We disable entire field from search integration and make sure for all
|
||||
// synonyms search results are empty.
|
||||
synonyms_behavior_settings_delete($this->fields['enabled']['instance']['id'], $this->behavior);
|
||||
$this->cronRun();
|
||||
foreach ($this->terms as $k => $term) {
|
||||
$items = field_get_items('taxonomy_term', $term, $this->fields['enabled']['field']['field_name']);
|
||||
if (is_array($items)) {
|
||||
foreach ($items as $synonym) {
|
||||
$this->assertSearchResults($synonym['value'], array(), 'Searching by ' . $k . ' term synonym, which field was recently disabled from search behavior yields no results.');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert search results.
|
||||
*
|
||||
* @param $keyword string
|
||||
* Keyword to supply to the search mechanism
|
||||
* @param $results array
|
||||
* Array of fully loaded nodes that are expected to be on search results
|
||||
* @param $message string
|
||||
* Drupal assertion message to display on test results page
|
||||
*/
|
||||
protected function assertSearchResults($keyword, $results, $message) {
|
||||
$matches = $this->getSearchResults($keyword);
|
||||
if (count($matches) != count($results)) {
|
||||
$this->fail($message);
|
||||
return;
|
||||
}
|
||||
$matches = implode('', $matches);
|
||||
foreach ($results as $node) {
|
||||
if (strpos($matches, 'node/' . $node->nid) === FALSE) {
|
||||
$this->fail($message);
|
||||
return;
|
||||
}
|
||||
}
|
||||
$this->pass($message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test Synonyms module integration with Drupal search for taxonomy terms.
|
||||
*/
|
||||
class TermSearchSynonymsWebTestCase extends AbstractSearchSynonymsWebTestCase {
|
||||
|
||||
protected $search_type = 'term';
|
||||
|
||||
/**
|
||||
* GetInfo method.
|
||||
*/
|
||||
public static function getInfo() {
|
||||
return array(
|
||||
'name' => 'Synonyms term search integration',
|
||||
'description' => 'Ensure that Synonyms module correctly integrates with the Term Search module.',
|
||||
'group' => 'Synonyms',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* SetUp method.
|
||||
*/
|
||||
public function setUp($modules = array()) {
|
||||
$modules[] = 'term_search';
|
||||
parent::setUp($modules);
|
||||
$active_searches = variable_get('search_active_modules', array('node', 'user'));
|
||||
$active_searches[] = 'term_search';
|
||||
variable_set('search_active_modules', $active_searches);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test searching terms by their synonyms.
|
||||
*/
|
||||
public function testSearchTermSynonym() {
|
||||
// Rebuilding Search index.
|
||||
$this->cronRun();
|
||||
|
||||
foreach ($this->terms as $k => $term) {
|
||||
$this->assertSearchResults($term->name, array($term), 'Searching by name of the term ' . $k);
|
||||
$items = field_get_items('taxonomy_term', $term, $this->fields['disabled']['field']['field_name']);
|
||||
if (is_array($items)) {
|
||||
foreach ($items as $delta => $item) {
|
||||
$this->assertSearchResults($item['value'], array(), 'Searching by not enabled search integration field value #' . $delta . ' of term ' . $k);
|
||||
}
|
||||
}
|
||||
|
||||
$items = field_get_items('taxonomy_term', $term, $this->fields['enabled']['field']['field_name']);
|
||||
if (is_array($items)) {
|
||||
foreach ($items as $delta => $item) {
|
||||
$this->assertSearchResults($item['value'], array($term), 'Searching by synonym #' . $delta . ' of the term ' . $k);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Removing a synonym from the term. Then asserting it got re-indexed with
|
||||
// new values of synonyms.
|
||||
$deleted_synonym = array_pop($this->terms['one_synonym']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE]);
|
||||
taxonomy_term_save($this->terms['one_synonym']);
|
||||
$this->cronRun();
|
||||
$this->assertSearchResults($deleted_synonym['value'], array(), 'Searching by recently deleted synonym of a taxonomy term yields no results.');
|
||||
|
||||
// Editing a synonym in a term. Then asserting it got re-indexed with new
|
||||
// values of synonyms.
|
||||
$ex_synonym = $this->terms['two_synonyms']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE][0]['value'];
|
||||
$this->terms['two_synonyms']->{$this->fields['enabled']['field']['field_name']}[LANGUAGE_NONE][0]['value'] = $this->randomName();
|
||||
taxonomy_term_save($this->terms['two_synonyms']);
|
||||
$this->cronRun();
|
||||
$this->assertSearchResults($ex_synonym, array(), 'Searching by recently changed synonym of a taxonomy term yields no results.');
|
||||
|
||||
// We disable entire field from search integration and make sure for all
|
||||
// synonyms search results are empty.
|
||||
synonyms_behavior_settings_delete($this->fields['enabled']['instance']['id'], $this->behavior);
|
||||
$this->cronRun();
|
||||
foreach ($this->terms as $k => $term) {
|
||||
$items = field_get_items('taxonomy_term', $term, $this->fields['enabled']['field']['field_name']);
|
||||
if (is_array($items)) {
|
||||
foreach ($items as $synonym) {
|
||||
$this->assertSearchResults($synonym['value'], array(), 'Searching by ' . $k . ' term synonym, which field was recently disabled from search behavior yields no results.');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert search results.
|
||||
*
|
||||
* @param $keyword string
|
||||
* Keyword to supply to the search mechanism
|
||||
* @param $results array
|
||||
* Array of fully loaded terms that are expected to be on search results
|
||||
* @param $message string
|
||||
* Drupal assertion message to display on test results page
|
||||
*/
|
||||
protected function assertSearchResults($keyword, $results, $message) {
|
||||
$matches = $this->getSearchResults($keyword);
|
||||
if (count($matches) != count($results)) {
|
||||
$this->fail($message);
|
||||
return;
|
||||
}
|
||||
$matches = implode('', $matches);
|
||||
foreach ($results as $term) {
|
||||
if (strpos($matches, 'taxonomy/term/' . $term->tid) === FALSE) {
|
||||
$this->fail($message);
|
||||
return;
|
||||
}
|
||||
}
|
||||
$this->pass($message);
|
||||
}
|
||||
|
||||
}
|
@ -7,11 +7,27 @@
|
||||
|
||||
/**
|
||||
* Implements hook_views_plugins_alter().
|
||||
*
|
||||
* @see hook_views_plugins_alter()
|
||||
*/
|
||||
function synonyms_views_plugins_alter(&$plugins) {
|
||||
// Replace default taxonomy term argument validator with our extended version,
|
||||
// which can also handle a term synonym as an argument.
|
||||
$plugins['argument validator']['taxonomy_term']['handler'] = 'synonyms_views_plugin_argument_validate_taxonomy_term';
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_field_views_data_alter().
|
||||
*/
|
||||
function synonyms_field_views_data_alter(&$result, $field, $module) {
|
||||
if ($field['type'] == 'taxonomy_term_reference') {
|
||||
// Add synonyms friendly autocomplete filter.
|
||||
foreach ($field['storage']['details']['sql'] as $table) {
|
||||
$tid_column = reset($table);
|
||||
$tid_column = $tid_column['tid'];
|
||||
$table = array_keys($table);
|
||||
$table = $table[0];
|
||||
if (isset($result[$table][$tid_column]['filter'])) {
|
||||
$result[$table][$tid_column]['filter']['handler'] = 'synonyms_views_handler_filter_term_tid';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,174 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Definition of synonyms_views_handler_filter_term_tid class.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Synonyms friendly taxonomy filter handler.
|
||||
*/
|
||||
class synonyms_views_handler_filter_term_tid extends views_handler_filter_term_node_tid {
|
||||
function extra_options_form(&$form, &$form_state) {
|
||||
parent::extra_options_form($form, $form_state);
|
||||
|
||||
$form['type']['#options']['synonyms_autocomplete'] = t('Synonyms friendly autocomplete');
|
||||
}
|
||||
|
||||
function value_form(&$form, &$form_state) {
|
||||
$restore_value = $this->options['type'] == 'synonyms_autocomplete';
|
||||
if ($restore_value) {
|
||||
$this->options['type'] = 'textfield';
|
||||
}
|
||||
parent::value_form($form, $form_state);
|
||||
|
||||
if ($restore_value) {
|
||||
// We need to determine the entity type onto which this field is attached
|
||||
// that is used in this view.
|
||||
$entity_type_base_table = $this->view->base_table;
|
||||
// TODO: it would be nice to consider the existence of relationships, but
|
||||
// I just couldn't figure it out at that time.
|
||||
|
||||
$entity_info = entity_get_info();
|
||||
$field_entity_type = FALSE;
|
||||
$field = field_info_field($this->definition['field_name']);
|
||||
|
||||
foreach ($field['bundles'] as $entity_type => $bundles) {
|
||||
if ($entity_info[$entity_type]['base table'] == $entity_type_base_table) {
|
||||
$field_entity_type = $entity_type;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$field_entity_type) {
|
||||
// Seems like we failed to determine the entity type which is used for
|
||||
// this field in the view. Well, it's not a fatal fail, we'll just use
|
||||
// whatever then.
|
||||
$field_entity_type = array_keys($field['bundles']);
|
||||
$field_entity_type = $field_entity_type[0];
|
||||
}
|
||||
|
||||
// We just grab the first instance of this field within the determined
|
||||
// entity type.
|
||||
$bundle = $field['bundles'][$field_entity_type][0];
|
||||
|
||||
$instance = field_info_instance($field_entity_type, $field['field_name'], $bundle);
|
||||
if ($instance['widget']['type'] == 'synonyms_autocomplete') {
|
||||
$widget = $instance['widget']['settings'];
|
||||
}
|
||||
else {
|
||||
$widget = field_info_widget_settings('synonyms_autocomplete');
|
||||
}
|
||||
$autocomplete_path = $widget['synonyms_autocomplete_path'];
|
||||
$size = $widget['size'];
|
||||
|
||||
$form['value']['#autocomplete_path'] = $autocomplete_path . '/' . $this->definition['field_name'] . '/' . $field_entity_type . '/' . $bundle;
|
||||
$form['value']['#size'] = $size;
|
||||
$form['value']['#auto_creation'] = FALSE;
|
||||
$form['value']['#attributes']['class'][] = 'synonyms-autocomplete';
|
||||
$form['value']['#attached']['js'][drupal_get_path('module', 'synonyms') . '/js/synonyms-autocomplete.js'] = array();
|
||||
$this->options['type'] = 'synonyms_autocomplete';
|
||||
}
|
||||
}
|
||||
|
||||
function value_validate($form, &$form_state) {
|
||||
if ($this->options['type'] == 'synonyms_autocomplete') {
|
||||
$values = drupal_explode_tags($form_state['values']['options']['value']);
|
||||
$tids = $this->synonyms_validate_term_strings($form['value'], $values);
|
||||
|
||||
if ($tids) {
|
||||
$form_state['values']['options']['value'] = $tids;
|
||||
}
|
||||
}
|
||||
else {
|
||||
parent::value_validate($form, $form_state);
|
||||
}
|
||||
}
|
||||
|
||||
function exposed_validate(&$form, &$form_state) {
|
||||
if ($this->options['type'] == 'synonyms_autocomplete') {
|
||||
if (empty($this->options['exposed'])) {
|
||||
return;
|
||||
}
|
||||
if (empty($this->options['expose']['identifier'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$identifier = $this->options['expose']['identifier'];
|
||||
$values = drupal_explode_tags($form_state['values'][$identifier]);
|
||||
$tids = $this->synonyms_validate_term_strings($form[$identifier], $values);
|
||||
|
||||
if ($tids) {
|
||||
$this->validated_exposed_input = $tids;
|
||||
}
|
||||
}
|
||||
else {
|
||||
parent::exposed_validate($form, $form_state);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the user string.
|
||||
*
|
||||
* In a great extend it does the same job as parent::validate_term_strings(),
|
||||
* just that this implementation is synonyms-aware.
|
||||
*
|
||||
* @param $element
|
||||
* The form element which is used, either the views ui or the exposed
|
||||
* filters.
|
||||
* @param $values
|
||||
* The taxonomy names/synonyms which will be converted to tids.
|
||||
*
|
||||
* @return array
|
||||
* The taxonomy ids for all validated terms.
|
||||
*/
|
||||
protected function synonyms_validate_term_strings($element, $values) {
|
||||
if (empty($values)) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$values = array_map('drupal_strtolower', $values);
|
||||
$missing = array_flip($values);
|
||||
|
||||
$tids = array();
|
||||
|
||||
$vocabulary = taxonomy_vocabulary_machine_name_load($this->options['vocabulary']);
|
||||
|
||||
// Firstly looking up the entered tags as if they were term names. Then,
|
||||
// the remaining tags are looked up as if they were synonyms of terms.
|
||||
// Lastly, if any tags are left at this point, we mark form validation
|
||||
// error.
|
||||
$query = db_select('taxonomy_term_data', 'td');
|
||||
$query->fields('td', array('tid', 'name'));
|
||||
$query->condition('td.vid', $vocabulary->vid);
|
||||
$query->condition('td.name', $values);
|
||||
$query->addTag('term_access');
|
||||
$result = $query->execute();
|
||||
foreach ($result as $term) {
|
||||
unset($missing[drupal_strtolower($term->name)]);
|
||||
$tids[] = $term->tid;
|
||||
}
|
||||
|
||||
$behavior_implementations = synonyms_behavior_get('autocomplete', 'taxonomy_term', $vocabulary->machine_name, TRUE);
|
||||
foreach ($behavior_implementations as $behavior_implementation) {
|
||||
if (!empty($missing)) {
|
||||
$condition = db_or();
|
||||
foreach ($missing as $tag => $v) {
|
||||
$condition->condition(AbstractSynonymsSynonymsBehavior::COLUMN_PLACEHOLDER, $tag);
|
||||
}
|
||||
$synonyms = synonyms_synonyms_find_behavior($condition, $behavior_implementation);
|
||||
foreach ($synonyms as $synonym) {
|
||||
$synonym->synonym = drupal_strtolower($synonym->synonym);
|
||||
unset($missing[$synonym->synonym]);
|
||||
$tids[] = $synonym->entity_id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($missing) && !empty($this->options['error_message'])) {
|
||||
form_error($element, format_plural(count($missing), 'Unable to find term: @terms', 'Unable to find terms: @terms', array('@terms' => implode(', ', array_keys($missing)))));
|
||||
}
|
||||
|
||||
return $tids;
|
||||
}
|
||||
}
|
@ -27,18 +27,10 @@ class synonyms_views_plugin_argument_validate_taxonomy_term extends views_plugin
|
||||
switch ($type) {
|
||||
case 'synonym':
|
||||
case 'synonym_tid':
|
||||
// We are requested to do synonyms friendly validation. We will have to
|
||||
// go through each of allowed vocabularies, collecting terms whose
|
||||
// synonyms might look like the argument we have. However, it is not
|
||||
// that easy to achieve due to the fact that synonyms may be kept in
|
||||
// whatever format in whatever column and only corresponding Synonyms
|
||||
// extractor class has the knowledge how it is organized. Firstly we are
|
||||
// going to query database trying to find a term with our argument's
|
||||
// We are requested to do synonyms friendly validation. Firstly we are
|
||||
// going to query the database trying to find a term with our argument's
|
||||
// name. If we find one, it is great and we stop right there. Otherwise,
|
||||
// the nightmare starts: we are going to use
|
||||
// AbstractSynonymsExtractor::processEntityFieldQuery() method to find
|
||||
// synonyms that are similar to our argument. Then we will load those
|
||||
// terms and manually in PHP will determine whether it is a match.
|
||||
// we look up by synonyms.
|
||||
$query = db_select('taxonomy_term_data', 't')
|
||||
->fields('t', array('tid', 'name'));
|
||||
if (!empty($vocabularies)) {
|
||||
@ -46,7 +38,7 @@ class synonyms_views_plugin_argument_validate_taxonomy_term extends views_plugin
|
||||
$query->condition('v.machine_name', $vocabularies);
|
||||
}
|
||||
if ($transform) {
|
||||
$query->where("replace(t.name, ' ', '-') = :name", array(':name' => $argument));
|
||||
$query->where("REPLACE(t.name, ' ', '-') = :name", array(':name' => $argument));
|
||||
}
|
||||
else {
|
||||
$query->condition('t.name', $argument, '=');
|
||||
@ -61,76 +53,32 @@ class synonyms_views_plugin_argument_validate_taxonomy_term extends views_plugin
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
$bundles = $vocabularies;
|
||||
if (empty($vocabularies)) {
|
||||
// At this point we want to convert an empty $vocabulary (implicitly
|
||||
// meaning "all") into actually a list of all fully loaded
|
||||
// vocabularies.
|
||||
$vocabularies = taxonomy_vocabulary_load_multiple(FALSE);
|
||||
// At this point we want to convert an empty $vocabularies (implicitly
|
||||
// meaning "all") into actually a list of all vocabularies.
|
||||
$bundles = array_keys(taxonomy_vocabulary_get_names());
|
||||
}
|
||||
else {
|
||||
// We need to load the filtered vocabularies based on their machine
|
||||
// names.
|
||||
foreach ($vocabularies as $v) {
|
||||
$vocabularies[$v] = taxonomy_vocabulary_machine_name_load($v);
|
||||
|
||||
foreach ($bundles as $bundle) {
|
||||
$condition = db_and();
|
||||
if ($transform) {
|
||||
$condition->where("REPLACE(" . AbstractSynonymsSynonymsBehavior::COLUMN_PLACEHOLDER . ", ' ', '-') = :argument", array(
|
||||
':argument' => $argument,
|
||||
));
|
||||
}
|
||||
else {
|
||||
$condition->condition(AbstractSynonymsSynonymsBehavior::COLUMN_PLACEHOLDER, $argument);
|
||||
}
|
||||
$synonyms = synonyms_synonyms_find($condition, 'taxonomy_term', $bundle);
|
||||
if (!empty($synonyms)) {
|
||||
$term = taxonomy_term_load($synonyms[0]->entity_id);
|
||||
$this->argument->argument = $this->synonyms_get_term_property($term);
|
||||
$this->argument->validated_title = check_plain(entity_label('taxonomy_term', $term));
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
// We haven't had much luck, seems like we have to do the hard part. We
|
||||
// will pull up terms that are similar to our argument using the same
|
||||
// functionality as for synonyms friendly autocomplete widget and then
|
||||
// will find those who actually match among this narrowed set of terms.
|
||||
// Since $match will be use as value for LIKE SQL operator, we are not
|
||||
// sure whether we need to substitute dash (-) with spacebar, or keep
|
||||
// the dash, to match both we will use underscore (_) since this symbol
|
||||
// matches one symbol in LIKE operator.
|
||||
$match = $transform ? str_replace('-', '_', $argument) : $argument;
|
||||
$tids = array();
|
||||
// Unfortunately we have to query multiple times the database for each
|
||||
// vocabulary for each synonyms-source field.
|
||||
foreach ($vocabularies as $vocabulary) {
|
||||
$bundle = field_extract_bundle('taxonomy_term', $vocabulary);
|
||||
foreach (synonyms_synonyms_fields($vocabulary) as $field) {
|
||||
$field = field_info_field($field);
|
||||
$instance = field_info_instance('taxonomy_term', $field['field_name'], $bundle);
|
||||
$query = new EntityFieldQuery();
|
||||
$query->entityCondition('entity_type', 'taxonomy_term')
|
||||
->entityCondition('bundle', $vocabulary->machine_name);
|
||||
|
||||
// We let the class that defines this field type as a source of
|
||||
// synonyms filter out and provide its suggestions based on this
|
||||
// field.
|
||||
$class = synonyms_extractor_info($field['type']);
|
||||
call_user_func(array($class, 'processEntityFieldQuery'), $match, $query, $field, $instance);
|
||||
if (!empty($tids)) {
|
||||
$query->entityCondition('entity_id', $tids, 'NOT IN');
|
||||
}
|
||||
$result = $query->execute();
|
||||
if (isset($result['taxonomy_term'])) {
|
||||
$tids = array_merge($tids, array_keys($result['taxonomy_term']));
|
||||
}
|
||||
}
|
||||
}
|
||||
$terms = taxonomy_term_load_multiple($tids);
|
||||
// We have an array of terms whose synonyms look like they could be
|
||||
// equal to our argument, let us iterate over each term's synonyms to
|
||||
// actually see if they match.
|
||||
$argument = mb_strtoupper($argument, 'UTF-8');
|
||||
foreach ($terms as $term) {
|
||||
foreach (synonyms_get_term_synonyms($term) as $synonym) {
|
||||
$synonym = mb_strtoupper($synonym['safe_value'], 'UTF-8');
|
||||
if ($transform) {
|
||||
$synonym = str_replace(' ', '-', $synonym);
|
||||
}
|
||||
if ($synonym == $argument) {
|
||||
// Finally we found a synonym that matches our argument. We are
|
||||
// going to use the corresponding term and there is no point to
|
||||
// continue searching.
|
||||
$this->argument->argument = $this->synonyms_get_term_property($term);
|
||||
$this->argument->validated_title = check_plain(entity_label('taxonomy_term', $term));
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
// We haven't found any synonym that matched our argument, so there is
|
||||
// no term to return.
|
||||
return FALSE;
|
||||
|
Loading…
x
Reference in New Issue
Block a user