You are here

themekey_build.inc in ThemeKey 7.2

The functions in this file are the back end of ThemeKey which should be used only if you configure something, but not when ThemeKey switches themes.

@author Markus Kalkbrenner | bio.logis GmbH

@author profix898

File

themekey_build.inc
View source
<?php

/**
 * @file
 * The functions in this file are the back end of ThemeKey which should be
 * used only if you configure something, but not when ThemeKey switches themes.
 *
 * @author Markus Kalkbrenner | bio.logis GmbH
 *   @see http://drupal.org/user/124705
 *
 * @author profix898
 *   @see http://drupal.org/user/35192
 */

/**
 * Creates options array for a theme select box.
 *
 * Example:
 *   $form['theme'] = array(
 *     '#type' => 'select',
 *     '#title' => t('Theme'),
 *     '#options' => themekey_theme_options(),
 *   );
 *
 * @param $default
 *   Boolean to indicate if options array should contain
 *   'System default' theme. Default is TRUE.
 * @param $admin
 *   Boolean to indicate if options array should contain
 *   'Administration theme'. Default is FALSE.
 *
 * @return
 *   options array for a theme select box
 */
function themekey_theme_options($default = TRUE, $admin = FALSE) {
  $themes = list_themes();
  ksort($themes);
  $options_themes = array();
  if ($default) {
    $options_themes['default'] = '=> ' . t('System default');
  }
  foreach ($themes as $theme) {
    if ($theme->status || variable_get('themekey_allthemes', 0)) {
      $options_themes[$theme->name] = $theme->info['name'];
    }
  }
  if ($admin) {
    $options_themes['ThemeKeyAdminTheme'] = '=> ' . t('Administration theme');
  }
  return $options_themes;
}

/**
 * Rebuilds all ThemeKey-related Drupal variables
 * by calling ThemeKey's hooks:
 * - hook_themekey_properties()
 * - hook_themekey_paths()
 */
function themekey_rebuild() {

  // includes all modules in the themekey/modules subfolder (internal modules)
  themekey_scan_modules();
  module_load_include('inc', 'themekey', 'themekey_base');

  // Get property definitions (from internal and other modules)
  $properties = array_merge_recursive(themekey_invoke_modules('themekey_properties'), module_invoke_all('themekey_properties'));

  // Attributes
  $attributes = isset($properties['attributes']) ? $properties['attributes'] : array();
  ksort($attributes);
  $property_names = array();
  foreach ($attributes as $property_name => $attribute) {
    if (empty($attribute['static'])) {
      if (preg_match("/^[\\w-]+:[:\\w-]+\$/", $property_name)) {
        $property_names[$property_name] = $property_name;
      }
      else {
        drupal_set_message(t('%property is not a valid ThemeKey property name.', array(
          '%property' => $property_name,
        )), 'error');
      }
    }
    if (!isset($attribute['page cache'])) {
      $attributes[$property_name]['page cache'] = THEMEKEY_PAGECACHE_UNSUPPORTED;
    }
  }
  variable_set('themekey_attributes', $attributes);
  variable_set('themekey_properties', $property_names);

  // Property maps
  $maps = isset($properties['maps']) ? $properties['maps'] : array();
  variable_set('themekey_maps', $maps);

  // Get (and register) paths from themekey modules
  $paths = array_merge_recursive(themekey_invoke_modules('themekey_paths'), module_invoke_all('themekey_paths'));

  // assign fit factor and weight to this item
  array_walk($paths, 'themekey_path_set');
  $paths_sort = array();
  foreach ($paths as $item) {
    $paths_sort[$item['fit']][$item['weight']][] = $item;
  }
  ksort($paths_sort, SORT_NUMERIC);
  $paths = array();
  foreach (array_reverse($paths_sort) as $same_fit) {
    ksort($same_fit, SORT_NUMERIC);
    foreach (array_reverse($same_fit) as $same_weight) {
      foreach ($same_weight as $item) {
        $paths[] = $item;
      }
    }
  }
  variable_set('themekey_paths', $paths);
}

/**
 * Scans directory themekey/modules for suitable files
 * which provide ThemeKey properties mapping function and so on
 * and stores the file names, for later use, in a Drupal variable
 * called 'themekey_modules'.
 *
 * @see themekey_rebuild()
 * @see themekey_invoke_modules()
 *
 * @param $blacklist
 *   array of module names that should not be included
 */
