' . t('To view a summary of the default meta tags and the inheritance, click on a meta tag type.') . '

'; } elseif ($path == 'admin/help#metatag') { return '

' . t('The Metatag module provides a options to let each page have customized meta data added to the "meta" tags in the HEAD section of the document.') . '

'; } elseif ($path == 'admin/config/search/metatags/bulk-revert') { return '

' . t('This form will wipe out all custom meta tags for the selected entities, reverting them to the default configuration assigned at the Defaults tab. For example, if the meta tags are changed for an article they will be removed if the "Node: Article" checkbox is selected.', array('@url' => url('admin/config/search/metatags'))) . '

'; } } /** * Implements hook_theme(). */ function metatag_theme() { $info['metatag'] = array( 'render element' => 'element', 'file' => 'metatag.theme.inc', ); $info['metatag_http_equiv'] = array( 'render element' => 'element', 'file' => 'metatag.theme.inc', ); $info['metatag_link_rel'] = array( 'render element' => 'element', 'file' => 'metatag.theme.inc', ); $info['metatag_link_rev'] = array( 'render element' => 'element', 'file' => 'metatag.theme.inc', ); $info['metatag_property'] = array( 'render element' => 'element', 'file' => 'metatag.theme.inc', ); return $info; } /** * Implements hook_ctools_plugin_api(). */ function metatag_ctools_plugin_api($owner, $api) { if ($owner == 'metatag' && $api == 'metatag') { return array('version' => 1); } } /** * Implements hook_hook_info(). */ function metatag_hook_info() { $hooks = array( 'metatag_config_default', 'metatag_config_default_alter', 'metatag_config_delete', 'metatag_config_insert', 'metatag_config_instance_info', 'metatag_config_instance_info_alter', 'metatag_config_load', 'metatag_config_load_presave', 'metatag_config_update', 'metatag_info', 'metatag_info_alter', ); return array_fill_keys($hooks, array('group' => 'metatag')); } /** * Implements hook_permission(). */ function metatag_permission() { $permissions['administer meta tags'] = array( 'title' => t('Administer meta tags'), 'restrict access' => TRUE, 'description' => t('Control the main settings pages and modify per-object meta tags.'), ); $permissions['edit meta tags'] = array( 'title' => t('Edit meta tags'), 'description' => t('Modify meta tags on individual entity records (nodes, terms, users, etc).'), ); // Optional extended edit permissions. if (variable_get('metatag_extended_permissions', FALSE)) { $permissions['edit meta tags']['description'] .= '
' . t('Extended Permissions has been enabled. Roles have the :admin permission will see all meta tags on edit forms, otherwise the permissions below will control which meta tags are available and are needed in addition to Edit meta tags.', array(':admin' => t('Administer meta tags'))); $metatags = metatag_get_info(); foreach ($metatags['tags'] as $metatag_name => $metatag) { $permissions['edit meta tag: ' . $metatag_name] = array( 'title' => t('Extended permission: Edit :tag meta tag', array(':tag' => $metatag['label'])), 'description' => t('Customize the :tag meta tag on individual forms.', array(':tag' => $metatag['label'])), ); } } return $permissions; } /** * Implements hook_menu(). */ function metatag_menu() { $items['admin/config/search/metatags'] = array( 'title' => 'Metatag', 'description' => 'Configure Metatag defaults.', 'page callback' => 'metatag_config_overview', 'access arguments' => array('administer meta tags'), 'file' => 'metatag.admin.inc', ); $items['admin/config/search/metatags/config'] = array( 'title' => 'Defaults', 'type' => MENU_DEFAULT_LOCAL_TASK, 'weight' => -10, ); $items['admin/config/search/metatags/config/add'] = array( 'title' => 'Add a Metatag default', 'page callback' => 'drupal_get_form', 'page arguments' => array('metatag_config_add_form'), 'access arguments' => array('administer meta tags'), 'file' => 'metatag.admin.inc', 'type' => MENU_LOCAL_ACTION, ); $items['admin/config/search/metatags/config/%metatag_config'] = array( 'title callback' => 'metatag_config_title', 'title arguments' => array(5), 'page callback' => 'drupal_get_form', 'page arguments' => array('metatag_config_edit_form', 5), 'access arguments' => array('administer meta tags'), 'file' => 'metatag.admin.inc', ); $items['admin/config/search/metatags/config/%metatag_config/edit'] = array( 'title' => 'Edit', 'type' => MENU_DEFAULT_LOCAL_TASK, 'weight' => -10, ); $items['admin/config/search/metatags/config/%metatag_config/enable'] = array( 'title' => 'Enable', 'page callback' => 'metatag_config_enable', 'page arguments' => array(5), 'access callback' => 'metatag_config_access', 'access arguments' => array('enable', 5), 'file' => 'metatag.admin.inc', ); $items['admin/config/search/metatags/config/%metatag_config/disable'] = array( 'title' => 'Disable', 'page callback' => 'metatag_config_disable', 'page arguments' => array(5), 'access callback' => 'metatag_config_access', 'access arguments' => array('disable', 5), 'file' => 'metatag.admin.inc', ); $items['admin/config/search/metatags/config/%metatag_config/revert'] = array( 'title' => 'Revert', 'page callback' => 'drupal_get_form', 'page arguments' => array('metatag_config_delete_form', 5), 'access callback' => 'metatag_config_access', 'access arguments' => array('revert', 5), 'file' => 'metatag.admin.inc', 'type' => MENU_LOCAL_TASK, ); $items['admin/config/search/metatags/config/%metatag_config/delete'] = array( 'title' => 'Delete', 'page callback' => 'drupal_get_form', 'page arguments' => array('metatag_config_delete_form', 5), 'access callback' => 'metatag_config_access', 'access arguments' => array('delete', 5), 'file' => 'metatag.admin.inc', ); $items['admin/config/search/metatags/config/%metatag_config/export'] = array( 'title' => 'Export', 'page callback' => 'metatag_config_export_form', 'page arguments' => array(5), 'access arguments' => array('administer meta tags'), 'file' => 'metatag.admin.inc', 'type' => MENU_LOCAL_TASK, 'weight' => 10, ); $items['admin/config/search/metatags/settings'] = array( 'title' => 'Advanced settings', 'page callback' => 'drupal_get_form', 'page arguments' => array('metatag_admin_settings_form'), 'access arguments' => array('administer meta tags'), 'type' => MENU_LOCAL_TASK, 'weight' => 30, 'file' => 'metatag.admin.inc', ); $items['admin/config/search/metatags/bulk-revert'] = array( 'title' => 'Bulk revert', 'page callback' => 'drupal_get_form', 'page arguments' => array('metatag_bulk_revert_form'), 'access arguments' => array('administer meta tags'), 'type' => MENU_LOCAL_TASK, 'weight' => 40, 'file' => 'metatag.admin.inc', ); return $items; } /** * Implements hook_flush_caches(). */ function metatag_flush_caches() { return array('cache_metatag'); } /** * Load a metatag configuration record with all the defaults merged in. * * For example, given the configuration instance 'node:article', this function * will load the configuration records for 'node:article', then 'node', and * then finally 'global', with each attempt using an array merge. * * The levels of defaults is arranged by splitting the $instance variable by * the colon character, and always using a 'global' instance at the end. */ function metatag_config_load_with_defaults($instance, $include_global = TRUE) { $defaults = &drupal_static(__FUNCTION__, array()); // Statically cache defaults since they can include multiple levels. $cid = "config:{$instance}" . ($include_global ? ':withglobal' : ':withoutglobal'); if (!isset($defaults[$cid])) { if ($cache = metatag_cache_get($cid)) { $defaults[$cid] = $cache->data; } else { $defaults[$cid] = array(); $instances = metatag_config_get_parent_instances($instance, $include_global); $configs = metatag_config_load_multiple($instances); foreach ($instances as $key) { // Ignore disabled configurations. if (!isset($configs[$key]) || !empty($configs[$key]->disabled)) { continue; } // Add config to the defaults array. if (!empty($configs[$key]->config)) { $defaults[$cid] += $configs[$key]->config; } } metatag_cache_set($cid, $defaults[$cid]); } } return $defaults[$cid]; } /** * Load a metatag configuration record. */ function metatag_config_load($instance) { $results = metatag_config_load_multiple(array($instance)); return !empty($results[$instance]) ? $results[$instance] : FALSE; } /** * Load multiple metatag configuration records. */ function metatag_config_load_multiple(array $instances) { // Load the data. ctools_include('export'); $configs = ctools_export_load_object('metatag_config', 'names', $instances); // "Fix" any records that might be using old values. Ideally these will be // permanently fixed by being re-saved or re-exported. foreach (metatag_config_get_replacements() as $old_tag => $new_tag) { foreach ($configs as $config_name => $config) { if (isset($config->config[$old_tag])) { $config->config[$new_tag] = $config->config[$old_tag]; unset($config->config[$old_tag]); } } } return $configs; } /** * Identify the meta tags that have been deprecated and replaced by others. */ function metatag_config_get_replacements() { $replacements = &drupal_static(__FUNCTION__); if (!isset($replacements)) { $replacements = array(); foreach (metatag_get_info('tags') as $tag_name => $tag_info) { if (!empty($tag_info['replaces'])) { if (!is_array($tag_info['replaces'])) { $tag_info['replaces'] = array($tag_info['replaces']); } foreach ($tag_info['replaces'] as $replaces) { $replacements[$replaces] = $tag_name; } } } } return $replacements; } /** * Save a metatag configuration record to the database. */ function metatag_config_save($config) { $config->is_new = empty($config->cid); // Allow modules to alter the configuration before it is saved using // hook_metatag_config_presave(). module_invoke_all('metatag_config_presave', $config); // Update the i18n string if (function_exists('i18n_string_update')) { $instance = $config->instance; foreach ($config->config as $field => $item) { $name = "metatag:" . $instance . ":" . $field; i18n_string_update($name, $item['value']); } } if ($config->is_new) { drupal_write_record('metatag_config', $config); // Allow modules to act upon the record insertion using // hook_metatag_config_insert(). module_invoke_all('metatag_config_insert', $config); } else { drupal_write_record('metatag_config', $config, array('cid')); // Allow modules to act upon the record update using // hook_metatag_config_insert(). module_invoke_all('metatag_config_update', $config); } unset($config->is_new); // Clear any caches. metatag_config_cache_clear(); } /** * Delete a metatag configuration record. */ function metatag_config_delete($instance) { db_delete('metatag_config') ->condition('instance', $instance) ->execute(); // Clear any caches. metatag_config_cache_clear(); } /** * Clear the metatag configuration cache. */ function metatag_config_cache_clear() { cache_clear_all('*', 'cache_metatag', TRUE); drupal_static_reset('metatag_config_load_with_defaults'); drupal_static_reset('metatag_entity_has_metatags'); drupal_static_reset('metatag_entity_supports_metatags'); ctools_include('export'); ctools_export_load_object_reset('metatag_config'); } /** * Load an entity's tags. * * @param $entity_type * The entity type to load. * @param $entity_id * The ID of the entity to load. * * @return * An array of tag data keyed by revision ID and language. */ function metatag_metatags_load($entity_type, $entity_id) { $metatags = metatag_metatags_load_multiple($entity_type, array($entity_id)); return !empty($metatags) ? reset($metatags) : array(); } /** * Load tags for multiple entities. * * @param $entity_type * The entity type to load. * @param $entity_ids * The list of entity IDs. * * @return * An array of tag data, keyed by entity ID, revision ID and language. */ function metatag_metatags_load_multiple($entity_type, array $entity_ids, array $revision_ids = array()) { // Double check entity IDs are numeric thanks to Entity API module. $entity_ids = array_filter($entity_ids, 'is_numeric'); if (empty($entity_ids)) { return array(); } // Also need to check if the metatag table exists since this condition could // fire before the table has been installed yet. if (!variable_get('metatag_schema_installed', FALSE)) { if (db_table_exists('metatag')) { variable_set('metatag_schema_installed', TRUE); } else { watchdog('metatag', 'The system tried to load metatag data before the schema was fully loaded.', array(), WATCHDOG_WARNING); return array(); } } // Verify that the metatag.revision_id field has been added to the {metatag} // table schema. if (!variable_get('metatag_has_revision_id', FALSE)) { if (db_field_exists('metatag', 'revision_id')) { variable_set('metatag_has_revision_id', TRUE); } else { watchdog('metatag', 'The database updates need to be ran.', array(), WATCHDOG_WARNING); return array(); } } // Get all translations of tag data for this entity. $query = db_select('metatag', 'm') ->fields('m', array('entity_id', 'revision_id', 'language', 'data')) ->condition('m.entity_type', $entity_type) ->orderBy('entity_id') ->orderBy('revision_id'); // Filter by revision_ids if they are available. If not, filter by entity_ids. if (!empty($revision_ids)) { $query->condition('m.revision_id', $revision_ids, 'IN'); } else { $query->condition('m.entity_id', $entity_ids, 'IN'); } $result = $query->execute(); // Marshal it into an array keyed by entity ID. Each value is an array of // translations keyed by language code. $metatags = array(); while ($record = $result->fetchObject()) { $data = unserialize($record->data); // "Fix" any records that might be using old values. Ideally these will be // permanently fixed by being re-saved or re-exported. foreach (metatag_config_get_replacements() as $old_tag => $new_tag) { if (isset($data[$old_tag])) { $data[$new_tag] = $data[$old_tag]; unset($data[$old_tag]); } } $metatags[$record->entity_id][$record->revision_id][$record->language] = $data; } return $metatags; } /** * Save an entity's tags. * * @param $entity_type * The entity type to load * @param $entity_id * The entity's ID * @param $revision_id * The entity's VID. * @param $metatags * All of the tag information * @param $language * The language of the translation set */ function metatag_metatags_save($entity_type, $entity_id, $revision_id, $metatags, $langcode, $old_vid = NULL) { // If no language assigned, or the language doesn't exist, use the // has-no-language language. $languages = language_list(); if (empty($langcode) || !isset($languages[$langcode])) { $langcode = LANGUAGE_NONE; } // Check that $entity_id is numeric because of Entity API and string IDs. if (!is_numeric($entity_id)) { return; } // The revision_id must be a numeric value; some entities use NULL for the // revision so change that to a zero. if (is_null($revision_id)) { $revision_id = 0; } // Handle scenarios where the metatags are completely empty. if (empty($metatags)) { $metatags = array(); // Add an empty array record for each language. $languages = db_query("SELECT language, '' FROM {metatag} WHERE (entity_type = :type) AND (entity_id = :id) AND (revision_id = :revision)", array( ':type' => $entity_type, ':id' => $entity_id, ':revision' => $revision_id, ))->fetchAllKeyed(); foreach ($languages as $oldlang => $empty) { $metatags[$oldlang] = array(); } } // Update each of the per-language metatag configurations in turn. foreach ($metatags as $langcode => $new_metatags) { // Allow other modules to alter the meta tags prior to saving using // hook_metatag_presave(). foreach (module_implements('metatag_presave') as $module) { $function = "{$module}_metatag_presave"; $function($new_metatags, $entity_type, $entity_id, $revision_id, $langcode); } // If the data array is empty, there is no data to actually save, so just // delete the record from the database. if (empty($new_metatags)) { db_delete('metatag') ->condition('entity_type', $entity_type) ->condition('entity_id', $entity_id) ->condition('revision_id', $revision_id) ->condition('language', $langcode) ->execute(); } // Otherwise save the data for this entity. else { db_merge('metatag') ->key(array( 'entity_type' => $entity_type, 'entity_id' => $entity_id, 'language' => $langcode, 'revision_id' => $revision_id, )) ->fields(array( 'data' => serialize($new_metatags), )) ->execute(); } } // Clear cached data. metatag_metatags_cache_clear($entity_type, $entity_id); } /** * Delete an entity's tags. * * @param $entity_type * The entity type * @param $entity_id * The entity's ID * @param $revision_id * The entity's VID. * @param $langcode * The language ID of the entry to delete. If left blank, all language * entries for this entity will be deleted. */ function metatag_metatags_delete($entity_type, $entity_id, $revision_id = NULL, $langcode = NULL) { $revision_ids = array(); if (!empty($revision_id)) { $revision_ids[] = $revision_id; } return metatag_metatags_delete_multiple($entity_type, array($entity_id), $revision_ids, $langcode); } /** * Delete multiple entities' tags. * * @param $entity_type * The entity type * @param $entity_ids * The list of IDs * @param $revision_id * An optional list of VIDs, if omitted all revisions will be deleted. * @param $langcode * The language ID of the entities to delete. If left blank, all language * entries for the enities will be deleted. */ function metatag_metatags_delete_multiple($entity_type, array $entity_ids, array $revision_ids = array(), $langcode = NULL) { // Double check entity IDs are numeric thanks to Entity API module. $entity_ids = array_filter($entity_ids, 'is_numeric'); if ($metatags = metatag_metatags_load_multiple($entity_type, $entity_ids, $revision_ids)) { $transaction = db_transaction(); try { // Let other modules know about the records being deleted using // hook_metatag_metatags_delete(). module_invoke_all('metatag_metatags_delete', $entity_type, $entity_ids, $revision_ids, $langcode); // Set the entity to delete. $query = db_delete('metatag') ->condition('entity_type', $entity_type) ->condition('entity_id', $entity_ids, 'IN'); // Optionally delete a specific revision. if (!empty($revision_ids)) { $query->condition('revision_id', $revision_ids, 'IN'); } // Specify a language if there is one. if (!empty($langcode)) { $query->condition('language', $langcode); } // Perform the deletion(s). $query->execute(); // Clear cached data. metatag_metatags_cache_clear($entity_type, $entity_ids); } catch (Exception $e) { $transaction->rollback(); watchdog_exception('metatag', $e); throw $e; } } } /** * Clear the cached records for a given entity type or entity ID. * * @param string $entity_type * The entity type to clear. */ function metatag_metatags_cache_clear($entity_type, $entity_ids = NULL) { if (empty($entity_ids)) { cache_clear_all("output:$entity_type", 'cache_metatag', TRUE); } else { if (!is_array($entity_ids)) { $entity_ids = array($entity_ids); } foreach ($entity_ids as $entity_id) { cache_clear_all("output:$entity_type:$entity_id", 'cache_metatag', TRUE); } } } /** * Implements hook_entity_load(). */ function metatag_entity_load($entities, $entity_type) { // Get the revision_ids. $revision_ids = array(); foreach ($entities as $key => $entity) { list($entity_id, $revision_id) = entity_extract_ids($entity_type, $entity); $revision_id = intval($revision_id); if (!empty($revision_id)) { $revision_ids[] = $revision_id; } } // Wrap this in a try-catch block to work around occasions when the schema // hasn't been updated yet. try { if (metatag_entity_supports_metatags($entity_type)) { $metatags = metatag_metatags_load_multiple($entity_type, array_keys($entities), $revision_ids); // Assign the metatag records for the correct revision ID. foreach ($entities as $entity_id => $entity) { list($entity_id, $revision_id) = entity_extract_ids($entity_type, $entity); $revision_id = intval($revision_id); $entities[$entity_id]->metatags = isset($metatags[$entity_id][$revision_id]) ? $metatags[$entity_id][$revision_id] : array(); } } } catch (Exception $e) { watchdog('metatag', 'Error loading meta tag data, do the database updates need to be run? The error occurred when loading record(s) %ids for the %type entity type. The error message was: %error', array('@update' => base_path() . 'update.php', '%ids' => implode(', ', array_keys($entities)), '%type' => $entity_type, '%error' => $e->getMessage()), WATCHDOG_WARNING); // Don't display the same message twice for Drush. if (drupal_is_cli()) { drupal_set_message(t('Run your updates, like drush updb.')); } // Only message people who can see it in watchdog and can likely fix it. elseif (user_access('access site reports')) { drupal_set_message(t('Error loading meta tag data, do the database updates need to be run?', array('@update' => base_path() . 'update.php')), 'error'); } } } /** * Implements hook_entity_insert(). */ function metatag_entity_insert($entity, $entity_type) { if (isset($entity->metatags)) { list($entity_id, $revision_id) = entity_extract_ids($entity_type, $entity); $revision_id = intval($revision_id); // Determine the entity's language. $langcode = entity_language($entity_type, $entity); // Unfortunately due to how core works, the normal entity_language() // function returns 'und' instead of the node's language during node // creation. if ((empty($langcode) || $langcode == LANGUAGE_NONE) && !empty($entity->language)) { $langcode = $entity->language; } // If no language was still found, use the 'no language' value. if (empty($langcode)) { $langcode = LANGUAGE_NONE; } // Work-around for initial entity creation where a language was selection // but where it's different to the form's value. if (!isset($entity->metatags[$langcode]) && isset($entity->metatags[LANGUAGE_NONE])) { $entity->metatags[$langcode] = $entity->metatags[LANGUAGE_NONE]; unset($entity->metatags[LANGUAGE_NONE]); } // Support for Workbench Moderation v1. if ($entity_type == 'node' && _metatag_isdefaultrevision($entity)) { return; } metatag_metatags_save($entity_type, $entity_id, $revision_id, $entity->metatags, $langcode); } } /** * Implements hook_entity_update(). */ function metatag_entity_update($entity, $entity_type) { if (!metatag_entity_supports_metatags($entity_type)) { return; } list($entity_id, $revision_id) = entity_extract_ids($entity_type, $entity); $revision_id = intval($revision_id); if (isset($entity->metatags)) { // Determine the entity's new language. This will always be accurate as the // language value will already have been updated by the time this function // executes, and it will be loaded for the correct edit process. $new_language = metatag_entity_get_language($entity_type, $entity); // If applicable, determine the entity's original language. This cannot be // obtained via the normal API as that data will already have been updated, // instead check to see if the entity has an old-fasioned 'language' value. if (isset($entity->original) && isset($entity->language) && isset($entity->original->language)) { $old_language = $entity->original->language; // If the language has changed then additional checking needs to be done. // Need to compare against the entity's raw language value as they will // be different when updating a translated entity, versus an untranslated // entity or a source entity for translation, and give a false positive. if ($new_language == $entity->language && $new_language != $old_language) { // If this entity is not translated, or if it is translated but the // translation was previously created, then some language cleanup needs // to be done. if (!isset($entity->translation) || (isset($entity->translation) && !empty($entity->translation['created']))) { // Delete the old language record. This will not affect old revisions. db_delete('metatag') ->condition('entity_type', $entity_type) ->condition('entity_id', $entity_id) ->condition('revision_id', $revision_id) ->condition('language', $old_language) ->execute(); // Swap out the metatag values for the two languages. if (isset($entity->metatags[$old_language])) { $entity->metatags[$new_language] = $entity->metatags[$old_language]; unset($entity->metatags[$old_language]); } } } } // Support for Workbench Moderation v1. if ($entity_type == 'node' && _metatag_isdefaultrevision($entity)) { return; } // Save the record. $old_vid = isset($entity->old_vid) ? $entity->old_vid : NULL; metatag_metatags_save($entity_type, $entity_id, $revision_id, $entity->metatags, $new_language, $old_vid); } else { // Still ensure the meta tag output is cached. metatag_metatags_cache_clear($entity_type, $entity_id); } } /** * Implements hook_entity_delete(). */ function metatag_entity_delete($entity, $entity_type) { list($entity_id) = entity_extract_ids($entity_type, $entity); metatag_metatags_delete($entity_type, $entity_id); } /** * Implements hook_field_attach_delete_revision(). */ function metatag_field_attach_delete_revision($entity_type, $entity) { list($entity_id, $revision_id) = entity_extract_ids($entity_type, $entity); $revision_id = intval($revision_id); metatag_metatags_delete($entity_type, $entity_id, $revision_id); } /** * Implements hook_entity_view(). * * Provides additional argument to allow the display to be forced, to work * around problems elsewhere in the APIs. */ function metatag_entity_view($entity, $entity_type, $view_mode, $langcode, $force = FALSE) { // Only run this function once per page load. static $i_will_say_this_only_once = FALSE; // Only proceed if this entity object is the page being viewed. if (_metatag_entity_is_page($entity_type, $entity)) { // Some API calls need to force the data loading. if (!$force) { // Only run this function once per page load. if ($i_will_say_this_only_once) { return; } $i_will_say_this_only_once = TRUE; } // CTools uses 'page_manager' view mode to indicate the full entity display // page rather than 'full', so streamline the internal processes. if ($view_mode == 'page_manager') { $view_mode = 'full'; } // Generate metatags output. if ($output = metatag_generate_entity_metatags($entity, $entity_type, $langcode, $view_mode)) { list($entity_id, $revision_id, $bundle) = entity_extract_ids($entity_type, $entity); $instance = "{$entity_type}:{$bundle}"; // We need to register the term's metatags, so we can later fetch them. // @see metatag_page_build(). metatag_page_set_metatags($instance, $output); } } } /** * Generate the metatags for a given entity. * * @param object $entity * The entity object to generate the metatags for. * @param string $entity_type * The entity type of the entity. * @param string $langcode * The language code used for rendering the entity. * @param string $view_mode * The view mode the entity is rendered in. * @param bool $cached * TRUE if metatags can be loaded from and saved to the cache. FALSE if the * cache should be bypassed. * * @return array * A renderable array of metatags for the given entity. */ function metatag_generate_entity_metatags($entity, $entity_type, $langcode = NULL, $view_mode = 'full', $cached = TRUE) { // If this entity object isn't allowed meta tags, don't continue. if (!metatag_entity_has_metatags($entity_type, $entity)) { return array(); } // Obtain some details of the entity that are needed elsewhere. list($entity_id, $revision_id, $bundle) = entity_extract_ids($entity_type, $entity); $revision_id = intval($revision_id); // Check if a specific metatag config exists, otherwise just use the global // one, stripping out the bundle. $instance = "{$entity_type}:{$bundle}"; if (!metatag_config_load_with_defaults($instance, FALSE)) { $instance = "{$entity_type}"; } // Determine the language this entity actually uses. $entity_language = metatag_entity_get_language($entity_type, $entity); // If no language was requested, try the language defined for this page // request. if (empty($langcode)) { $langcode = $GLOBALS['language_content']->language; } // This entity doesn't have any languages defined, i.e. it uses 'und'. This // can't conflict with loading the wrong language as entities either have no // language or they have specific one(s), they can't have both. if ($entity_language == LANGUAGE_NONE) { $langcode = LANGUAGE_NONE; } // If there are no meta tags for the currently identified language, and there // *are* meta tags defined for the entity's default language, use the entity's // default language's values, unless the "Don't load entity's default // language values if no languages match" option is enabled on the advanced // settings page. elseif (empty($entity->metatags[$langcode]) && !empty($entity->metatags[$entity_language]) && !variable_get('metatag_entity_no_lang_default', FALSE)) { $langcode = $entity_language; } // Other scenarios. else { // There's no need to do anything else - either there are meta tag values // created for the requested language or there aren't. } $cid = FALSE; if ($cached) { // All applicable pieces for this current page. $cid_parts = array( 'entity_type' => $entity_type, 'bundle' => $bundle, 'entity_id' => $entity_id, 'revision_id' => $revision_id, // Cache separately based on the language of the passed-in entity and the // overall active language of the page. 'langcode' => $langcode, 'language_content' => $GLOBALS['language_content']->language, 'view_mode' => $view_mode, ); $cid = metatag_cache_default_cid_parts($cid_parts); } if ($cid && $cache = metatag_cache_get($cid)) { $output = $cache->data; } else { // Separate the meta tags. $metatags = isset($entity->metatags) ? $entity->metatags : array(); // Build options for meta tag rendering. $options = array( 'entity' => $entity, 'entity_type' => $entity_type, 'view_mode' => $view_mode, ); // Ensure we actually pass a language object rather than language code. $languages = language_list(); if (isset($languages[$langcode])) { $options['language'] = $languages[$langcode]; } // Reload the entity object from cache as it may have been altered. $token_type = token_get_entity_mapping('entity', $entity_type); $entities = entity_load($entity_type, array($entity_id)); $options['token data'][$token_type] = $entities[$entity_id]; $options['entity'] = $entities[$entity_id]; // Render the metatags and save to the cache. $output = metatag_metatags_view($instance, $metatags, $options); if ($cid) { metatag_cache_set($cid, $output); } } return $output; } /** * Generate the metatags for a given entity. * * @param object $entity_id * The entity id of the entity to generate the metatags for. * @param string $entity_type * The entity type of the entity to generate the metatags for. * @param string $langcode * The language code used for rendering the entity. * * @return array * A renderable array of metatags for the given entity. */ function metatags_get_entity_metatags($entity_id, $entity_type, $langcode = NULL) { $entities = entity_load($entity_type, array($entity_id)); $entity = reset($entities); return !empty($entity) ? metatag_generate_entity_metatags($entity, $entity_type, $langcode) : array(); } /** * Build a renderable array of meta tag output. * * @param string $instance * The configuration instance key of the meta tags to use, e.g. * "node:article". * @param array $metatags * An array of meta tag data. * @param array $options * (optional) An array of options including the following keys and values: * - language: A language object. * - token data: An array of data to pass into token_replace() during * meta tag value generation. */ function metatag_metatags_view($instance, array $metatags = array(), array $options = array()) { $output = array(); // Convert language codes to a language object. if (isset($options['language']) && is_string($options['language'])) { $languages = language_list(); $options['language'] = isset($languages[$options['language']]) ? $languages[$options['language']] : NULL; } // If there are any tags, determine the translation to display. if (!empty($metatags)) { // Get the display language; default to the entity's language. if (isset($options['language']) && isset($options['language']->language) && isset($metatags[$options['language']->language])) { $metatags = $metatags[$options['language']->language]; } // If no language requested, use the no-language value. elseif (!empty($metatags[LANGUAGE_NONE])) { $metatags = $metatags[LANGUAGE_NONE]; } else { $metatags = array(); } } // Add any default tags to the mix. $metatags += metatag_config_load_with_defaults($instance); $options['instance'] = $instance; foreach ($metatags as $metatag => $data) { if ((!empty($data['value']) || (isset($data['value']) && is_numeric($data['value']))) && $metatag_instance = metatag_get_instance($metatag, $data)) { $output[$metatag] = $metatag_instance->getElement($options); } } // Allow the output meta tags to be modified using // hook_metatag_metatags_view_alter(). drupal_alter('metatag_metatags_view', $output, $instance, $options); return $output; } function metatag_metatags_values($instance, array $metatags = array(), array $options = array()) { $values = array(); // Apply defaults to the data for each language. foreach ($metatags as $language => $metatag) { $metatags[$language] += metatag_config_load_with_defaults($instance); } // Generate output only if we have a valid language. if (isset($options['language']) && is_string($options['language']) && isset($metatags[$options['language']])) { $language = $options['language']; // Convert language codes to a language object. $languages = language_list(); $options['language'] = isset($languages[$language]) ? $languages[$language] : NULL; $options['instance'] = $instance; // Get output elements. foreach ($metatags[$language] as $metatag => $data) { if ($metatag_instance = metatag_get_instance($metatag, $data)) { $values[$metatag] = $metatag_instance->getValue($options); } } } return array_filter($values, 'drupal_strlen'); } /** * Build a FAPI array for editing meta tags. * * @param array $form * The current FAPI array. * @param string $instance * The configuration instance key of the metatags to use, e.g. "node:article". * @param array $metatags * An array of metatag data. * @param array $options * (optional) An array of options including the following keys and values: * - token types: An array of token types to be passed to theme_token_tree(). */ function metatag_metatags_form(array &$form, $instance, array $metatags = array(), array $options = array()) { $info = metatag_get_info(); if (empty($info['tags'])) { return; } // Work out the language code to use, default to NONE. $langcode = LANGUAGE_NONE; if (isset($form['#entity_type']) && isset($form['#entity'])) { $langcode = metatag_entity_get_language($form['#entity_type'], $form['#entity']); } // Merge in the default options. $options += array( 'token types' => array(), 'defaults' => metatag_config_load_with_defaults($instance), 'instance' => $instance, ); $form['metatags'] = array( '#type' => 'fieldset', '#title' => t('Meta tags'), '#collapsible' => TRUE, '#collapsed' => TRUE, '#multilingual' => TRUE, '#tree' => TRUE, '#access' => user_access('edit meta tags') || user_access('administer meta tags'), '#weight' => 40, '#language' => $langcode, '#attributes' => array( 'class' => array('metatags-form'), ), ); $form['metatags'][$langcode] = array( '#metatag_defaults' => $options['defaults'], '#type' => 'container', '#multilingual' => TRUE, '#tree' => TRUE, ); // Show a different intro message for entity pages vs config pages. if (isset($form['#entity'])) { $form['metatags'][$langcode]['intro_text'] = array( '#markup' => '

' . t('Configure the meta tags below. Tokens, e.g. "[node:summary]", automatically insert the corresponding information from that field or value, which helps to avoid redundant meta data and possible search engine penalization; see the "Browse available tokens" popup for more details.') . '

', '#weight' => -10, ); } else { $form['metatags'][$langcode]['intro_text'] = array( '#markup' => '

' . t('Configure the meta tags below. Use tokens (see the "Browse available tokens" popup) to avoid redundant meta data and search engine penalization. For example, a \'keyword\' value of "example" will be shown on all content using this configuration, whereas using the [node:field_keywords] automatically inserts the "keywords" values from the current entity (node, term, etc).') . '

