You are here

simplemeta.module in Simple Meta 7

SimpleMeta module.

File

simplemeta.module
View source
<?php

/**
 * @file
 * SimpleMeta module.
 */

/**
 * Implements hook_init().
 */
function simplemeta_init() {
  if (variable_get('simplemeta_form_enable', TRUE) && user_access('administer simplemeta') && !path_is_admin(current_path())) {
    $modulepath = drupal_get_path('module', 'simplemeta');
    drupal_add_css($modulepath . '/css/simplemeta.css', array(
      'preprocess' => FALSE,
    ));
    drupal_add_js($modulepath . '/js/simplemeta.js', array(
      'preprocess' => FALSE,
    ));
  }
}

/**
 * Implements hook_permission().
 */
function simplemeta_permission() {
  return array(
    'administer simplemeta' => array(
      'title' => t('Administer SimpleMeta'),
    ),
  );
}

/**
 * Implements hook_menu().
 */
function simplemeta_menu() {
  $items = array();
  $items['admin/content/simplemeta'] = array(
    'title' => 'SimpleMeta',
    'page callback' => 'simplemeta_meta_list',
    'access arguments' => array(
      'administer simplemeta',
    ),
    'file' => 'simplemeta.admin.inc',
  );
  $items['admin/content/simplemeta/list'] = array(
    'title' => 'SimpleMeta List',
    'page callback' => 'simplemeta_meta_list',
    'access arguments' => array(
      'administer simplemeta',
    ),
    'type' => MENU_DEFAULT_LOCAL_TASK,
    'file' => 'simplemeta.admin.inc',
    'weight' => 0,
  );
  $items['admin/content/simplemeta/add'] = array(
    'title' => 'Add SimpleMeta',
    'page callback' => 'simplemeta_add',
    'page arguments' => array(),
    'access arguments' => array(
      'administer simplemeta',
    ),
    'type' => MENU_LOCAL_TASK,
    'file' => 'simplemeta.admin.inc',
    'weight' => 1,
  );
  $items['admin/content/simplemeta/%simplemeta_meta/edit'] = array(
    'title' => 'Add SimpleMeta',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'simplemeta_meta_form',
      3,
    ),
    'access arguments' => array(
      'administer simplemeta',
    ),
    'type' => MENU_CALLBACK,
  );
  $items['admin/content/simplemeta/%simplemeta_meta/delete'] = array(
    'title' => 'Delete SimpleMeta',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'simplemeta_meta_delete_confirm',
      3,
    ),
    'access arguments' => array(
      'administer simplemeta',
    ),
    'type' => MENU_CALLBACK,
    'file' => 'simplemeta.admin.inc',
  );
  $items['admin/content/simplemeta/settings'] = array(
    'title' => 'Settings',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'simplemeta_settings_form',
    ),
    'access arguments' => array(
      'administer simplemeta',
    ),
    'file' => 'simplemeta.admin.inc',
  );
  return $items;
}

/**
 * Implements hook_page_alter().
 */
function simplemeta_page_alter(&$page) {
  global $language;
  if (variable_get('simplemeta_form_enable', TRUE) && user_access('administer simplemeta') && !path_is_admin(current_path())) {
    $path = $_GET['q'];
    $lang = $language->language;
    $meta = simplemeta_meta_load_by_path($path, $lang);

    // Try to load language-neutral
    if (!$meta) {
      $meta = simplemeta_meta_load_by_path($path);
    }
    if (!$meta) {
      $meta = new stdClass();
      $meta->path = $path;
      $meta->data = array();
      $meta->language = '';
    }
    $form = drupal_get_form('simplemeta_page_meta_form', $meta);
    $page['page_bottom']['simplemeta'] = array(
      '#markup' => drupal_render($form),
    );
  }
}

/**
 * Implements hook_flush_caches().
 */
function simplemeta_flush_caches() {
  return array(
    'cache_simplemeta',
  );
}

/**
 * Implements hook_theme().
 */
