yoast_seo.module in Real-time SEO for Drupal 7
Same filename and directory in other branches
Primary hook implementations for Yoast SEO for Drupal module.
File
yoast_seo.moduleView source
<?php
/**
* @file
* Primary hook implementations for Yoast SEO for Drupal module.
*/
/**
* Implements hook_permission().
*/
function yoast_seo_permission() {
$permissions['administer yoast seo'] = array(
'title' => t('Administer Real-time SEO for Drupal'),
'restrict access' => TRUE,
'description' => t('Control the main settings pages and configure the settings per content types.'),
);
$permissions['use yoast seo'] = array(
'title' => t('Use Real-time SEO for Drupal'),
'description' => t('Modify Realtime SEO for Drupal configuration per individual nodes.'),
);
return $permissions;
}
/**
* Implements hook_menu().
*/
function yoast_seo_menu() {
$items['admin/config/search/yoast'] = array(
'title' => 'Real-time SEO for Drupal',
'description' => 'Configure Real-time SEO for Drupal settings',
'page callback' => 'drupal_get_form',
'page arguments' => array(
'yoast_seo_admin_settings_form',
),
'access arguments' => array(
'administer yoast seo',
),
'file' => 'includes/yoast_seo.admin.inc',
);
return $items;
}
/**
* Implements hook_views_api().
*/
function yoast_seo_views_api() {
return array(
'api' => 3,
'path' => drupal_get_path('module', 'yoast_seo') . '/views',
);
}
/**
* Build a FAPI array for editing meta tags.
*
* @param array $form
* The current FAPI array.
* @param string $instance
* The configuration instance key to use, e.g. "node:article".
* @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 yoast_seo_configuration_form(array &$form, $instance, array $options = array()) {
// Making sure we have the necessary form elements and we have access to the
// form elements. Otherwise we're executing code we'll never use.
if (!empty($form['path']['#access']) && !empty($form['metatags']['#access']) && (user_access('use yoast seo') || user_access('administer yoast seo'))) {
// Work out the language code to use, default to NONE.
if (!empty($form['#entity_type'])) {
if (!empty($form['#entity'])) {
$langcode = entity_language($form['#entity_type'], (object) $form['#entity']);
}
if (empty($langcode)) {
$langcode = LANGUAGE_NONE;
}
}
// Merge in the default options.
$options += array(
'instance' => $instance,
);
$form['yoast_seo'] = array(
'#type' => 'container',
'#title' => t('Real-time SEO'),
'#collapsible' => TRUE,
'#collapsed' => TRUE,
'#multilingual' => TRUE,
'#tree' => TRUE,
'#weight' => 35,
'#language' => $langcode,
'#attributes' => array(
'class' => array(
'yoast-seo-form',
),
),
);
$form['yoast_seo'][$langcode] = array(
'#type' => 'container',
'#multilingual' => TRUE,
'#tree' => TRUE,
);
// Put Yoast in a vertical tab if setting is set.
$yoast_seo_vertical_tab = variable_get('yoast_seo_vertical_tab');
if (!empty($yoast_seo_vertical_tab)) {
$form['yoast_seo']['#type'] = 'fieldset';
}
// 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['yoast_seo']['#group'] = $key;
break;
}
}
$metatag_config = metatag_config_load_with_defaults($options['instance']);
$new_entity = empty($form['#entity']->nid) ? TRUE : FALSE;
// Add the focus keyword field.
$form['yoast_seo'][$langcode]['focus_keyword'] = array(
'#type' => 'textfield',
'#title' => t('Focus keyword'),
'#description' => t('Pick the main keyword or keyphrase that this post/page is about.'),
'#default_value' => !empty($form['#entity']->yoast_seo[$langcode]['focus_keyword']) ? $form['#entity']->yoast_seo[$langcode]['focus_keyword'] : '',
'#id' => drupal_html_id('focus-keyword'),
);
// Add the SEO status field.
$form['yoast_seo'][$langcode]['seo_status'] = array(
'#type' => 'hidden',
'#default_value' => !empty($form['#entity']->yoast_seo[$langcode]['seo_status']) ? $form['#entity']->yoast_seo[$langcode]['seo_status'] : 0,
'#attributes' => array(
'id' => drupal_html_id('seo-status'),
),
);
// Set placeholder tekst if there is a default in metatags and it is a new
// entity. This means we have some default configuration for the title.
if (!empty($form['metatags'][$langcode]['basic']['title'])) {
// Checking to see if we have to empty the default value or not.
if ($form['metatags'][$langcode]['basic']['title']['value']['#default_value'] == $metatag_config['title']['value']) {
if ($new_entity) {
$form['metatags'][$langcode]['basic']['title']['value']['#default_value'] = $metatag_config['title']['value'];
}
else {
$form['metatags'][$langcode]['basic']['title']['value']['#default_value'] = token_replace($metatag_config['title']['value'], array(
'node' => $form['#entity'],
));
}
}
}
// Set placeholder tekst if there is a default in metatags and it is a new
// entity. This means we have some default configuration for the
// description.
if (!empty($form['metatags'][$langcode]['basic']['description']) && isset($metatag_config['description'])) {
// Checking to see if we have to empty the default value or not.
if ($form['metatags'][$langcode]['basic']['description']['value']['#default_value'] == $metatag_config['description']['value']) {
if ($new_entity) {
$form['metatags'][$langcode]['basic']['description']['value']['#default_value'] = $metatag_config['description']['value'];
}
else {
$form['metatags'][$langcode]['basic']['description']['value']['#default_value'] = strip_tags(token_replace($metatag_config['description']['value'], array(
'node' => $form['#entity'],
)));
}
}
}
// Add a form after build to add the JavaScript.
$form['#after_build'][] = 'yoast_seo_configuration_form_after_build';
// Add a submit handler to compare the submitted values against the default
// values.
$form += array(
'#submit' => array(),
);
array_unshift($form['#submit'], 'yoast_seo_configuration_form_submit');
}
}
/**
* Actually we need access to all fields listed or selected.
*
* @param array $form
*
* @return bool
*/
function _yoast_check_fields_access(array $form) {
$return = array();
$bundle = $form['#bundle'];
$required_fields = array(
'yoast_seo',
'path',
'metatags',
);
$fields = variable_get('yoast_seo_body_fields_' . $bundle, array());
$default_fields = array_merge($required_fields, $fields);
if (isset($form['body'])) {
$return[] = $form['body']['#access'];
}
elseif (empty($fields)) {
// We don't have body and fields selected.
$return[] = FALSE;
}
foreach ($default_fields as $field) {
$return[] = !empty($form[$field]['#access']);
}
return array_sum($return) === count($return);
}
/**
* Form API submission callback.
*
* Moving submitted values back into the metatag fields.
*/
function yoast_seo_configuration_form_submit($form, &$form_state) {
if (!empty($form_state['values']['metatags'])) {
$langcode = $form_state['values']['language'];
// Moving the title field value.
if (isset($form_state['values']['metatags'][$langcode]['title']['value']) && isset($form_state['values']['metatags'][$langcode]['title']['default'])) {
$title = trim($form_state['values']['metatags'][$langcode]['title']['value']);
// If the title is the placeholder we had before, we would like to replace
// it with Metatag defaults.
if (empty($title)) {
$form_state['values']['metatags'][$langcode]['title']['value'] = $form_state['values']['metatags'][$langcode]['title']['default'];
}
}
// Moving the description field value.
if (isset($form_state['values']['metatags'][$langcode]['description']['value']) && isset($form_state['values']['metatags'][$langcode]['description']['default'])) {
$description = trim($form_state['values']['metatags'][$langcode]['description']['value']);
// If the description is the placeholder we had before, we would like to
// replace it with Metatag defaults.
if (empty($description)) {
$form_state['values']['metatags'][$langcode]['description']['value'] = $form_state['values']['metatags'][$langcode]['description']['default'];
}
}
}
}
/**
* In this afterbuild function we add the configuration to the JavaScript.
*
* At this point we have access to the HTML id's of form elements we need for
* the JavaScript to function.
*/
function yoast_seo_configuration_form_after_build($form, &$form_state) {
// Work out the language code to use, default to NONE. This is used on the
// yoast_seo settings per language and for metatags..
if (!empty($form['#entity_type'])) {
if (!empty($form['#entity'])) {
$langcode = entity_language($form['#entity_type'], (object) $form['#entity']);
}
}
if (empty($langcode)) {
$langcode = LANGUAGE_NONE;
}
// Lets put the container in the bottom of page, above additional_settings
// tabs.
$form['yoast_seo']['#weight'] = $form['additional_settings']['#weight'] - 2;
// Generate unique HTML IDs.
$wrapper_target_id = drupal_html_id('yoast-wrapper');
$output_target_id = drupal_html_id('yoast-output');
$overallscore_target_id = drupal_html_id('yoast-overallscore');
$snippet_target_id = drupal_html_id('yoast-snippet');
$score = !empty($form['yoast_seo'][$langcode]['seo_status']['#value']) ? yoast_seo_score_rating($form['yoast_seo'][$langcode]['seo_status']['#value']) : yoast_seo_score_rating(0);
// Add the Yoast SEO title before the wrapper.
$form['yoast_seo'][$langcode]['focus_keyword']['#prefix'] = '<h3 class="wrapper-title">' . t('Real-time SEO for Drupal') . '</h3>';
// Output the overall score next to the focus keyword.
$form['yoast_seo'][$langcode]['focus_keyword']['#suffix'] = '<div id="yoast-overallscore" class="overallScore ' . $score . '">
<div class="score_circle"></div>
<span class="score_title">' . t('SEO') . ': <strong>' . $score . '</strong></span>
</div>';
// Output the markup for the snippet and overall score above the body field.
$form['yoast_seo'][$langcode]['snippet_analysis'] = array(
'#weight' => $form['yoast_seo']['#weight'] - 1,
'#markup' => '<div id="' . $wrapper_target_id . '"><div id="wpseo_meta"></div><label>' . t('Snippet editor') . '</label><div id="' . $snippet_target_id . '"></div><label>' . t('Content analysis') . '</label><div id="' . $output_target_id . '"></div></div>',
);
// Minified JS file.
drupal_add_js(yoast_seo_library_path('js-text-analysis') . '/yoast-seo.min.js', array(
'type' => 'external',
'scope' => 'footer',
));
// Our own JavaScript.
drupal_add_js(drupal_get_path('module', 'yoast_seo') . '/js/yoast_seo.js');
// Our own CSS.
drupal_add_css(drupal_get_path('module', 'yoast_seo') . '/css/yoast_seo.css');
global $base_root;
$default_url = '';
if (!empty($form['path']['alias']['#default_value'])) {
$default_url = $form['path']['alias']['#default_value'];
}
elseif (!empty($form['path']['source']['#value'])) {
$default_url = $form['path']['source']['#value'];
}
// Check if the SEO title field is overwritten.
$seo_title_overwritten = FALSE;
if (!empty($form['metatags'][$langcode]['basic']['title']['value']['#default_value'])) {
$seo_title_overwritten = TRUE;
}
// Collect all body fields and field id's.
$text_content = array();
if (isset($form['body'])) {
// We use the language of the body element so we know where to add the
// correct yoast_seo settings / fields and elements.
$body_language = $form['body']['#language'];
$text_content += _yoast_seo_process_field($form['body'][$body_language]);
}
$yoast_fields = variable_get('yoast_seo_body_fields_' . $form['#bundle'], array());
foreach ($yoast_fields as $yoast_field) {
// We use the language of the field element.
$field_language = $form[$yoast_field]['#language'];
$text_content += _yoast_seo_process_field($form[$yoast_field][$field_language]);
}
// Create the configuration we will sent to the content analysis library.
$configuration = array(
'analyzer' => TRUE,
'snippetPreview' => TRUE,
'targetId' => $wrapper_target_id,
'targets' => array(
'output' => $output_target_id,
'overall' => $overallscore_target_id,
'snippet' => $snippet_target_id,
),
'defaultText' => array(
'url' => $default_url,
'title' => !empty($form['metatags'][$langcode]['basic']['title']['value']['#default_value']) ? $form['metatags'][$langcode]['basic']['title']['value']['#default_value'] : '',
'keyword' => !empty($form['yoast_seo'][$langcode]['focus_keyword']['#default_value']) ? $form['yoast_seo'][$langcode]['focus_keyword']['#default_value'] : '',
'meta' => !empty($form['metatags'][$langcode]['basic']['description']['value']['#default_value']) ? $form['metatags'][$langcode]['basic']['description']['value']['#default_value'] : '',
'body' => trim(implode(PHP_EOL, $text_content)),
),
'fields' => array(
'focusKeyword' => $form['yoast_seo'][$langcode]['focus_keyword']['#id'],
'seoStatus' => $form['yoast_seo'][$langcode]['seo_status']['#attributes']['id'],
'pageTitle' => module_exists('seo_ui') ? $form['seo_vtab']['metatags_title']['title']['value']['#id'] : $form['metatags'][$langcode]['basic']['title']['value']['#id'],
'nodeTitle' => $form['title']['#id'],
'description' => module_exists('seo_ui') ? $form['seo_vtab']['metatags'][$langcode]['basic']['description']['value']['#id'] : $form['metatags'][$langcode]['basic']['description']['value']['#id'],
'bodyText' => array_keys($text_content),
'url' => module_exists('seo_ui') ? $form['seo_vtab']['path']['alias']['#id'] : $form['path']['alias']['#id'],
),
'placeholderText' => array(
'title' => t('Please click here to alter your page meta title'),
'description' => t('Please click here and alter your page meta description.'),
'url' => t('example-post'),
),
'SEOTitleOverwritten' => $seo_title_overwritten,
'textFormat' => !empty($form['body'][$langcode][0]['format']['#id']) ? $form['body'][$langcode][0]['format']['#id'] : '',
'baseRoot' => $base_root,
);
// Allow other modules to alter the configuration we sent to the content
// analysis library by implementing hook_yoast_seo_configuration_alter().
drupal_alter('yoast_seo_configuration', $configuration);
// Add the configuration to the front-end for the content analysis library.
drupal_add_js(array(
'yoast_seo' => $configuration,
), 'setting');
return $form;
}
/**
* Helper function that extracts field values and id's.
*
* @param array $field
*
* @return array
*/
function _yoast_seo_process_field(array $field) {
$fields_content = array();
if ((int) $field['#cardinality'] === 1) {
// Single filed
// Store the id of field, it can be filled later and we need them for js.
$fields_content[$field[0]['value']['#id']] = '';
if (isset($field[0]['value']['#default_value']) && !empty($field[0]['value']['#default_value'])) {
$fields_content[$field[0]['value']['#id']] = $field[0]['value']['#default_value'];
}
}
else {
// Multi field.
for ($i = 0; $i <= $field['#max_delta']; $i++) {
$fields_content[$field[$i]['value']['#id']] = '';
if (isset($field[$i]['value']['#default_value']) && !empty($field[$i]['value']['#default_value'])) {
$fields_content[$field[$i]['value']['#id']] = $field[$i]['value']['#default_value'];
}
}
}
return $fields_content;
}
/**
* Implements hook_field_attach_form().
*/
function yoast_seo_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 (!yoast_seo_entity_supports_yoast_seo($entity_type, $bundle)) {
return;
}
$instance = "{$entity_type}:{$bundle}";
$options['token types'] = array(
token_get_entity_mapping('entity', $entity_type),
);
$options['context'] = $entity_type;
// Allow hook_metatag_token_types_alter() to modify the defined tokens. We do
// this because we are using metatags own fields.
drupal_alter('metatag_token_types', $options);
$form['#yoast_seo'] = array(
'instance' => $instance,
'options' => $options,
);
}
/**
* Implements hook_form_alter().
*
* @todo Remove this when https://www.drupal.org/node/1284642 is fixed in core.
*/
function yoast_seo_form_alter(&$form, $form_state, $form_id) {
if (!empty($form['#yoast_seo']) && !isset($form['yoast_seo'])) {
extract($form['#yoast_seo']);
yoast_seo_configuration_form($form, $instance, $options);
unset($form['#yoast_seo']);
}
}
/**
* Implements hook_form_FORM_ID_alter().
*
* Add the custom Yoast score to the overview if the user has access.
*/
function yoast_seo_form_node_admin_content_alter(&$form, &$form_state, $form_id) {
if (user_access('use yoast seo')) {
// Add our own CSS.
drupal_add_css(drupal_get_path('module', 'yoast_seo') . '/css/yoast_seo.css');
if (!empty($form['admin']['nodes']['#options'])) {
// Get all the current options node nids.
$nids = array_keys($form['admin']['nodes']['#options']);
// Add our custom SEO score header to the admin overview.
$form['admin']['nodes']['#header']['yoast_seo'] = t('SEO score');
// For every result, we fetch the score from the database on nid.
foreach (entity_load('node', $nids) as $node) {
list($nid, , $bundle) = entity_extract_ids('node', $node);
// Check if entity has SEO availability otherwise show a message.
if (yoast_seo_entity_supports_yoast_seo('node', $bundle)) {
// Score will be either 0 or a higher int. 0 is default.
$score = yoast_seo_get_score($nid);
// Class will represent classname for current score. Like poor or bad
// it's used for theming purposes.
$class = yoast_seo_score_rating($score);
// Add Yoast score to the overview.
$form['admin']['nodes']['#options'][$nid]['yoast_seo'] = '<div id="yoast-overallscore" class="overallScore ' . $class . '"><div class="score_circle"></div></div>';
}
else {
$form['admin']['nodes']['#options'][$nid]['yoast_seo'] = '';
}
}
}
}
}
/**
* Load an entity's SEO info.
*
* @param string $entity_type
* The entity type to load.
* @param int $entity_id
* The ID of the entity to load.
*
* @return array
* An array of SEO info data keyed by revision ID and language.
*/
function yoast_seo_configuration_load($entity_type, $entity_id) {
$seo_info = yoast_seo_configuration_load_multiple($entity_type, array(
$entity_id,
));
return !empty($seo_info) ? reset($seo_info) : array();
}
/**
* Custom function which will get the SEO score from a nid.
*
* @param int $entity_id
* The entity id of the score we need to load.
*
* @return int
* An int representing the SEO score.
*/
function yoast_seo_get_score($entity_id) {
// Get the SEO score for this entity.
$result = db_select('yoast_seo', 'y')
->fields('y', array(
'seo_status',
))
->condition('y.entity_id', $entity_id)
->execute()
->fetchAssoc();
return !empty($result['seo_status']) ? $result['seo_status'] : '0';
}
/**
* Custom function to build up score div, same as in scoreToRating.js.
*
* @param int $score
* The score of the node.
*
* @return string
* A string that we can use as classname and automatically will be styled.
*/
function yoast_seo_score_rating($score) {
$score_rate = '';
if ($score <= 4) {
$score_rate = "bad";
}
if ($score > 4 && $score <= 7) {
$score_rate = "ok";
}
if ($score > 7) {
$score_rate = "good";
}
if ($score == 0) {
$score_rate = "na";
}
return $score_rate;
}
/**
* Load SEO info for multiple entities.
*
* @param string $entity_type
* The entity type to load.
* @param array $entity_ids
* The list of entity IDs.
*
* @return array
* An array of SEO info data, keyed by entity ID, revision ID and language.
*/
function yoast_seo_configuration_load_multiple($entity_type, array $entity_ids, array $revision_ids = array()) {
// Double check entity IDs are numeric thanks to Entity API module.
$entity_ids = array_filter($entity_ids, 'is_numeric');
if (empty($entity_ids)) {
return array();
}
// Also need to check if the Yoast SEO table exists since this condition could
// fire before the table has been installed yet.
if (!db_table_exists('yoast_seo')) {
watchdog('yoast_seo', 'The system tried to load Real-time SEO for Drupal data before the schema was fully loaded.', array(), WATCHDOG_WARNING);
return array();
}
// Get all translations of data for this entity.
$query = db_select('yoast_seo', 'y')
->fields('y', array(
'entity_id',
'revision_id',
'language',
'focus_keyword',
'seo_status',
))
->condition('y.entity_type', $entity_type)
->orderBy('entity_id')
->orderBy('revision_id');
// Filter by revision_ids if they are available. If not, filter by entity_ids.
if (!empty($revision_ids)) {
$query
->condition('y.revision_id', $revision_ids, 'IN');
}
else {
$query
->condition('y.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.
$seo_info = array();
while ($record = $result
->fetchObject()) {
$data = array(
'focus_keyword' => $record->focus_keyword,
'seo_status' => $record->seo_status,
);
$seo_info[$record->entity_id][$record->revision_id][$record->language] = $data;
}
return $seo_info;
}
/**
* Implements hook_entity_load().
*/
function yoast_seo_entity_load($entities, $entity_type) {
if (yoast_seo_entity_supports_yoast_seo($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 Yoast SEO.
if (yoast_seo_entity_supports_yoast_seo($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 SEO info for these entities.
$seo_info = yoast_seo_configuration_load_multiple($entity_type, $entity_ids, $revision_ids);
// Assign the metatag records for the correct revision ID.
if (!empty($seo_info)) {
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]->yoast_seo = isset($seo_info[$entity_id][$revision_id]) ? $seo_info[$entity_id][$revision_id] : array();
}
}
}
}
}
/**
* Implements hook_entity_insert().
*/
function yoast_seo_entity_insert($entity, $entity_type) {
if (isset($entity->yoast_seo)) {
list($entity_id, $revision_id, $bundle) = entity_extract_ids($entity_type, $entity);
// Verify that this entity type / bundle is supported.
if (!yoast_seo_entity_supports_yoast_seo($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->yoast_seo[$langcode]) && isset($entity->yoast_seo[LANGUAGE_NONE])) {
$entity->yoast_seo[$langcode] = $entity->yoast_seo[LANGUAGE_NONE];
unset($entity->yoast_seo[LANGUAGE_NONE]);
}
yoast_seo_configuration_save($entity_type, $entity_id, $revision_id, $entity->yoast_seo, $langcode);
}
}
/**
* Implements hook_entity_update().
*/
function yoast_seo_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 (!yoast_seo_entity_supports_yoast_seo($entity_type, $bundle)) {
return;
}
$revision_id = intval($revision_id);
if (isset($entity->yoast_seo)) {
// 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 = entity_language($entity_type, (object) $entity);
if (empty($new_language)) {
$new_language = LANGUAGE_NONE;
}
// 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('yoast_seo')
->condition('entity_type', $entity_type)
->condition('entity_id', $entity_id)
->condition('revision_id', $revision_id)
->condition('language', $old_language)
->execute();
// Swap out the SEO values for the two languages.
if (isset($entity->yoast_seo[$old_language])) {
$entity->yoast_seo[$new_language] = $entity->yoast_seo[$old_language];
unset($entity->yoast_seo[$old_language]);
}
}
}
}
// Save the record.
$old_vid = isset($entity->old_vid) ? $entity->old_vid : NULL;
yoast_seo_configuration_save($entity_type, $entity_id, $revision_id, $entity->yoast_seo, $new_language, $old_vid);
}
}
/**
* Implements hook_entity_delete().
*/
function yoast_seo_entity_delete($entity, $type) {
// Currently we only support nodes, so checking for a node ID seems like a
// good idea.
if (!empty($entity->nid)) {
// Delete the record from our table.
db_delete('yoast_seo')
->condition('entity_type', $type)
->condition('entity_id', $entity->nid)
->execute();
}
}
/**
* Save an entity's SEO settings.
*
* @param string $entity_type
* The entity type to load.
* @param int $entity_id
* The entity's ID.
* @param int $revision_id
* The entity's VID.
* @param array $seo_info
* All of the SEO information.
* @param string $langcode
* The language of the translation set.
* @param int $old_vid
* (optional) The old revision.
*/
function yoast_seo_configuration_save($entity_type, $entity_id, $revision_id, $seo_info, $langcode, $old_vid = NULL) {
// If no language assigned, or the language doesn't exist, use the
// has-no-language language.
$languages = language_list();
if (empty($langcode) || !isset($languages[$langcode])) {
$langcode = LANGUAGE_NONE;
}
// Check that $entity_id is numeric because of Entity API and string IDs.
if (!is_numeric($entity_id)) {
return;
}
// The revision_id must be a numeric value; some entities use NULL for the
// revision so change that to a zero.
if (is_null($revision_id)) {
$revision_id = 0;
}
// Handle scenarios where the seo info is completely empty.
if (empty($seo_info)) {
$seo_info = array();
// Add an empty array record for each language.
$languages = db_query("SELECT language, ''\n FROM {yoast_seo}\n WHERE (entity_type = :type)\n AND (entity_id = :id)\n AND (revision_id = :revision)", array(
':type' => $entity_type,
':id' => $entity_id,
':revision' => $revision_id,
))
->fetchAllKeyed();
foreach ($languages as $oldlang => $empty) {
$seo_info[$oldlang] = array();
}
}
// Update each of the per-language SEO info configurations in turn.
foreach ($seo_info as $langcode => $new_seo_info) {
// If the data array is empty, there is no data to actually save, so just
// delete the record from the database.
if (empty($new_seo_info)) {
db_delete('yoast_seo')
->condition('entity_type', $entity_type)
->condition('entity_id', $entity_id)
->condition('revision_id', $revision_id)
->condition('language', $langcode)
->execute();
}
else {
db_merge('yoast_seo')
->key(array(
'entity_type' => $entity_type,
'entity_id' => $entity_id,
'language' => $langcode,
'revision_id' => $revision_id,
))
->fields(array(
'focus_keyword' => $new_seo_info['focus_keyword'],
'seo_status' => $new_seo_info['seo_status'],
))
->execute();
}
}
}
/**
* Implements hook_node_type_delete().
*
* Remove configuration when the content type is removed.
*/
function yoast_seo_node_type_delete($info) {
variable_del('yoast_seo_enable_node__' . $info->type);
}
/**
* Implements hook_form_FORM_ID_alter().
*
* Adds extra settings to the node content type edit page.
*/
function yoast_seo_form_node_type_form_alter(&$form, $form_state) {
form_load_include($form_state, 'inc', 'yoast_seo', 'includes/yoast_seo.forms');
_yoast_seo_process_node_settings_form($form);
}
/**
* Check whether the requested entity type (and bundle) support Yoast SEO.
*
* 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_supports_metatags()
*/
function yoast_seo_entity_supports_yoast_seo($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) {
// The entity type technically supports entities.
// @todo, make this more generic when we are supporting more entities.
if ($entity_name == 'node') {
// Entity 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('yoast_seo_enable_' . $entity_name, FALSE) == FALSE) {
$entity_types[$entity_name] = FALSE;
}
else {
$entity_types[$entity_name] = array();
foreach ($entity_info['bundles'] as $bundle_name => $bundle_info) {
// Allow bundles to be disabled by assigning a variable
// 'yoast_seo_enable_{$entity_type}__{$bundle}' the value FALSE,
// e.g.:
if (variable_get('yoast_seo_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]);
}
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.
*
* @param string $entity_type
* The entity's type.
* @param string $bundle
* The entity's bundle.
*/
function yoast_seo_entity_type_enable($entity_type, $bundle = NULL) {
// The bundle was defined.
if (isset($bundle)) {
variable_set('yoast_seo_enable_' . $entity_type . '__' . $bundle, TRUE);
}
// Always enable the entity type, because otherwise there's no point in
// enabling the bundle.
variable_set('yoast_seo_enable_' . $entity_type, TRUE);
// Clear the static cache so that the entity type / bundle will work.
drupal_static_reset('yoast_seo_entity_supports_yoast_seo');
}
/**
* Disable support for a specific entity type.
*
* @param string $entity_type
* The entity's type.
* @param string $bundle
* The entity's bundle.
*/
function yoast_seo_entity_type_disable($entity_type, $bundle = NULL) {
// The bundle was defined.
if (isset($bundle)) {
variable_set('yoast_seo_enable_' . $entity_type . '__' . $bundle, FALSE);
}
else {
variable_set('yoast_seo_enable_' . $entity_type, FALSE);
}
// Clear the static cache so that the entity type / bundle will work.
drupal_static_reset('yoast_seo_entity_supports_yoast_seo');
}
/**
* Generates the path to the Yoast SEO JavaScript library files.
*
* @param string $mode
* (optional) Mode the path should be generated as.
*
* @return string
* Generated path to the library in the mode that was requested.
*/
function yoast_seo_library_path($library = 'js-text-analysis', $mode = 'relative') {
$lib_path = drupal_get_path('module', 'yoast_seo') . '/js';
if ($mode == 'local') {
return './' . $lib_path . '/' . $library;
}
else {
return base_path() . $lib_path . '/' . $library;
}
}
Functions
Name | Description |
---|---|
yoast_seo_configuration_form | Build a FAPI array for editing meta tags. |
yoast_seo_configuration_form_after_build | In this afterbuild function we add the configuration to the JavaScript. |
yoast_seo_configuration_form_submit | Form API submission callback. |
yoast_seo_configuration_load | Load an entity's SEO info. |
yoast_seo_configuration_load_multiple | Load SEO info for multiple entities. |
yoast_seo_configuration_save | Save an entity's SEO settings. |
yoast_seo_entity_delete | Implements hook_entity_delete(). |
yoast_seo_entity_insert | Implements hook_entity_insert(). |
yoast_seo_entity_load | Implements hook_entity_load(). |
yoast_seo_entity_supports_yoast_seo | Check whether the requested entity type (and bundle) support Yoast SEO. |
yoast_seo_entity_type_disable | Disable support for a specific entity type. |
yoast_seo_entity_type_enable | Enable support for a specific entity type. |
yoast_seo_entity_update | Implements hook_entity_update(). |
yoast_seo_field_attach_form | Implements hook_field_attach_form(). |
yoast_seo_form_alter | Implements hook_form_alter(). |
yoast_seo_form_node_admin_content_alter | Implements hook_form_FORM_ID_alter(). |
yoast_seo_form_node_type_form_alter | Implements hook_form_FORM_ID_alter(). |
yoast_seo_get_score | Custom function which will get the SEO score from a nid. |
yoast_seo_library_path | Generates the path to the Yoast SEO JavaScript library files. |
yoast_seo_menu | Implements hook_menu(). |
yoast_seo_node_type_delete | Implements hook_node_type_delete(). |
yoast_seo_permission | Implements hook_permission(). |
yoast_seo_score_rating | Custom function to build up score div, same as in scoreToRating.js. |
yoast_seo_views_api | Implements hook_views_api(). |
_yoast_check_fields_access | Actually we need access to all fields listed or selected. |
_yoast_seo_process_field | Helper function that extracts field values and id's. |