function themekey_scan_modules($blacklist = array()) {
  $modules = array();
  $files = file_scan_directory(dirname(__FILE__) . '/modules', '/^themekey\\.[^.]+\\.inc$/');
  foreach ($files as $file) {
    list(, $module) = explode('.', $file->name);
    if (!in_array($module, $blacklist) && module_exists($module)) {
      $modules[] = $module;
    }
  }
  variable_set('themekey_modules', $modules);
}

/**
 * Named wildcards in ThemeKey rules based on property
 * drupal:path are stored as serialized array in the database.
 *
 * This function de-serializes those wildcards and injects them back
 * into the value of the rule. This format is needed by ThemeKey's
 * administration interface.
 *
 * It's the counterpart of these functions:
 * @see themekey_prepare_path()
 * @see themekey_prepare_custom_path()
 *
 * @see themekey_load_rules()
 *
 * @param $item
 *   reference to an inject
 *   containing a ThemeKey rule as returned
 *   directly from database
 */
function themekey_complete_path($item) {
  $item->wildcards = unserialize($item->wildcards);
  if (count($item->wildcards)) {
    $parts = explode('/', $item->value, MENU_MAX_PARTS);
    foreach ($item->wildcards as $index => $wildcard) {
      $parts[$index] .= $wildcard;
    }
    $item->value = implode('/', $parts);
  }
}

/**
 * Examines ThemeKey paths created by modules
 * via hook_themekey_paths() in database and
 * assigns a fit factor and a weight.
 *
 * @see themekey_rebuild()
 *
 * @param $item
 *   reference to an associative array
 *   containing a ThemeKey path structure
 */
function themekey_path_set(&$item) {
  $item['callbacks'] = isset($item['callbacks']) && !empty($item['callbacks']) ? $item['callbacks'] : array();
  list($item['fit'], $item['weight'], $item['wildcards']) = themekey_prepare_path($item['path']);
}

/**
 * Extracts named wildcards from ThemeKey paths returned
 * by modules via hook_themekey_paths() and associates a
 * weight and a fit factor to this path.
 *
 * @param $item
 *   reference to path as string
 *
 * @return
 *   array containing three elements:
 *   - fit as integer
 *   - weight as integer
 *   - named wildcards as array
 */
function themekey_prepare_path(&$path) {
  $fit = 0;
  $weight = 0;
  $wildcards = array();
  $parts = explode('/', $path, MENU_MAX_PARTS);
  $slashes = count($parts) - 1;
  foreach ($parts as $index => $part) {
    if (preg_match('/^(\\%|\\#)([a-z0-9_:]*)$/', $part, $matches)) {
      $parts[$index] = $matches[1];
      if (!empty($matches[2])) {
        $wildcards[$index] = $matches[2];
      }
      if ($matches[1] == '#') {
        $weight |= 1 << $slashes - $index;
      }
    }
    else {
      $fit |= 1 << $slashes - $index;
    }
  }
  $path = implode('/', $parts);
  return array(
    $fit,
    $weight,
    $wildcards,
  );
}

/**
 * Extracts named wildcards from paths entered as value
 * in a ThemeKey rule with property drupal:path.
 *
 * @param $path
 *   path as string
 *
 * @return
 *   array containing two elements:
 *   - path with unnamed wildcards
 *   - named wildcards as array
 */
function themekey_prepare_custom_path($path) {
  $wildcards = array();
  $parts = explode('/', $path, MENU_MAX_PARTS);
  foreach ($parts as $index => $part) {
    if (preg_match('/^(\\%|\\#)([a-z0-9_:]*)$/', $part, $matches)) {
      $parts[$index] = $matches[1];
      if (!empty($matches[2])) {
        $wildcards[$index] = $matches[2];
      }
    }
  }
  $path = implode('/', $parts);
  return array(
    $path,
    $wildcards,
  );
}

/**
 * Loads all ThemeKey Rules from the database.
 * Therefore, it uses recursion to build the rule chains.
 *
 * @param $parent
 *   id of the parent rule. Default is '0'.
 *   During the recursion this parameter changes.
 *
 * @param $depth
 *   Integer that represents the 'indentation'
 *   in current rule chain. Default is '0'.
 *   During the recursion this parameter changes.
 *
 * @return
 *   sorted array containing all ThemeKey rules
 */