function simplemeta_theme() {
  return array(
    'simplemeta_meta_list' => array(
      'render element' => 'items',
      'file' => 'simplemeta.theme.inc',
    ),
    'simplemeta_meta_title' => array(
      'render element' => 'meta',
      'file' => 'simplemeta.theme.inc',
    ),
    'simplemeta_meta_description' => array(
      'render element' => 'meta',
      'file' => 'simplemeta.theme.inc',
    ),
    'simplemeta_meta_keywords' => array(
      'render element' => 'meta',
      'file' => 'simplemeta.theme.inc',
    ),
  );
}

/**
 * Implements $module_preprocess_$hook().
 */
function simplemeta_preprocess_html(&$vars) {
  global $language;
  if ($meta = simplemeta_get_page_meta($_GET['q'], $language->language)) {

    // Set page title.
    if (!empty($meta->data['title'])) {
      if (module_exists('token')) {
        $meta->data['title'] = token_replace($meta->data['title']);
      }
      $vars['head_title'] = check_plain($meta->data['title']);
    }
    foreach ($meta->view as $key => $item) {
      if (module_exists('token')) {
        $item = token_replace($item);
      }
      $head_item = array(
        '#type' => 'markup',
        '#markup' => $item,
      );
      drupal_add_html_head($head_item, 'simplemeta_' . $key);
    }
  }
}

/**
 * Implements hook_simplemeta_info().
 */
function simplemeta_simplemeta_info() {
  $info = array();
  $info['title'] = array(
    'title' => t('Title'),
    'form' => 'simplemeta_form_title',
    'theme' => 'simplemeta_meta_title',
  );
  $info['description'] = array(
    'title' => t('Description'),
    'form' => 'simplemeta_form_description',
    'theme' => 'simplemeta_meta_description',
  );
  $info['keywords'] = array(
    'title' => t('Keywords'),
    'form' => 'simplemeta_form_keywords',
    'theme' => 'simplemeta_meta_keywords',
  );
  return $info;
}

/**
 * Meta title form element callback.
 * 
 * @param object $meta
 *   meta object
 * 
 * @return array
 *   form element
 */
function simplemeta_form_title($meta) {
  $form = array(
    '#type' => 'textfield',
    '#title' => t('Title'),
    '#maxlength' => 255,
    '#default_value' => isset($meta->data['title']) ? $meta->data['title'] : '',
  );
  return $form;
}

/**
 * Meta description form element callback.
 * 
 * @param object $meta
 *   meta data
 * 
 * @return array
 *   form element
 */
function simplemeta_form_description($meta) {
  $form = array(
    '#type' => 'textarea',
    '#title' => t('Description'),
    '#default_value' => isset($meta->data['description']) ? $meta->data['description'] : '',
  );
  return $form;
}

/**
 * Meta keywords form element callback.
 * 
 * @param object $meta
 *   meta object
 * 
 * @return array
 *   form element
 */
function simplemeta_form_keywords($meta) {
  $form = array(
    '#type' => 'textfield',
    '#title' => t('Keywords'),
    '#maxlength' => 255,
    '#default_value' => isset($meta->data['keywords']) ? $meta->data['keywords'] : '',
  );
  return $form;
}

/**
 * Save meta data.
 * 
 * @param object $meta
 *   meta data
 */
function simplemeta_meta_save($meta) {
  $is_new = !isset($meta->sid) && !simplemeta_meta_load_by_path($meta->path, $meta->language);
  $meta->fit = _simplemeta_meta_calculate_fit($meta->path);
  $record = clone $meta;
  $record->data = serialize($record->data);
  if ($is_new) {
    $result = drupal_write_record('simplemeta', $record);
    if (!empty($record->sid)) {
      $meta->sid = $record->sid;
    }
    module_invoke_all('simplemeta', $meta, 'insert');
  }
  else {
    $result = drupal_write_record('simplemeta', $record, array(
      'sid',
    ));
    module_invoke_all('simplemeta', $meta, 'update');
  }
  return $result;
}

/**
 * Load meta data.
 * 
 * @param int $sid 
 *   SimpleMeta data id
 * 
 * @return object|FALSE
 *   object representing metadata or FALSE on failure
 */
function simplemeta_meta_load($sid) {
  $result = db_select('simplemeta', 's')
    ->fields('s')
    ->condition('s.sid', $sid, '=')
    ->execute();
  if ($meta = $result
    ->fetchObject()) {
    $meta->data = unserialize($meta->data);
    return $meta;
  }
  return FALSE;
}

