'Skinr', 'page callback' => 'skinr_admin', 'access arguments' => array('administer skinr'), 'file' => 'skinr.admin.inc', ); $items['admin/settings/skinr/settings'] = array( 'title' => 'Settings', 'type' => MENU_DEFAULT_LOCAL_TASK, 'weight' => -10, ); $items['admin/settings/skinr/import'] = array( 'title' => 'Import', 'page callback' => 'drupal_get_form', 'page arguments' => array('skinr_import_form'), 'type' => MENU_LOCAL_TASK, 'access arguments' => array('administer skinr'), 'parent' => 'admin/settings/skinr', 'weight' => 1, 'file' => 'skinr.admin.inc', ); $items['admin/settings/skinr/export'] = array( 'title' => 'Export', 'page callback' => 'drupal_get_form', 'page arguments' => array('skinr_export_form'), 'type' => MENU_LOCAL_TASK, 'access arguments' => array('administer skinr'), 'parent' => 'admin/settings/skinr', 'weight' => 2, 'file' => 'skinr.admin.inc', ); return $items; } /** * Implementation of hook_help(). */ function skinr_help($path, $arg) { switch ($path) { case 'admin/help#skinr': return t('Please refer to @skinr introduction and documentation.', array('@skinr' => 'Skinr')); break; case 'admin/settings/skinr': return t('Please refer to @skinr introduction and documentation.', array('@skinr' => 'Skinr')); break; } } /** * Implementation of hook_init(). */ function skinr_init() { static $run = FALSE; if (!$run) { skinr_include('handlers'); skinr_module_include('skinr.inc'); $run = TRUE; } } /** * Implementation of hook_form_alter(). */ function skinr_form_alter(&$form, $form_state, $form_id) { $skinr_data = skinr_fetch_data(); $info = skinr_process_info_files(); foreach ($skinr_data as $module => $settings) { if (isset($settings['form'][$form_id])) { $form_settings = array_merge(_skinr_fetch_data_defaults('form'), $settings['form'][$form_id]); // Check for access. if (!skinr_handler('access_handler', 'access skinr', $form_settings['access_handler'], $form, $form_state)) { // Deny access. break; } // Ensure we have the required preprocess_hook or preprocess_hook_callback. if (empty($form_settings['preprocess_hook']) && empty($form_settings['preprocess_hook_callback'])) { trigger_error(sprintf("No preprocess_hook or preprocess_hook_callback was found for form_id '%s' in module '%s'.", $form_id, $module), E_USER_ERROR); } $themes = list_themes(); ksort($themes); // There's a bug that sometimes disables all themes. This will check for // the bug in question. http://drupal.org/node/305653 $enabled_themes = 0; foreach ($themes as $theme) { if (!$theme->status) { continue; } $enabled_themes++; $preprocess_hook = !empty($form_settings['preprocess_hook']) ? $form_settings['preprocess_hook'] : skinr_handler('preprocess_hook_callback', '', $form_settings['preprocess_hook_callback'], $form, $form_state); if (!$form_state['submitted']) { $skinr_data = skinr_handler('data_handler', 'form', $form_settings['data_handler'], $form, $form_state, $theme->name, $module, $form_settings); $defaults = $skinr_data; $additional_default = isset($skinr_data['_additional']) ? $skinr_data['_additional'] : ''; $template_default = isset($skinr_data['_template']) ? $skinr_data['_template'] : ''; } else { // Handle preview before submit. $defaults = $form_state['values']['widgets']; $additional_default = $form_state['values']['_additional']; $template_default = $form_state['values']['_template']; } if (!isset($form['skinr_settings'])) { $form['skinr_settings'] = array( '#tree' => TRUE, '#weight' => $form_settings['skinr_weight'], ); } if (!isset($form['skinr_settings'][$module .'_group'])) { $form['skinr_settings'][$module .'_group'] = array( '#type' => 'fieldset', '#title' => t('@skinr_title @title', array('@skinr_title' => $form_settings['skinr_title'], '@title' => $form_settings['title'])), '#description' => t($form_settings['description']) .' '. $preprocess_hook .'.', '#weight' => $form_settings['weight'], '#collapsible' => TRUE, '#collapsed' => $form_settings['collapsed'], ); } $form['skinr_settings'][$module .'_group'][$theme->name] = array( '#type' => 'fieldset', '#title' => $theme->info['name'] . ($theme->name == skinr_current_theme() ? ' ('. t('enabled + default') .')' : ''), '#collapsible' => TRUE, '#collapsed' => $theme->name == skinr_current_theme() ? FALSE : TRUE, ); if ($theme->name == skinr_current_theme()) { $form['skinr_settings'][$module .'_group'][$theme->name]['#weight'] = -10; } // Create individual widgets for each skin. $template_options = array(); if (isset($info[$theme->name]['skins'])) { foreach ($info[$theme->name]['skins'] as $skin_id => $skin) { // Check if this skin applies to this hook. if (!is_array($skin['features']) || (!in_array('*', $skin['features']) && !in_array($preprocess_hook, $skin['features']))) { continue; } // Create widget. switch ($skin['type']) { case 'checkboxes': $form['skinr_settings'][$module .'_group'][$theme->name]['widgets'][$skin_id] = array( '#type' => 'checkboxes', '#multiple' => TRUE, '#title' => t($skin['title']), '#options' => skinr_info_options_to_form_options($skin['options']), '#default_value' => isset($defaults[$skin_id]) ? $defaults[$skin_id] : array(), '#description' => t($skin['description']), '#weight' => isset($skin['weight']) ? $skin['weight'] : NULL, ); break; case 'radios': $form['skinr_settings'][$module .'_group'][$theme->name]['widgets'][$skin_id] = array( '#type' => 'radios', '#title' => t($skin['title']), '#options' => array_merge(array('' => '<none>'), skinr_info_options_to_form_options($skin['options'])), '#default_value' => isset($defaults[$skin_id]) ? $defaults[$skin_id] : '', '#description' => t($skin['description']), '#weight' => isset($skin['weight']) ? $skin['weight'] : NULL, ); break; case 'select': $form['skinr_settings'][$module .'_group'][$theme->name]['widgets'][$skin_id] = array( '#type' => 'select', '#title' => t($skin['title']), '#options' => array_merge(array('' => ''), skinr_info_options_to_form_options($skin['options'])), '#default_value' => isset($defaults[$skin_id]) ? $defaults[$skin_id] : '', '#description' => t($skin['description']), '#weight' => isset($skin['weight']) ? $skin['weight'] : NULL, ); break; } // Prepare templates. $templates = skinr_info_templates_filter($skin['templates'], $preprocess_hook); $template_options = array_merge($template_options, skinr_info_templates_to_form_options($templates)); } } // Check for access. if (skinr_handler('access_handler', 'access skinr classes', $form_settings['access_handler'], $form, $form_state)) { $form['skinr_settings'][$module .'_group'][$theme->name]['advanced'] = array( '#type' => 'fieldset', '#title' => t('Advanced options'), '#collapsible' => TRUE, '#collapsed' => empty($additional_default), ); $form['skinr_settings'][$module .'_group'][$theme->name]['advanced']['_additional'] = array( '#type' => 'textfield', '#title' => t('Apply additional CSS classes'), '#description' => t('Optionally add additional CSS classes. Example: my-first-class my-second-class'), '#default_value' => $additional_default, ); $form['skinr_settings'][$module .'_group'][$theme->name]['advanced']['_template'] = array( '#type' => 'select', '#title' => t('Template file'), '#options' => array_merge(array('' => 'Default'), $template_options), '#default_value' => $template_default, '#description' => t('Optionally, select a template file to associate with this !hook. Selecting "Default" will let Drupal handle this.', array('!hook' => $preprocess_hook)), ); // Only add validation handler once. if (!in_array('skinr_form_validate', $form['#validate'])) { $form['#validate'][] = 'skinr_form_validate'; } // Special for views. if (isset($form['buttons']['submit']['#validate']) && !in_array('skinr_form_validate', $form['buttons']['submit']['#validate'])) { $form['buttons']['submit']['#validate'][] = 'skinr_form_validate'; } } } if ($enabled_themes == 0) { drupal_set_message(t('Skinr has detected that none of your themes are enabled. This is likely related the Drupal bug: Themes disabled during update. Please re-enable your theme to continue using Skinr.'), 'warning', FALSE); } // Only add submit handler once. eval('$element =& $form'. $form_settings['submit_handler_attach_to'] .';'); if (!empty($element) && !in_array('skinr_form_submit', $element)) { $string = $element[] = 'skinr_form_submit'; } // Keep looping, there might be other modules that implement the same form_id. } } } /** * Validation handler. */ function skinr_form_validate(&$form, &$form_state) { $form_id = $form_state['values']['form_id']; $skinr_data = skinr_fetch_data(); foreach ($skinr_data as $module => $settings) { if (isset($settings['form'][$form_id]) && isset($form_state['values']['skinr_settings'][$module .'_group'])) { foreach ($form_state['values']['skinr_settings'][$module .'_group'] as $theme_name => $theme) { if (isset($theme['advanced']['_additional'])) { $form_settings = array_merge(_skinr_fetch_data_defaults('form'), $settings['form'][$form_id]); // Validate additional classes field. if (preg_match('/[^a-zA-Z0-9\-\_\s]/', $theme['advanced']['_additional'])) { form_set_error('skinr_settings]['. $module .'_group]['. $theme_name .'][advanced][_additional', t('Additional classes for Skinr may only contain alphanumeric characters, spaces, - and _.')); } // Keep looping, there might be other modules that implement the same form_id. } } } } } /** * Submit handler. */ function skinr_form_submit(&$form, &$form_state) { $form_id = $form_state['values']['form_id']; $skinr_data = skinr_fetch_data(); foreach ($skinr_data as $module => $settings) { if (isset($settings['form'][$form_id])) { $form_settings = array_merge(_skinr_fetch_data_defaults('form'), $settings['form'][$form_id]); skinr_handler('submit_handler', '', $form_settings['submit_handler'], $form, $form_state, $module, $form_settings); // Keep looping, there might be other modules that implement the same form_id. } } } /** * Implementation of hook_preprocess(). */ function skinr_preprocess(&$vars, $hook) { // Let's make sure this has been run. There have been problems where other // modules implement a theme function in their hook_init(), so we make doubly // sure that the includes are included. skinr_init(); $skinr_data = skinr_fetch_data(); $info = skinr_process_info_files(); $current_theme = skinr_current_theme(); $theme_registry = theme_get_registry(); // Add any base themes. $themes = array_keys(system_find_base_themes(_system_theme_data(), $current_theme)); // Add the current theme. $themes[] = $current_theme; $original_hook = $hook; if (isset($theme_registry[$hook]['original hook'])) { $original_hook = $theme_registry[$hook]['original hook']; } $vars['skinr'] = ''; foreach ($skinr_data as $module => $settings) { if (!empty($settings['preprocess'][$original_hook])) { $preprocess_settings = $settings['preprocess'][$original_hook]; $key = skinr_handler('preprocess_index_handler', 'preprocess', $preprocess_settings['index_handler'], $vars); if (!empty($key)) { if (is_array($key)) { $style = array(); foreach ($key as $keykey) { $style = skinr_get(NULL, $module, $keykey) + $style; } } else { $style = skinr_get(NULL, $module, $key); } foreach ($style as $skin => $classes) { // Add custom CSS files. if (!empty($info[$current_theme]['skins'][$skin]['stylesheets'])) { foreach ($info[$current_theme]['skins'][$skin]['stylesheets'] as $media => $stylesheets) { foreach ($stylesheets as $stylesheet) { _skinr_add_file($stylesheet, $themes, 'css', $media); } } } // Add custom JS files. if (isset($info[$current_theme]['skins'][$skin]['scripts'])) { foreach ($info[$current_theme]['skins'][$skin]['scripts'] as $script) { _skinr_add_file($script, $themes, 'js'); } } } // Add template files. if (!empty($style['_template'])) { $vars['template_files'][] = $style['_template']; unset($style['_template']); } $vars['skinr'] = skinr_style_array_to_string($style); } } } } /** * Helper function to add CSS and JS files. * * The function checks an array of paths for the existence of the file to account for base themes. */ function _skinr_add_file($filename, $themes, $type, $media = NULL) { if (!is_array($themes)) { $themes = array($themes); } foreach ($themes as $theme) { $file = drupal_get_path('theme', $theme) .'/'. $filename; if (file_exists($file)) { if ($type == 'css') { drupal_add_css($file, 'theme', $media); } else { drupal_add_js($file, 'theme'); } break; } } } /** * Helper function to convert an array of classes settings into a string, usable * in HTML. */ function skinr_style_array_to_string($style) { $return = array(); foreach ($style as $entry) { if (is_array($entry)) { foreach ($entry as $subentry) { if (!empty($subentry)) { $return[] = check_plain($subentry); } } } elseif (!empty($entry)) { $return[] = check_plain($entry); } } return implode(' ', $return); } // ------------------------------------------------------------------ // Include file helpers. /** * Include skinr .inc files as necessary. */ function skinr_include($file) { require_once './'. drupal_get_path('module', 'skinr') ."/includes/$file.inc"; } /** * Load views files on behalf of modules. */ function skinr_module_include($file) { foreach (skinr_get_module_apis() as $module => $info) { if (file_exists("./$info[path]/$module.$file")) { require_once "./$info[path]/$module.$file"; } } } /** * Get a list of modules that support skinr. */ function skinr_get_module_apis() { static $cache = NULL; if (is_null($cache)) { $cache = array(); foreach (module_implements('skinr_api') as $module) { $function = $module .'_skinr_api'; $info = $function(); if (isset($info['api']) && $info['api'] == 1.000) { if (!isset($info['path'])) { $info['path'] = drupal_get_path('module', $module); } $cache[$module] = $info; } } } return $cache; } // ----------------------------------------------------------------------- // Skinr data handling functions. /** * Save an entry to Skinr. */ function skinr_set($theme, $module, $key, $value) { $skinr = variable_get('skinr_'. $theme, array()); // Make sure we're getting valid data. if (empty($module) || empty($key)) { return; } if (!$value && isset($skinr[$module][$key])) { unset($skinr[$module][$key]); } else { if (!isset($skinr[$module])) { $skinr[$module] = array(); } $skinr[$module][$key] = $value; } variable_set('skinr_'. $theme, $skinr); } /** * Retrieves the desired classes. * If no hook or key are specified, it will return all skinr classes. * * @return * An array. */ function skinr_get($theme = NULL, $module = NULL, $key = NULL) { if (is_null($theme)) { $theme = skinr_current_theme(); } $skinr = variable_get('skinr_'. $theme, array()); if (is_null($module) || is_null($key)) { return $skinr; } elseif (isset($skinr[$module][$key])) { return $skinr[$module][$key]; } else { return array(); } } /** * Helper function to retrieve the current theme. * The global variable $theme_key doesn't work for our purposes when an admin * theme is enabled. * * @param $exclude_admin_theme * Optional. Set to TRUE to exclude the admin theme from possible themes to * return. */ function skinr_current_theme($exclude_admin_theme = FALSE) { global $user, $custom_theme; if (!empty($user->theme)) { $current_theme = $user->theme; } elseif (!empty($custom_theme) && !($exclude_admin_theme && $custom_theme == variable_get('admin_theme', '0'))) { // Don't return the admin theme if we're editing skinr settings. $current_theme = $custom_theme; } else { $current_theme = variable_get('theme_default', 'garland'); } return $current_theme; } /** * Retrieves all the Skinr skins from theme parents. Theme skins * will override any skins of the same name from its parents. */ function skinr_inherited_skins($theme, $themes) { $all_skins = $skins = array(); $base_theme = (!empty($themes[$theme]->info['base theme'])) ? $themes[$theme]->info['base theme'] : ''; while ($base_theme) { $all_skins[] = (!empty($themes[$base_theme]->info['skinr'])) ? (array)$themes[$base_theme]->info['skinr'] : array(); $base_theme = (!empty($themes[$base_theme]->info['base theme'])) ? $themes[$base_theme]->info['base theme'] : ''; } array_reverse($all_skins); foreach ($all_skins as $new_skin) { $skins = array_merge($skins, $new_skin); } return $skins; } /** * Themes are allowed to set Skinr styles in their .info files. * * @todo Do we want to allow manual addition of styles? * @todo Use DB caching. No need to keep processing things every page load. */ function skinr_process_info_files() { static $cache = NULL; if (is_null($cache)) { $themes = _system_theme_data(); foreach ($themes as $theme) { $cache[$theme->name] = array( 'options' => array(), 'skins' => array(), ); if (!empty($theme->info['skinr'])) { $info = (array)$theme->info['skinr']; // Store skinr options. if (!empty($info['options'])) { $cache[$theme->name]['options'] = $info['options']; unset($info['options']); } // Inherit skins from parent theme, if inherit_skins is set to true. if (!empty($cache[$theme->name]['options']['inherit_skins'])) { $info = array_merge(skinr_inherited_skins($theme->name, $themes), $info); } foreach ($info as $id => $skin) { $processed_skin = array( 'title' => isset($skin['title']) ? $skin['title'] : '', 'type' => isset($skin['type']) ? $skin['type'] : 'checkboxes', 'description' => isset($skin['description']) ? $skin['description'] : '', 'weight' => isset($skin['weight']) ? $skin['weight'] : NULL, 'features' => isset($skin['features']) ? $skin['features'] : array('*'), 'templates' => isset($skin['templates']) ? $skin['templates'] : array(), 'options' => isset($skin['options']) ? $skin['options'] : array(), 'stylesheets' => isset($skin['stylesheets']) ? $skin['stylesheets'] : array(), 'scripts' => isset($skin['scripts']) ? $skin['scripts'] : array(), ); $cache[$theme->name]['skins'][$id] = $processed_skin; } } } } return $cache; } /** * Helper function to convert an array of options, as specified in the info * file, into an array usable by form api. */ function skinr_info_options_to_form_options($options) { $form_options = array(); foreach ($options as $option) { $form_options[$option['class']] = t($option['label']); } return $form_options; } /** * Helper function to convert an array of template filenames, as specified in * the info file, into an array usable by form api. */ function skinr_info_templates_to_form_options($templates) { $form_options = array(); foreach ($templates as $template) { // If it exists, strip .tpl.php from template. $template = str_replace('.tpl.php', '', $template); $form_options[$template] = $template .'.tpl.php'; } return $form_options; } /** * Helper function to filter templates by preprocess_hook. */ function skinr_info_templates_filter($templates, $preprocess_hook) { $filtered_templates = array(); foreach ($templates as $template) { // If it exists, strip .tpl.php from template $template = str_replace('.tpl.php', '', $template); if (drupal_substr(str_replace('_', '-', $template), (drupal_strlen($preprocess_hook) * -1)) == str_replace('_', '-', $preprocess_hook)) { $filtered_templates[] = $template; } } return $filtered_templates; } /** * Fetch Skinr configuration data from modules. */ function skinr_fetch_data() { static $cache = NULL; if (is_null($cache)) { $cache = module_invoke_all('skinr_data'); foreach (module_implements('skinr_data_alter') as $module) { $function = $module .'_skinr_data_alter'; $function($cache); } } return $cache; } /** * Fetch default configuration data for modules. */ function _skinr_fetch_data_defaults($setting) { switch ($setting) { case 'form': $data = array( 'access_handler' => 'skinr_access_handler', 'data_handler' => 'skinr_data_handler', 'submit_handler' => 'skinr_submit_handler', 'submit_handler_attach_to' => "['#submit']", 'skinr_title' => t('Skinr'), 'skinr_weight' => 1, 'title' => '', 'description' => t('Manage which skins you want to apply to this'), 'collapsed' => TRUE, 'weight' => 0, ); return $data; } } /** * Execute a module's data handler. */ function skinr_handler($type, $op, $handler, &$a3, $a4 = NULL, $a5 = NULL, $a6 = NULL, $a7 = NULL) { if (is_callable($handler)) { switch ($type) { case 'preprocess_index_handler': return $handler($a3); case 'preprocess_hook_callback': return $handler($a3, $a4); case 'data_handler': case 'submit_handler': return $handler($a3, $a4, $a5, $a6, $a7); default: return $handler($op, $a3, $a4); } } }