' . t('Configure global meta tag default values below. Meta tags may be left as the default.') . '
'
. '' . t('Meta tag patterns are passed down from one level to the next unless they are overridden. To view a summary of the individual meta tags and the pattern for a specific configuration, click on its name below.') . '
'
. '' . t('If the top-level configuration is not specific enough, additional default meta tag configurations can be added for a specific entity type or entity bundle, e.g. for a specific content type, using the "Add default meta tags" form.') . '
'
. '' . t('Meta tags can be further refined on a per-entity basis, e.g. for individual nodes, from the "Meta tags" fields that show on those edit forms.')
. '
' . t('To view a summary of the default meta tags and the inheritance, click on a meta tag type.') . '
';
}
elseif ($path == 'admin/config/search/metatags/config/add') {
return '' . t('Use the following form to override the global default meta tags for a specific entity type or entity bundle. In practical terms, this allows the meta tags to be customized for a specific content type or taxonomy vocabulary, so that its content will have different meta tags default values than others.') . '
'
. '' . t('As a reminder, the meta tags can be further refined on a per entity basis using the "Meta tags" field, but it is recommended to set reasonable defaults for each entity type or bundle (e.g. content types).') . '
';
}
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_ctools_plugin_directory().
*/
function metatag_ctools_plugin_directory($owner, $plugin_type) {
if ($owner == 'ctools' && $plugin_type == 'content_types') {
return "plugins/$plugin_type";
}
}
/**
* 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 default meta tags',
'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' => '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',
);
// Optional integration with the i18n_string module for translating the
// configurations. Note: this should also check for 'metatag_i18n_disabled'
// but doing so would require rebuilding the menu cache every time the Metatag
// settings page was saved, which may not be advised. Instead the links to
// these pages on the config pages *do* check the variable, which is close
// enough.
if (module_exists('i18n_string')) {
$items['admin/config/search/metatags/config/%metatag_config/translate'] = array(
'title' => 'Translate',
'access arguments' => array('administer meta tags'),
'page callback' => 'i18n_string_object_translate_page',
'page arguments' => array('metatag_config', 5),
'type' => MENU_LOCAL_TASK,
);
$items['admin/config/search/metatags/config/%metatag_config/translate/%i18n_language'] = array(
'title' => 'Translate',
'access arguments' => array('administer meta tags'),
'page callback' => 'i18n_string_object_translate_page',
'page arguments' => array('metatag_config', 5, 7),
'type' => MENU_CALLBACK,
);
}
return $items;
}
/**
* Implements hook_flush_caches().
*/
function metatag_flush_caches() {
return array('cache_metatag');
}
/**
* Implements hook_action_info().
* Provides integration with Views bulk operations.
*/
function metatag_action_info() {
return array(
'metatag_modify_metatags_action' => array(
'type' => 'entity',
'label' => t('Modify entity metatags'),
'configurable' => FALSE,
'vbo_configurable' => TRUE,
'behavior' => array('changes_property'),
'triggers' => array('any'),
'permissions' => array('edit meta tags'),
),
);
}
/**
* Updates entity metatags with values from the action form.
*
* @param object $entity
* The entity housing the metatags to modify.
* @param array $context
* Contextual information passed from the View bulk operation configuration
* form. The updated metatag values for the entity are stored in
* $context['updates'].
*/
function metatag_modify_metatags_action($entity, $context) {
if (empty($entity)) {
drupal_set_message(t("Error while trying to update an entity's metatags."),
'warning', FALSE);
return;
}
$updates = $context['updates'];
$language = $entity->language;
// Reset metatags to the entity default configs.
if ($context['reset_default']) {
$instance = $context['entity_type'] . ':' . $entity->type;
$entity_defaults = metatag_config_load_with_defaults($instance);
// Clean up empty values.
foreach ($entity_defaults as $name => $tag) {
if (empty($tag['value'])) {
unset($entity_defaults[$name]);
}
}
$entity->metatags[$language] = $entity_defaults;
}
// Otherwise, we're updating existing values. Ensure that the entity has any
// metatags already set. If so then merge the updates to avoid overwriting
// existing values that may exist as an array. E.g. robots.
elseif (!empty($entity->metatags) && !empty($entity->metatags[$language])) {
foreach ($updates as $tag => $value_array) {
if (is_array($updates[$tag]['value']) && !empty($entity->metatags[$language][$tag]['value'])) {
$entity->metatags[$language][$tag]['value'] = array_merge($entity->metatags[$language][$tag]['value'],
array_filter($updates[$tag]['value']));
}
elseif (!empty($updates[$tag]['value'])) {
$entity->metatags[$language][$tag]['value'] = $updates[$tag]['value'];
}
}
}
// Otherwise just set the net new tags.
else {
$entity->metatags[$language] = $updates;
}
entity_save($context['entity_type'], $entity);
}
/**
* The Views bulk operation configuration form for modifying metatags.
*
* @param array $context
* Contextual information passed from the View bulk operation configuration
* form.
*
* @return array
* A form API compatible array.
*/
function metatag_modify_metatags_action_form($context) {
$form = array(
'#entity_type' => $context['entity_type'],
);
// There can be multiple instances being edited here. So the 2nd param passed
// here is as generic as possible.
metatag_metatags_form($form, 'global');
// Force the metatags tab to be fully visible and save a click from the user.
$form['metatags']['#collapsed'] = FALSE;
$form['metatags']['#collapsible'] = FALSE;
// If we're reseting to the entity default, then we don't need to show the
// form fields.
$form['metatags']['#states'] = array(
'visible' => array(
':input[name="reset_default"]' => array('checked' => FALSE),
),
);
// Add an option to reset selected entities to the default configuration.
$form['reset_default'] = array(
'#type' => 'checkbox',
'#title' => t('Reset to metatag defaults'),
'#default_value' => FALSE,
'#description' => t('Check to fully reset all metatags on
the entities being modified to their default
configuration.', array(
'@settings' => url('admin/config/search/metatags'),
)),
);
return $form;
}
/**
* Submit handler for metatag_modify_metatags_action_form(). Filters out
* the user entered values from the defaults and returns the updated values to
* the $context array.
*
* @return array
* The updated metatag values that is ultimately keyed at $context['updates'].
*/
function metatag_modify_metatags_action_submit($form, &$form_state) {
$updates = $form_state['values']['metatags'][LANGUAGE_NONE];
$defaults = metatag_config_load_with_defaults($form['#entity_type']);
// Filter out the true updates.
metatag_filter_values_from_defaults($updates, $defaults);
return array(
'updates' => $updates,
'reset_default' => $form_state['values']['reset_default'],
);
}
/**
* 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());
// Use the current page's locale.
$langcode = $GLOBALS['language_content']->language;
// Statically cache defaults since they can include multiple levels.
$cid = "config:{$instance}:{$langcode}" . ($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 $instance => $config) {
if (isset($config->config[$old_tag])) {
$config->config[$new_tag] = $config->config[$old_tag];
unset($config->config[$old_tag]);
}
}
}
// Translate the configuration.
if (module_exists('i18n_string') && !variable_get('metatag_i18n_disabled', FALSE)) {
$options = array();
// By default disable the watchdog logging of translation messages.
$options['watchdog'] = variable_get('metatag_i18n_enable_watchdog', FALSE);
foreach ($configs as $instance => &$config) {
foreach ($config->config as $tag => &$value) {
if (isset($value['value']) && is_string($value['value'])) {
$value['value'] = i18n_string_translate(array(
'metatag',
'metatag_config',
$instance,
$tag,
),
$value['value'],
$options);
}
}
}
}
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);
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($config) {
db_delete('metatag_config')
->condition('instance', $config->instance)
->execute();
// Allow modules to act upon the record deletion using
// hook_metatag_config_delete().
module_invoke_all('metatag_config_delete', $config);
// 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_supports_metatags');
drupal_static_reset('metatag_config_instance_info');
drupal_static_reset('metatag_get_info');
ctools_include('export');
ctools_export_load_object_reset('metatag_config');
}
/**
* Load an entity's tags.
*
* @param string $entity_type
* The entity type to load.
* @param int $entity_id
* The ID of the entity to load.
* @param mixed $revision_id
* Optional revision ID to load instead of the entity ID.
*
* @return array
* An array of tag data keyed by language for the entity's current active
* revision.
*/
function metatag_metatags_load($entity_type, $entity_id, $revision_id = NULL) {
// A specific revision ID was not requested, so get the active revision ID.
if (is_null($revision_id)) {
// Unfortunately, the only way of getting the active revision ID is to
// first load the entity, and then extract the ID. This is a bit
// performance intensive, but it seems to be the only way of doing it.
$entities = entity_load($entity_type, array($entity_id));
if (!empty($entities[$entity_id])) {
// We only care about the revision_id.
list(, $revision_id,) = entity_extract_ids($entity_type, $entities[$entity_id]);
}
}
// This returns an array nested by the entity ID, the revision ID and the
// langcode.
$metatags = metatag_metatags_load_multiple($entity_type, array($entity_id), array($revision_id));
// Look for records for the requested revision ID.
if (isset($metatags[$entity_id][$revision_id])) {
return $metatags[$entity_id][$revision_id];
}
// Getting to this point means that no meta tags were identified earlier, so
// return an empty array.
return array();
}
/**
* Load tags for multiple entities.
*
* @param string $entity_type
* The entity type to load.
* @param array $entity_ids
* The list of entity IDs.
* @param array $revision_ids
* Optional revision ID to load instead of the entity ID.
*
* @return array
* 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 all numeric.
$entity_ids = array_filter($entity_ids, 'is_numeric');
if (empty($entity_ids)) {
return array();
}
// Ensure that the revision IDs are all numeric too.
$revision_ids = array_filter($revision_ids, 'is_numeric');
// Verify that there aren't any empty values copied in from
// metatag_metatags_load(). Note: a zero indicates that the entity record does
// not support revisions, so this is ok to do.
$revision_ids = array_filter($revision_ids);
// 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);
// 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 string $entity_type
* The entity type to load.
* @param int $entity_id
* The entity's primary ID.
* @param int $revision_id
* The entity's revision ID.
* @param array $metatags
* All of the tag information, keyed by the language code. Most meta tags use
* the 'value' element, so the structure should look like:
* array(
* LANGUAGE_NONE => array(
* 'title' => array(
* 'value' => "This node's title!",
* ),
* 'og:title' => array(
* 'value' => "This node's title for Open Graph!",
* ),
* 'og:image' => array(
* 'value' => "[node:field_thumbnail]",
* ),
* ),
* );
* @param string|null $bundle
* The bundle of the entity that is being saved. Optional.
*/
function metatag_metatags_save($entity_type, $entity_id, $revision_id, $metatags, $bundle = NULL) {
// Check that $entity_id is numeric because of Entity API and string IDs.
if (!is_numeric($entity_id)) {
return;
}
// Don't do anything if the entity type is not supported.
if (!metatag_entity_supports_metatags($entity_type)) {
return;
}
// Verify the entity bundle is supported, if not available just check the
// entity type.
if (!empty($bundle)) {
if (!metatag_entity_supports_metatags($entity_type, $bundle)) {
return;
}
}
else {
if (!metatag_entity_supports_metatags($entity_type)) {
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, this will have
// the effect of erasing the meta tags for those this entity.
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,
))->fetchCol();
foreach ($languages as $oldlang) {
$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);
// Clear the entity cache.
entity_get_controller($entity_type)->resetCache(array($entity_id));
}
/**
* Delete an entity's tags.
*
* @param string $entity_type
* The entity type.
* @param int $entity_id
* The entity's ID.
* @param int $revision_id
* The entity's VID.
* @param string $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 string $entity_type
* The entity type.
* @param array $entity_ids
* The list of IDs.
* @param array $revision_ids
* An optional list of revision IDs; if omitted all revisions will be deleted.
* @param string $langcode
* The language ID of the entities to delete. If left blank, all language
* entries for the enities will be deleted.
*
* @return bool
* If any problems were encountered will return FALSE, otherwise TRUE.
*/
function metatag_metatags_delete_multiple($entity_type, array $entity_ids, array $revision_ids = array(), $langcode = NULL) {
// Double check entity IDs and revision IDs are numeric.
$entity_ids = array_filter($entity_ids, 'is_numeric');
$revision_ids = array_filter($revision_ids, 'is_numeric');
if (!empty($entity_ids) || !empty($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);
// If revision IDs were specified then just use those in the query.
if (!empty($revision_ids)) {
$query->condition('revision_id', $revision_ids, 'IN');
}
// No revision IDs were specified, so work from the entity IDs.
else {
$query->condition('entity_id', $entity_ids, 'IN');
}
// Limit to a language if one was specified.
if (!empty($langcode)) {
$query->condition('language', $langcode);
}
// Perform the deletion(s).
$query->execute();
// Clear cached data.
metatag_metatags_cache_clear($entity_type, $entity_ids);
// Clear the caches for these entities.
entity_get_controller($entity_type)->resetCache($entity_ids);
return TRUE;
}
catch (Exception $e) {
$transaction->rollback();
watchdog_exception('metatag', $e);
throw $e;
}
}
else {
watchdog('metatag', 'No entity IDs or revision IDs were submitted to metatag_metatags_delete_multiple().');
}
return FALSE;
}
/**
* 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');
}
}
}
/**
* Implements hook_entity_load().
*/
function metatag_entity_load($entities, $entity_type) {
// 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)) {
// Get the revision_ids.
$revision_ids = array();
// Track the entity IDs for values to load.
$entity_ids = array();
// Some entities don't support revisions.
$supports_revisions = TRUE;
// Extract the revision ID and verify the entity's bundle is supported.
foreach ($entities as $key => $entity) {
list($entity_id, $revision_id, $bundle) = entity_extract_ids($entity_type, $entity);
// Verify that each entity bundle supports Metatag.
if (metatag_entity_supports_metatags($entity_type, $bundle)) {
$entity_ids[] = $entity_id;
if (!empty($revision_id)) {
$revision_ids[] = $revision_id;
}
}
}
// Only proceed if either there were revision IDs identified, or the
// entity doesn't support revisions anyway.
if (!empty($entity_ids)) {
// Load all meta tags for these entities.
$metatags = metatag_metatags_load_multiple($entity_type, $entity_ids, $revision_ids);
// Assign the metatag records for the correct revision ID.
if (!empty($metatags)) {
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, $bundle) = entity_extract_ids($entity_type, $entity);
// Verify that this entity type / bundle is supported.
if (!metatag_entity_supports_metatags($entity_type, $bundle)) {
return;
}
$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.
if ($entity_type == 'node' && _metatag_isdefaultrevision($entity)) {
return;
}
metatag_metatags_save($entity_type, $entity_id, $revision_id, $entity->metatags, $bundle);
}
}
/**
* Implements hook_entity_update().
*/
function metatag_entity_update($entity, $entity_type) {
list($entity_id, $revision_id, $bundle) = entity_extract_ids($entity_type, $entity);
// If this entity object isn't allowed meta tags, don't continue.
if (!metatag_entity_supports_metatags($entity_type, $bundle)) {
return;
}
$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.
if ($entity_type == 'node' && _metatag_isdefaultrevision($entity)) {
return;
}
// Save the record.
metatag_metatags_save($entity_type, $entity_id, $revision_id, $entity->metatags, $bundle);
}
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);
}
/**
* Build and alter metatag instance name.
*
* @param object $entity
* The entity object to generate the metatags instance name for.
* @param string $entity_type
* The entity type of the entity.
* @param string $bundle
* The bundle of the entity.
*
* @return string
* The resulting name of the config instance.
*/
function metatag_get_entity_metatags_instance($entity, $entity_type, $bundle) {
$instance = "{$entity_type}:{$bundle}";
drupal_alter('metatag_get_entity_metatags_instance', $instance, $entity, $entity_type, $bundle);
return $instance;
}
/**
* 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, for an entity which is allowed
// metatags.
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)) {
// Get the entity's extra information.
list($entity_id, $revision_id, $bundle) = entity_extract_ids($entity_type, $entity);
// If this entity object isn't allowed meta tags, don't continue.
if (!metatag_entity_supports_metatags($entity_type, $bundle)) {
return;
}
// 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)) {
$instance = metatag_get_entity_metatags_instance($entity, $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 mixed
* A renderable array of metatags for the given entity.
* If this entity object isn't allowed meta tags, return FALSE (empty).
*/
function metatag_generate_entity_metatags($entity, $entity_type, $langcode = NULL, $view_mode = 'full', $cached = TRUE) {
// Obtain some details of the entity that are needed elsewhere.
list($entity_id, $revision_id, $bundle) = entity_extract_ids($entity_type, $entity);
// If this entity object isn't allowed meta tags, don't continue.
if (!metatag_entity_supports_metatags($entity_type, $bundle)) {
return;
}
$revision_id = intval($revision_id);
// Check if a specific metatag config exists, otherwise just use the global
// one, stripping out the bundle.
$instance = metatag_get_entity_metatags_instance($entity, $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;
$key = FALSE;
$metatag_variants = array();
// Caching is enabled.
if ($cached && variable_get('metatag_cache_output', FALSE)) {
// All possible variants of the metatags for this entity are stored in a
// single cache entry.
$cid = "output:$entity_type:$entity_id";
// All applicable pieces for this current page.
$key_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,
);
$key = metatag_cache_default_cid_parts($key_parts);
if ($cache = metatag_cache_get($cid)) {
$metatag_variants = $cache->data;
}
}
// If a cached object exists for this key, return it.
if (!empty($key) && isset($metatag_variants[$key])) {
$output = $metatag_variants[$key];
}
// Otherwise, generate the output tags.
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];
}
// Include token replacement data. Don't reload the entity object as doing
// so would conflict with content editorial workflows.
if (!empty($entity_id)) {
$token_type = token_get_entity_mapping('entity', $entity_type);
$options['token data'][$token_type] = $entity;
}
// Render the metatags and save to the cache.
$output = metatag_metatags_view($instance, $metatags, $options);
// If output caching is enabled, store the data for later.
if (!empty($key) && !empty($cid)) {
$metatag_variants[$key] = $output;
metatag_cache_set($cid, $metatag_variants);
}
}
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 (empty($options['language'])) {
$options['language'] = $GLOBALS['language_content'];
}
// 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;
// Trigger hook_metatag_metatags_alter(). Allow the raw meta tags to be
// modified prior to rendering.
drupal_alter('metatag_metatags', $metatags, $instance, $options);
// Don't output meta tags that only contain the pager.
$current_pager = metatag_get_current_pager();
foreach ($metatags as $metatag => $data) {
if ((!empty($data['value']) || (isset($data['value']) && is_numeric($data['value'])))
&& $metatag_instance = metatag_get_instance($metatag, $data)) {
$tag_output = $metatag_instance->getElement($options);
// Don't output the pager if that's all there is.
if ($tag_output != $current_pager) {
$output[$metatag] = $tag_output;
}
}
}
// Allow the output meta tags to be modified using
// hook_metatag_metatags_view_alter().
drupal_alter('metatag_metatags_view', $output, $instance, $options);
return $output;
}
/**
* Get the pager string for the current page.
*
* @return string
* Returns a string based upon the 'metatag_pager_string' variable and the
* current page number.
*/
function metatag_get_current_pager() {
if (isset($_GET['page']) && !empty($_GET['page']) && is_numeric($_GET['page'])) {
$page = intval($_GET['page']) + 1;
if ($page > 1) {
$pager = variable_get('metatag_pager_string', 'Page PAGER | ');
return str_replace('PAGER', $page, $pager);
}
}
}
/**
* Returns metatags values.
*/
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 (!empty($form['#entity_type'])) {
if (!empty($form['#entity'])) {
$langcode = metatag_entity_get_language($form['#entity_type'], $form['#entity']);
}
else {
$entity_info = entity_get_info($form['#entity_type']);
if (!empty($entity_info['token type'])) {
$entity_key = '#' . $entity_info['token type'];
if (!empty($form[$entity_key])) {
$langcode = metatag_entity_get_language($form['#entity_type'], $form[$entity_key]);
}
}
}
}
// Merge in the default options.
$options += array(
'token types' => array(),
'defaults' => metatag_config_load_with_defaults($instance),
'instance' => $instance,
);
// Trigger hook_metatag_token_types_alter().
// Allow the defined tokens to be modified.
drupal_alter('metatag_token_types', $options);
$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']['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']['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);
if (isset($metatag_form['value']['#default_value']) && is_array($metatag_form['value']['#default_value'])) {
$metatag_form['value']['#default_value'] = array_filter($metatag_form['value']['#default_value']);
}
// Add a default value form element.
if (isset($options['defaults'][$metatag]['value'])) {
if (is_array($options['defaults'][$metatag]['value'])) {
$options['defaults'][$metatag]['value'] = array_filter($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' => $group['label'],
'#description' => !empty($group['description']) ? $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']) {
// Check if each meta tag group is being displayed.
if (!empty($group_metatag_access)) {
// Built the token browser link. For value "all" theme_token_tree()
// compares with string, not array.
if (in_array('all', $options['token types'])) {
$options['token types'] = 'all';
}
$token_listing_link = theme('token_tree',
array(
'token_types' => $options['token types'],
'dialog' => TRUE,
)
);
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'] = '';
}
// Add the token browser popup link.
$form['metatags'][$langcode][$group_key]['#description'] .= $token_listing_link;
}
}
}
}
// Add a submit handler to compare the submitted values against the default
// values.
$form += array('#submit' => array());
if (module_exists('commerce') && isset($form['#entity_type']) && $form['#entity_type'] == 'commerce_product') {
$form['actions']['submit']['#submit'][] = 'metatag_commerce_product_form_submit';
}
else {
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;
}
}
}
}
}
/**
* Form API submission callback for Commerce product.
*
* Unlike metatag_metatags_form_submit.
*
* @see metatag_metatags_save()
*/
function metatag_commerce_product_form_submit($form, &$form_state) {
// Trigger the normal meta tag form submission.
metatag_metatags_form_submit($form, $form_state);
// The entity being saved.
$entity_type = 'commerce_product';
$product = $form_state[$entity_type];
$entity_id = $product->product_id;
$revision_id = $product->revision_id;
// Get the full entity details.
list(, , $bundle) = entity_extract_ids($entity_type, $product);
// Update the meta tags for this entity type.
metatag_metatags_save($entity_type, $entity_id, $revision_id, $form_state['values']['metatags'], $bundle);
}
/**
* Implements hook_field_extra_fields().
*/
function metatag_field_extra_fields() {
$extra = array();
foreach (entity_get_info() as $entity_type => $entity_info) {
if (!empty($entity_info['bundles'])) {
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 whether the requested entity type (and bundle) support metatag.
*
* By default the entities are disabled, only certain entities will have been
* enabled during installation. If an entity type is enabled it is assumed that
* the entity bundles will also be enabled by default.
*
* @see metatag_entity_type_is_suitable()
*/
function metatag_entity_supports_metatags($entity_type = NULL, $bundle = NULL) {
$entity_types = &drupal_static(__FUNCTION__);
// Identify which entities & bundles are supported the first time the
// function is called.
if (!isset($entity_types)) {
foreach (entity_get_info() as $entity_name => $entity_info) {
// Verify that this entity type is suitable.
$entity_types[$entity_name] = metatag_entity_type_is_suitable($entity_name, $entity_info);
// The entity type technically supports entities.
if (!empty($entity_types[$entity_name])) {
// Entiy types are enabled by default.
// 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 Settings page.
if (variable_get('metatag_enable_' . $entity_name, FALSE) == FALSE) {
$entity_types[$entity_name] = FALSE;
}
// Check each bundle.
else {
$entity_types[$entity_name] = array();
foreach ($entity_info['bundles'] as $bundle_name => $bundle_info) {
// If only one bundle exists, take configuration for entity, to
// reflect options as they are available in the UI.
if (count($entity_info['bundles']) === 1) {
$entity_types[$entity_name][$bundle_name] = variable_get('metatag_enable_' . $entity_name, FALSE) == TRUE;
continue;
}
// Allow bundles 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 Settings page.
if (variable_get('metatag_enable_' . $entity_name . '__' . $bundle_name, TRUE) == FALSE) {
$entity_types[$entity_name][$bundle_name] = FALSE;
}
else {
$entity_types[$entity_name][$bundle_name] = TRUE;
}
}
}
}
}
}
// It was requested to check a specific entity.
if (isset($entity_type)) {
// It was also requested to check a specific bundle for this entity.
if (isset($bundle)) {
$supported = !empty($entity_types[$entity_type][$bundle]);
}
// Check the entity.
else {
$supported = !empty($entity_types[$entity_type]);
}
return $supported;
}
// If nothing specific was requested, return the complete list of supported
// entities & bundles.
return $entity_types;
}
/**
* Enable support for a specific entity type if setting does not exist.
*
* @param string $entity_type
* The entity type.
* @param string $bundle
* The bundle of the entity.
* @param bool $force_enable
* If TRUE, then the type is enabled regardless of any stored variables.
*
* @return bool
* TRUE if either the bundle or entity type was enabled by this function.
*/
function metatag_entity_type_enable($entity_type, $bundle = NULL, $force_enable = FALSE) {
// The bundle was defined.
$bundle_set = FALSE;
if (isset($bundle)) {
$stored_bundle = variable_get('metatag_enable_' . $entity_type . '__' . $bundle, NULL);
if ($force_enable || !isset($stored_bundle)) {
variable_set('metatag_enable_' . $entity_type . '__' . $bundle, TRUE);
$bundle_set = TRUE;
}
}
// Always enable the entity type, because otherwise there's no point in
// enabling the bundle.
$entity_type_set = FALSE;
$stored_entity_type = variable_get('metatag_enable_' . $entity_type, NULL);
if ($force_enable || !isset($stored_entity_type)) {
variable_set('metatag_enable_' . $entity_type, TRUE);
$entity_type_set = TRUE;
}
// Clear the static cache so that the entity type / bundle will work.
drupal_static_reset('metatag_entity_supports_metatags');
return $bundle_set || $entity_type_set;
}
/**
* Disable support for a specific entity type.
*
* @param string $entity_type
* The entity type.
* @param string $bundle
* The bundle of the entity.
*/
function metatag_entity_type_disable($entity_type, $bundle = NULL) {
// The bundle was defined.
if (isset($bundle)) {
variable_set('metatag_enable_' . $entity_type . '__' . $bundle, FALSE);
}
// The bundle was not defined.
else {
variable_set('metatag_enable_' . $entity_type, FALSE);
}
// Clear the static cache so that the entity type / bundle will work.
drupal_static_reset('metatag_entity_supports_metatags');
}
/**
* 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;
}
// Special consideration for the Me module, which uses the "user/me" path and
// will cause problems.
if (arg(0) == 'user' && arg(1) == 'me' && function_exists('me_menu_alter')) {
return;
}
// The page region can be changed.
$region = variable_get('metatag_page_region', 'content');
// Ensure these arrays exist, otherwise several use cases will fail.
if (!isset($page[$region]) || !is_array($page[$region])) {
$page[$region] = array();
}
if (!isset($page[$region]['metatags']) || !is_array($page[$region]['metatags'])) {
$page[$region]['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 (variable_get('metatag_cache_output', FALSE)) {
if ($cache = metatag_cache_get($cid)) {
$metatags = $cache->data;
}
else {
$metatags = metatag_metatags_view($instance, array());
metatag_cache_set($cid, $metatags);
}
}
else {
$metatags = metatag_metatags_view($instance, array());
}
$page[$region]['metatags'][$instance] = $metatags;
}
// Load any meta tags assigned via metatag_page_set_metatags(). Note: this
// must include the necessary defaults.
else {
$page[$region]['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[$region]['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());
// If output caching is enabled, save this for later.
if (variable_get('metatag_cache_output', FALSE)) {
metatag_cache_set($cid, $metatags);
}
}
$page[$region]['metatags'][$instance] = $metatags;
}
}
/**
* Returns whether the current page is the page of the passed in entity.
*
* @param string $entity_type
* The entity type; e.g. 'node' or 'user'.
* @param object $entity
* The entity object.
*
* @return mixed
* TRUE if the current page is the page of the specified entity, or FALSE
* otherwise. If $entity_type == 'comment' return empty (FALSE).
*/
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 - 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;
}
// Support for Views' taxonomy_term pages with depth specifier, e.g.
// "taxonomy_term/123/1/all".
elseif ($entity_type == 'taxonomy_term' && strpos($current_path, $uri['path']) === 0 && count(explode('/', $current_path)) == 4) {
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);
$config->instance = $instance_old;
metatag_config_delete($config);
}
}
/**
* Implements hook_field_attach_delete_bundle().
*/
function metatag_field_attach_delete_bundle($entity_type, $bundle) {
$instance = $entity_type . ':' . $bundle;
if ($config = metatag_config_load($instance)) {
metatag_config_delete($config);
}
}
/**
* Implements hook_field_attach_form().
*/
function metatag_field_attach_form($entity_type, $entity, &$form, &$form_state, $langcode) {
// 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);
if (!metatag_entity_supports_metatags($entity_type, $bundle)) {
return;
}
$instance = metatag_get_entity_metatags_instance($entity, $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();
}
// Certain circumstances can result in $metatags not being an array.
if (!is_array($metatags)) {
$metatags = array();
}
$options['token types'] = array(
token_get_entity_mapping('entity', $entity_type),
);
$options['context'] = $entity_type;
// @todo Remove metatag_form_alter() when https://www.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 https://www.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 string $name
* 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;
}
}
/**
* Return instance of metatag.
*/
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 string $metatag
* The meta tag string.
* @param array $data
* The array of data for the meta tag class instance.
* @param array $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 string
* 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);
if (is_array($value)) {
$value = implode(', ', $value);
}
}
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().
*
* Hide tags added by core that are now handled by metatag.
*/
function metatag_html_head_alter(&$elements) {
$metatags = array();
$other_tags = array();
foreach ($elements as $key => $data) {
// Identify meta tags added by the Metatag module.
if (strpos($key, 'metatag_') === 0) {
$metatags[] = $key;
}
// Identify meta tags added by other modules.
else {
$other_tags[] = $key;
}
}
// The meta tag keys to look for.
$metatag_keys = array('name', 'property');
// Attributes to look for.
$attributes = array('name', 'rel');
// Look for meta tags that were added by other modules and hide them.
foreach ($metatags as $metatag_name) {
$metatag = &$elements[$metatag_name];
// Setting the #access attribute to these will stop them from being output
// but still leave the tags present for other modules to interact with.
foreach ($other_tags as $other_tag) {
$other = &$elements[$other_tag];
// Look for other meta tags that have one of the defined attributes which
// matches one of the values from Metatag's tag.
foreach ($attributes as $attribute) {
if (isset($other['#attributes'], $other['#attributes'][$attribute]) && is_string($other['#attributes'][$attribute])) {
foreach ($metatag_keys as $metatag_key) {
if (isset($metatag[$metatag_key])) {
if (strtolower($other['#attributes'][$attribute]) == $metatag[$metatag_key]) {
$other['#access'] = FALSE;
}
}
}
}
}
}
}
// If the 'leave core tags' option is disabled then the following meta tags
// will be removed if they're provided by core.
if (!variable_get('metatag_leave_core_tags', FALSE)) {
$core_tags = array(
'generator',
'canonical',
'shortlink',
// Leave the shortcut icon, that's more of a theming thing.
// 'shortcut icon',.
);
foreach ($elements as $name => &$element) {
// Ignore meta tags provided by Metatag.
if (strpos($name, 'metatag_') === 0) {
continue;
}
// Setting the #access attribute to these will stop them from being output
// but still leave the tags present for other modules to interact with.
foreach ($core_tags as $tag) {
if (!empty($element['#attributes']['rel']) && $element['#attributes']['rel'] == $tag) {
$element['#access'] = FALSE;
}
elseif (!empty($element['#attributes']['name']) && strtolower($element['#attributes']['name']) == $tag) {
$element['#access'] = FALSE;
}
}
}
}
}
/**
* Implements hook_get_form().
*/
function metatag_metatag_get_form($metatag, array $data = array(), array $options = array()) {
$instance = metatag_get_instance($metatag, $data);
return $instance->getForm($options);
}
/**
* Returns Instance info if exists otherwise return FALSE.
*/
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 string $instance
* A meta tag configuration instance.
*
* @return array
* 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 string $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) {
$instances = metatag_config_get_parent_instances($instance, $include_global);
$configs = metatag_config_load_multiple($instances);
// Check if one of the configs is enabled.
foreach ($configs as $config) {
if (empty($config->disabled)) {
return TRUE;
}
}
// No enabled configs found.
return FALSE;
}
else {
$config = metatag_config_load($instance);
return !empty($config) && empty($config->disabled);
}
}
/**
* Wrapper around entity_language().
*
* @param mixed $entity_type
* An entity type's machine name.
* @param object $entity
* The entity to review; will be typecast to an object if an array is passed.
*
* @return string
* A string indicating the language code to be used; returns LANGUAGE_NONE if
* the entity does not have a language assigned.
*/
function metatag_entity_get_language($entity_type, $entity) {
// Determine the entity's language, af.
$langcode = entity_language($entity_type, (object) $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().
*
* Try loading meta tags from a Views page display.
*/
function metatag_views_post_render(&$view, &$output, &$cache) {
// 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;
}
// If display is not set for some reason, get out to prevent PHP notices.
if (!isset($view->display[$view->current_display])) {
return;
}
// Build a shortcut to the current display object.
$display = $view->display[$view->current_display];
// Only proceed if this view is a full page and has a valid path, don't
// process block or other Views display objects.
if ($display->display_plugin == 'page' && !empty($display->display_options['path'])) {
// Try to work out what entity type this is.
$entity_type = '';
// All paths must have a placeholder.
$placeholder_pos = strpos($display->display_options['path'], '%');
if ($placeholder_pos !== FALSE) {
// The first argument must be a numeric entity ID.
if (!empty($view->args) && is_numeric($view->args[0])) {
// The first argument should be an entity ID.
$id = $view->args[0];
// Taxonomy terms are the most commonly used item, so check that first.
if ($display->display_options['path'] == 'taxonomy/term/%' || $display->display_options['path'] == 'taxonomy/term/%/%') {
$entity_type = 'taxonomy_term';
}
// Node pages.
elseif ($display->display_options['path'] == 'node/%') {
$entity_type = 'node';
}
// User pages.
elseif ($display->display_options['path'] == 'user/%') {
$entity_type = 'user';
}
}
}
// Check for other types of entities.
if (empty($entity_type)) {
// Trigger hook_metatag_views_post_render_get_entity().
$hook = 'metatag_views_post_render_get_entity';
foreach (module_implements($hook) as $module) {
$function = $module . '_' . $hook;
if (function_exists($function)) {
$entity_type = call_user_func($function, $view);
// If an entity type was identified, stop checking.
if (!empty($entity_type)) {
break;
}
}
}
}
// If an entity type was identified, try loading the entity.
if (!empty($entity_type)) {
// Try loading the requested entity.
$entities = entity_load($entity_type, array($id));
if (!empty($entities)) {
$entity = array_pop($entities);
// Trigger our hook_entity_view().
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) {
// 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;
}
// 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'])) {
// Loop over each context.
foreach ($context['contexts'] as $context_argument) {
if (is_array($context_argument->type) && !empty($context_argument->data)) {
if (in_array('entity', $context_argument->type)) {
$entity = $context_argument->data;
$entity_type = str_replace('entity:', '', $context_argument->plugin);
// Verify this is an appropriate entity.
$entity_info = entity_get_info($entity_type);
if (!empty($entity_info) && _metatag_entity_is_page($entity_type, $entity)) {
// Load the meta tags for this entity.
global $language;
metatag_entity_view($entity, $entity_type, 'full', $language->language, TRUE);
// Don't bother looping over any more contexts, an entity has been
// found.
break;
}
}
}
}
}
}
/**
* Checks if this entity is the default revision (published).
*
* Only needed when running Workbench Moderation v1; v3 is skipped.
*
* @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.
// - https://www.drupal.org/node/1879482
// - https://www.drupal.org/node/218755
// - https://www.drupal.org/node/1522154
//
// Every moderation module saving a forward revision needs to return FALSE.
// @todo: Refactor this workaround under D8.
// Workbench Moderation v1 uses the hook_node_presave() for some custom logic.
// This was replaced with hook_entity_presave() in v3, so only proceed if the
// old hook implementation is present.
if (function_exists('workbench_moderation_node_presave')) {
// 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']) && is_numeric($_GET['page'])) {
$cid_parts['page'] = intval($_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 255 chars.
return substr(implode(':', $cid_parts), 0, 255);
}
/**
* Wrapper for cache_set.
*
* @see cache_set()
*/
function metatag_cache_set($cid, $data) {
// By default the cached data will not expire.
$expire = CACHE_PERMANENT;
// Triggers hook_metatag_cache_set_expire_alter().
drupal_alter("metatag_cache_set_expire", $expire, $cid, $data);
return cache_set($cid, $data, 'cache_metatag', $expire);
}
/**
* 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
* String of error.
*/
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 '';
}
/**
* Implements hook_admin_menu_cache_info().
*/
function metatag_admin_menu_cache_info() {
$caches['metatag'] = array(
'title' => t('Metatag'),
'callback' => 'metatag_config_cache_clear',
);
return $caches;
}
/**
* Identify whether an entity type is technically capable of having meta tags.
*
* In order to be capable of having meta tags, an entity type must have view
* modes, must be fieldable, and may not be a configuration entity.
*
* @param string $entity_type
* The entity type.
* @param array $entity_info
* Entity information.
*
* @return bool
* Return TRUE if suitable.
*/
function metatag_entity_type_is_suitable($entity_type, $entity_info = array()) {
$suitable = TRUE;
// If the entity info was not passed along, load it.
if (empty($entity_info)) {
$entity_info = entity_get_info($entity_type);
}
// Configuration entities may not have meta tags.
if (isset($entity_info['configuration']) && $entity_info['configuration'] == TRUE) {
$suitable = FALSE;
}
// Entities must have bundles.
elseif (empty($entity_info['bundles'])) {
$suitable = FALSE;
}
// The entity type must be fieldable.
elseif (empty($entity_info['fieldable'])) {
$suitable = FALSE;
}
else {
// Ignore some view modes that are automatically added by certain modules.
unset($entity_info['view modes']['ical']);
unset($entity_info['view modes']['diff_standard']);
unset($entity_info['view modes']['token']);
// There must be view modes.
if (empty($entity_info['view modes'])) {
$suitable = FALSE;
}
else {
// Specifically disable some entity types.
$excluded = array(
// Comment module.
'comment',
// Field Collection module.
'field_collection_item',
// Paragraphs module.
'paragraphs_item',
);
if (in_array($entity_type, $excluded)) {
$suitable = FALSE;
}
}
}
// Trigger hook_metatag_entity_type_is_supported_alter() to allow other
// modules to either enable or disable certain entity types.
drupal_alter('metatag_entity_type_is_supported', $suitable, $entity_type, $entity_info);
return $suitable;
}
/**
* Implements hook_node_type_insert().
*
* When a content type is created, enable it for use with Metatag.
*/
function metatag_node_type_insert($info) {
if (metatag_entity_supports_metatags('node')) {
if (metatag_entity_type_enable('node', $info->type)) {
drupal_set_message(t('Metatag support has been enabled for the @label content type.', array('@label' => $info->name)));
}
}
}
/**
* Implements hook_node_type_delete().
*
* When a content type is deleted, remove the corresponding Metatag variable.
*/
function metatag_node_type_delete($info) {
variable_del('metatag_enable_node__' . $info->type);
}
/**
* Implements hook_taxonomy_vocabulary_insert().
*
* When a vocabulary is created, enable it for use with Metatag.
*/
function metatag_taxonomy_vocabulary_insert($vocabulary) {
if (metatag_entity_supports_metatags('taxonomy_term')) {
if (metatag_entity_type_enable('taxonomy_term', $vocabulary->machine_name)) {
drupal_set_message(t('Metatag support has been enabled for the @label vocabulary.', array('@label' => $vocabulary->name)));
}
}
}
/**
* Implements hook_taxonomy_vocabulary_delete().
*
* When a vocabulary is deleted, remove the corresponding Metatag variable.
*/
function metatag_taxonomy_vocabulary_delete($info) {
variable_del('metatag_enable_taxonomy_term__' . $info->machine_name);
}
/**
* Implements hook_workbench_moderation_transition().
*
* Clear a node's caches when its Workbench Moderation state is changed.
*/
function metatag_workbench_moderation_transition($node, $previous_state, $new_state) {
metatag_metatags_cache_clear('node', array($node->nid));
}
/**
* Sort callback for sorting by metatag instance string values.
*/
function _metatag_config_instance_sort($a, $b) {
$a_contexts = explode(':', $a);
$b_contexts = explode(':', $b);
// Global config always comes first.
if ($a_contexts[0] == 'global' && $b_contexts[0] != 'global') {
return -1;
}
elseif ($b_contexts[0] == 'global' && $a_contexts[0] != 'global') {
return 1;
}
for ($i = 0; $i < max(count($a_contexts), count($b_contexts)); $i++) {
$a_context = isset($a_contexts[$i]) ? $a_contexts[$i] : '';
$b_context = isset($b_contexts[$i]) ? $b_contexts[$i] : '';
if ($a_context == $b_context) {
continue;
}
else {
return strcmp($a_context, $b_context);
}
}
}
/**
* Translations & internationalization (i18n).
*/
/**
* 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 integration is enabled.
*
* @param string $string
* String in default language or array of strings to be translated.
* @param string $tag_name
* The internal name of the meta tag being translated.
* @param mixed $context
* A context string to use with i18n, or the $options array from a Metatag::
* getValue() method; if the latter it will be used to generate the full
* context.
* @param string $langcode
* The language code to submit instead of the current page's language.
* @param bool $update
* Whether or not to create/update records in {locales_source}.
*
* @return string
* The translated string if i18n_string is enabled, otherwise just returns the
* original string.
*
* @see i18n_string_translate()
*/
function metatag_translate_metatag($string, $tag_name, $context, $langcode = NULL, $update = TRUE) {
if (module_exists('i18n_string') && !variable_get('metatag_i18n_disabled', FALSE)) {
// 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.
static $page_is_admin;
if (is_null($page_is_admin)) {
$page_is_admin = FALSE;
if (path_is_admin(current_path()) && !variable_get('metatag_tag_admin_pages', FALSE)) {
$page_is_admin = TRUE;
}
}
if ($page_is_admin) {
return $string;
}
// If the context is an array then it is the $options from the meta tag
// generator and needs some custom tailoring. Doing it this way to avoid an
// unnecessary entity_extract_ids() call when i18n isn't being used.
if (is_array($context)) {
// Optionally disable output generation.
if (!variable_get('metatag_i18n_translate_output', FALSE)) {
return $string;
}
// Output generation was enabled, so continue as normal.
$new_context = 'output:';
if (drupal_is_front_page()) {
$new_context .= 'frontpage';
}
// If this is an entity page, use the entity as the context.
elseif (!empty($context['entity_type']) && !empty($context['entity'])) {
list($entity_id, $revision_id, $bundle) = entity_extract_ids($context['entity_type'], $context['entity']);
$new_context .= $context['entity_type'] . ':' . $entity_id;
}
// Otherwise, use the page URL.
else {
// Trim this to avoid SQL errors on the {locales_source} table.
// length = 255 - strlen('metatag:output:page:') - strlen(metatag);
$strlen = 255 - strlen('metatag:output:page:' . $tag_name);
$new_context .= 'page:' . drupal_substr(current_path(), 0, $strlen);
}
$context = $new_context;
}
$options = array(
// Automatically create/update the {locales_source} record if one wasn't
// found.
'update' => $update,
// Translate the string.
'translate' => TRUE,
);
// If the langcode was passed in, add it to the options passed to i18n.
if (!empty($langcode)) {
$options['langcode'] = $langcode;
}
// By default disable the watchdog logging of translation messages.
$options['watchdog'] = variable_get('metatag_i18n_enable_watchdog', FALSE);
// Triggers hook_metatag_i18n_context_alter() - allows the i18n string to
// be altered before being used.
drupal_alter('metatag_i18n_context', $context, $tag_name);
// If the context was erased just send back the original string - it's
// unlikely, but it could happen.
if (empty($context)) {
return $string;
}
// The 'name' is split up by i18n_string into two components - the textgroup
// is the first item, the others are joined together with a ':' separator
// to make the context. In order to have the contexts show with "metatag" as
// the first part of the context, it has to be added twice to the name.
$name = array(
'metatag',
$context,
$tag_name,
);
// Notify i18n of the string, and obtain a translation if one is available.
return i18n_string($name, $string, $options);
}
// If the i18n_string module isn't enabled then just pass back the string
// as-is.
else {
return $string;
}
}
/**
* Translate a set of metatags to the current language.
*
* @param array $metatags
* List of meta tags to be translated.
* @param string $context
* An optional context to use with i18n.
* @param string $langcode
* The language code to submit instead of the current page's language.
* @param bool $update
* Whether or not to create/update records in {locales_source}.
*/
function metatag_translate_metatags(&$metatags, $context = NULL, $langcode = NULL, $update = TRUE) {
if (!empty($metatags)) {
foreach ($metatags as $key => $data) {
if (!empty($data['value']) && is_string($data['value'])) {
$metatags[$key] = array(
'value' => metatag_translate_metatag($data['value'], $key, $context, $langcode, $update),
);
}
}
}
}
/**
* Update the translated definitions of meta tags.
*
* @param array $metatags
* List of meta tags to have their translations updated.
* @param string $context
* The string that will be used to group strings in the translation UI.
*/
function metatag_translations_update($metatags, $context) {
// Store the context as it was originally provided.
$original_context = $context;
// Update the i18n string.
if (module_exists('i18n_string') && !variable_get('metatag_i18n_disabled', FALSE)) {
$options = array();
// By default disable the watchdog logging of translation messages.
$options['watchdog'] = variable_get('metatag_i18n_enable_watchdog', FALSE);
foreach ($metatags as $tag_name => $data) {
// Revert the context, so that it can be changed if needed.
$context = $original_context;
if (!empty($data['value']) && is_string($data['value'])) {
// Triggers hook_metatag_i18n_context_alter() - allows the i18n string
// to be altered before being used.
drupal_alter('metatag_i18n_context', $context, $tag_name);
// Don't do anything if the context was erased - it's unlikely, but it
// could happen.
if (empty($context)) {
continue;
}
// The textgroup is the first part of the string.
i18n_string_update("metatag:{$context}:{$tag_name}", $data['value'], $options);
}
}
}
}
/**
* Remove the translated definitions of meta tags.
*
* @param array $metatags
* List of meta tags to have their translations updated.
* @param string $context
* The string that will be used to group strings in the translation UI.
*/
function metatag_translations_delete($metatags, $context) {
// Store the context as it was originally provided.
$original_context = $context;
// Update the i18n string.
if (module_exists('i18n_string') && !variable_get('metatag_i18n_disabled', FALSE)) {
$options = array();
// By default disable the watchdog logging of translation messages.
$options['watchdog'] = variable_get('metatag_i18n_enable_watchdog', FALSE);
foreach ($metatags as $tag_name => $data) {
// Revert the context, so that it can be changed if needed.
$context = $original_context;
if (!empty($data['value']) && is_string($data['value'])) {
// Triggers hook_metatag_i18n_context_alter() - allows the i18n string
// to be altered before being used.
drupal_alter('metatag_i18n_context', $context, $tag_name);
// Don't do anything if the context was erased - it's unlikely, but it
// could happen.
if (empty($context)) {
continue;
}
// The textgroup is the first part of the string.
i18n_string_remove("metatag:{$context}:{$tag_name}", $data['value'], $options);
}
}
}
}
/**
* Implements hook_config_insert().
*
* Implements hook_metatag_config_insert() on behalf of i18n_string.
*/
function i18n_string_metatag_config_insert($config) {
$context = 'metatag_config:' . $config->instance;
metatag_translations_update($config->config, $context);
}
/**
* Implements hook_config_update().
*
* Implements hook_metatag_config_update() on behalf of i18n_string.
*/
function i18n_string_metatag_config_update($config) {
// Defer to the 'insert' function.
i18n_string_metatag_config_insert($config);
}
/**
* Implements hook_config_delete().
*
* Implements hook_metatag_config_delete() on behalf of i18n_string.
*/
function i18n_string_metatag_config_delete($config) {
$context = 'metatag_config:' . $config->instance;
metatag_translations_delete($config->config, $context);
}