/**
 * Load meta data by path.
 * 
 * @param string $path
 *   page's path to fetch meta data
 *  
 * @param string $language
 *   language code
 * 
 * @return object|FALSE
 *   object representing metadata or FALSE on failure
 */
function simplemeta_meta_load_by_path($path, $language = '') {
  $result = db_select('simplemeta', 's')
    ->fields('s')
    ->condition('s.path', $path, '=')
    ->condition('s.language', $language, '=')
    ->execute();
  if ($meta = $result
    ->fetchObject()) {
    $meta->data = unserialize($meta->data);
    return $meta;
  }
  return FALSE;
}

/**
 * Get page's meta data.
 * 
 * @staticvar array $meta
 * 
 * @param string $path
 *   page's path to fetch meta data
 * 
 * @param string $language
 *   language code
 * 
 * @param bool $reset
 *   whether to reset static cache
 * 
 * @return object|FALSE
 *   object representing metadata or FALSE on failure
 */
function simplemeta_get_page_meta($path = NULL, $language = '', $reset = FALSE) {
  static $meta = array();
  if (!isset($path)) {
    $path = $_GET['q'];
  }
  if (!isset($meta[$path]) || $reset) {
    $meta[$path] = FALSE;
    $cid = $path . ':' . $language;
    if ($cache = cache_get($cid, 'cache_simplemeta')) {
      $meta[$path] = $cache->data;
    }
    else {
      $row = NULL;
      $original_map = arg(NULL, $path);
      $parts = array_slice($original_map, 0, MENU_MAX_PARTS);
      $ancestors = simplemeta_path_get_ancestors($parts);
      array_unshift($ancestors, $path);
      $query = db_select('simplemeta', 's')
        ->fields('s')
        ->condition('s.path', $ancestors, 'IN')
        ->condition('s.language', $language, '=')
        ->orderBy('s.fit', 'DESC')
        ->range(0, 1);
      $row = $query
        ->execute()
        ->fetchObject();

      // If there is no language-specific meta, try to load language-neutral.
      if (!$row && $language) {
        $query = db_select('simplemeta', 's')
          ->fields('s')
          ->condition('s.path', $ancestors, 'IN')
          ->condition('s.language', '', '=')
          ->orderBy('s.fit', 'DESC')
          ->range(0, 1);
        $row = $query
          ->execute()
          ->fetchObject();
      }
      if ($row) {
        $row->data = unserialize($row->data);
        $row->view = array();
        $info = simplemeta_get_info();
        foreach ($info as $key => $definition) {
          $themed = theme($definition['theme'], array(
            'meta' => $row,
          ));
          if ($themed) {
            $row->view[$key] = $themed;
          }
        }
        cache_set($cid, $row, 'cache_simplemeta');
        $meta[$path] = $row;
      }
    }
  }
  return $meta[$path];
}

/**
 * Delete meta data.
 * 
 * @param int $sid
 *   SimpleMeta id
 */
function simplemeta_meta_delete($sid) {
  $meta = simplemeta_meta_load($sid);
  if ($meta) {
    db_delete('simplemeta')
      ->condition('sid', $sid, '=')
      ->execute();
    module_invoke_all('simplemeta', $meta, 'delete');
  }
}

/**
 * SimpleMeta form builder.
 */
