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, ); // 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'] = 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'] = '

' . t('Real-time SEO for Drupal') . '

'; // Output the overall score next to the focus keyword. $form['yoast_seo'][$langcode]['focus_keyword']['#suffix'] = '
' . t('SEO') . ': ' . $score . '
'; // 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' => '
', ); // 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'] = '
'; } 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, '' FROM {yoast_seo} WHERE (entity_type = :type) AND (entity_id = :id) AND (revision_id = :revision)", array( ':type' => $entity_type, ':id' => $entity_id, ':revision' => $revision_id, ))->fetchAllKeyed(); foreach ($languages as $oldlang => $empty) { $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(); } // Otherwise save the data for this entity. 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; } // Check each bundle. 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]); } // 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. * * @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); } // The bundle was not defined. 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; } }