function themekey_load_rules($parent = 0, $depth = 0) {
  $properties = array();
  $result = db_select('themekey_properties', 'tp')
    ->fields('tp')
    ->condition('parent', $parent)
    ->orderBy('weight', 'asc')
    ->execute();
  foreach ($result as $item) {
    if ('drupal:path' == $item->property) {
      themekey_complete_path($item);
    }
    $item->depth = $depth;
    $properties[$item->id] = get_object_vars($item);
    $properties = $properties + themekey_load_rules($item->id, $depth + 1);
  }
  return $properties;
}

/**
 * Stores ThemeKey rules in database.
 * It creates a new dataset or updates an existing one.
 *
 * @param $item
 *   reference to an associative array
 *   containing a ThemeKey rule structure:
 *   - id
 *   - property
 *   - operator
 *   - value
 *   - weight
 *   - theme
 *   - enabled
 *   - wildcards
 *   - parent
 *
 * @param $module
 *   name of the module that sets the item
 *
 * @throws ThemeKeyRuleConflictException
 */
function themekey_rule_set(&$item, $module = 'themekey') {
  if ('drupal:path' == $item['property']) {
    list($item['value'], $item['wildcards']) = themekey_prepare_custom_path($item['value']);
  }
  else {
    $item['wildcards'] = array();
  }
  if (empty($item['module'])) {
    $item['module'] = trim($module);
  }

  // TRANSACTIONS - SEE http://drupal.org/node/355875
  // The transaction opens here.
  $txn = db_transaction();
  if (empty($item['id'])) {
    if ($item['enabled']) {
      $id = db_select('themekey_properties', 'tp')
        ->fields('tp', array(
        'id',
      ))
        ->condition('property', $item['property'])
        ->condition('operator', $item['operator'])
        ->condition('value', $item['value'])
        ->condition('parent', $item['parent'])
        ->condition('enabled', 1)
        ->execute()
        ->fetchField();
      if ($id) {
        throw new ThemeKeyRuleConflictException(t('New rule conflicts with an existing rule on the same level.'), $id);
      }
    }

    // new entry should be added at the end of the chain
    $result = db_select('themekey_properties', 'tp');
    $result
      ->addExpression('MAX(weight)', 'weight');
    $weight = $result
      ->execute()
      ->fetchField();

    // if query fails $weight will be FALSE which will cause $item['weight'] to be set to '1'
    $item['weight'] = 1 + $weight;
    drupal_write_record('themekey_properties', $item, array());
  }
  else {
    if ($item['enabled']) {
      $id = db_select('themekey_properties', 'tp')
        ->fields('tp', array(
        'id',
      ))
        ->condition('property', $item['property'])
        ->condition('operator', $item['operator'])
        ->condition('value', $item['value'])
        ->condition('parent', $item['parent'])
        ->condition('enabled', 1)
        ->condition('id', $item['id'], '<>')
        ->execute()
        ->fetchField();
      if ($id) {
        throw new ThemeKeyRuleConflictException(t('Updated rule conflicts with an existing rule on the same level.'), $id);
      }
    }
    drupal_write_record('themekey_properties', $item, 'id');
  }

  // $txn goes out of scope here, and the entire transaction commits.
}

/**
 * Deletes a ThemeKey rule from database.
 *
 * @param $id
 *   id of the rule to be deleted from database
 *
 * @throws ThemeKeyRuleDeletionException
 */
function themekey_rule_del($id) {

  // TRANSACTIONS - SEE http://drupal.org/node/355875
  // The transaction opens here.
  $txn = db_transaction();
  $result = db_select('themekey_properties', 'tp');
  $result
    ->condition('parent', $id);
  $result
    ->addExpression('COUNT(*)', 'num_childs');
  $num_childs = $result
    ->execute()
    ->fetchField();
  if (FALSE !== $num_childs) {
    if ($num_childs > 0) {
      throw new ThemeKeyRuleDeletionException(t('ThemeKey rule could not be deleted because it has children in the chain'));
    }
    else {
      $result = db_delete('themekey_properties')
        ->condition('id', $id)
        ->execute();
      if (!$result) {
        throw new ThemeKeyRuleDeletionException(t('Error while deleting ThemeKey rule'));
      }
      else {

        // $txn goes out of scope here, and the entire transaction commits.
        return TRUE;
      }
    }
  }
  else {
    throw new ThemeKeyRuleDeletionException(t('Error while deleting ThemeKey rule'));
  }
}