function simplemeta_meta_form($form, &$form_state, $meta) {

  // Let's use _ as prefix to not conflict with other elements.
  $form['_meta'] = array(
    '#type' => 'value',
    '#value' => $meta,
  );
  if (!isset($meta->path)) {
    $form['_path'] = array(
      '#type' => 'textfield',
      '#title' => t('Path'),
      '#description' => t('% may be used as placeholder for system pathes, for example, news/archive/%'),
      '#required' => TRUE,
    );
  }
  else {
    $form['_path'] = array(
      '#type' => 'value',
      '#value' => $meta->path,
    );
  }
  if (variable_get('simplemeta_language_enable', FALSE)) {
    $form['_language'] = array(
      '#type' => 'select',
      '#title' => t('Language'),
      '#options' => _simplemeta_langauge_list(),
      '#default_value' => $meta->language,
    );

    // Do not allow change language for existing meta.
    if (isset($meta->sid)) {
      $form['_language']['#disabled'] = TRUE;
      $form['_language']['#value'] = $meta->language;
    }
  }
  else {
    $form['_language'] = array(
      '#type' => 'value',
      '#value' => $meta->language,
    );
  }
  $form += simplemeta_get_form_elements($meta);
  $form['_buttons'] = array(
    '#type' => 'actions',
  );
  $form['_buttons']['save'] = array(
    '#type' => 'submit',
    '#value' => t('Save'),
    '#validate' => array(
      'simplemeta_meta_form_validate_save',
    ),
    '#submit' => array(
      'simplemeta_meta_form_submit_save',
    ),
    '#attributes' => array(
      'class' => array(
        'button-save',
      ),
    ),
  );
  if (!empty($meta->sid)) {
    $form['_buttons']['delete'] = array(
      '#type' => 'link',
      '#title' => t('Delete'),
      '#href' => 'admin/content/simplemeta/' . $meta->sid . '/delete',
    );
  }
  return $form;
}

/**
 * SimpleMeta form validation callback on save.
 */
function simplemeta_meta_form_validate_save($form, &$form_state) {
  $values = $form_state['values'];
  $meta = $values['_meta'];
  $path = $values['_path'];
  $lang = $values['_language'];
  $normal_path = drupal_get_normal_path($path);
  if ($path != $normal_path) {
    $path = $normal_path;
  }
  if (!url_is_external($path)) {
    $parsed_link = parse_url($path);
    if ($path != $parsed_link['path']) {
      $path = $parsed_link['path'];
    }

    // @todo do we need to check the access?
    // @see menu_edit_item_validate()
    if (!trim($path)) {
      form_set_error('_path', t('Path is invalid'));
    }
    $form_state['values']['_path'] = $path;
  }
  else {
    form_set_error('_path', t('Path can be only internal'));
  }
  if (isset($meta->sid) && !simplemeta_meta_load($meta->sid)) {
    form_set_error('_meta', t("Meta #%sid doesn't exist anymore", array(
      '%id' => $meta->sid,
    )));
  }
  elseif (!isset($meta->sid) && simplemeta_meta_load_by_path($path, $lang)) {
    form_set_error('_meta', t("Meta for this page in this language already exists"));
  }
  $info = simplemeta_get_info();
  foreach ($info as $key => $definition) {
    if (isset($definition['validate']) && function_exists($definition['validate'])) {
      $function = $definition['validate'];
      $function($form, $form_state);
    }
  }
}

/**
 * SimpleMeta form submit callback on save.
 */
function simplemeta_meta_form_submit_save($form, &$form_state) {
  $values = $form_state['values'];
  $meta = $values['_meta'];
  $meta->path = $values['_path'];
  $meta->language = $values['_language'];
  $meta->data = array_intersect_key($values, simplemeta_get_form_elements());
  $info = simplemeta_get_info();
  foreach ($info as $key => $definition) {
    if (isset($definition['submit']) && function_exists($definition['submit'])) {
      $function = $definition['submit'];

      // @todo should we pass the $form? Think about
      $function($meta, $form_state);
    }
  }
  simplemeta_meta_save($meta);
  cache_clear_all('*', 'cache_simplemeta', TRUE);
  drupal_set_message(t('Meta has been saved'));
  $form_state['redirect'] = 'admin/content/simplemeta/list';
}

/**
 * On-page SimpleMeta form builder.
 */
