<?php
/**
 * @file
 * Core functions for the Setup module.
 */

/**
 * Include additional files.
 */
foreach (module_list() as $module) {
  if (file_exists($file = dirname(__FILE__) . "/includes/{$module}.inc")) {
    require_once $file;
  }
}

/**
 * Implements hook_init().
 */
function setup_init() {
  if (setup_enabled() && in_array(variable_get('setup_source', 'profile'), array_keys(setup_sources()))) {
    $options = setup_options();

    // Put site into maintenance mode.
    if ($options['maintenance_mode']) {
      variable_set('maintenance_mode', TRUE);
    }

    // Force authorized user to Setup wizard.
    if (setup_init_access()) {
      drupal_goto('setup');
    }
  }

  module_invoke_all('setup_init');
}

/**
 * Implements hook_custom_theme().
 */
function setup_custom_theme() {
  if ('setup' == arg(0) && ($source_info = setup_source_info())) {
    if (isset($source_info['options']['theme'])) {
      return $source_info['options']['theme'];
    }
  }
}

/**
 * Implements hook_theme().
 */
function setup_theme($existing, $type, $theme, $path) {
  $items = array();

  // Template for Setup pages.
  $items['setup'] = array(
    'template' => 'setup',
    'path' => drupal_get_path('module', 'setup') . '/templates',
  );

  // Allow other modules to modify theme items.
  drupal_alter('setup_theme', $items);

  return $items;
}

/**
 * Implements hook_preprocess_page().
 */
function setup_preprocess_page(&$vars) {
  if (arg(0) == 'setup') {
    $vars['theme_hook_suggestions'][] = 'setup';
    drupal_add_css(drupal_get_path('module', 'setup') . '/setup.css');

    // Profile logo.
    $vars['logo'] = NULL;
    if (file_exists(drupal_get_path('module', $GLOBALS['conf']['install_profile']) . '/logo.png')) {
      $vars['logo'] = theme('image', array(
        'path' => drupal_get_path('module', $GLOBALS['conf']['install_profile']) . '/logo.png',
        'attributes' => array(
          'id' => 'logo',
        ),
      ));
    }

    // Attach the Setup style.
    $options = setup_options();
    $styles = setup_styles();
    if ($options['style'] && in_array($options['style'], array_keys($styles))) {
      drupal_process_attached(array('#attached' => $styles[$options['style']]));
    }

    // Hide messages.
    if (!$options['show_messages']) {
      drupal_get_messages();
    }
  }
}

/**
 * Implements hook_menu().
 */
function setup_menu() {
  $items = array();
  $items['setup'] = array(
    'title' => 'Setup',
    'page callback' => 'setup_wizard',
    'access arguments' => array('access setup'),
    'type' => MENU_CALLBACK,
  );
  $items['setup/skip'] = array(
    'title' => 'Skip setup',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('setup_skip'),
    'access arguments' => array('defer setup'),
    'type' => MENU_CALLBACK,
  );
  return $items;
}

/**
 * Implements hook_permission().
 */
function setup_permission() {
  return array(
    'access setup' => array(
      'title' => t('Access setup wizard'),
    ),
    'defer setup' => array(
      'title' => t('Defer setup wizard'),
    ),
  );
}

/**
 * Implements hook_css_alter().
 */
function setup_css_alter(&$css) {
  if (arg(0) == 'setup') {
    $options = setup_options();
    switch ($options['style_override']) {
      case 'theme':
        foreach (array_keys($css) as $key) {
          if (strpos($key, 'themes') === 0) {
            unset($css[$key]);
          }
        }
        break;
    }
  }
}

/**
 * Implements hook_form_alter().
 *
 * @TODO - Add description text.
 */
function setup_form_alter(&$form, $form_state, $form_id) {
  if ($form_id == 'system_site_maintenance_mode') {
    $sources = setup_sources(TRUE);

    $form['setup_enabled'] = array(
      '#type' => 'checkbox',
      '#title' => t('Put site into setup mode'),
      '#default_value' => variable_get('setup_enabled', FALSE),
      '#weight' => 10,
      '#disabled' => empty($sources),
    );
    if (!empty($sources)) {
      $form['setup_source'] = array(
        '#type' => 'select',
        '#title' => t('Setup source'),
        '#options' => $sources,
        '#default_value' => variable_get('setup_source', 'profile_default'),
        '#weight' => 11,
        '#states' => array(
          'invisible' => array(
           'input[name="setup_enabled"]' => array('checked' => FALSE),
          ),
        ),
      );
    }
  }
}

