'Autocomplete Synonyms', 'page callback' => 'synonyms_autocomplete', 'page arguments' => array(2, 3, 4), 'access arguments' => array('access content'), 'file' => 'synonyms.pages.inc', 'type' => MENU_CALLBACK, ); return $items; } /** * 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; } } /** * 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(); $properties = &$info['taxonomy_term']['properties']; $properties['synonyms'] = array( 'label' => t('Synonyms'), 'description' => t('Synonyms of entity.'), 'type' => 'list', 'getter callback' => 'synonyms_get_sanitized', 'computed' => TRUE, 'sanitized' => TRUE, 'raw getter callback' => 'synonyms_get_raw', ); return $info; } /** * Implements hook_field_delete_instance(). */ function synonyms_field_delete_instance($instance) { // Remove, if necessary, any synonyms behaviors enabled on this instance. $result = db_select('synonyms_settings', 's') ->fields('s', array('behavior')) ->condition('s.instance_id', $instance['id']) ->execute(); foreach ($result as $row) { synonyms_behavior_settings_delete($instance['id'], $row->behavior); } } /** * Implements hook_synonyms_behavior_implementation_info(). */ function synonyms_synonyms_behavior_implementation_info($behavior) { switch ($behavior) { case 'autocomplete': case 'select': case 'synonyms': return array( 'number_integer' => 'TextSynonymsBehavior', 'number_decimal' => 'TextSynonymsBehavior', 'number_float' => 'TextSynonymsBehavior', 'text' => 'TextSynonymsBehavior', 'taxonomy_term_reference' => 'TaxonomySynonymsBehavior', 'entityreference' => 'EntityReferenceSynonymsBehavior', ); break; } return array(); } /** * Implements hook_form_FORM_ID_alter(). */ function synonyms_form_taxonomy_form_vocabulary_alter(&$form, &$form_state) { 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, '#tree' => TRUE, ); $behaviors = synonyms_behaviors(); $bundle = field_extract_bundle('taxonomy_term', $form['#vocabulary']); $form['synonyms']['behaviors'] = array( '#theme' => 'synonyms_behaviors_settings', '#id' => 'synonyms-behaviors-settings-wrapper', ); foreach ($behaviors as $behavior => $behavior_info) { $form['synonyms']['behaviors'][$behavior] = array( '#title' => $behavior_info['title'], ); $behavior_implementations = synonyms_behavior_get($behavior, 'taxonomy_term', $bundle); foreach ($behavior_implementations as $implementation) { $instance = field_info_instance($implementation['entity_type'], $implementation['field_name'], $implementation['bundle']); $form['synonyms']['behaviors'][$behavior][$implementation['instance_id']]['#title'] = $instance['label']; if (isset($form_state['values']['synonyms']['behaviors'][$behavior][$implementation['instance_id']])) { $behavior_settings = (bool) $form_state['values']['synonyms']['behaviors'][$behavior][$implementation['instance_id']]['enabled']; } else { $behavior_settings = !is_null($implementation['settings']); } if ($behavior_settings) { if (isset($form_state['values']['synonyms']['behaviors'][$behavior][$implementation['instance_id']]['settings'])) { $behavior_settings = $form_state['values']['synonyms']['behaviors'][$behavior][$implementation['instance_id']]['settings']; } elseif ($implementation['settings']) { $behavior_settings = $implementation['settings']; } else { $behavior_settings = array(); } } $form['synonyms']['behaviors'][$behavior][$implementation['instance_id']]['enabled'] = array( '#type' => 'checkbox', '#title' => t('Enable'), '#default_value' => $behavior_settings !== FALSE, ); $settings_form = ctools_plugin_get_function($behavior_info, 'settings form callback'); if ($settings_form) { $form['synonyms']['behaviors'][$behavior][$implementation['instance_id']]['enabled']['#ajax'] = array( 'callback' => 'synonyms_behaviors_settings_form_ajax', 'wrapper' => $form['synonyms']['behaviors']['#id'], ); if ($behavior_settings !== FALSE) { $form['synonyms']['behaviors'][$behavior][$implementation['instance_id']]['settings'] = $settings_form($form, $form_state, $behavior_settings); } } } } $form['#submit'][] = 'synonyms_taxonomy_form_vocabulary_submit'; } /** * Submit handler for Taxonomy vocabulary edit form. * * Store synonyms behavior settings. */ function synonyms_taxonomy_form_vocabulary_submit($form, &$form_state) { $values = $form_state['values']; if ($values['op'] == $form['actions']['submit']['#value']) { foreach ($values['synonyms']['behaviors'] as $behavior => $settings) { foreach ($settings as $instance_id => $behavior_settings) { if ($behavior_settings['enabled']) { synonyms_behavior_settings_save(array( 'instance_id' => $instance_id, 'behavior' => $behavior, 'settings' => isset($behavior_settings['settings']) ? $behavior_settings['settings'] : NULL, )); } else { synonyms_behavior_settings_delete($instance_id, $behavior); } } } } } /** * Ajax callback function for synonyms behavior settings form. */ function synonyms_behaviors_settings_form_ajax($form, &$form_state) { return $form['synonyms']['behaviors']; } /** * Implements hook_field_widget_info(). */ function synonyms_field_widget_info() { return array( 'synonyms_autocomplete' => array( 'label' => t('Synonyms friendly autocomplete term widget'), 'field types' => array('taxonomy_term_reference'), 'settings' => array( 'size' => 60, 'synonyms_autocomplete_path' => 'synonyms/autocomplete', 'suggestion_size' => 10, 'suggest_only_unique' => FALSE, 'auto_creation' => 1, ), 'behaviors' => array( 'multiple values' => FIELD_BEHAVIOR_CUSTOM, ), ), 'synonyms_select' => array( 'label' => t('Synonyms friendly select list'), 'field types' => array('taxonomy_term_reference'), 'settings' => array( 'sort' => 'weight', ), 'behaviors' => array( 'multiple values' => FIELD_BEHAVIOR_CUSTOM, ), ), ); } /** * 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': $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': $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 = array(); foreach ($items as $item) { $default_value[] = $item['tid']; } switch ($instance['widget']['type']) { case 'synonyms_autocomplete': $tags = taxonomy_term_load_multiple($default_value); $element += array( '#type' => 'textfield', '#default_value' => taxonomy_implode_tags($tags), '#autocomplete_path' => $instance['widget']['settings']['synonyms_autocomplete_path'] . '/' . $field['field_name'] . '/' . $instance['entity_type'] . '/' . $instance['bundle'], '#size' => $instance['widget']['settings']['size'], '#maxlength' => 1024, '#element_validate' => array('taxonomy_autocomplete_validate', 'synonyms_autocomplete_validate'), '#auto_creation' => $instance['widget']['settings']['auto_creation'], '#attached' => array( 'js' => array( drupal_get_path('module', 'synonyms') . '/js/synonyms-autocomplete.js' => array(), ), ), '#attributes' => array( 'class' => array('synonyms-autocomplete'), ), ); break; case 'synonyms_select': $multiple = $field['cardinality'] > 1 || $field['cardinality'] == FIELD_CARDINALITY_UNLIMITED; $options = array(); foreach ($field['settings']['allowed_values'] as $tree) { if ($vocabulary = taxonomy_vocabulary_machine_name_load($tree['vocabulary'])) { switch ($instance['widget']['settings']['sort']) { case 'weight': if ($terms = taxonomy_get_tree($vocabulary->vid, $tree['parent'], NULL, TRUE)) { $behavior_implementations = synonyms_behavior_get('select', 'taxonomy_term', field_extract_bundle('taxonomy_term', $vocabulary), TRUE); foreach ($terms as $term) { $options[] = synonyms_select_option($term); foreach ($behavior_implementations as $implementation) { foreach (synonyms_extract_synonyms($term, $implementation) as $synonym) { $options[] = synonyms_select_option($term, $synonym, $implementation); } } } } break; case 'name': // TODO: is there any way to leverage DB for the sorting routine? $options = synonyms_select_sort_name_options_recursive($vocabulary, $tree['parent']); break; } } } if (!$multiple && !$element['#required']) { $options = array('' => t('- None -')) + $options; } $element += array( '#type' => 'select', '#multiple' => $multiple, '#options' => $options, '#default_value' => $default_value, '#element_validate' => array('synonyms_select_form_to_storage'), ); break; } return $element; } /** * Implements hook_field_widget_error(). */ function synonyms_field_widget_error($element, $error, $form, &$form_state) { form_error($element, $error['message']); } /** * Form element validate handler. * * Handle validation for taxonomy term synonym-friendly autocomplete element. */ function synonyms_autocomplete_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']); $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 = synonyms_synonyms_find_behavior(db_and()->condition(AbstractSynonymsSynonymsBehavior::COLUMN_PLACEHOLDER, $term['name']), $behavior_implementation); foreach ($synonyms as $synonym) { $synonym_tid = $synonym->entity_id; break(2); } } } if ($synonym_tid != 0) { $value[$delta]['tid'] = $synonym_tid; } elseif (!$element['#auto_creation']) { unset($value[$delta]); } } } $value = array_values($value); form_set_value($element, $value, $form_state); } /** * Try to find a term by its name or synonym. * * @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) { $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(AbstractSynonymsSynonymsBehavior::COLUMN_PLACEHOLDER, $name), 'taxonomy_term', $bundle); foreach ($synonyms as $synonym) { if (!$parent || synonyms_taxonomy_term_is_child_of($synonym->entity_id, $parent)) { // TODO: similarly here, as above, we could have more than 1 match, but // for now we will simply return the first one encountered. return $synonym->entity_id; } } // 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) { $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 a list of sanitized synonyms of a taxonomy term. * * @param $item object * Fully loaded taxonomy term * * @return array * List of sanitized synonyms of a taxonomy term */ function synonyms_get_sanitized($item) { $synonyms = array(); foreach (synonyms_get_term_synonyms($item) as $synonym) { $synonyms[] = $synonym['safe_value']; } return $synonyms; } /** * Retrieve a list of raw synonyms of a taxonomy term. * * @param $item object * Fully loaded taxonomy term * * @return array * List of raw synonyms of a taxonomy term */ function synonyms_get_raw($item) { $synonyms = array(); foreach (synonyms_get_term_synonyms($item) as $synonym) { $synonyms[] = $synonym['value']; } 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) { $synonyms = array(); $vocabulary = taxonomy_vocabulary_load($term->vid); $bundle = field_extract_bundle('taxonomy_term', $vocabulary); $behavior_implementations = synonyms_behavior_get('synonyms', 'taxonomy_term', $bundle, TRUE); foreach ($behavior_implementations as $implementation) { foreach (synonyms_extract_synonyms($term, $implementation) as $synonym) { $synonyms[] = array( 'value' => $synonym, 'safe_value' => check_plain($synonym), ); } } return $synonyms; } /** * Extract synonyms of an entity within a certain field and behavior. * * Do not use this function, if you want to get synonyms of an entity, unless * you know what you are doing. This function extracts the synonyms from a field * that is specified by $behavior_implementation parameter. The behavior may not * necessarily be of 'synonyms' type, so it's not 100% valid to say that the * entity has the returned array as its synonyms. However, this function is very * useful for behaviors that "extend" the basic synonyms behavior. * * @param object $entity * Fully loaded entity, synonyms from which should be extracted * @param array $behavior_implementation * Fully loaded behavior implementation. Supply here one of the values from * the return of synonyms_behavior_get() function * * @return array * Array of synonyms that reside in the field dictated by * $behavior_implementation parameter */ function synonyms_extract_synonyms($entity, $behavior_implementation) { $synonyms = array(); $field = field_info_field($behavior_implementation['field_name']); $instance = field_info_instance($behavior_implementation['entity_type'], $behavior_implementation['field_name'], $behavior_implementation['bundle']); $items = field_get_items($behavior_implementation['entity_type'], $entity, $field['field_name']); if (is_array($items) && !empty($items)) { $object = synonyms_behavior_implementation_class($behavior_implementation['behavior'], $field); $object = new $object(); $synonyms = array_merge($synonyms, $object->extractSynonyms($items, $field, $instance, $entity, $behavior_implementation['entity_type'])); } return $synonyms; } /** * Look up entities by their synonyms. * * @param QueryConditionInterface $condition * Object of QueryConditionInterface that specifies conditions by which you * want to find synonyms. When building this condition object, use * AbstractSynonymsSynonymsBehavior::COLUMN_PLACEHOLDER as a placeholder for * real column name that contains synonym as text. For example, if you were to * find all entities with synonyms that begin with "synonym-come-here" * substring, case insensitive and replacing all spaces in original synonym * string by a dash sign, then you would have to create the following * condition object: * db_and() * ->where("LOWER(REPLACE(" . AbstractSynonymsSynonymsBehavior::COLUMN_PLACEHOLDER . ", ' ', '-')) LIKE 'synonym-come-here%'") * And then just supply this object as an input parameter to this function * @param string $entity_type * Among synonyms of what entity type to search * @param string $bundle * Among synonyms of what bundle to search * * @return array * Array of found synonyms and entity IDs to which those belong. Each element * in the array will be an object and will have the following structure: * - synonym: (string) Synonym that was found and which satisfies the * $condition you specified * - entity_id: (int) ID of the entity to which the found synonym belongs */ function synonyms_synonyms_find(QueryConditionInterface $condition, $entity_type, $bundle) { $rows = array(); $behavior_implementations = synonyms_behavior_get('synonyms', $entity_type, $bundle, TRUE); foreach ($behavior_implementations as $behavior_implementation) { foreach (synonyms_synonyms_find_behavior($condition, $behavior_implementation) as $row) { $rows[] = $row; } } return $rows; } /** * Find entities with a provided synonym within certain behavior implementation. * * Do not use this function, if you want to find entities that have specific * synonyms, unless you know what you are doing. This function searches for the * entities with synonyms from a field that is specified by * $behavior_implementation parameter. The behavior may not necessarily be of * 'synonyms' type, so it's not 100% valid to say that the returned entities * have the specified synonyms. However, this function is very useful for * behaviors that "extend" the basic synonyms behavior. * * You have full SQL flexibility to specify parameters of how to search for * synonyms. You can create arbitrary set of SQL conditions that will be plugged * into specific SELECT queries by behavior implementations. * * @param QueryConditionInterface $condition * Object of QueryConditionInterface that specifies conditions by which you * want to find synonyms. When building this condition object, use * AbstractSynonymsSynonymsBehavior::COLUMN_PLACEHOLDER as a placeholder for * real column name that contains synonym as text. For example, if you were to * find all entities with synonyms that begin with "synonym-come-here" * substring, case insensitive and replacing all spaces in original synonym * string by a dash sign, then you would have to create the following * condition object: * db_and() * ->where("LOWER(REPLACE(" . AbstractSynonymsSynonymsBehavior::COLUMN_PLACEHOLDER . ", ' ', '-')) LIKE 'synonym-come-here%'") * And then just supply this object as an input parameter to this function * @param array $behavior_implementation * Fully loaded behavior implementation. Supply here one of the values from * the return of synonyms_behavior_get() function * * @return Traversable * Traversable result set of found synonyms and entity IDs to which those * belong. Each element in the result set will be an object and will have the * following structure: * - synonym: (string) Synonym that was found and which satisfies the * $condition you specified * - entity_id: (int) ID of the entity to which the found synonym belongs */ function synonyms_synonyms_find_behavior(QueryConditionInterface $condition, $behavior_implementation) { $field = field_info_field($behavior_implementation['field_name']); $instance = field_info_instance($behavior_implementation['entity_type'], $behavior_implementation['field_name'], $behavior_implementation['bundle']); $object = synonyms_behavior_implementation_class($behavior_implementation['behavior'], $field); $object = new $object(); return $object->synonymsFind($condition, $field, $instance); } /** * Allow to merge $synonym_entity as a synonym into $trunk_entity. * * 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 */ function synonyms_add_entity_as_synonym($trunk_entity, $trunk_entity_type, $field, $synonym_entity, $synonym_entity_type) { if ($trunk_entity_type != 'taxonomy_term') { // Currently synonyms module only operates on taxonomy terms. return FALSE; } $bundle = entity_extract_ids($trunk_entity_type, $trunk_entity); $bundle = $bundle[2]; $behavior_implementations = synonyms_behavior_get('synonyms', $trunk_entity_type, $bundle, TRUE); $field = field_info_field($field); $instance = field_info_instance($trunk_entity_type, $field['field_name'], $bundle); if (!isset($behavior_implementations[$instance['id']])) { // $field either doesn't exist in the $trunk_entity or it does not have // enabled the behavior of synonyms. return FALSE; } $items = field_get_items($trunk_entity_type, $trunk_entity, $field['field_name']); $items = is_array($items) ? $items : array(); $object = synonyms_behavior_implementation_class('synonyms', $field); $object = new $object(); $extra_items = $object->mergeEntityAsSynonym($items, $field, $instance, $synonym_entity, $synonym_entity_type); if (empty($extra_items)) { // For some reason the behavior implementation couldn't merge it. Otherwise // it would have not returned an empty array. return FALSE; } // Merging extracted synonym items into the values of the field that already // exist. // @todo: Currently we hardcode to LANGUAGE_NONE, but in future it would be // nice to have multilanguage support. $items = array_merge($items, $extra_items); // Now we want to keep only unique values of the $items. Since we know nothing // about what determines uniqueness of an item, we will ask the synonym // behavior to hash each of them and then will compare hashes. $unique_items = array(); foreach ($items as $item) { $unique_items[$object->synonymItemHash($item, $field, $instance)] = $item; } $items = array_values($unique_items); $trunk_entity->{$field['field_name']}[LANGUAGE_NONE] = $items; // In future if this module eventually becomes a gateway for synonyms for any // entity types, we'll substitute it with entity_save(). taxonomy_term_save($trunk_entity); 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) { $fields = array(); $bundle = field_extract_bundle('taxonomy_term', $vocabulary); $behavior_implementations = synonyms_behavior_get('synonyms', 'taxonomy_term', $bundle, TRUE); foreach ($behavior_implementations as $v) { $fields[] = $v['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 * Optional filter to limit the search for existing implementations only to * those that apply to the provided entity type * @param string $bundle * Optional filter to limit the search for existing implementations only to * those that apply to the provided bundle and entity type (the $entity_type * argument) * @param bool $only_enabled * Optional filter to limit the search for existing implementations only to * those that are currently enabled * @param bool $include_deleted * Optional filter to include the behaviors from deleted instances * * @return array * Array of loaded existing synonyms behavior implementations. It is keyed * by ID of the field instance to which the behavior implementation applies. * The underlying array will have the following structure: * - behavior: (string) Behavior name of this behavior implementation, i.e. * name of a ctools plugin of "behavior" type * - settings: (mixed) Behavior settings, its internal structure depends on * the type of behavior. If this value is NULL, it means the behavior * implementation is currently disabled for the field instance * - entity_type: (string) Entity type to which this behavior implementation * applies * - bundle: (string) Bundle name to which this behavior implementation * applies * - field_name: (string) Name of a field to which this behavior * implementation applies * - instance_id: (int) ID of the instance to which this behavior * implementation applies */ function synonyms_behavior_get($behavior, $entity_type = NULL, $bundle = NULL, $only_enabled = FALSE, $include_deleted = FALSE) { $behavior_implementation_info = synonyms_behavior_implementation_info($behavior); $supported_field_types = array_keys($behavior_implementation_info); if (empty($supported_field_types)) { return array(); } $query = db_select('field_config_instance', 'i'); $field_alias = $query->innerJoin('field_config', 'f', 'f.id = i.field_id'); $query->condition($field_alias . '.type', $supported_field_types); if ($entity_type) { $query->condition('i.entity_type', $entity_type); } if ($bundle) { $query->condition('i.bundle', $bundle); } if (!$include_deleted) { $query->condition('i.deleted', 0); } $settings_alias = $query->addJoin($only_enabled ? 'INNER' : 'LEFT OUTER', 'synonyms_settings', 's', 's.instance_id = i.id AND s.behavior = :behavior', array( ':behavior' => $behavior, )); $query->fields($settings_alias, array('behavior', 'settings_serialized')); $query->fields('i', array('entity_type', 'bundle', 'field_name')); $query->addField('i', 'id', 'instance_id'); $result = $query->execute(); $result = $result->fetchAllAssoc('instance_id', PDO::FETCH_ASSOC); return synonyms_behavior_settings_unpack($result); } /** * Retrieve information about all ctools plugins of type 'synonyms behavior'. * * @return array * Array of information on all available synonyms behavior plugins */ function synonyms_behaviors() { ctools_include('plugins'); return ctools_get_plugins('synonyms', 'behavior'); } /** * Fetch information about synonyms behaviors implementations per field type. * * Fetch the map between field types and the PHP classes that implement synonyms * behaviors for them. * * @param string $behavior * What specific behavior is queried. Supply here keys from the return of * synonyms_behaviors() function * * @return array * Array of information about what field types implement the provided behavior * through what PHP classes. Keys of this array are field types, whereas their * values are names of PHP classes that implement the provided behavior for * that particular field type */ function synonyms_behavior_implementation_info($behavior) { $info = module_invoke_all('synonyms_behavior_implementation_info', $behavior); drupal_alter('synonyms_behavior_implementation_info', $info, $behavior); return $info; } /** * Determine what PHP class implements specific behavior for specific field. * * @param string $behavior * Name of the behavior, implementation of which is requested. It should be * one of the keys of the return of synonyms_behaviors() function. * @param array $field * Field definition array for which PHP class implementing $behavior is * requested * * @return string * Name of the PHP class that implements $behavior for $field field */ function synonyms_behavior_implementation_class($behavior, $field) { $map = synonyms_behavior_implementation_info($behavior); return $map[$field['type']]; } /** * Execute unpacking operation on the just loaded synonyms behavior settings. * * @param array $settings * Array of the just loaded settings. Each sub array should contain the * following keys: * - instance_id: (int) ID of the instance to which it applies * - behavior: (string) name of the synonyms behavior to which these settings * apply * - settings_serialized: (string) serialized content of the settings * * @return array * Unpacked version of the provided $settings */ function synonyms_behavior_settings_unpack($settings) { foreach ($settings as &$setting) { $setting['settings'] = $setting['settings_serialized'] ? unserialize($setting['settings_serialized']) : NULL; } return $settings; } /** * Save the provided synonyms behavior settings into the database. * * @param array $settings * Array of settings. It must have the following structure: * - instance_id: (int) ID of the instance to which it applies * - behavior: (string) name of the synonyms behavior to which it applies * - settings: (mixed) the content of settings themselves */ function synonyms_behavior_settings_save($settings) { if (!isset($settings['settings'])) { $settings['settings'] = array(); } $settings['settings_serialized'] = serialize($settings['settings']); $result = db_merge('synonyms_settings') ->key(array( 'instance_id' => $settings['instance_id'], 'behavior' => $settings['behavior'], )) ->fields(array( 'instance_id' => $settings['instance_id'], 'behavior' => $settings['behavior'], 'settings_serialized' => $settings['settings_serialized'], )) ->execute(); switch ($result) { case MergeQuery::STATUS_INSERT: $behavior_definition = synonyms_behaviors(); $behavior_definition = $behavior_definition[$settings['behavior']]; $enabled_callback = ctools_plugin_get_function($behavior_definition, 'enabled callback'); if ($enabled_callback) { $enabled_callback($behavior_definition, $settings['settings'], synonyms_instance_id_load($settings['instance_id'])); } break; } } /** * Delete settings for specific behavior and field instance. * * @param int $instance_id * ID of the instance for which settings should be removed * @param string $behavior * Name of behavior for which settings should be removed */ function synonyms_behavior_settings_delete($instance_id, $behavior) { $behavior_definition = synonyms_behaviors(); $behavior_definition = $behavior_definition[$behavior]; $disabled_callback = ctools_plugin_get_function($behavior_definition, 'disabled callback'); if ($disabled_callback) { $instance = synonyms_instance_id_load($instance_id); $behavior_implementation = synonyms_behavior_get($behavior, $instance['entity_type'], $instance['bundle'], FALSE, TRUE); $behavior_implementation = $behavior_implementation[$instance_id]; $disabled_callback($behavior_definition, $behavior_implementation, $instance); } db_delete('synonyms_settings') ->condition('instance_id', $instance_id) ->condition('behavior', $behavior) ->execute(); } /** * Supportive function to load a field instance by its ID. * * @param int $instance_id * ID of the instance that should be loaded * * @return array * Instance definition array */ function synonyms_instance_id_load($instance_id) { $result = db_select('field_config_instance', 'i') ->fields('i', array('entity_type', 'bundle', 'field_name')) ->condition('id', $instance_id) ->execute() ->fetchAssoc(); return field_info_instance($result['entity_type'], $result['field_name'], $result['bundle']); } /** * Convert synonyms friendly select widget values for storage friendly format. * * It acts similar to what the _options_form_to_storage() function does - * bridges between how values are returned from form API to how they are * expected by Field module. */ function synonyms_select_form_to_storage($element, &$form_state) { $value = array(); if ($element['#multiple']) { $value = $element['#value']; } else { $value[] = $element['#value']; } foreach ($value as $k => $v) { // For the cases when a synonym was selected and not a term option, we // process the selected values stripping everything that goes after // semicolon. if (!is_numeric($v)) { $tid = explode(':', $v); $value[$k] = $tid[0]; } } // The user also might have selected multiple times the same term, given that // a term can be represented by more than 1 option (a term and its synonym), // then it's possible in theory, so we should be ready for this scenario. $value = array_unique($value); $form_state_value = array(); foreach ($value as $tid) { $form_state_value[] = array('tid' => $tid); } form_set_value($element, $form_state_value, $form_state); } /** * Check whether a taxonomy term $tid is a child of a taxonomy term $parent_tid. * * Supportive function, used throughout this module for parent constrains. * * @param int $tid * {taxonomy_term}.tid of the term that is tested for being a child of the * $parent_tid term * @param int $parent_tid * {taxonomy_term}.tid of the term that is tested for being parent of the $tid * term * * @return bool * Whether $tid is a child of $parent_tid */ function synonyms_taxonomy_term_is_child_of($tid, $parent_tid) { $term_parents = taxonomy_get_parents_all($tid); // Dropping out the term itself from its array of parents. array_shift($term_parents); foreach ($term_parents as $term_parent) { if ($term_parent->tid == $parent_tid) { return TRUE; } } return FALSE; } /** * Format an option for select form element. * * @param object $term * Fully loaded taxonomy term which is represented by this option * @param string $synonym * If the provided term is represented in this option by a synonym, then * provide it here * @param array $behavior_implementation * Behavior implementation array from which the $synonym comes from * * @return object * An option for select form element */ function synonyms_select_option($term, $synonym = NULL, $behavior_implementation = NULL) { $key = $synonym ? $term->tid . ':' . drupal_html_class($synonym) : $term->tid; $wording = $term->name; if ($synonym) { $instance = field_info_instance($behavior_implementation['entity_type'], $behavior_implementation['field_name'], $behavior_implementation['bundle']); $wording = format_string($behavior_implementation['settings']['wording'], array( '@synonym' => $synonym, '@term' => $term->name, '@field_name' => drupal_strtolower($instance['label']), )); } return (object) array( 'option' => array($key => str_repeat('-', $term->depth) . $wording), ); } /** * Supportive function to build options array with sorting by name logic. * * The function starts from the 0-depth level and starts to recursively build * the options and to sort the labels on each level, then it merges the bottom * to up all the levels maintaining correct order within the final options * array. * * @param object $vocabulary * Within which vocabulary to execute the function. Supply here the fully * loaded taxonomy vocabulary object * @param int $parent * Only children of this term will be included in the output. You can supply * 0 which means to include all the terms from the vocabulary * @param int $depth * Used for internal purposes. Clients of this function should supply here 0, * unless they know what they are doing. It is used internally to keep track * of the nesting level * * @return array * Array of options that can be inserted directly into 'select' form element. * The options will be sorted by name (term or synonym), respecting the * hierarchy restrictions */ function synonyms_select_sort_name_options_recursive($vocabulary, $parent = 0, $depth = 0) { // We statically cache behavior implementations in order to not DDOS the data // base. $behavior_implementations = drupal_static(__FUNCTION__, array()); $bundle = field_extract_bundle('taxonomy_term', $vocabulary); if (!isset($behavior_implementations[$bundle])) { $behavior_implementations[$bundle] = synonyms_behavior_get('select', 'taxonomy_term', $bundle, TRUE); } $options = array(); if ($terms = taxonomy_get_tree($vocabulary->vid, $parent, 1, TRUE)) { $options = array(); foreach ($terms as $term) { $term->depth = $depth; $options[] = synonyms_select_option($term); foreach ($behavior_implementations[$bundle] as $implementation) { foreach (synonyms_extract_synonyms($term, $implementation) as $synonym) { $options[] = synonyms_select_option($term, $synonym, $implementation); } } } usort($options, 'synonyms_select_sort_name'); // Now recursively go one level nested into each of the terms that we have // on this level. $options_copy = $options; $i = 0; foreach ($options_copy as $v) { $i++; $tid = array_keys($v->option); $tid = $tid[0]; if (is_numeric($tid)) { $nested_options = synonyms_select_sort_name_options_recursive($vocabulary, $tid, $depth + 1); $options = array_merge(array_slice($options, 0, $i), $nested_options, array_slice($options, $i)); } } } return $options; } /** * Supportive function. * * It is used for string comparison within synonyms friendly select widget. */ function synonyms_select_sort_name($a, $b) { return strcasecmp(reset($a->option), reset($b->option)); }