', '#weight' => -10, ); } // Only support vertical tabs if there is a vertical tab element. foreach (element_children($form) as $key) { if (isset($form[$key]['#type']) && $form[$key]['#type'] == 'vertical_tabs') { $form['metatags']['#group'] = $key; $form['metatags']['#attached']['js']['vertical-tabs'] = drupal_get_path('module', 'metatag') . '/metatag.vertical-tabs.js'; break; } } // Merge in the default meta tag configurations. $metatags += $options['defaults']; // This will be used later. $group_metatag_access = array(); // Build the form for each metatag. foreach ($info['tags'] as $metatag => $metatag_info) { // @todo Replace context matching with hook_metatag_access(). if (isset($options['context']) && isset($metatag_info['context'])) { if (!in_array($options['context'], $metatag_info['context'])) { continue; } } $metatag_instance = metatag_get_instance($metatag, isset($metatags[$metatag]) ? $metatags[$metatag] : array()); if (empty($metatag_instance)) { continue; } // Get the form element from the meta tag class. $metatag_form = $metatag_instance->getForm($options); // Add a default value form element. if (isset($options['defaults'][$metatag]['value'])) { $metatag_form['default'] = array( '#type' => 'hidden', '#value' => $options['defaults'][$metatag]['value'], ); } // Optional extended edit permissions. if (variable_get('metatag_extended_permissions', FALSE)) { $metatag_form['#access'] = user_access('edit meta tag: ' . $metatag) || user_access('administer meta tags'); } else { $metatag_form['#access'] = $form['metatags']['#access']; } if (!empty($metatag_info['group'])) { $group_key = $metatag_info['group']; if (isset($info['groups'][$group_key]['label']) && !isset($form['metatags'][$langcode][$group_key])) { $group = $info['groups'][$group_key] + array('form' => array(), 'description' => NULL); $form['metatags'][$langcode][$group_key] = $group['form'] + array( '#type' => 'fieldset', '#title' => check_plain($group['label']), '#description' => filter_xss($group['description']), '#collapsible' => TRUE, '#collapsed' => TRUE, ); } $form['metatags'][$langcode][$group_key][$metatag] = $metatag_form + array('#parents' => array('metatags', $langcode, $metatag)); // Hide the fieldset itself if there is not at least one of the meta tag // fields visible. if (variable_get('metatag_extended_permissions', FALSE)) { $form['metatags'][$langcode][$group_key]['#access'] = count(element_get_visible_children($form['metatags'][$langcode][$group_key])) > 0; } else { $form['metatags'][$langcode][$group_key]['#access'] = $form['metatags']['#access']; } // Structure the access parameter into this array, and make use of it // later when we move on. Besides, this foreach is getting heavy. $group_metatag_access[$group_key] = $form['metatags'][$langcode][$group_key]['#access']; } else { $form['metatags'][$langcode][$metatag] = $metatag_form; } } // Hide the fieldset itself if there is not at least one of the meta tag // fields visible; only bother checking this if the user had edit access in // the first place. if ($form['metatags']['#access'] && variable_get('metatag_extended_permissions', FALSE)) { $form['metatags']['#access'] = count(element_get_visible_children($form['metatags'][$langcode])) > 0; } // Check the #access of each group. If it passed, we display options for // tokens. By this we update the #description of each group. if ($form['metatags']['#access']) { // Built the token list. $token_listing_link = theme('token_tree', array('token_types' => $options['token types'], 'dialog' => TRUE)); // Add the token list to the top of the fieldset. $form['metatags'][$langcode]['#description'] = $token_listing_link; // Check if each meta tag group is being displayed. if (!empty($group_metatag_access)) { foreach ($group_metatag_access as $group_key => $token_access) { if ($token_access) { // Update the description. if (isset($form['metatags'][$langcode][$group_key]['#description'])) { $form['metatags'][$langcode][$group_key]['#description'] .= '
'; } else { $form['metatags'][$langcode][$group_key]['#description'] = ''; } $form['metatags'][$langcode][$group_key]['#description'] .= $token_listing_link; } } } } // Add a submit handler to compare the submitted values against the deafult // values. $form += array('#submit' => array()); array_unshift($form['#submit'], 'metatag_metatags_form_submit'); } /** * Form API submission callback. * * Unset meta tag values that equal their default values, and load any * additional meta tag values for other languages so that they can be properly * saved later on. * * @see metatag_metatags_save() */ function metatag_metatags_form_submit($form, &$form_state) { if (!empty($form_state['values']['metatags'])) { // Unset meta tag values that equal their default values. foreach ($form_state['values']['metatags'] as $langcode => $values) { if (!empty($form['metatags'][$langcode]['#metatag_defaults'])) { metatag_filter_values_from_defaults($form_state['values']['metatags'][$langcode], $form['metatags'][$langcode]['#metatag_defaults']); } } // Need to load the entity's values for other languages, otherwise they will // be incorrectly deleted later on. if (isset($form['#entity']) && !empty($form['#entity']->metatags)) { foreach ($form['#entity']->metatags as $langcode => $values) { if (!isset($form_state['values']['metatags'][$langcode])) { $form_state['values']['metatags'][$langcode] = $values; } } } } } /** * Implements hook_field_extra_fields(). */ function metatag_field_extra_fields() { $extra = array(); foreach (entity_get_info() as $entity_type => $entity_info) { foreach (array_keys($entity_info['bundles']) as $bundle) { if (metatag_entity_supports_metatags($entity_type, $bundle)) { $extra[$entity_type][$bundle]['form']['metatags'] = array( 'label' => t('Meta tags'), 'description' => t('Meta tag module form elements.'), 'weight' => 40, ); } } } return $extra; } /** * Check if an individual entity has meta tags defined, or has defaults. * * @param string $entity_type * An entity type. * @param object $entity * An entity object. * * @return boolean * TRUE or FALSE if the entity should have a form for or process meta tags. */ function metatag_entity_has_metatags($entity_type, $entity) { // If an entity has custom meta tags assigned, then we should return TRUE. if (!empty($entity->metatags)) { return TRUE; } // Otherwise, check to see if there exists any enabed configuration for // either the entity type, or bundle (even if the configuration is empty). // If no configuration exists, then we should not be displaying the meta tag // forms or processing meta tags on entity view. $config_exists = &drupal_static(__FUNCTION__, array()); list( , , $bundle) = entity_extract_ids($entity_type, $entity); // Do not pretend to have metatags when the bundle does not support them. if (!metatag_entity_supports_metatags($entity_type, $bundle)) { return FALSE; } $instance = "{$entity_type}:{$bundle}"; if (!isset($config_exists[$instance])) { // Check if the intstance or its parents (excluding global) are enabled. $config_exists[$instance] = metatag_config_is_enabled($instance, TRUE, FALSE); } return !empty($config_exists[$instance]); } /** * Check whether the requested entity type (and bundle) support metatag. * * By default this will be FALSE, support has to be specifically enabled by * assigning 'metatag' => TRUE within the hook_entity_info() definition for the * entity. */ function metatag_entity_supports_metatags($entity_type = NULL, $bundle = NULL) { $entity_types = &drupal_static(__FUNCTION__); if (!isset($entity_types)) { $entity_types = array(); foreach (entity_get_info() as $entity_type_key => $entity_info) { if (empty($entity_info['metatags'])) { $entity_types[$entity_type_key] = FALSE; continue; } $entity_types[$entity_type_key] = array(); foreach ($entity_info['bundles'] as $bundle_key => $bundle_info) { $entity_types[$entity_type_key][$bundle_key] = !isset($bundle_info['metatags']) || !empty($bundle_info['metatags']); } } } if (isset($entity_type) && isset($bundle)) { // Allow entities to be disabled by assigning a variable // 'metatag_enable_{$entity_type}__{$bundle}' the value FALSE, e.g.: // // // Disable metatags for carousel nodes. // $conf['metatag_enable_node__carousel'] = FALSE; // // @see Advanced settings page. if (variable_get('metatag_enable_' . $entity_type . '__' . $bundle, 'monkey') == FALSE) { return FALSE; } return isset($entity_types[$entity_type][$bundle]) ? $entity_types[$entity_type][$bundle] : FALSE; } elseif (isset($entity_type)) { // Allow entities to be disabled by assigning a variable // 'metatag_enable_{$entity_type}' the value FALSE, e.g.: // // // Disable metatags for file_entity. // $conf['metatag_enable_file'] = FALSE; // // @see Advanced settings page. if (variable_get('metatag_enable_' . $entity_type, 'monkey') == FALSE) { return FALSE; } return isset($entity_types[$entity_type]) ? ($entity_types[$entity_type] !== FALSE) : FALSE; } return $entity_types; } /** * Implements hook_entity_info_alter(). * * Enables Metatag support for the core entities. */ function metatag_entity_info_alter(&$info) { $defaults['node'] = array( 'path' => 'node/%node', 'metatags' => TRUE, ); $defaults['user'] = array( 'path' => 'user/%user', 'metatags' => TRUE, ); $defaults['taxonomy_term'] = array( 'path' => 'taxonomy/term/%taxonomy_term', 'metatags' => TRUE, ); // Just running taxonomy_vocabulary_load() here would cause problems for // EntityCache in certain circumstances, so instead the query is executed // directly instead. if (module_exists('forum') && ($vocab_id = variable_get('forum_nav_vocabulary', 0))) { $vocab_name = db_query("SELECT machine_name FROM {taxonomy_vocabulary} WHERE vid = :vid", array(':vid' => $vocab_id))->fetchField(); if (!empty($vocab_name)) { $defaults['taxonomy_term']['bundles'][$vocab_name]['path'] = 'forum/%taxonomy_term'; } } foreach ($defaults as $key => $entity_defaults) { if (isset($info[$key])) { $info[$key] = drupal_array_merge_deep($entity_defaults, $info[$key]); } } } /** * Given a path determine if it is an entity default path. * * @param $path * The internal path. The id of the entity should be in the string as '[id]'. * @return * An array with the entity type and the loaded entity object. */ function metatag_load_entity_from_path($path) { $entity_paths = &drupal_static(__FUNCTION__); $result = FALSE; if (!isset($entity_paths)) { $entity_paths = array(); foreach (entity_get_info() as $entity_type => $entity_info) { if (isset($entity_info['default path'])) { $default_path = $entity_info['default path']; $default_path = preg_quote($default_path, '/'); $default_path = str_replace('\[id\]', '(\d+)', $default_path); $entity_paths[$entity_type] = $default_path; } } } foreach ($entity_paths as $entity_type => $default_path) { if (preg_match("/^{$default_path}$/", $path, $matches)) { if ($entity = entity_load($entity_type, array($matches[1]))) { $result = array('entity_type' => $entity_type, 'entity' => reset($entity)); } break; } } // Allow other modules to customize the data using // hook_metatag_load_entity_from_path_alter(). drupal_alter('metatag_load_entity_from_path', $path, $result); return $result; } /** * Add meta tags to be added later with metatag_page_build(). * * @param string $instance * The configuration instance key of the meta tags, e.g. "node:article". * @param array $metatags * An array of meta tags from metatag_metatags_view(). */ function metatag_page_set_metatags($instance, $metatags) { $page_metatags = &drupal_static(__FUNCTION__, array()); $page_metatags[$instance] = $metatags; } /** * Retrieve the array of meta tags to be added with metatag_page_build(). */ function metatag_page_get_metatags() { // @todo Add alter to this result? return drupal_static('metatag_page_set_metatags', array()); } /** * Implements hook_page_build(). */ function metatag_page_build(&$page) { // By default do not add meta tags to admin pages. To enable meta tags on // admin pages set the 'metatag_tag_admin_pages' variable to TRUE. if (path_is_admin(current_path()) && !variable_get('metatag_tag_admin_pages', FALSE)) { return; } // Ensure these arrays exist, otherwise several use cases will fail. if (!isset($page['content']) || !is_array($page['content'])) { $page['content'] = array(); } if (!isset($page['content']['metatags']) || !is_array($page['content']['metatags'])) { $page['content']['metatags'] = array(); } // The front page has special consideration. Also, check if this is an error // (403/404) page, those also require separate handling. $instance = 'global:frontpage'; if ((drupal_is_front_page() && metatag_config_is_enabled($instance)) || ($instance = metatag_is_error_page())) { // Generate the cache ID. $cid_parts = array( 'instance' => $instance, ); $cid = metatag_cache_default_cid_parts($cid_parts); if ($cache = metatag_cache_get($cid)) { $metatags = $cache->data; } else { $metatags = metatag_metatags_view($instance, array()); metatag_cache_set($cid, $metatags); } $page['content']['metatags'][$instance] = $metatags; } // Load any meta tags assigned via metatag_page_set_metatags(). Note: this // must include the necessary defaults. else { $page['content']['metatags'] += metatag_page_get_metatags(); } // If no meta tags were loaded at least load the global defaults. This may be // disabled, see README.txt for details. if (empty($page['content']['metatags']) && variable_get('metatag_load_all_pages', TRUE)) { $instance = 'global'; // Generate the cache ID. $cid_parts = array( 'instance' => $instance, 'path' => request_path(), ); $cid = metatag_cache_default_cid_parts($cid_parts); if ($cache = metatag_cache_get($cid)) { $metatags = $cache->data; } else { $metatags = metatag_metatags_view($instance, array()); metatag_cache_set($cid, $metatags); } $page['content']['metatags'][$instance] = $metatags; } } /** * Returns whether the current page is the page of the passed in entity. * * @param $entity_type * The entity type; e.g. 'node' or 'user'. * @param $entity * The entity object. * * @return * TRUE if the current page is the page of the specified entity, or FALSE * otherwise. */ function _metatag_entity_is_page($entity_type, $entity) { // Exclude comment entities as this conflicts with comment_fragment.module. if ($entity_type == 'comment') { return; } $uri = entity_uri($entity_type, $entity); $current_path = current_path(); // Support for Workbench Moderation v1 - if this is a node, check if the // content type supports moderation. if ($entity_type == 'node' && function_exists('workbench_moderation_node_type_moderated') && workbench_moderation_node_type_moderated($entity->type) === TRUE) { return !empty($uri['path']) && ($current_path == $uri['path'] || $current_path == $uri['path'] . '/draft'); } // Support for core node revisions. elseif (!empty($uri['path']) && strpos($current_path, $uri['path']) === 0 && strpos($current_path, '/revisions/') && strpos($current_path, '/view')) { return TRUE; } // Any other page. else { return !empty($uri['path']) && $current_path == $uri['path']; } } /** * Implements hook_field_attach_rename_bundle(). */ function metatag_field_attach_rename_bundle($entity_type, $bundle_old, $bundle_new) { $instance_old = $entity_type . ':' . $bundle_old; $instance_new = $entity_type . ':' . $bundle_new; if ($config = metatag_config_load($instance_old)) { $config->instance = $instance_new; metatag_config_save($config); metatag_config_delete($instance_old); } } /** * Implements hook_field_attach_delete_bundle(). */ function metatag_field_attach_delete_bundle($entity_type, $bundle) { $instance = $entity_type . ':' . $bundle; metatag_config_delete($instance); } /** * Implements hook_field_attach_form(). */ function metatag_field_attach_form($entity_type, $entity, &$form, &$form_state, $langcode) { if (!metatag_entity_has_metatags($entity_type, $entity)) { return; } // Entity_Translation will trigger this hook again, skip it. if (!empty($form_state['entity_translation']['is_translation'])) { return; } list($entity_id, $revision_id, $bundle) = entity_extract_ids($entity_type, $entity); $instance = "{$entity_type}:{$bundle}"; // Grab the meta tags for display in the form if there are any. if (!empty($entity->metatags)) { // Identify the language to use with this entity. $entity_language = metatag_entity_get_language($entity_type, $entity); // If this is a new translation using Entity Translation, load the meta // tags from the entity's original language. if (module_exists('entity_translation') && empty($form['#entity_translation_source_form']) && ($handler = entity_translation_entity_form_get_handler($form, $form_state)) && isset($entity->metatags[$handler->getSourceLanguage()])) { $metatags = $entity->metatags[$handler->getSourceLanguage()]; } // Determine from where we should get the tags. elseif (isset($entity->metatags[$langcode])) { // Set the tags to the translation set matching that of the form. $metatags = $entity->metatags[$langcode]; } // There is no translation for this entity's tags in the current // language. Instead, display tags in the language of the entity, the // source language of translations. The will provide translators with the // original text to translate. elseif (isset($entity->metatags[$entity_language])) { $metatags = $entity->metatags[$entity_language]; } // This is a preview so set the tags to the raw submission data. No // language has been set. else { $metatags = $entity->metatags; } } else { $metatags = array(); } $options['token types'] = array(token_get_entity_mapping('entity', $entity_type)); $options['context'] = $entity_type; // Allow hook_metatag_token_types_alter() to modify the defined tokens. drupal_alter('metatag_token_types', $options); // @todo Remove metatag_form_alter() when http://drupal.org/node/1284642 is fixed in core. //metatag_metatags_form($form, $instance, $metatags, $options); $form['#metatags'] = array( 'instance' => $instance, 'metatags' => $metatags, 'options' => $options, ); } /** * Implements hook_form_alter(). * * @todo Remove this when http://drupal.org/node/1284642 is fixed in core. */ function metatag_form_alter(&$form, $form_state, $form_id) { if (!empty($form['#metatags']) && !isset($form['metatags'])) { extract($form['#metatags']); metatag_metatags_form($form, $instance, $metatags, $options); unset($form['#metatags']); } } /** * Get the meta tag information array of a meta tag. * * @param $metatag * The meta tag name, e.g. description, for which the info shall be returned, * or NULL to return an array with info about all meta tags. */ function metatag_get_info($type = NULL, $name = NULL) { // Use the advanced drupal_static() pattern, since this is called very often. static $drupal_static_fast; if (!isset($drupal_static_fast)) { $drupal_static_fast['metatag_info'] = &drupal_static(__FUNCTION__); } $info = &$drupal_static_fast['metatag_info']; global $language; if (!isset($info)) { // hook_metatag_info() includes translated strings, so each language is cached // separately. $cid = 'info:' . $language->language; if ($cache = metatag_cache_get($cid)) { $info = $cache->data; } else { // Obtain all metatag specs defined in other modules using // hook_metatag_info(). $info = module_invoke_all('metatag_info'); $info += array('tags' => array(), 'groups' => array()); // Merge in default values. foreach ($info['tags'] as $key => $data) { $info['tags'][$key] += array( // Merge in default values. 'name' => $key, 'class' => 'DrupalTextMetaTag', ); } // Let other modules alter the entity info using // hook_metatag_info_alter(). drupal_alter('metatag_info', $info); metatag_cache_set($cid, $info); } } if (isset($type) && isset($name)) { return isset($info[$type][$name]) ? $info[$type][$name] : FALSE; } elseif (isset($type)) { return isset($info[$type]) ? $info[$type] : array(); } else { return $info; } } function metatag_get_instance($metatag, array $data = array()) { $info = metatag_get_info('tags', $metatag); if (!empty($info['class']) && class_exists($info['class'])) { $class = $info['class']; return new $class($info, $data); } } /** * Return the string value of a meta tag. * * @param $metatag * The meta tag string. * @param $data * The array of data for the meta tag class instance. * @param $options * An optional array of additional options to pass to the getValue() method * of the meta tag class instance. * - raw: A boolean if TRUE will not perform token replacement. * * @return * A string value. */ function metatag_get_value($metatag, array $data, array $options = array()) { $value = ''; if ($metatag_instance = metatag_get_instance($metatag, $data)) { $options["instance"] = $metatag; $value = $metatag_instance->getValue($options); } return $value; } /** * Set a variable to be altered in metatag_preprocess_html(). * * @see metatag_get_preprocess_variables() * @see metatag_preprocess_html() * @see metatag_preprocess_maintenance_page() */ function metatag_set_preprocess_variable($hook, $variable, $value) { $variables = &drupal_static(__FUNCTION__, array()); $variables[$hook][$variable] = $value; } /** * Return an array of variables to be altered in preprocess functions. * * @see metatag_set_preprocess_variable() * @see metatag_preprocess_html() * @see metatag_preprocess_maintenance_page() */ function metatag_get_preprocess_variables($hook) { $variables = drupal_static('metatag_set_preprocess_variable', array()); return isset($variables[$hook]) ? $variables[$hook] : array(); } /** * Implements hook_preprocess_html(). */ function metatag_preprocess_html(&$variables) { foreach (metatag_get_preprocess_variables('html') as $variable => $value) { $variables[$variable] = $value; } } /** * Implements hook_preprocess_maintenance_page(). */ function metatag_preprocess_maintenance_page(&$variables) { foreach (metatag_get_preprocess_variables('html') as $variable => $value) { $variables[$variable] = $value; } } /** * Implements hook_html_head_alter(). */ function metatag_html_head_alter(&$elements) { // Remove duplicate link tags if found. $metatags = metatag_get_info('tags'); foreach (array_keys($metatags) as $name) { if (!isset($elements['metatag_' . $name]) || $elements['metatag_' . $name]['#tag'] != 'link') { // Only check for link tags added by the metatags module. continue; } foreach (array_keys($elements) as $key) { if (strpos($key, 'drupal_add_html_head_link:' . $name . ':') === 0) { unset($elements[$key]); } } } // Remove the default generator meta tag. unset($elements['system_meta_generator']); } function metatag_metatag_get_form($metatag, array $data = array(), array $options = array()) { $instance = metatag_get_instance($metatag, $data); return $instance->getForm($options); } function metatag_config_instance_info($instance = NULL) { global $language; $info = &drupal_static(__FUNCTION__); // hook_metatag_info() includes translated strings, so each language is cached // separately. $cid = 'metatag:config:instance:info:' . $language->language; if (!isset($info)) { if ($cache = metatag_cache_get($cid)) { $info = $cache->data; } else { // Allow modules to act upon the record insertion using // hook_metatag_config_instance_info(). $info = module_invoke_all('metatag_config_instance_info'); // Allow other modules to customize the data using // hook_metatag_config_instance_info_alter(). drupal_alter('metatag_config_instance_info', $info); metatag_cache_set($cid, $info); } } if (isset($instance)) { return isset($info[$instance]) ? $info[$instance] : FALSE; } else { return $info; } } /** * Filter out meta tag values that equal the default values. * * @todo Use information in $values[$metatag]['default'] rather than a $defaults parameter. */ function metatag_filter_values_from_defaults(array &$values, array $defaults = array()) { foreach ($values as $metatag => $data) { $default = isset($data['default']) ? $data['default'] : (isset($defaults[$metatag]['value']) ? $defaults[$metatag]['value'] : NULL); if (isset($default) && isset($data['value']) && $default === $data['value']) { // Meta tag has a default, and it matches user-submitted value. unset($values[$metatag]); } elseif (!isset($default) && (is_string($data['value']) && !drupal_strlen($data['value']) || (is_array($data['value']) && !array_filter($data['value'])))) { // Metatag does not have a default, and user did not submit a value. unset($values[$metatag]); } if (isset($values[$metatag]['default'])) { // Unset the default hidden value. unset($values[$metatag]['default']); } } } /** * Return all the parents of a given configuration instance. * * @param $instance * A meta tag configuration instance. * * @return * An array of instances starting with the $instance parameter, with the end * of the array being the global instance. */ function metatag_config_get_parent_instances($instance, $include_global = TRUE) { $parents = array(); $segments = explode(':', $instance); while (count($segments) > 0) { $parents[] = implode(':', $segments); array_pop($segments); } if ($include_global && end($parents) !== 'global') { $parents[] = 'global'; } reset($parents); return $parents; } /** * Get the proper label of a configuration instance. * * @param $instance * A meta tag configuration instance. */ function metatag_config_instance_label($instance) { $labels = &drupal_static(__FUNCTION__, array()); if (!isset($labels[$instance])) { $instance_parts = explode(':', $instance); $instance_part = array_pop($instance_parts); if ($context = metatag_config_instance_info($instance)) { $labels[$instance] = $context['label']; } else { $labels[$instance] = t('Unknown (@instance)', array('@instance' => $instance_part)); } // Normally the following would use metatag_config_get_parent_instances() // but since we already sliced the instance by separator and removed the // last segment, putting the array back together gives us this instance's // parent. if (!empty($instance_parts)) { $labels[$instance] = metatag_config_instance_label(implode(':', $instance_parts)) . ': ' . $labels[$instance]; } } return $labels[$instance]; } /** * Title callback for meta tag configuration instances. */ function metatag_config_title($config) { return metatag_config_instance_label($config->instance); } /** * Access callback for meta tag configuration instances. */ function metatag_config_access($op, $config = NULL) { if (!user_access('administer meta tags')) { return FALSE; } if ($op == 'enable') { return !empty($config->disabled); } elseif ($op == 'disable') { return empty($config->disabled); } elseif ($op == 'delete') { return ($config->export_type & EXPORT_IN_DATABASE) && !($config->export_type & EXPORT_IN_CODE); } elseif ($op == 'revert') { return ($config->export_type & EXPORT_IN_DATABASE) && ($config->export_type & EXPORT_IN_CODE); } return FALSE; } /** * Checks if a metatag configuration record is enabled. * * @param string $instance * The configuration instance machine name. * * @return bool * TRUE if the configuration is enabled, or FALSE otherwise. */ function metatag_config_is_enabled($instance, $include_defaults = FALSE, $include_global = TRUE) { if ($include_defaults) { return (bool) metatag_config_load_with_defaults($instance, $include_global); } else { $config = metatag_config_load($instance); return !empty($config) && empty($config->disabled); } } /** * Wrapper around entity_language() to use LANGUAGE_NONE if the entity does not * have a language assigned. * * @param $entity_type * An entity type's machine name. * @param $entity * The entity to review; * * @return * A string indicating the language code to be used. */ function metatag_entity_get_language($entity_type, $entity) { // Determine the entity's language. $langcode = entity_language($entity_type, $entity); // If no matching language was found, which will happen for e.g. terms and // users, it is normally recommended to use the system default language. // However, as the system default language can change, this could potentially // cause data loss / confusion problems; as a result use the system "no // language" value to avoid any potential problems. if (empty($langcode)) { $langcode = LANGUAGE_NONE; } return $langcode; } /** * Implements hook_features_api(). */ function metatag_features_api() { $components = array( 'metatag' => array( 'name' => t('Metatag'), 'feature_source' => TRUE, 'default_hook' => 'metatag_export_default', 'default_file' => FEATURES_DEFAULTS_INCLUDED, 'file' => drupal_get_path('module', 'metatag') . '/metatag.features.inc', ), ); return $components; } /** * Implements hook_views_post_render(). */ function metatag_views_post_render(&$view, &$output, &$cache) { // Build a shortcut to the current display object. $display = $view->display[$view->current_display]; // Only proceed if this view is a full page, don't process block or other // Views display objects. if ($display->display_plugin == 'page' && isset($display->display_options['path'])) { // Check if this is an entity display page, if so trigger // hook_entity_view(). foreach (entity_get_info() as $entity_type => $entity_info) { // Entity paths will include an auto-loader that matches the entity's // name, thus the path will be 'some/path/%entity_name'. if (isset($entity_info['path']) && ($display->display_options['path'] . $entity_type) == $entity_info['path']) { // Only proceed if this entity type supports meta tags. if (metatag_entity_supports_metatags($entity_type)) { // There must be at least one argument and the first argument must be // numerical. if (!empty($view->args) && is_numeric($view->args[0])) { // Only the first argument is used. $entities = entity_load($entity_type, array($view->args[0])); // Only if the entity actually exists. if (!empty($entities)) { $entity = array_pop($entities); metatag_entity_view($entity, $entity_type, 'full', NULL, TRUE); } } } } } } } /** * Implements hook_ctools_render_alter(). * * Temporary solution to load meta tags on entity pages that are driven by * CTools display handlers. */ function metatag_ctools_render_alter(&$info, $page, $context) { // Only proceed if this is a full page (don't process individual panes) and // there's an 'admin path' for the current task. if ($page && !empty($context['task']['admin path'])) { // Check if this is an entity display page, if so trigger // hook_entity_view(). foreach (entity_get_info() as $entity_type => $entity_info) { // Entity paths will include an auto-loader that matches the entity's // name, thus the path will be 'some/path/%entity_name'. if (isset($entity_info['path']) && $context['task']['admin path'] == $entity_info['path']) { // Only proceed if this entity type supports meta tags. if (metatag_entity_supports_metatags($entity_type)) { // There must be at least one argument and the first argument must be // numerical. if (!empty($context['args']) && is_numeric($context['args'][0])) { // We need to pop entity from contexts array. $first_context = array_pop($context['contexts']); // Only if the context actually exists, which would be an entity. if (!empty($first_context->data)) { $langcode = $GLOBALS['language_content']->language; metatag_entity_view($first_context->data, $entity_type, 'full', $langcode, TRUE); } } } } } } } /** * Implements hook_entity_translation_delete(). * * Required for content translations being handled via Entity_Translation to * remove the appropriate record when a translation is removed without the * corresponding entity record also being removed. */ function metatag_entity_translation_delete($entity_type, $entity, $langcode) { // Get the entity's ID. list($entity_id, $revision_id) = entity_extract_ids($entity_type, $entity); $revision_id = intval($revision_id); // Delete the translation. metatag_metatags_delete($entity_type, $entity_id, $revision_id, $langcode); } /** * Translates the metatag if i18n_string is enabled. * @param $name * Array or string concatenated with ':' that contains textgroup and string context * @param string $string * String in default language or array of strings to be translated * @param $options * An associative array of additional options. @see i18n_string_translate() */ function metatag_translate($name, $string, $langcode = NULL, $update = FALSE) { if (function_exists('i18n_string_translate')) { $options = array( 'langcode' => $langcode, 'update' => $update, ); return i18n_string_translate($name, $string, $options); } else { return $string; } } /** * Checks if this entity is the default revision (published). * * @param object $entity * The entity object, e.g., $node. * * @return bool * TRUE if the entity is the default revision, FALSE otherwise. */ function _metatag_isdefaultrevision($entity) { // D7 "Forward revisioning" is complex and causes a node_save() with the // future node in node table. This fires hook_node_update() twice and cause // abnormal behaviour in metatag. // // The steps taken by Workbench Moderation is to save the forward revision // first and overwrite this with the live version in a shutdown function in // a second step. This will confuse metatag. D7 has no generic property // in the node object, if the node that is updated is the 'published' version // or only a draft of a future version. // // This behaviour will change in D8 where $node->isDefaultRevision has been // introduced. See below links for more details. // - http://drupal.org/node/1879482 // - http://drupal.org/node/218755 // - http://drupal.org/node/1522154 // // Every moderation module saving a forward revision needs to return FALSE. // @todo: Refactor this workaround under D8. // Support for Workbench Moderation v1 - if this is a node, check if the // content type supports moderation. if (function_exists('workbench_moderation_node_type_moderated') && workbench_moderation_node_type_moderated($entity->type) === TRUE) { return !empty($entity->workbench_moderation['updating_live_revision']); } return FALSE; } /** * Generate the cache ID to use with metatag_cache_get/metatag_cache_set calls. * * @param array $cid_parts * A list of values to be used. * * @return string * The cache ID string. */ function metatag_cache_default_cid_parts(array $cid_parts = array()) { // The initial parts, control the order of the parts. $cid_part_defaults = array( 'cache_type' => 'output', 'instance' => '', 'entity_type' => '', 'entity_id' => '', 'bundle' => '', 'langcode' => $GLOBALS['language_content']->language, 'revision_id' => '', 'view_mode' => '', 'status' => 200, 'protocol' => $GLOBALS['is_https'] ? 'https' : 'http', 'hostname' => $_SERVER['HTTP_HOST'], 'base_path' => base_path(), ); $cid_parts = array_merge($cid_part_defaults, $cid_parts); // Add the HTTP status code. $headers = drupal_get_http_header(); if (isset($headers['status'])) { $cid_parts['status'] = intval($headers['status']); } // Allow each page in a sequence to have different values. if (isset($_GET['page'])) { $cid_parts['page'] = $_GET['page']; } // Allow other modules to customize the data using // hook_metatag_page_cache_cid_parts_alter(). drupal_alter('metatag_page_cache_cid_parts', $cid_parts); // Remove empty parts. $cid_parts = array_filter($cid_parts); // Concatenate the cache ID parts, trim the results to 128 chars. return substr(implode(':', $cid_parts), 0, 128); } /** * Wrapper for cache_set. * * @see cache_set(). */ function metatag_cache_set($cid, $data) { // Cache the data for later. return cache_set($cid, $data, 'cache_metatag'); } /** * Wrapper for cache_get. * * @see cache_get(). */ function metatag_cache_get($cid) { // Try to load the object. return cache_get($cid, 'cache_metatag'); } /** * Determines if we are in an error page and return the appropriate instance. * * @return string */ function metatag_is_error_page() { $known_errors = array( 'global:403' => '403 Forbidden', 'global:404' => '404 Not Found', ); $headers = drupal_get_http_header(); if (isset($headers['status'])) { foreach ($known_errors as $error => $status) { if ($status == $headers['status']) { return $error; } } } return ''; }