/**
 * Page callback for Setup wizard.
 */
function setup_wizard() {
  // @TODO - Do this better so that it uses a proper Access Callback and doesn't
  // use the custom theme.
  if (!setup_enabled() && !user_access('access setup')) {
    drupal_not_found();
    return;
  }

  $element = array();

  // Setup object cache.
  $object_id = 'setup_wizard';
  $object = setup_cache_get($object_id);
  $form_state = array(
    'object_id' => $object_id,
    'object' => &$object,
  );

  // Get hooks and profile info.
  $hooks_info = setup_info();
  $source_info = setup_source_info();

  // Get Setup options.
  $options = setup_options($source_info);

  // Build CTools wizard object.
  $form_info = array(
    'id' => 'setup_wizard',
    'path' => 'setup/%step',
    'show back' => $options['show_back'],
    'show cancel' => (user_access('defer setup') && $options['show_cancel'] != -1) || $options['show_cancel'] == 1,
    'cancel text' => t('Skip setup'),
    'order' => array(),
    'forms' => array(),
  );
  foreach ($source_info['steps'] as $key => $step) {
    if (isset($hooks_info[$step['type']]['form callback']) && function_exists($hooks_info[$step['type']]['form callback'])) {
      if (arg(1) == $key || (arg(1) == NULL && empty($form_state['setup_step']))) {
        if (isset($step['title'])) {
          drupal_set_title($step['title']);
        }

        // Arguments.
        if (isset($step['arguments'])) {
          foreach ($step['arguments'] as $argument => $index) {
            $index = explode('[', str_replace(']', '', $index));
            $step['arguments'][$argument] = drupal_array_get_nested_value($form_state['object'], $index);
          }
        }

        // Help region.
        if (isset($step['help'])) {
          $element[] = array(
            '#markup' => isset($step['help']['filter'])
              ? check_markup($step['help']['markup'], $step['help']['filter'])
              : filter_xss($step['help']['markup']),
          );
        }

        $form_state['setup_step'] = $step;
      }

      // Set CTools wizard values.
      $form_info['order'][$key] = isset($step['title']) ? $step['title'] : $key;
      $form_info['forms'][$key] = array(
        'form id' => $hooks_info[$step['type']]['form callback'],
      );
    }
  }

  // Check if step exists.
  if (!isset($form_state['setup_step'])) {
    drupal_not_found();
    return;
  }

  // Build CTools wizard form.
  ctools_include('wizard');
  $element[] = ctools_wizard_multistep_form($form_info, arg(1), $form_state);

  // Steps list.
  if ($options['show_steps']) {
    $active = FALSE;
    foreach ($form_info['order'] as $key => &$order) {
      if (arg(1) == $key || (arg(1) == NULL && !$active)) {
        $active = TRUE;
        $order = array(
          'data' => $order,
          'class' => array('active'),
        );
      }
      if (!$active) {
        $order = array(
          'data' => $order,
          'class' => array('done'),
        );
      }
    }
    $element[] = array(
      '#theme' => 'item_list',
      '#items' => $form_info['order'],
      '#attributes' => array(
        'id' => 'steps',
      ),
    );
  }

  return $element;
}

/**
 * Next callback for Setup wizard form.
 */
function setup_wizard_next(&$form_state) {
  // This is a temporary workaround for the Node Edit method as the Values and
  // the Input don't matchup, this is likely due to the way the node edit form
  // is being embeded.
  //
  // @TODO - Fix this.
  $form_state['object'][$form_state['step']] = array_merge($form_state['values'], $form_state['input']);
  setup_cache_set($form_state['object_id'], $form_state['object']);
}

/**
 * Cancel callback for Setup wizard form.
 */
function setup_wizard_cancel(&$form_state) {
  if (user_access('defer setup')) {
    drupal_goto('setup/skip');
  }

  else {
    $form = array();
    $form_state = array();

    setup_skip_submit($form, $form_state);
  }
}

/**
 * Finish callback for Setup wizard form.
 */