function simplemeta_page_meta_form($form, &$form_state, $meta) {
  $form_id = 'simplemeta_meta_form';
  $_form_state = $form_state;
  $_form_state['build_info'] = array(
    'args' => array(
      $meta,
    ),
  );

  // Use simplemeta_meta_form as a base so alter hooks for simplemeta_meta_form will work here too.
  // @todo do not use simplemeta_meta_form as a base, but hook_form_alter() (and its more specific version) will need to alter both forms (in case changes need to appear on both).
  $form = drupal_retrieve_form($form_id, $_form_state);
  drupal_prepare_form($form_id, $form, $_form_state);
  if (variable_get('simplemeta_language_enable', FALSE)) {
    $form['_language']['#disabled'] = FALSE;
    unset($form['_language']['#value']);
    $form['_language']['#ajax'] = array(
      'wrapper' => 'simplemeta-meta-form-ajax-wrapper',
      'callback' => 'simplemeta_page_meta_form_ajax_language',
    );
  }
  $form['_buttons']['save']['#submit'][] = 'simplemeta_page_meta_form_submit_save';
  if (!empty($meta->sid)) {
    $form['_buttons']['delete'] = array(
      '#type' => 'submit',
      '#value' => t('Delete'),
      '#attributes' => array(
        'class' => array(
          'button-delete',
        ),
      ),
      '#submit' => array(
        'simplemeta_page_meta_form_submit_delete',
      ),
    );
  }
  $form['#prefix'] = '<div id="simplemeta-meta-form-ajax-wrapper">';
  $form['#suffix'] = '</div>';
  $form['#attributes']['class'] = array(
    'simplemeta-meta-form-ajax',
  );

  // Use the same id attribute for both admin and on-page forms.
  $form['#id'] = 'simplemeta-meta-form';
  return $form;
}

/**
 * On-page SimpleMeta form submit callback on save.
 */
function simplemeta_page_meta_form_submit_save($form, &$form_state) {
  unset($form_state['redirect']);
}

/**
 * On-page SimpleMeta form submit callback on delete.
 */
function simplemeta_page_meta_form_submit_delete($form, &$form_state) {
  simplemeta_meta_delete($form_state['values']['_meta']->sid);
  cache_clear_all('*', 'cache_simplemeta', TRUE);
  drupal_set_message(t('Meta has been deleted'));
}

/**
 * Get info about meta elements from modules.
 * 
 * Basically, invokes all implementations of hook_simplemeta_info().
 * Caches info in the {cache} table.
 * 
 * @param bool $reset
 *   indicates whether use cache or get info from implementations directly
 * 
 * @return array 
 *   info
 */
function simplemeta_get_info($reset = FALSE) {
  $cid = 'simplemeta:info';
  if (!$reset && ($cache = cache_get($cid, 'cache'))) {
    return $cache->data;
  }
  $info = array();
  foreach (module_implements('simplemeta_info') as $module) {
    $function = $module . '_simplemeta_info';
    $result = $function();
    $info = array_merge($info, $result);
  }
  cache_set($cid, $info, 'cache');
  return $info;
}

/**
 * Get all implemented form elements for SimpleMeta form.
 */
function simplemeta_get_form_elements($meta = NULL, $reset = FALSE) {
  $info = simplemeta_get_info($reset);
  $form = array();
  foreach ($info as $key => $definition) {
    $function = $definition['form'];
    if (function_exists($function)) {
      $form[$key] = $function($meta);
    }
  }
  return $form;
}

/**
 * AJAX callback for language element of the simplemeta_page_meta_form.
 */
function simplemeta_page_meta_form_ajax_language($form, $form_state) {
  $_meta = $form_state['values']['_meta'];
  $path = $_meta->path;
  $lang = $form_state['values']['_language'];
  $meta = simplemeta_meta_load_by_path($path, $lang);

  // Try to load language-neutral
  if (!$meta) {
    $meta = new stdClass();
    $meta->path = $path;
    $meta->data = array();
    $meta->language = $lang;
  }
  $form_state = array(
    'build_info' => array(
      'args' => array(
        $meta,
      ),
    ),
    'rebuild_info' => array(
      'copy' => array(
        '#build_id' => TRUE,
        '#action' => TRUE,
      ),
    ),
    'values' => array(),
  ) + form_state_defaults();
  return drupal_rebuild_form('simplemeta_page_meta_form', $form_state, $form);
}

/**
 * Get path ancestors of the path (represented as parts) to find page's SimpleMeta configuration.
 * @see menu_get_ancestors()
 */
