'Synonyms', 'description' => 'Manage synonyms settings for all entity types.', 'page callback' => 'synonyms_settings_overview', 'access arguments' => array('administer synonyms'), 'file' => 'synonyms.pages.inc', 'type' => MENU_NORMAL_ITEM, ); $items['admin/structure/synonyms/%synonyms_entity_type/%synonyms_bundle'] = array( 'title' => 'Synonyms settings', 'title callback' => 'synonyms_settings_title', 'title arguments' => array(3, 4), 'description' => 'Manage synonyms settings.', 'page callback' => 'drupal_get_form', 'page arguments' => array('synonyms_settings_form', 3, 4), 'access arguments' => array('administer synonyms'), 'load arguments' => array(3), 'file' => 'synonyms.pages.inc', ); if (module_exists('taxonomy')) { $items['synonyms/autocomplete-taxonomy-term/%/%/%'] = array( 'title' => 'Autocomplete Synonyms', 'page callback' => 'synonyms_autocomplete_taxonomy_term', 'page arguments' => array(2, 3, 4), 'access arguments' => array('access content'), 'file' => 'synonyms.pages.inc', 'type' => MENU_CALLBACK, ); } $items['synonyms/autocomplete-entity/%/%/%'] = array( 'title' => 'Autocomplete Synonyms', 'page callback' => 'synonyms_autocomplete_entity', 'page arguments' => array(2, 3, 4), 'access callback' => 'entityreference_autocomplete_access_callback', 'access arguments' => array('tags', 2, 3, 4), 'file' => 'synonyms.pages.inc', 'type' => MENU_CALLBACK, ); return $items; } /** * Implements hook_permission(). */ function synonyms_permission() { return array( 'administer synonyms' => array( 'title' => t('Administer synonyms'), 'description' => t('Administer synonyms of all entity types.'), ), ); } /** * Implements hook_ctools_plugin_type(). */ function synonyms_ctools_plugin_type() { $plugins = array(); $plugins['behavior'] = array( 'defaults' => array( 'title' => NULL, 'description' => NULL, 'settings form callback' => NULL, 'interface' => NULL, 'enabled callback' => NULL, 'disabled callback' => NULL, ), ); return $plugins; } /** * Implements hook_ctools_plugin_directory(). */ function synonyms_ctools_plugin_directory($owner, $plugin_type) { switch ($owner) { case 'synonyms': switch ($plugin_type) { case 'behavior': return 'plugins/' . $plugin_type; } break; case 'ctools': switch ($plugin_type) { case 'arguments': return 'plugins/' . $plugin_type; } break; case 'feeds_tamper': switch ($plugin_type) { case 'plugins': return 'plugins/feeds_tamper'; } break; } } /** * Implements hook_theme(). */ function synonyms_theme() { return array( 'synonyms_behaviors_settings' => array( 'render element' => 'element', 'file' => 'synonyms.pages.inc', ), ); } /** * Implements hook_entity_property_info(). */ function synonyms_entity_property_info() { $info = array(); foreach (entity_get_info() as $entity_type => $entity_info) { $entity_type = synonyms_entity_type_load($entity_type); if ($entity_type) { $info[$entity_type]['properties']['synonyms'] = array( 'label' => t('Synonyms'), 'description' => t('Synonyms of entity.'), 'type' => 'list', 'getter callback' => 'synonyms_get_sanitized', 'computed' => TRUE, 'sanitized' => TRUE, 'raw getter callback' => 'synonyms_get_raw', 'translatable' => TRUE, ); } } return $info; } /** * Implements hook_form_FORM_ID_alter(). */ function synonyms_form_taxonomy_form_vocabulary_alter(&$form, &$form_state) { // TODO: remove this function after a few releases of Synonyms module, since // now the module oversees synonyms of all entities, not only taxonomy terms. if (isset($form_state['confirm_delete']) && $form_state['confirm_delete']) { return; } if (!isset($form['#vocabulary']->vid) || !$form['#vocabulary']->vid) { return; } $form['synonyms'] = array( '#type' => 'fieldset', '#title' => t('Synonyms'), '#collapsible' => TRUE, ); $form['synonyms']['link'] = array( '#markup' => t('You can configure synonyms of @vocabulary following this link.', array( '@vocabulary' => $form['#vocabulary']->name, '@url' => url('admin/structure/synonyms/taxonomy_term/' . $form['#vocabulary']->machine_name, array('query' => drupal_get_destination())), )), ); } /** * Implements hook_field_widget_info(). */ function synonyms_field_widget_info() { return array( 'synonyms_autocomplete_taxonomy_term' => array( 'label' => t('Synonyms friendly autocomplete'), 'field types' => array('taxonomy_term_reference'), 'settings' => array( 'size' => 60, 'synonyms_autocomplete_path' => 'synonyms/autocomplete-taxonomy-term', 'suggestion_size' => 10, 'suggest_only_unique' => FALSE, 'auto_creation' => 1, ), 'behaviors' => array( 'multiple values' => FIELD_BEHAVIOR_CUSTOM, ), ), 'synonyms_autocomplete_entity' => array( 'label' => t('Synonyms friendly autocomplete'), 'field types' => array('entityreference'), 'settings' => array( 'size' => 60, 'synonyms_autocomplete_path' => 'synonyms/autocomplete-entity', 'suggestion_size' => 10, 'suggest_only_unique' => FALSE, ), 'behaviors' => array( 'multiple values' => FIELD_BEHAVIOR_CUSTOM, ), ), 'synonyms_select_taxonomy_term' => array( 'label' => t('Synonyms friendly select list'), 'field types' => array('taxonomy_term_reference'), 'settings' => array( 'sort' => 'weight', ), 'behaviors' => array( 'multiple values' => FIELD_BEHAVIOR_CUSTOM, ), ), 'synonyms_select_entity' => array( 'label' => t('Synonyms friendly select list'), 'field types' => array('entityreference'), 'settings' => array(), 'behaviors' => array( 'multiple values' => FIELD_BEHAVIOR_CUSTOM, ), ), ); } /** * Implements hook_field_widget_settings_form(). */ function synonyms_field_widget_settings_form($field, $instance) { $widget = $instance['widget']; $settings = $widget['settings'] + field_info_widget_settings($widget['type']); $form = array(); switch ($widget['type']) { case 'synonyms_autocomplete_taxonomy_term': case 'synonyms_autocomplete_entity': if ($widget['type'] == 'synonyms_autocomplete_taxonomy_term') { $form['auto_creation'] = array( '#type' => 'checkbox', '#title' => t('Allow auto-creation?'), '#description' => t('Whether users may create a new term by typing in a non-existing name into this field.'), '#default_value' => $settings['auto_creation'], ); } $form['suggestion_size'] = array( '#type' => 'textfield', '#title' => t('Suggestions Size'), '#description' => t('Please, enter how many suggested entities to show in the autocomplete textfield.'), '#required' => TRUE, '#element_validate' => array('element_validate_integer_positive'), '#default_value' => $settings['suggestion_size'], ); $form['suggest_only_unique'] = array( '#type' => 'checkbox', '#title' => t('Suggest only one entry per term'), '#description' => t('If you want to include only term name or a single synonym, suggesting a particular term, while disregarding all ongoing ones, please, tick this checkbox on.'), '#default_value' => $settings['suggest_only_unique'], ); break; case 'synonyms_select_taxonomy_term': $form['sort'] = array( '#type' => 'radios', '#title' => t('Sort'), '#description' => t('Choose by what criterion the items within select should be sorted.'), '#options' => array( 'weight' => t('As in taxonomy vocabulary (by weight)'), 'name' => t('By name of terms and their synonyms'), ), '#default_value' => $settings['sort'], ); break; } return $form; } /** * Implements hook_field_widget_form(). */ function synonyms_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) { $default_value = synonyms_select_default_value($field, $instance, $items); switch ($instance['widget']['type']) { case 'synonyms_autocomplete_taxonomy_term': case 'synonyms_autocomplete_entity': switch ($instance['widget']['type']) { case 'synonyms_autocomplete_taxonomy_term': $default_value_string = taxonomy_implode_tags(taxonomy_term_load_multiple($default_value)); $element_validate = array('taxonomy_autocomplete_validate', 'synonyms_autocomplete_taxonomy_term_validate'); break; case 'synonyms_autocomplete_entity': $default_value_string = array(); $entity = isset($element['#entity']) ? $element['#entity'] : NULL; $handler = entityreference_get_selection_handler($field, $instance, $instance['entity_type'], $entity); $target_entities = entity_load($field['settings']['target_type'], $default_value); foreach ($target_entities as $target_entity_id => $target_entity) { $default_value_string[] = synonyms_autocomplete_escape($handler->getLabel($target_entity)); } $default_value_string = drupal_implode_tags($default_value_string); $element_validate = array('synonyms_autocomplete_entity_validate'); break; } $element += array( '#type' => 'textfield', '#default_value' => $default_value_string, '#autocomplete_path' => $instance['widget']['settings']['synonyms_autocomplete_path'] . '/' . $field['field_name'] . '/' . $instance['entity_type'] . '/' . $instance['bundle'], '#size' => $instance['widget']['settings']['size'], '#maxlength' => 1024, '#element_validate' => $element_validate, '#attached' => array( 'js' => array( drupal_get_path('module', 'synonyms') . '/js/synonyms-autocomplete.js' => array(), ), ), '#attributes' => array( 'class' => array('synonyms-autocomplete'), ), ); if (isset($instance['widget']['settings']['auto_creation'])) { $element['#auto_creation'] = $instance['widget']['settings']['auto_creation']; } break; case 'synonyms_select_taxonomy_term': $multiple = $field['cardinality'] > 1 || $field['cardinality'] == FIELD_CARDINALITY_UNLIMITED; $options = array(); foreach ($field['settings']['allowed_values'] as $tree) { if ($vocabulary = taxonomy_vocabulary_machine_name_load($tree['vocabulary'])) { switch ($instance['widget']['settings']['sort']) { case 'weight': if ($terms = taxonomy_get_tree($vocabulary->vid, $tree['parent'], NULL, TRUE)) { $behavior_implementations = synonyms_behavior_get('select', 'taxonomy_term', field_extract_bundle('taxonomy_term', $vocabulary), TRUE); foreach ($terms as $term) { $options[] = synonyms_select_option_entity($term, 'taxonomy_term', NULL, NULL, array('depth')); foreach ($behavior_implementations as $implementation) { foreach ($implementation['object']->extractSynonyms($term) as $synonym) { $options[] = synonyms_select_option_entity($term, 'taxonomy_term', $synonym, $implementation, array('depth')); } } } } break; case 'name': // TODO: is there any way to leverage DB for the sorting routine? $options = synonyms_select_taxonomy_term_sort_name_options_recursive($vocabulary, $tree['parent']); break; } } } $element += array( '#type' => 'select', '#multiple' => $multiple, '#options' => $options, '#default_value' => $default_value, '#element_validate' => array('synonyms_select_validate', 'synonyms_select_form_to_storage'), '#empty_option' => t('- None -'), ); break; case 'synonyms_select_entity': $multiple = $field['cardinality'] > 1 || $field['cardinality'] == FIELD_CARDINALITY_UNLIMITED; $element += array( '#type' => 'select', '#multiple' => $multiple, '#options' => synonyms_select_entity_options($field, $instance, $element['#entity']), '#default_value' => $default_value, '#element_validate' => array('synonyms_select_validate', 'synonyms_select_form_to_storage'), '#empty_option' => t('- None -'), ); break; } return $element; } /** * Implements hook_field_widget_error(). */ function synonyms_field_widget_error($element, $error, $form, &$form_state) { form_error($element, $error['message']); } /** * Implements hook_features_api(). */ function synonyms_features_api() { return array( 'synonyms' => array( 'name' => t('Synonyms'), 'file' => drupal_get_path('module', 'synonyms') . '/synonyms.features.inc', 'default_hook' => 'default_synonyms', 'feature_source' => TRUE, ), ); } /** * Implements hook_modules_disabled(). */ function synonyms_modules_disabled($modules) { foreach ($modules as $module) { db_delete('synonyms_settings') ->condition('provider', db_like($module) . '%', 'LIKE') ->execute(); } } /** * Form element validate handler. * * Handle validation for taxonomy term synonym-friendly autocomplete element. */ function synonyms_autocomplete_taxonomy_term_validate($element, &$form_state) { // After taxonomy_autocomplete_validate() has finished its job any terms it // didn't find have been set for autocreation. We need to: // (a) Double-check that those terms are not synonyms. // (b) Check that synonyms' configurable auto-creation option is enabled. $value = drupal_array_get_nested_value($form_state['values'], $element['#parents']); $tids = array(); $field = field_widget_field($element, $form_state); foreach ($value as $delta => $term) { if ($term['tid'] == 'autocreate') { $synonym_tid = 0; foreach ($field['settings']['allowed_values'] as $tree) { $behavior_implementations = synonyms_behavior_get('autocomplete', 'taxonomy_term', $tree['vocabulary'], TRUE); foreach ($behavior_implementations as $behavior_implementation) { $synonyms = $behavior_implementation['object']->synonymsFind(db_and()->condition(AbstractSynonymsBehavior::COLUMN_SYNONYM_PLACEHOLDER, $term['name'])); foreach ($synonyms as $synonym) { $synonym_tid = $synonym->entity_id; break(2); } } } if ($synonym_tid != 0 && !in_array($synonym_tid, $tids)) { $value[$delta]['tid'] = $synonym_tid; $tids[] = $synonym_tid; } elseif (!$element['#auto_creation']) { unset($value[$delta]); } } else { $tids[] = $term['tid']; } } $value = array_values($value); form_set_value($element, $value, $form_state); } /** * Form element validate handler. * * Validate entity reference synonyms friendly autocomplete element. */ function synonyms_autocomplete_entity_validate($element, &$form_state) { $input = drupal_map_assoc(drupal_explode_tags(drupal_strtolower($element['#value']))); $value = array(); if (!empty($input)) { $field = field_info_field($element['#field_name']); $instance = field_info_instance($element['#entity_type'], $field['field_name'], $element['#bundle']); $handler = entityreference_get_selection_handler($field, $instance); foreach ($input as $k => $v) { $matches = $handler->getReferencableEntities($v, '='); foreach ($matches as $bundle => $entity_ids) { $entities = entity_load($field['settings']['target_type'], array_keys($entity_ids)); foreach ($entity_ids as $entity_id => $label) { $value[] = $entity_id; unset($input[drupal_strtolower(entity_label($field['settings']['target_type'], $entities[$entity_id]))]); } } } if (!empty($input)) { $behavior_implementations = synonyms_behavior_get('autocomplete', $field['settings']['target_type'], synonyms_field_target_bundles($field), TRUE); foreach ($behavior_implementations as $implementation) { $condition = db_and(); $condition->condition(AbstractSynonymsBehavior::COLUMN_SYNONYM_PLACEHOLDER, $input, 'IN'); foreach ($implementation['object']->synonymsFind(clone $condition) as $synonym) { $value[] = $synonym->entity_id; unset($input[drupal_strtolower($synonym->synonym)]); if (empty($input)) { break(2); } } } } } $tmp = array_unique($value); $value = array(); foreach ($tmp as $target_id) { $value[] = array('target_id' => $target_id); } form_set_value($element, $value, $form_state); } /** * Try finding an entity by its name or synonym. * * @param string $entity_type * What entity type is being searched * @param string $name * The look up keyword (the supposed name or synonym) * @param string $bundle * Optionally limit the search within a specific bundle name of the provided * entity type * * @return int * ID of the looked up entity. If such entity was not found, then 0 is * returned */ function synonyms_get_entity_by_synonym($entity_type, $name, $bundle = NULL) { $name = trim($name); $entity_info = entity_get_info($entity_type); // This is somewhat hacky, but it's the best we can do: user.module does not // declare 'label' entity key on 'user' entity type, while there is clearly // one: the 'name' column. In fact, entityreference.module does about the same // thing in EntityReference_SelectionHandler_Generic_user class. if ($entity_type == 'user') { $entity_info['entity keys']['label'] = 'name'; } if (isset($entity_info['entity keys']['label'])) { $efq = new EntityFieldQuery(); $efq->entityCondition('entity_type', $entity_type); if ($bundle) { $efq->entityCondition('bundle', $bundle); } $efq->propertyCondition($entity_info['entity keys']['label'], $name); $result = $efq->execute(); if (isset($result[$entity_type])) { $result = array_keys($result[$entity_type]); return reset($result); } } $synonyms = synonyms_synonyms_find(db_and()->condition(AbstractSynonymsBehavior::COLUMN_SYNONYM_PLACEHOLDER, $name), $entity_type, $bundle); if (!empty($synonyms)) { return reset($synonyms)->entity_id; } return 0; } /** * Try to find a term by its name or synonym. * * You are advised to use the more general function * synonyms_get_entity_by_synonym() unless you really need $parent input * argument from this function for additional filtering by Taxonomy hierarchy. * * @param string $name * The string to be searched for its {taxonomy_term_data}.tid * @param object $vocabulary * Fully loaded vocabulary object in which you wish to search * @param int $parent * Optional. In case you want to narrow your search scope, this parameter * takes in the {taxonomy_term_data}.tid of the parent term, letting you * search only among its children * * @return int * If the look up was successful returns the {taxonomy_term_data}.tid of the * found term, otherwise returns 0 */ function synonyms_get_term_by_synonym($name, $vocabulary, $parent = 0) { if (!module_exists('taxonomy')) { return 0; } $name = trim($name); $terms = taxonomy_get_term_by_name($name, $vocabulary->machine_name); foreach ($terms as $term) { if (!$parent || synonyms_taxonomy_term_is_child_of($term->tid, $parent)) { // TODO: actually it could be so that there is more than 1 term that // satisfies the search query, i.e. the name and parent constraints. At // the moment we are going to return the first one we encounter, though // something better could be thought of in the future. return $term->tid; } } // We have failed to find a term with the provided $name. So let's search now // among the term synonyms. $bundle = field_extract_bundle('taxonomy_term', $vocabulary); $synonyms = synonyms_synonyms_find(db_and()->condition(AbstractSynonymsBehavior::COLUMN_SYNONYM_PLACEHOLDER, $name), 'taxonomy_term', $bundle); foreach ($synonyms as $synonym) { if (!$parent || synonyms_taxonomy_term_is_child_of($synonym->entity_id, $parent)) { // TODO: similarly here, as above, we could have more than 1 match, but // for now we will simply return the first one encountered. return $synonym->entity_id; } } // If we have reached down here, this means we haven't got any match // as fallback we return 0. return 0; } /** * Look up a term considering synonyms and if nothing is found add one. * * This function is useful for automated creation of new terms as it won't * generate the same terms over and over again. * * @param string $name * The string to be searched for its {taxonomy_term_data}.tid * @param object $vocabulary * Fully loaded vocabulary object in which you wish to search * @param int $parent * Optional. In case you want to narrow your search scope, this parameter * takes in the {taxonomy_term_data}.tid of the parent term, letting you * search only among its children * * @return int * If a term already exists, its {taxonomy_term_data}.tid is returned, * otherwise it creates a new term and returns its {taxonomy_term_data}.tid */ function synonyms_add_term_by_synonym($name, $vocabulary, $parent = 0) { if (!module_exists('taxonomy')) { return 0; } $tid = synonyms_get_term_by_synonym($name, $vocabulary, $parent); if ($tid) { // We found some term, returning its tid. return $tid; } // We haven't found any term, so we create one. $term = (object) array( 'name' => $name, 'vid' => $vocabulary->vid, 'parent' => array($parent), ); taxonomy_term_save($term); if (isset($term->tid)) { return $term->tid; } // Normally we shouldn't reach up to here, because a term would have got // created and the just created tid would have been returned. Nevertheless, // as a fallback in case of any error we return 0. return 0; } /** * Retrieve list of sanitized synonyms of an entity. * * @param $entity object * Fully loaded entity * * @return array * List of sanitized synonyms of an entity */ function synonyms_get_sanitized($entity, array $options, $name, $entity_type, &$context) { return array_map('check_plain', synonyms_get_raw($entity, $options, $name, $entity_type, $context)); } /** * Retrieve list of raw synonyms of an entity. * * @param $entity object * Fully loaded entity * * @return array * List of raw synonyms of an entity */ function synonyms_get_raw($entity, array $options, $name, $entity_type, &$context) { $synonyms = array(); $bundle = entity_extract_ids($entity_type, $entity); $bundle = $bundle[2]; $langcode = NULL; if (isset($options['language']) && $options['language']) { $langcode = $options['language']->language; } $behavior_implementations = synonyms_behavior_get_all_enabled($entity_type, $bundle); $providers = array(); foreach ($behavior_implementations as $implementation) { if (!in_array($implementation['provider'], $providers)) { $synonyms = array_merge($synonyms, $implementation['object']->extractSynonyms($entity, $langcode)); $providers[] = $implementation['provider']; } } return $synonyms; } /** * Public function for retrieving synonyms of a taxonomy term. * * You are encouraged to use synonyms_get_sanitized() or synonyms_get_raw() * instead. This function soon will be removed from the source code. * * @param object $term * Fully loaded taxonomy term for which the synonyms are desired * * @return array * Array of synonyms, if synonyms are disabled for the taxonomy term's * vocabulary, an empty array is returned. Each synonym subarray consists of * the following keys: * - value: (string) the value of a synonym as it was input by user * - safe_value: (string) a sanitized value of a synonym * * @deprecated */ function synonyms_get_term_synonyms($term) { if (!module_exists('taxonomy')) { return array(); } $synonyms = array(); $vocabulary = taxonomy_vocabulary_load($term->vid); $bundle = field_extract_bundle('taxonomy_term', $vocabulary); $behavior_implementations = synonyms_behavior_get_all_enabled('taxonomy_term', $bundle); foreach ($behavior_implementations as $implementation) { foreach ($implementation['object']->extractSynonyms($term) as $synonym) { $synonyms[] = array( 'value' => $synonym, 'safe_value' => check_plain($synonym), ); } } return $synonyms; } /** * Look up entities by their synonyms. * * @param QueryConditionInterface $condition * Object of QueryConditionInterface that specifies conditions by which you * want to find synonyms. When building this condition object, you can use * the following column placeholders: * - AbstractSynonymsBehavior::COLUMN_PLACEHOLDER: as a placeholder for real * column name that contains synonym as text * - AbstractSynonymsBehavior::COLUMN_ENTITY_ID_PLACEHOLDER: as a placeholder * for real column name that holds entity ID * For example, if you were to find all entities with synonyms that begin with * "synonym-come-here" substring, case insensitive and replacing all spaces * in original synonym string by a dash sign, then you would have to create * the following condition object: * db_and() * ->where("LOWER(REPLACE(" . AbstractSynonymsBehavior::COLUMN_PLACEHOLDER . ", ' ', '-')) LIKE :synonym", array( * ':synonym' => '%' . db_like($some-var) . '%' * )) * And then just supply this object as an input parameter to this function * @param string $entity_type * Among synonyms of what entity type to search * @param string|array $bundle * Optionally specify among synonyms of what bundle(-s) to search. You can * specify here a string - bundle name to search within or an array of bundles * within which to search * * @return array * Array of found synonyms and entity IDs to which those belong. Each element * in the array will be an object and will have the following structure: * - synonym: (string) Synonym that was found and which satisfies the * $condition you specified * - entity_id: (int) ID of the entity to which the found synonym belongs */ function synonyms_synonyms_find(QueryConditionInterface $condition, $entity_type, $bundle = NULL) { $rows = array(); $behavior_implementations = synonyms_behavior_get_all_enabled($entity_type, $bundle); foreach ($behavior_implementations as $behavior_implementation) { foreach ($behavior_implementation['object']->synonymsFind(clone $condition) as $row) { $rows[] = $row; } } return $rows; } /** * Allow to merge $synonym_entity as a synonym into $trunk_entity. * * Helpful function during various merging operations. It allows you to add a * synonym (where possible) into one entity, which will represent another entity * in the format expected by the field in which the synonym is being added. * Important note: if the cardinality limit of the field into which you are * adding synonym has been reached, calling to this function will take no * effect. * * @param object $trunk_entity * Fully loaded entity object in which the synonym is being added * @param string $trunk_entity_type * Entity type of $trunk_entity * @param string $field * Field name that should exist in $trunk_entity and have enabled the * "synonyms" behavior. Into this field synonym will be added * @param object $synonym_entity * Fully loaded entity object which will be added as a synonym * @param string $synonym_entity_type * Entity type of $synonym_entity * * @return bool * Whether synonym has been successfully added * * TODO: This should be shifted into Term Merge module. */ function synonyms_add_entity_as_synonym($trunk_entity, $trunk_entity_type, $field, $synonym_entity, $synonym_entity_type) { $bundle = entity_extract_ids($trunk_entity_type, $trunk_entity); $bundle = $bundle[2]; // TODO: this somehow must be incorporated into synonyms_provider_field // submodule. $behavior_implementations = synonyms_behavior_get_all_enabled($trunk_entity_type, $bundle, synonyms_provider_field_provider_name(field_info_field($field))); if (empty($behavior_implementations)) { // $field either doesn't exist in the $trunk_entity or it does not have any // enabled behavior. return FALSE; } $behavior_implementation = reset($behavior_implementations); $behavior_implementation['object']->mergeEntityAsSynonym($trunk_entity, $synonym_entity, $synonym_entity_type); entity_save($trunk_entity_type, $trunk_entity); return TRUE; } /** * Return array of field names that are sources of synonyms. * * Return array of field names that are currently have enabled the synonyms * behavior in the supplied vocabulary. This function is deprecated and shortly * will be removed from the code. All clients of this function are encourage to * use synonyms_behavior_get() function, which provides a richer set of * functionality than this one. * * @param object $vocabulary * Fully loaded taxonomy vocabulary object * * @return array * Array of field names * * @deprecated */ function synonyms_synonyms_fields($vocabulary) { // TODO: remove this ugly function as soon as possible. It has got hacky since // now not only fields may be providers of synonyms. if (!module_exists('taxonomy')) { return array(); } $fields = array(); $bundle = field_extract_bundle('taxonomy_term', $vocabulary); $behavior_implementations = synonyms_behavior_get_all_enabled('taxonomy_term', $bundle); foreach ($behavior_implementations as $v) { $field_name = synonyms_provider_field_field_name($v['provider']); if (field_info_field($field_name)) { $fields[] = $field_name; } } return $fields; } /** * Implements hook_views_api(). */ function synonyms_views_api() { return array( 'api' => 3, 'path' => drupal_get_path('module', 'synonyms') . '/views', ); } /** * Load function for existing implementations of synonyms behaviors. * * @param string $behavior * Name of the synonyms behavior whose existing implementations should be * loaded. Basically it has to be name of a ctools plugin of "behavior" type. * @param string $entity_type * Behavior implementations of what entity type should be looked up * @param string|array $bundle * Behavior implementations of what bundle(-s) should be looked up. If you * want to look up only a single bundle, provide a string. If you want to * look up multiple bundles at a time, provide an array of bundles. Empty * array here would mean to include all bundles that are known within provided * $entity_type * @param bool $only_enabled * Optional filter to limit the search for existing implementations only to * those that are currently enabled * * @return array * Array of loaded existing synonyms behavior implementations. The underlying * array will have the following structure: * - behavior: (string) Behavior name of this behavior implementation, i.e. * name of a ctools plugin of "behavior" type * - entity_type: (string) Entity type to which this behavior implementation * applies * - bundle: (string) Bundle name to which this behavior implementation * applies * - provider: (string) Machine name of this synonyms behavior implementation * - label: (string) Human friendly name of this synonyms behavior * implementation * - class: (string) Name of PHP class that implements behavior interface * - settings: (mixed) Behavior settings, its internal structure depends on * the type of behavior. If this value is NULL, it means the behavior * implementation is currently disabled * - object: (SynonymsBehavior) If the synonyms behavior implementation is * enabled, this property will contain a fully initialized object that * corresponds to this behavior implementation. This object is ready for * use: to query for synonyms or invoke any other methods behavior interface * declares * - module: (string) Name of the module that provides this synonyms behavior * implementation */ function synonyms_behavior_get($behavior, $entity_type, $bundle, $only_enabled = FALSE) { $behavior_implementations = array(); $enabled_behavior_implementations = array(); $query = db_select('synonyms_settings', 's'); $query->fields('s'); $query->condition('behavior', $behavior); $query->condition('entity_type', $entity_type); $query->condition('bundle', synonyms_bundle_normalize($entity_type, $bundle)); $result = $query->execute(); foreach ($result as $row) { $row = (array) $row; if ($only_enabled) { $behavior_implementations[] = $row; } else { $enabled_behavior_implementations[$row['bundle']][$row['provider']] = $row; } } if (!$only_enabled) { foreach (synonyms_bundle_normalize($entity_type, $bundle) as $bundle_name) { foreach (synonyms_behavior_implementation_info($entity_type, $bundle_name, $behavior) as $provider_info) { if (isset($enabled_behavior_implementations[$bundle_name][$provider_info['provider']])) { $provider_info['settings_serialized'] = $enabled_behavior_implementations[$bundle_name][$provider_info['provider']]['settings_serialized']; } $behavior_implementations[] = $provider_info; } } } return synonyms_behavior_settings_unpack($behavior_implementations); } /** * Load all enabled behavior implementations on an entity_type and a bundle. * * This is useful when you want to do some operation on all enabled behavior * implementations for a specific entity_type and a bundle. * * @param string $entity_type * Optionally filter by entity type whose enabled behavior implementations * should be loaded * @param string|array $bundle * Optionally filter by bundle whose enabled behavior implementations should * be loaded. You can supply here string - single bundle name or an array of * bundle names. Empty array implies all existing bundle names for a provided * $entity_type * @param string $provider * Optional filter to only return enabled synonyms behavior implementations of * a specific provider * @param string $behavior * Optional filter to only return enabled synonyms behavior implementations of * a specific behavior * * @return array * Array of enabled behavior implementations for a provided entity type and a * bundle. Return structure of this function is identical to the return * structure of synonyms_behavior_get() */ function synonyms_behavior_get_all_enabled($entity_type = NULL, $bundle = array(), $provider = NULL, $behavior = NULL) { $query = db_select('synonyms_settings', 's'); $query->fields('s'); if ($entity_type) { $query->condition('entity_type', $entity_type); $query->condition('bundle', synonyms_bundle_normalize($entity_type, $bundle)); } if ($provider) { $query->condition('provider', $provider); } if ($behavior) { $query->condition('behavior', $behavior); } $result = $query->execute(); $behavior_implementations = array(); foreach ($result as $row) { $behavior_implementations[] = (array) $row; } return synonyms_behavior_settings_unpack($behavior_implementations); } /** * Retrieve information about all cTools plugins of type 'synonyms behavior'. * * @return array * Array of information on all available synonyms behavior plugins */ function synonyms_behaviors() { ctools_include('plugins'); return ctools_get_plugins('synonyms', 'behavior'); } /** * Collect info on available synonyms behavior implementations. * * @param string $entity_type * Entity type whose available synonyms behavior implementations to collect * @param string $bundle * Bundle whose available synonyms behavior implementations to collect * @param string $behavior * Name of behavior whose available synonyms behavior implementations to * collect * * @return array * Array of available synonyms behavior implementations. Each synonym behavior * implementation will be an array with the following structure: * - provider: (string) Machine name of the synonyms behavior implementation * - label: (string) Human name of the synonyms behavior implementation * - class: (string) Name of PHP class that implements behavior interface * which is stated in cTools behavior plugin definition * - entity_type: (string) Entity type that corresponds to this synonyms * behavior implementation * - bundle: (string) Bundle that corresponds to this synonyms behavior * implementation * - behavior: (string) Name of behavior that corresponds to this synonyms * behavior implementation * - module: (string) Name of the module that provides this synonyms behavior * implementation */ function synonyms_behavior_implementation_info($entity_type, $bundle, $behavior) { $providers = array(); foreach (module_implements('synonyms_behavior_implementation_info') as $module) { foreach (module_invoke($module, 'synonyms_behavior_implementation_info', $entity_type, $bundle, $behavior) as $provider) { $provider['entity_type'] = $entity_type; $provider['behavior'] = $behavior; $provider['bundle'] = $bundle; $provider['module'] = $module; $providers[$provider['provider']] = $provider; } } return $providers; } /** * Execute unpacking on the just loaded synonyms behavior implementations. * * @param array $behavior_implementations * Array of the just loaded behavior_implementations. Each sub array should * contain the following keys: * - entity_type: (string) Entity type that corresponds to this behavior * implementation * - bundle: (string) Bundle that corresponds to this behavior implementation * - provider: (string) Machine name of this synonyms behavior implementation * - behavior: (string) name of the synonyms behavior to which these settings * apply * - settings_serialized: (string) serialized content of the settings * * @return array * Unpacked version of the provided $settings */ function synonyms_behavior_settings_unpack($behavior_implementations) { // Array of previously queried synonyms provider info. $cache = array(); foreach ($behavior_implementations as &$behavior_implementation) { if (!isset($cache[$behavior_implementation['behavior']][$behavior_implementation['entity_type']][$behavior_implementation['bundle']])) { $cache[$behavior_implementation['behavior']][$behavior_implementation['entity_type']][$behavior_implementation['bundle']] = synonyms_behavior_implementation_info($behavior_implementation['entity_type'], $behavior_implementation['bundle'], $behavior_implementation['behavior']); } // Behavior implementation info may be not available in some rare extreme // cases. For example, when a field instance is being deleted. if (isset($cache[$behavior_implementation['behavior']][$behavior_implementation['entity_type']][$behavior_implementation['bundle']][$behavior_implementation['provider']])) { $behavior_implementation += $cache[$behavior_implementation['behavior']][$behavior_implementation['entity_type']][$behavior_implementation['bundle']][$behavior_implementation['provider']]; } if (isset($behavior_implementation['settings_serialized'])) { $behavior_implementation['settings'] = unserialize($behavior_implementation['settings_serialized']); if (isset($behavior_implementation['class'])) { $class = $behavior_implementation['class']; $behavior_implementation['object'] = new $class($behavior_implementation); } } } unset($behavior_implementation); return $behavior_implementations; } /** * Save the provided synonyms behavior implementation into the database. * * @param array $behavior_implementation * Behavior implementation array, such as one from synonyms_behavior_get() or * alike */ function synonyms_behavior_implementation_save($behavior_implementation) { if (!isset($behavior_implementation['settings'])) { $behavior_implementation['settings'] = array(); } $behavior_implementation['settings_serialized'] = serialize($behavior_implementation['settings']); $result = db_merge('synonyms_settings') ->key(array( 'entity_type' => $behavior_implementation['entity_type'], 'bundle' => $behavior_implementation['bundle'], 'provider' => $behavior_implementation['provider'], 'behavior' => $behavior_implementation['behavior'], )) ->fields(array( 'entity_type' => $behavior_implementation['entity_type'], 'bundle' => $behavior_implementation['bundle'], 'provider' => $behavior_implementation['provider'], 'behavior' => $behavior_implementation['behavior'], 'settings_serialized' => $behavior_implementation['settings_serialized'], )) ->execute(); switch ($result) { case MergeQuery::STATUS_INSERT: $behavior_definition = synonyms_behaviors(); $behavior_definition = $behavior_definition[$behavior_implementation['behavior']]; $enabled_callback = ctools_plugin_get_function($behavior_definition, 'enabled callback'); if ($enabled_callback) { $enabled_callback($behavior_definition, $behavior_implementation); } break; } } /** * Delete behavior implementation from database. * * @param array $behavior_implementation * Behavior implementation array, such as one from synonyms_behavior_get() or * alike */ function synonyms_behavior_implementation_delete($behavior_implementation) { $behavior_definition = synonyms_behaviors(); $behavior_definition = $behavior_definition[$behavior_implementation['behavior']]; $disabled_callback = ctools_plugin_get_function($behavior_definition, 'disabled callback'); if ($disabled_callback) { foreach (synonyms_behavior_get($behavior_implementation['behavior'], $behavior_implementation['entity_type'], $behavior_implementation['bundle'], TRUE) as $enabled_behavior_implementation) { if ($enabled_behavior_implementation['provider'] == $behavior_implementation['provider']) { $disabled_callback($behavior_definition, $behavior_implementation); break; } } } db_delete('synonyms_settings') ->condition('provider', $behavior_implementation['provider']) ->condition('entity_type', $behavior_implementation['entity_type']) ->condition('bundle', $behavior_implementation['bundle']) ->condition('behavior', $behavior_implementation['behavior']) ->execute(); } /** * Convert synonyms friendly select widget values for storage friendly format. * * It acts similar to what the _options_form_to_storage() function does - * bridges between how values are returned from form API to how they are * expected by Field module. */ function synonyms_select_form_to_storage($element, &$form_state) { $form_state_value = array(); foreach (drupal_array_get_nested_value($form_state['values'], $element['#parents']) as $entity_id) { $form_state_value[] = array($element['#columns'][0] => $entity_id); } form_set_value($element, $form_state_value, $form_state); } /** * Element validate handler. * * Convert selected synonyms into their terms and save the updated data in the * value of the form element. */ function synonyms_select_validate($element, &$form_state) { $value = array(); if ($element['#multiple']) { $value = $element['#value']; } else { $value[] = $element['#value']; } foreach ($value as $k => $v) { // For the cases when a synonym was selected and not an entity option, we // process the selected values stripping everything that goes after // semicolon. if (!is_numeric($v)) { $entity_id = explode(':', $v); $value[$k] = $entity_id[0]; } } // The user also might have selected multiple times the same entity, given // that an entity can be represented by more than 1 option (an entity and its // synonym), then it's possible in theory, so we should be ready for this // scenario. $value = array_unique($value); form_set_value($element, $value, $form_state); } /** * Check whether a taxonomy term $tid is a child of a taxonomy term $parent_tid. * * Supportive function, used throughout this module for parent constrains. * * @param int $tid * {taxonomy_term}.tid of the term that is tested for being a child of the * $parent_tid term * @param int $parent_tid * {taxonomy_term}.tid of the term that is tested for being parent of the $tid * term * * @return bool * Whether $tid is a child of $parent_tid */ function synonyms_taxonomy_term_is_child_of($tid, $parent_tid) { $term_parents = taxonomy_get_parents_all($tid); // Dropping out the term itself from its array of parents. array_shift($term_parents); foreach ($term_parents as $term_parent) { if ($term_parent->tid == $parent_tid) { return TRUE; } } return FALSE; } /** * Format an option for entity reference select form element. * * @param object $entity * Fully loaded entity which is represented by this option * @param string $entity_type * Entity type of the $entity object * @param string $synonym * If the provided entity is represented in this option by a synonym, then * provide it here * @param array $behavior_implementation * Behavior implementation array from which the $synonym comes from * @param array $options * Array of additional settings or options that may influence execution of * this function. Currently supported options are: * - depth: Whether to prefix wording of option labels with depth of the * entity. This will work only for taxonomy term entities, as they are the * only ones that have notion of depth * * @return object * An option for entity reference select form element */ function synonyms_select_option_entity($entity, $entity_type, $synonym = NULL, $behavior_implementation = NULL, $options = array()) { $entity_id = entity_extract_ids($entity_type, $entity); $entity_id = $entity_id[0]; $key = $synonym ? $entity_id . ':' . drupal_html_class($synonym) : $entity_id; $wording = entity_label($entity_type, $entity); if ($synonym) { $wording = format_string($behavior_implementation['settings']['wording'], array( '@synonym' => $synonym, '@entity' => entity_label($behavior_implementation['entity_type'], $entity), '@field_name' => drupal_strtolower($behavior_implementation['label']), )); } if (in_array('depth', $options) && $entity_type == 'taxonomy_term') { $depth = count(taxonomy_get_parents_all($entity_id)) - 1; $wording = str_repeat('-', $depth) . $wording; } return (object) array( 'option' => array($key => $wording), ); } /** * Construct options array for entity reference synonyms friendly select list. * * @param array $field * Field definition array of entityreference type for which to construct the * options * @param array $instance * Field instance definition array that corresponds to $field * @param object $entity * If entity is known for which the options should be generated, provide it * here. It is safe to omit this parameter. Frankly, I do not understand well * why or how it is used. It is just directly passed into entityreference * selection handler class * * @return array * Options array that can be plugged in directly into any #select form element */ function synonyms_select_entity_options($field, $instance, $entity = NULL) { $options = entityreference_get_selection_handler($field, $instance, $instance['entity_type'], $entity)->getReferencableEntities(); $synonyms_options = array(); $target_entity_info = entity_get_info($field['settings']['target_type']); $entity_ids = array(); foreach ($options as $bundle_entity_ids) { $entity_ids = array_merge($entity_ids, array_keys($bundle_entity_ids)); } $entities = entity_load($field['settings']['target_type'], $entity_ids); foreach ($options as $bundle => $bundle_entity_ids) { $synonyms_options[$target_entity_info['bundles'][$bundle]['label']] = array(); $behavior_implementations = synonyms_behavior_get('select', $field['settings']['target_type'], $bundle, TRUE); foreach ($bundle_entity_ids as $entity_id => $v) { $entity = $entities[$entity_id]; $synonyms_options[$target_entity_info['bundles'][$bundle]['label']][] = synonyms_select_option_entity($entity, $field['settings']['target_type']); foreach ($behavior_implementations as $behavior_implementation) { foreach ($behavior_implementation['object']->extractSynonyms($entity) as $synonym) { $synonyms_options[$target_entity_info['bundles'][$bundle]['label']][] = synonyms_select_option_entity($entity, $field['settings']['target_type'], $synonym, $behavior_implementation); } } } usort($synonyms_options[$target_entity_info['bundles'][$bundle]['label']], 'synonyms_select_sort_name'); } if (count($synonyms_options) == 1) { $synonyms_options = reset($synonyms_options); } return $synonyms_options; } /** * Supportive function to build taxonomy term options array sorted by name. * * The function starts from the 0-depth level and starts to recursively build * the options and to sort the labels on each level, then it merges the bottom * to up all the levels maintaining correct order within the final options * array. * * @param object $vocabulary * Within which vocabulary to execute the function. Supply here the fully * loaded taxonomy vocabulary object * @param int $parent * Only children of this term will be included in the output. You can supply * 0 which means to include all the terms from the vocabulary * @param int $depth * Used for internal purposes. Clients of this function should supply here 0, * unless they know what they are doing. It is used internally to keep track * of the nesting level * * @return array * Array of options that can be inserted directly into 'select' form element. * The options will be sorted by name (term or synonym), respecting the * hierarchy restrictions */ function synonyms_select_taxonomy_term_sort_name_options_recursive($vocabulary, $parent = 0, $depth = 0) { // We statically cache behavior implementations in order to not DDOS the data // base. $behavior_implementations = &drupal_static(__FUNCTION__, array()); $bundle = field_extract_bundle('taxonomy_term', $vocabulary); if (!isset($behavior_implementations[$bundle])) { $behavior_implementations[$bundle] = synonyms_behavior_get('select', 'taxonomy_term', $bundle, TRUE); } $options = array(); if ($terms = taxonomy_get_tree($vocabulary->vid, $parent, 1, TRUE)) { $options = array(); foreach ($terms as $term) { $term->depth = $depth; $options[] = synonyms_select_option_entity($term, 'taxonomy_term', NULL, NULL, array('depth')); foreach ($behavior_implementations[$bundle] as $implementation) { foreach ($implementation['object']->extractSynonyms($term) as $synonym) { $options[] = synonyms_select_option_entity($term, 'taxonomy_term', $synonym, $implementation, array('depth')); } } } usort($options, 'synonyms_select_sort_name'); // Now recursively go one level nested into each of the terms that we have // on this level. $options_copy = $options; $i = 0; foreach ($options_copy as $v) { $i++; $tid = array_keys($v->option); $tid = $tid[0]; if (is_numeric($tid)) { $nested_options = synonyms_select_taxonomy_term_sort_name_options_recursive($vocabulary, $tid, $depth + 1); $options = array_merge(array_slice($options, 0, $i), $nested_options, array_slice($options, $i)); } } } return $options; } /** * Supportive function. * * It is used for string comparison within synonyms friendly select widget. */ function synonyms_select_sort_name($a, $b) { return strcasecmp(reset($a->option), reset($b->option)); } /** * Test if entity type is applicable for having synonyms. * * @param string $entity_type * Entity type to test * * @return bool|string * Whether the provided entity type is applicable for having synonyms. If it * is applicable, the $entity_type input argument will be returned. Otherwise * FALSE is returned */ function synonyms_entity_type_load($entity_type) { if (module_exists('synonyms_provider_property')) { // If this submodule is enabled, we get into infinite recursion. Moreover, // it is very likely all entity types will have at least 1 property on them, // so the synonyms_provider_property module will get at least 1 synonym for // every entity type. So it is quite safe assumption to say we accept about // any entity type. return $entity_type; } $bundles = synonyms_bundle_normalize($entity_type, array()); foreach (synonyms_behaviors() as $behavior => $behavior_definition) { foreach ($bundles as $bundle) { $behavior_implementations = synonyms_behavior_implementation_info($entity_type, $bundle, $behavior); if (!empty($behavior_implementations)) { return $entity_type; } } } return FALSE; } /** * Test if provided entity type and bundle are applicable for having synonyms. * * @param string $bundle * Bundle name to test for ability to have synonyms * @param string $entity_type * Entity type to test for ability to have synonyms * * @return bool|string * Whether the provided entity type and bundle may have synonyms. If they do, * the $bundle input argument is returned. Otherwise FALSE is returned */ function synonyms_bundle_load($bundle, $entity_type) { foreach (synonyms_behaviors() as $behavior => $behavior_definition) { $behavior_implementations = synonyms_behavior_implementation_info($entity_type, $bundle, $behavior); if (!empty($behavior_implementations)) { return $bundle; } } return FALSE; } /** * Menu title callback function for synonyms settings page. * * @param string $entity_type * Entity type whose synonyms settings are managed * @param string $bundle * Bundle whose synonyms settings are managed * * @return string * Title of the synonyms settings page */ function synonyms_settings_title($entity_type, $bundle) { $entity_info = entity_get_info($entity_type); $entity_label = isset($entity_info['plural label']) ? $entity_info['plural label'] : $entity_info['label']; if ($entity_type == $bundle) { return t('Synonyms settings of @entity_type', array( '@entity_type' => $entity_label, )); } return t('Synonyms settings of @bundle @entity_type', array( '@bundle' => $entity_info['bundles'][$bundle]['label'], '@entity_type' => $entity_label, )); } /** * Escape string to safely use in autocomplete text field as default value. * * @param string $value * String to be escaped * * @return string * Escaped string $value */ function synonyms_autocomplete_escape($value) { // Commas or quotes must be wrapped in quotes. if (strpos($value, ',') !== FALSE || strpos($value, '"') !== FALSE) { $value = '"' . str_replace('"', '""', $value) . '"'; } return $value; } /** * Determine a list of target bundles of an entityreference field. * * @param array $field * Field definition array. This field must be of type 'entityreference' * * @return array|null * List of target bundles per the settings of provided field or NULL if the * provided field does not expose any limitations on target bundles */ function synonyms_field_target_bundles($field) { $target_bundles = isset($field['settings']['handler_settings']['target_bundles']) ? array_values($field['settings']['handler_settings']['target_bundles']) : array(); if (empty($target_bundles)) { $target_bundles = NULL; } return $target_bundles; } /** * Normalize bundle argument. * * @param string $entity_type * Entity type to define scope of bundles * @param string|array $bundle * Either a single bundle name or an array of bundle names. Empty array * implies all known bundles for $entity_type * * @return array * Normalized array bundle names */ function synonyms_bundle_normalize($entity_type, $bundle) { $bundle = (array) $bundle; if (empty($bundle)) { $bundle = array_keys(field_info_bundles($entity_type)); } return $bundle; } /** * Extract default value for a select widget of Synonyms module. * * @param array $field * Field definition array whose default value should be extracted * @param array $instance * Field instance definition array whose default value should be extracted * @param array $items * Array of items that should compose default value * * @return array * Array of default value that can be plugged in into a select widget */ function synonyms_select_default_value($field, $instance, $items) { $default_value = array(); $column = array_keys($field['columns']); $column = reset($column); foreach ($items as $item) { $default_value[] = $item[$column]; } return $default_value; }