function setup_wizard_finish(&$form_state) {
  // Invoke Setup wizard Next callback to deal with object cache.
  setup_wizard_next($form_state);

  // Get object from cache.
  $object_id = 'setup_wizard';
  $object = setup_cache_get($object_id);

  $hooks_info = setup_info();
  $source_info = setup_source_info();
  foreach ($source_info['steps'] as $key => $step) {
    if (isset($hooks_info[$step['type']]['finish callback']) && function_exists($hooks_info[$step['type']]['finish callback'])) {
      // Arguments.
      if (isset($step['arguments'])) {
        foreach ($step['arguments'] as $argument => $index) {
          $index = explode('[', str_replace(']', '', $index));
          $step['arguments'][$argument] = drupal_array_get_nested_value($object, $index);
        }
      }

      $step_form_state = array(
        'values' => isset($object[$key]) ? $object[$key] : array(),
        'setup_step' => $step,
      );
      $hooks_info[$step['type']]['finish callback']($step_form_state);
    }
  }

  // Clear object cache.
  setup_cache_clear($object_id);

  // Disable setup run flag.
  variable_set('setup_enabled', FALSE);

  // Get Setup options.
  $options = setup_options($source_info);

  // Take site out of maintenance mode.
  if ($options['maintenance_mode']) {
    variable_set('maintenance_mode', FALSE);
  }

  // Hide messages.
  if (!$options['show_messages']) {
    drupal_get_messages();
  }

  // Display a completion message.
  if ($options['finish_message']) {
    drupal_set_message($options['finish_message']);
  }

  // Auto-disable Setup module.
  if ($options['finish_disable']) {
    module_disable(array('setup'));
  }

  // Invoke hook_setup_finish.
  module_invoke_all('setup_finish');

  // Redirect back to the homepage.
  drupal_goto($options['finish_redirect']);
}

/**
 * Form callback for Setup wizard skip link when user has the 'defer setup'
 * permission.
 */
function setup_skip($form, &$form_state) {
  // Set SESSION variable to skip setup wizard.
  $_SESSION['setup_skip'] = TRUE;

  // Get Setup options.
  $options = setup_options();

  return confirm_form(
    $form,
    t('Skip setup'),
    $options['cancel_redirect'],
    t("Skipping setup will prevent setup from running automatically in the future, however you can simply 'defer' the setup wizard for your current session."),
    t('Skip'),
    t('Defer')
  );
}

/**
 * Submit callback for Setup wizard skip link when user has the 'defer setup'
 * permission.
 */
function setup_skip_submit($form, &$form_state) {
  // Disable setup flag.
  variable_set('setup_enabled', FALSE);

  // Get Setup options.
  $options = setup_options();

  // Take site out off maintenance mode.
  if ($options['maintenance_mode']) {
    variable_set('maintenance_mode', FALSE);
  }

  // Display a cancelation message.
  if ($options['cancel_message']) {
    drupal_set_message($options['cancel_message']);
  }

  // Auto-disable Setup module.
  if ($options['cancel_disable']) {
    module_disable(array('setup'));
  }

  // Redirect back to the homepage.
  drupal_goto($options['cancel_redirect']);
}

/**
 * Cache helper; Set object cache.
 */
function setup_cache_set($object_id, $object) {
  ctools_include('object-cache');
  ctools_object_cache_set('setup', $object_id, $object);
}

/**
 * Cache helper; Get object cache.
 */
function setup_cache_get($object_id) {
  ctools_include('object-cache');
  return ctools_object_cache_get('setup', $object_id);
}

/**
 * Cache helper; Clear object cache.
 */
function setup_cache_clear($object_id) {
  ctools_include('object-cache');
  ctools_object_cache_clear('setup', $object_id);
}

/**
 * Helper function; Returns all implementations of hook_setup_info().
 */
function setup_info() {
  $info = &drupal_static(__FUNCTION__);
  if (empty($info)) {
    $info = module_invoke_all('setup_info');
  }
  return $info;
}

/**
 * Helper function; Returns all implementations of hook_setup_styles().
 */
function setup_styles() {
  $styles = &drupal_static(__FUNCTION__);
  if (empty($styles)) {
    $styles = module_invoke_all('setup_styles');
  }
  return $styles;
}

/**
 * Helper function; Returns all available sources.
 */