function simplemeta_path_get_ancestors($parts) {
  $number_parts = count($parts);
  $ancestors = array();
  $length = $number_parts - 1;
  $end = (1 << $number_parts) - 1;

  // menu_masks actually takes defined menu paths (via hook_menu() implementation) into account.
  // @todo function probably should return list of all possible ancestors (but it's a bigger list actually).
  $masks = variable_get('menu_masks');

  // If the optimized menu_masks array is not available use brute force to get
  // the correct $ancestors and $placeholders returned. Do not use this as the
  // default value of the menu_masks variable to avoid building such a big
  // array.
  if (!$masks) {
    $masks = range(511, 1);
  }

  // Only examine patterns that actually exist as router items (the masks).
  foreach ($masks as $i) {
    if ($i > $end) {

      // Only look at masks that are not longer than the path of interest.
      continue;
    }
    elseif ($i < 1 << $length) {

      // We have exhausted the masks of a given length, so decrease the length.
      --$length;
    }

    // Path patterns which have less parts than original path must end with %
    // this also includes shorter paths without %
    if ($length < $number_parts - 1 && $i & 1) {
      continue;
    }
    $current = '';
    for ($j = $length; $j >= 0; $j--) {

      // Check the bit on the $j offset.
      if ($i & 1 << $j) {

        // Bit one means the original value.
        $current .= $parts[$length - $j];
      }
      else {

        // Bit zero means means wildcard.
        $current .= '%';
      }

      // Unless we are at offset 0, add a slash.
      if ($j) {
        $current .= '/';
      }
    }
    $ancestors[] = $current;
  }
  return $ancestors;
}

/**
 * Calculate fit of the path i.e. how specific path is.
 * 
 * @param string $path
 *   path to calculate fit
 * 
 * @return int
 *   fit
 * 
 * @see _menu_router_build()
 */
function _simplemeta_meta_calculate_fit($path) {
  $fit = 0;
  $parts = explode('/', $path, MENU_MAX_PARTS);
  $number_parts = count($parts);
  $slashes = $number_parts - 1;
  foreach ($parts as $k => $part) {
    if ($part != '%') {
      $fit |= 1 << $slashes - $k;
    }
  }
  return $fit;
}

/**
 * Get options for languages.
 */
function _simplemeta_langauge_list() {
  $languages = language_list();
  $options = array();
  foreach ($languages as $language) {
    $options[$language->language] = $language->name;
  }
  $options = array(
    '' => t('- Language neutral -'),
  ) + $options;
  return $options;
}

Functions

Namesort descending Description
simplemeta_flush_caches Implements hook_flush_caches().
simplemeta_form_description Meta description form element callback.
simplemeta_form_keywords Meta keywords form element callback.
simplemeta_form_title Meta title form element callback.
simplemeta_get_form_elements Get all implemented form elements for SimpleMeta form.
simplemeta_get_info Get info about meta elements from modules.
simplemeta_get_page_meta Get page's meta data.
simplemeta_init Implements hook_init().
simplemeta_menu Implements hook_menu().
simplemeta_meta_delete Delete meta data.
simplemeta_meta_form SimpleMeta form builder.
simplemeta_meta_form_submit_save SimpleMeta form submit callback on save.
simplemeta_meta_form_validate_save SimpleMeta form validation callback on save.
simplemeta_meta_load Load meta data.
simplemeta_meta_load_by_path Load meta data by path.
simplemeta_meta_save Save meta data.
simplemeta_page_alter Implements hook_page_alter().
simplemeta_page_meta_form On-page SimpleMeta form builder.
simplemeta_page_meta_form_ajax_language AJAX callback for language element of the simplemeta_page_meta_form.
simplemeta_page_meta_form_submit_delete On-page SimpleMeta form submit callback on delete.
simplemeta_page_meta_form_submit_save On-page SimpleMeta form submit callback on save.
simplemeta_path_get_ancestors Get path ancestors of the path (represented as parts) to find page's SimpleMeta configuration.
simplemeta_permission Implements hook_permission().
simplemeta_preprocess_html Implements $module_preprocess_$hook().
simplemeta_simplemeta_info Implements hook_simplemeta_info().
simplemeta_theme Implements hook_theme().
_simplemeta_langauge_list Get options for languages.
_simplemeta_meta_calculate_fit Calculate fit of the path i.e. how specific path is.