/**
 * Disables a ThemeKey rule and all children.
 *
 * @param $id
 *   id of the rule to be ddisabled
 */
function themekey_rule_disable($id) {

  // TRANSACTIONS - SEE http://drupal.org/node/355875
  // The transaction opens here.
  $txn = db_transaction();
  $childs = themekey_load_rules($id);
  $ids = array_merge(array(
    $id,
  ), array_keys($childs));
  foreach ($ids as $id) {
    db_update('themekey_properties')
      ->fields(array(
      'enabled' => 0,
    ))
      ->condition('id', $id)
      ->execute();
  }
}

/**
 * Loads ThemeKey rule from database.
 *
 * @param $id
 *   id of the rule to be loaded from database
 *
 * @return
 *   the rule as associative array or NULL
 */
function themekey_rule_get($id) {
  if ($result = db_select('themekey_properties', 'tp')
    ->fields('tp')
    ->condition('id', $id)
    ->execute()) {
    foreach ($result as $item) {
      if ('drupal:path' == $item->property) {
        themekey_complete_path($item);
      }
      return $item;
    }
  }
  return NULL;
}

/**
 * Adds or modifies a so-called static rule in the
 * database. Static rules can be moved around in the chain
 * and enabled or disabled by the site administrator, but the values
 * are immutable. There's one static rule per static property.
 *
 * @param $property
 *   name of the static property as string
 *
 * @param $state
 *   boolean:
 *   - TRUE the rule should be created or updated
 *   - FALSE the rule should be deleted
 */
function themekey_update_static_rule($property, $state) {
  $existing_rule = db_select('themekey_properties', 'tp')
    ->fields('tp', array(
    'id',
    'enabled',
    'parent',
  ))
    ->condition('property', $property)
    ->execute()
    ->fetchAssoc();
  if ($state) {
    $item = array(
      'property' => $property,
      'operator' => '=',
      'value' => 'static',
      'theme' => 'default',
    );
    if (!empty($existing_rule)) {
      $item['id'] = $existing_rule['id'];
      $item['enabled'] = $existing_rule['enabled'];
      $item['parent'] = $existing_rule['parent'];
    }
    else {

      // enable new rule
      $item['enabled'] = 1;
      $item['parent'] = 0;
    }
    themekey_rule_set($item);
  }
  elseif (!empty($existing_rule)) {
    themekey_rule_del($existing_rule['id']);
  }
}
class ThemeKeyRuleConflictException extends Exception {

}
class ThemeKeyRuleDeletionException extends Exception {

}

Functions

Namesort descending Description
themekey_complete_path Named wildcards in ThemeKey rules based on property drupal:path are stored as serialized array in the database.
themekey_load_rules Loads all ThemeKey Rules from the database. Therefore, it uses recursion to build the rule chains.
themekey_path_set Examines ThemeKey paths created by modules via hook_themekey_paths() in database and assigns a fit factor and a weight.
themekey_prepare_custom_path Extracts named wildcards from paths entered as value in a ThemeKey rule with property drupal:path.
themekey_prepare_path Extracts named wildcards from ThemeKey paths returned by modules via hook_themekey_paths() and associates a weight and a fit factor to this path.
themekey_rebuild Rebuilds all ThemeKey-related Drupal variables by calling ThemeKey's hooks:
themekey_rule_del Deletes a ThemeKey rule from database.
themekey_rule_disable Disables a ThemeKey rule and all children.
themekey_rule_get Loads ThemeKey rule from database.
themekey_rule_set Stores ThemeKey rules in database. It creates a new dataset or updates an existing one.
themekey_scan_modules Scans directory themekey/modules for suitable files which provide ThemeKey properties mapping function and so on and stores the file names, for later use, in a Drupal variable called 'themekey_modules'.
themekey_theme_options Creates options array for a theme select box.
themekey_update_static_rule Adds or modifies a so-called static rule in the database. Static rules can be moved around in the chain and enabled or disabled by the site administrator, but the values are immutable. There's one static rule per static property.

Classes