function setup_sources($grouped = FALSE) {
  $sources = &drupal_static(__FUNCTION__, array());

  if (empty($sources) && isset($GLOBALS['conf']['install_profile'])) {
    // Install profile option.
    $profile_info = system_get_info('module', $GLOBALS['conf']['install_profile']);
    if (isset($profile_info['setup'])) {
      $sources[t('Install profiles')] = array(
        'profile' => $profile_info['name'],
      );
    }

    // Module options.
    $modules = system_get_info('module');
    foreach ($modules as $module => $info) {
      if ($module !== $GLOBALS['conf']['install_profile'] && isset($info['setup'])) {
        $sources[t('Modules')] = isset($sources[t('Modules')]) ? $sources[t('Modules')] : array();
        $sources[t('Modules')]["module-{$module}"] = $info['name'];
      }
    }

    // Theme options.
    $themes = system_get_info('theme');
    foreach ($themes as $theme => $info) {
      if (isset($info['setup'])) {
        $sources[t('Themes')] = isset($sources[t('Themes')]) ? $sources[t('Themes')] : array();
        $sources[t('Themes')]["theme-{$theme}"] = $info['name'];
        if (variable_get('theme_default', NULL) == $theme) {
          $sources[t('Themes')] = array_merge(array('theme-default' => t('Default')), $sources[t('Themes')]);
        }
      }
    }

    // @TODO - Allow modules to add sources.
  }

  // Ungroup sources if flag is set to FALSE.
  if ($grouped == FALSE) {
    $ungrouped_sources = array();
    foreach ($sources as $source) {
      $ungrouped_sources = array_merge($ungrouped_sources, $source);
    }
    return $ungrouped_sources;
  }

  return $sources;
}

/**
 * Helper function; Returns setup info from current source.
 */
function setup_source_info($source = NULL) {
  $source = explode('-', is_null($source) ? variable_get('setup_source', 'profile') : $source);
  switch ($source[0]) {
    case 'profile':
      $info = system_get_info('module', $GLOBALS['conf']['install_profile']);
      if (!isset($info['setup'])) {
        return FALSE;
      }
      $info = $info['setup'];
      break;

    case 'module':
      if (!isset($source[1])) {
        return FALSE;
      }
      $info = system_get_info('module', $source[1]);
      if (!isset($info['setup'])) {
        return FALSE;
      }
      $info = $info['setup'];
      break;

    case 'theme':
      $source[1] = !isset($source[1]) || $source[1] == 'default' ? variable_get('theme_default', NULL) : $source[1];
      $info = system_get_info('theme', $source[1]);
      if (!isset($info['setup'])) {
        return FALSE;
      }
      $info = $info['setup'];
      break;

    default:
      $info = module_invoke_all('setup_source_info', $source);
  }

  if (!empty($info)) {
    _setup_source_info_token_replace($info);
    return $info;
  }
  return FALSE;
}

/**
 * Recursively run token_replace() on $info.
 */
function _setup_source_info_token_replace(&$info) {
  switch (TRUE) {
    case is_array($info):
      foreach (element_children($info) as $child) {
        _setup_source_info_token_replace($info[$child]);
      }
      break;

    default:
      $info = token_replace($info);
  }
}

/**
 * Helper function; Check if setup is enabled.
 */
function setup_enabled() {
  if (variable_get('setup_enabled', TRUE)) {
    return TRUE;
  }

  if (isset($_SESSION['setup_enabled']) && $_SESSION['setup_enabled']) {
    return TRUE;
  }

  return FALSE;
}

/**
 * Helper function; Returns Setup options.
 */
function setup_options($info = array(), $source = NULL) {
  $options = &drupal_static(__FUNCTION__);

  if (empty($options)) {
    // Load profile info if info not supplied.
    if (empty($info)) {
      $info = setup_source_info($source);
    }

    // Set default options.
    $options = array(
      'cancel_disable' => FALSE,
      'cancel_message' => 'Setup has been skipped.',
      'cancel_redirect' => '<front>',
      'finish_disable' => FALSE,
      'finish_message' => FALSE,
      'finish_redirect' => '<front>',
      'maintenance_mode' => TRUE,
      'show_back' => TRUE,
      'show_cancel' => TRUE,
      'show_steps' => TRUE,
      'show_messages' => TRUE,
      'style' => 'drupalsetup',
      'style_override' => 'theme',
    );

    // Merge defined and default options.
    if (isset($info['options']) && !empty($info['options'])) {
      $options = array_merge($options, $info['options']);
    }
  }

  return $options;
}

/**
 * Access checks for setup_init() redirect.
 */
function setup_init_access() {
  // Prevent setup from triggering during site installation.
  if (empty($GLOBALS['conf']['install_profile'])) {
    return FALSE;
  }

  // Don't redirect if the user doesn't have permission to access the setup
  // page.
  if (!user_access('access setup')) {
    return FALSE;
  }

  // Don't redirect if you're already at the setup page.
  if (arg(0) == 'setup') {
    return FALSE;
  }

  // Allow user to logout.
  if (strstr(request_uri(), 'user/logout')) {
    return FALSE;
  }

  // Setup wizard has been defered.
  if (isset($_SESSION['setup_skip']) && $_SESSION['setup_skip']) {
    return FALSE;
  }

  return TRUE;
}
