array( 'title' => t('Administer Skinr'), 'description' => t('Administer Skinr\'s settings.'), ), 'access skinr' => array( 'title' => t('Access Skinr\'s settings'), 'description' => t('Set Skinr options for individual themes.'), ), 'access skinr classes' => array( 'title' => t('Access Skinr\'s advanced settings'), 'description' => t('Set advanced Skinr options, such as custom CSS classes and custom template files.'), ), ); } /** * Implementation of hook_menu(). */ function skinr_menu() { $items['admin/settings/skinr'] = array( 'title' => 'Skinr', 'page callback' => 'skinr_admin', 'type' => MENU_CALLBACK, 'access arguments' => array('administer skinr'), '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; } } /** * Implement hook_init(). */ function skinr_init() { module_load_include('inc', 'skinr', 'skinr.handlers'); skinr_module_include('skinr.inc'); } /** * 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); foreach ($themes as $theme) { if (!$theme->status) { continue; } $preprocess_hook = $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' => $form_settings['skinr_title'] . ' ' . $form_settings['title'], '#description' => $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(); foreach ($info[$theme->name]['skins'] as $skin_id => $skin) { // Check if this skin applies to this hook. if (!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' => $skin['title'], '#options' => skinr_info_options_to_form_options($skin['options']), '#default_value' => isset($defaults[$skin_id]) ? $defaults[$skin_id] : array(), '#description' => $skin['description'], ); break; case 'radios': $form['skinr_settings'][$module . '_group'][$theme->name]['widgets'][$skin_id] = array( '#type' => 'radios', '#title' => $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' => $skin['description'], ); break; case 'select': $form['skinr_settings'][$module . '_group'][$theme->name]['widgets'][$skin_id] = array( '#type' => 'select', '#title' => $skin['title'], '#options' => array_merge(array('' => ''), skinr_info_options_to_form_options($skin['options'])), '#default_value' => isset($defaults[$skin_id]) ? $defaults[$skin_id] : '', '#description' => $skin['description'], ); 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'; } } } // Add weight to additional settings and submit form elements. $form['additional_settings']['#weight'] = 39; $form['submit']['#weight'] = 40; // 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])) { 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) { $skinr_data = skinr_fetch_data(); $info = skinr_process_info_files(); $current_theme = skinr_current_theme(); $theme_registry = theme_get_registry(); $themes = array($current_theme); $original_hook = $hook; if (isset($theme_registry[$hook]['original hook'])) { $original_hook = $theme_registry[$hook]['original hook']; } 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)) { $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'); } } } if (!empty($style['_template'])) { $vars['template_files'][] = $style['_template']; unset($style['_template']); } $vars['skinr'] = skinr_style_flatten_array($style); $vars['classes_array'] = array_merge($vars['classes_array'], $vars['skinr']); } } } } /** * Helper fuction 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) { $filename = drupal_get_path('theme', $theme) . '/' . $filename; if (file_exists($filename)) { if ($type == 'css') { drupal_add_css($filename, array('weight' => CSS_THEME, 'media' => $media)); } else { drupal_add_js($filename, array('weight' => JS_THEME)); } break; } } } /** * Helper function to convert an array of classes settings into a string, usable in HTML. */ function skinr_style_flatten_array($style) { $return = array(); foreach ($style as $entry) { if (is_array($entry)) { foreach ($entry as $subentry) { if (!empty($subentry)) { $return[] = $subentry; } } } elseif (!empty($entry)) { $return[] = $entry; } } return $return; } // ------------------------------------------------------------------ // Include file helpers /** * Load views files on behalf of modules. */ function skinr_module_include($file) { foreach (skinr_get_module_apis() as $module => $info) { $filepath = DRUPAL_ROOT . "/$info[path]/$module.$file"; if (is_file($filepath)) { require_once $filepath; } } } /** * Get a list of modules that support skinr. */ function skinr_get_module_apis() { $cache = &drupal_static(__FUNCTION__, 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 is specified, it will return all skinr classes. * @return An array * * @todo Account for admin theme. */ 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 out purposes when an admin * theme is enabled. */ function skinr_current_theme() { global $user; if ($user->theme) { $current_theme = $user->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() { $cache = &drupal_static(__FUNCTION__, NULL); if (is_null($cache)) { $themes = system_get_info('theme'); foreach ($themes as $theme_idx => $theme) { $cache[$theme_idx] = array( 'options' => array(), 'skins' => array(), ); if (!empty($theme['skinr'])) { $info = (array)$theme['skinr']; // Store skinr options if (!empty($info['options'])) { $cache[$theme_idx]['options'] = $info['options']; unset($info['options']); } // Inherit skins from parent theme, if inherit_skins is set to true. if (!empty($cache[$theme_idx]['options']['inherit_skins'])) { $info = array_merge(skinr_inherited_skins($theme_idx, $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'] : '', '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_idx]['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']] = $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($template, (drupal_strlen($preprocess_hook) * -1)) == $preprocess_hook) { $filtered_templates[] = $template; } } return $filtered_templates; } /** * Fetch Skinr configuration data from modules. */ function skinr_fetch_data() { $cache = &drupal_static(__FUNCTION__, 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); } } }