<?php
/**
 * @file
 * Node Subpages
 *
 * Allows admins to define subpages for content types. These can be used to
 * display fields or Views in subpages instead of (or in addition to) on the
 * regular node view.
 */

define('NODE_SUBPAGES_MINIMUM_CTOOLS_API_VERSION', '2.0');
define('NODE_SUBPAGES_MAXIMUM_CTOOLS_API_VERSION', '2.1');

/**
 * Check that the correct version of CTools is installed
 */
function node_subpages_check_ctools_version() {
  static $valid = '';

  if ($valid == '') {
    if (module_invoke('ctools', 'api_version', NODE_SUBPAGES_MINIMUM_CTOOLS_API_VERSION, NODE_SUBPAGES_MAXIMUM_CTOOLS_API_VERSION)) {
      $valid = TRUE;
    }
    else {
      $valid = FALSE;
    }
  }
  return $valid;
}

/**
 * Return a list of sub-pages
 * Possible fields:
 *    title: required
 *    field: name of CCK field, optional
 *    view: name of a view, optional
 *    view_display: name of the display to use in the view. Optional, but only specify if view is used
 */
function node_subpages_list() {
  static $subpages;

  if (empty($subpages)) {
    $subpages = module_invoke_all('node_subpages_list');
    if (empty($subpages)) {
      $subpages = array();
    }

    // Do some data cleanup
    foreach ($subpages as $content_type => $subpages_for_type) {
      // Make sure that the default_view_page key is set on all subpages
      foreach ($subpages_for_type as $path => $subpage_detail) {
        if (!isset($subpage_detail['default_view_page'])) {
          $subpages[$content_type][$path]['default_view_page'] = FALSE;
        }
      }

      // Sort the pages by weight
      uasort($subpages[$content_type], 'drupal_sort_weight');
    }
  }

  return $subpages;
}


/**
 * Implements hook_node_subpages_list().
 *
 * Load subpages that have been setup through the admin
 */
function node_subpages_node_subpages_list() {
  // Make sure CTools is installed correctly
  if (!node_subpages_check_ctools_version()) {
    return array();
  }

  ctools_include('export');
  $subpages = ctools_export_load_object('node_subpages', 'all');
  $return = array();
  foreach ($subpages as $machine_name => $subpage) {
    $return[$subpage->node_type][$subpage->subpath] = _node_subpages_prep_details($subpage);
  }
  return $return;
}


/**
 * Load a specific subpage by machine name from the node_subpages table. Calls
 * _node_subpages_prep_details() to prep the information
 */
function node_subpages_load_specific($machine_name) {
  ctools_include('export');
  $subpage = ctools_export_load_object('node_subpages', 'names', array($machine_name));
  if (!empty($subpage)) {
    // ctools_export_load_object returns an array keyed by machine name
    $subpage = _node_subpages_prep_details($subpage[$machine_name]);
    return $subpage;
  }
  return NULL;
}


/**
 * Given a row object from the node_subpages table, prep an array of details about it
 */
function _node_subpages_prep_details($row) {
  $info = array(
    'title' => $row->title,
    'machine_name' => $row->machine_name,
    'id' => $row->id,
    'subpath' => $row->subpath,
    'node_type' => $row->node_type,
    'default_view_page' => (bool)$row->is_default,
    'weight' => $row->weight,
    'plugin' => _node_subpages_get_content_plugin($row->source_type),
    'plugin_config' => $row->source_data,
  );

  return $info;
}

/**
 * Get the subpages for a specific node type
 */
function node_subpages_list_for_type($node_type) {
  static $all;
  if (empty($all)) {
    $all = node_subpages_list();
  }

  // If there are no subpages for this type, return an empty array
  if (!isset($all[$node_type])) {
    $all[$node_type] = array();
  }
  return $all[$node_type];
}

/**
 * Implements hook_block_info().
 */
function node_subpages_block_info() {
  $blocks['menu_bar']['info'] = t('Node Subpages Menu Bar');
  return $blocks;
}

/**
 * Implements hook_block_view().
 */
function node_subpages_block_view($delta) {
  if ($delta == 'menu_bar') {
    $block['subject'] = '';
    $block['content'] = array(
      '#markup' => _node_subpages_menu_bar(),
      '#attached' => array(
        'css' => array(
          drupal_get_path('module', 'node_subpages') . '/node_subpages.css',
        ),
      ),
    );
  }
  return $block;
}


/**
 * Content for the Node Subpages Menu Bar block
 */
function _node_subpages_menu_bar() {
  $current_node = menu_get_object();
  if (empty($current_node)) {
    $current_node = menu_get_object('node_subpage_menu');
  }

  // If not viewing a node, there are no subpage links to show
  if (!isset($current_node)) {
    return '';
  }

  $subpages = node_subpages_list_for_type($current_node->type);
  if (isset($subpages)) {
    $lang = $current_node->language;

    $have_default_view_tab = FALSE;

    foreach ($subpages as $subpage_path => $details) {
      if (!_node_subpages_tab_has_content($subpage_path, $details, $current_node)) {
        continue;
      }

      $tab_title = $details['title'];

      // Check if there's a field on the node to override the tab title
      /*
      $tab_title_field = 'field_' . $subpage_path . '_tab_title';
      if (isset($current_node->{$tab_title_field})) {
        if (isset($current_node->{$tab_title_field}[$lang][0]['value'])) {
          $tab_title = $current_node->{$tab_title_field}[$lang][0]['value'];
        }
      }
      */

      // If there's a subpage item for the main View tab, handle it a
      // little differently
      if ($details['default_view_page']) {
        $have_default_view_tab = TRUE;
        $items[] = array(
          'href' => 'node/' . $current_node->nid,
          'title' => $tab_title,
        );
      }
      // Not the View tab
      else {
        if (node_subpage_menu_access($current_node, $subpage_path)) {
          $items[] = array(
            'href' => 'node/' . $current_node->nid . '/' . $subpage_path,
            'title' => $tab_title,
          );
        }
      }
    }

    // Only show subpage links if there are any
    if (!empty($items)) {
      // Make sure that there is a link for the regular node view
      if (!$have_default_view_tab) {
        $view_tab = array(
          'href' => 'node/' . $current_node->nid,
          'title' => t('View'),
        );
        array_unshift($items, $view_tab);
      }
      return theme('links', array(
        'links' => $items,
        'attributes' => array(
          'class' => 'links clearfix items-' . count($items),
        )
      ));
    }

  }
  return '';
}



/**
 * Implements hook_menu().
 * Define the menu paths for node subpages
 */
function node_subpages_menu() {
  $items = array();

  // Add menu items for each subpage for each node type
  $node_subpages = node_subpages_list();
  foreach ($node_subpages as $node_type => $subpages) {
    foreach ($subpages as $path => $details) {
      if ($details['default_view_page']) {
        continue;
      }
      $items['node/%node_subpage_menu/' . $path] = array(
        'title callback' => 'node_page_title',
        'title arguments' => array(1),
        'page callback' => 'node_subpages_view',
        'page arguments' => array(1, 2),
        'access callback' => 'node_subpage_menu_access',
        'access arguments' => array(1, 2),
        'type' => MENU_NORMAL_ITEM,
      );
    }
  }

  // Add menu items for the management of the subpages for each node type
  $items['admin/structure/types/manage/%node_type/subpages'] = array(
    'title' => 'Subpages',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('node_subpages_admin_list', 4),
    'access arguments' => array('administer content types'),
    'file' => 'node_subpages.admin.inc',
    'type' => MENU_LOCAL_TASK,
    'weight' => 100,
  );
  $items['admin/structure/types/manage/%node_type/subpages/add'] = array(
    'title' => 'Add Subpage',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('node_subpages_add_subpage', 4),
    'access arguments' => array('administer content types'),
    'file' => 'node_subpages.admin.inc',
    'type' => MENU_LOCAL_ACTION,
    'weight' => 100,
  );
  $items['admin/structure/types/manage/%node_type/subpages/edit'] = array(
    'title' => 'Edit Subpage',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('node_subpages_add_subpage', 4),
    'access arguments' => array('administer content types'),
    'file' => 'node_subpages.admin.inc',
    'type' => MENU_CALLBACK,
    'weight' => 100,
  );
  $items['admin/structure/types/manage/%node_type/subpages/delete'] = array(
    'title' => 'Delete Subpage',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('node_subpages_delete_subpage', 4),
    'access arguments' => array('administer content types'),
    'file' => 'node_subpages.admin.inc',
    'type' => MENU_CALLBACK,
    'weight' => 100,
  );
  return $items;
}

/**
 * Menu loader callback. Only return a node for node types that have subpages, so that
 * the menu item isn't valid for other node types.
 */
function node_subpage_menu_load($nid) {
  if (!is_numeric($nid)) {
    return FALSE;
  }
  $node = node_load($nid);
  if (isset($node->type)) {
    $subpages = node_subpages_list_for_type($node->type);
    if (!empty($subpages)) {
      return $node;
    }
  }
  return FALSE;
}

/**
 * Menu access callback
 */
function node_subpage_menu_access($node, $subpage_path) {
  if (node_access('view', $node)) {
    $subpages = node_subpages_list_for_type($node->type);
    if (!empty($subpages) && !empty($subpages[$subpage_path])) {
      return _node_subpages_tab_has_content($subpage_path, $subpages[$subpage_path], $node);
    }
  }
  return FALSE;
}

/**
 * Check if a tab has content for a node
 */
function _node_subpages_tab_has_content($path, $tab_config, $node) {
  $has_content = &drupal_static(__FUNCTION__);

  // Make sure CTools is installed correctly
  if (!node_subpages_check_ctools_version()) {
    return '';
  }

  if (!isset($has_content[$node->nid][$path])) {
    $has_content[$node->nid][$path] = FALSE;

    if ($plugin = $tab_config['plugin']) {
      if($has_content_function = ctools_plugin_get_function($plugin, 'has content')) {
        $has_content[$node->nid][$path] = $has_content_function($node, $tab_config['plugin_config']);
      }
    }
  }


  return $has_content[$node->nid][$path];
}

/**
 * Get the content for a given tab on a given node
 */
function _node_subpages_tab_content($path, $tab_config, $node) {
  static $content;

  // Make sure CTools is installed correctly
  if (!node_subpages_check_ctools_version()) {
    return '';
  }

  if (!isset($content[$node->nid][$path])) {
    $tab_content = '';

    if ($plugin = $tab_config['plugin']) {
      if($content_function = ctools_plugin_get_function($plugin, 'content')) {
        $tab_content = $content_function($node, $tab_config['plugin_config']);
      }
    }

    // If the tab content is an array, render it
    if (is_array($tab_content)) {
      $content[$node->nid][$path] = trim(render($tab_content));
    }
    // Tab content is just a string, so trim it
    else {
      $content[$node->nid][$path] = trim($tab_content);
    }
  }

  return $content[$node->nid][$path];
}


/**
 * Implements hook_menu_alter().
 * Change the View local task on nodes to Overview
 */
function node_subpages_menu_alter(&$items) {
  $items['node/%node/view']['title callback'] = '_node_subpages_tab_title';
  $items['node/%node/view']['title arguments'] = array(1);
  unset($items['node/%node/view']['title']);
}


/**
 * Title callback for node/%node/view. For nodes with subpages, use the title set for the view subpage.
 * For all other nodes, return View.
 * This text is used for the View local task menu item
 */
function _node_subpages_tab_title($node) {
  $subpages = node_subpages_list_for_type($node->type);
  if (isset($subpages['view'])) {
    return $subpages['view']['title'];
  }
  else {
    return t('View');
  }
}

/**
 * Page callback for node subpages. If the node has content for the
 * requested sub-page, return that content. If not, redirect to the
 * Overview page.
 */
function node_subpages_view($node, $subpage_path) {
  $subpages = node_subpages_list_for_type($node->type);
  if (isset($subpages[$subpage_path])) {
    _node_subpages_fire_context($node->type, $subpage_path);

    // Set the menu trail so that menu items are highlighted correctly
    $item = menu_get_item();
    $item['href'] = 'node/' . $node->nid;
    menu_set_item(NULL, $item);

    // Get the subpage content, and if it's set, return it as the page content
    return _node_subpages_tab_content($subpage_path, $subpages[$subpage_path], $node);
  }

  // If the function gets this far, the field had no value, or the view couldn't
  // be retrieved. In either case, use the drupal_goto() below to go back to the
  // node overview tab

  // Not handled by subpages, so redirect back away from the subpage path
  drupal_goto('node/' . $node->nid);
}



/**
 * Implements hook_node_insert().
 */
function node_subpages_node_insert($node) {
  if (user_access('create url aliases') || user_access('administer url aliases')) {
    // Get a list of subpages for this node type
    $subpages = node_subpages_list_for_type($node->type);
    if (!empty($subpages)) {
      // If the node has a path
      if (isset($node->path) && trim($node->path['alias']) != '') {
        // Setup a path alias for each subpage
        foreach ($subpages as $sub_path => $title) {
          $path = array(
            'source' => 'node/' . $node->nid . '/' . $sub_path,
            'alias' => $node->path['alias'] . '/' . $sub_path,
            'language' => $node->path['language'],
          );
          path_save($path);
        }
      }
    }
  }
}


/**
 * Implements hook_node_update().
 * Just call node_subpages_node_insert() since it has the desired functionality for node update
 */
function node_subpages_node_update($node) {
  return node_subpages_node_insert($node);
}


/**
 * Implements hook_node_delete().
 */
function node_subpages_node_delete($node) {
  $subpages = node_subpages_list_for_type($node->type);
  $paths_do_delete = array();

  // Delete the alias for each sub-page for this node
  foreach ($subpages as $sub_path => $title) {
    $path = 'node/' . $node->nid . '/' . $sub_path;
    path_delete(array('source' => $path));
  }
}


/**
 * Implements hook_node_type_delete().
 * When a node type is deleted, delete its subpages too.
 */
function node_subpages_node_type_delete($info) {
  db_delete('node_subpages')
    ->condition('node_type', $info->type)
    ->execute();
}



/**
 * Trigger the Context condition for the current subpage
 */
function _node_subpages_fire_context($node_type, $subpage_path) {
  if (module_exists('context')) {
    if ($plugin = context_get_plugin('condition', 'node_subpage')) {
      $plugin->execute($node_type, $subpage_path);
    }
  }
}

/**
 * Implements hook_context_registry().
 * Register the Node Subpages condition
 */
function node_subpages_context_registry() {
  $registry = array();
  $registry['conditions'] = array(
    'node_subpage' => array(
      'title' => t('Node Subpages'),
      'description' => t('Set this context when viewing a sub-page on Node nodes. This condition does not apply to the Overview tab; to address that condition, use the regular "Node type" condition.'),
      'plugin' => 'node_subpages_condition_node_subpage',
    ),
  );
  return $registry;
}

/**
 * Implements hook_context_plugins().
 * Register the Node Subpages plugin for Contexts
 */
function node_subpages_context_plugins() {
  $plugins['node_subpages_condition_node_subpage'] = array(
    'handler' => array(
      'path' => drupal_get_path('module', 'node_subpages') . '/context_plugins',
      'file' => 'node_subpages_condition_node_subpage.inc',
      'class' => 'node_subpages_condition_node_subpage',
      'parent' => 'context_condition',
    ),
  );
  return $plugins;
}


/**
 * Implements hook_theme().
 */
function node_subpages_theme($existing, $type, $theme, $path) {
  return array(
    // Theme the subpage list for admins when editing a content type
    'node_subpages_admin_list' => array(
      'render element' => 'form',
      'file' => 'node_subpages.admin.inc',
    ),
  );
}







/******************************************************************************
 * CTools functions
 */

/**
 * Implementation of hook_ctools_plugin_directory().
 * Tell ctools where this module's plugins live
 */
function node_subpages_ctools_plugin_directory($module, $plugin) {
  if ($module == 'node_subpages') {
    return 'plugins/' . $plugin;
  }
}


/**
 * Get an array of all subpage content plugins
 */
function _node_subpages_get_content_plugins() {
  // Make sure CTools is installed correctly
  if (!node_subpages_check_ctools_version()) {
    return array();
  }

  ctools_include('plugins');
  return ctools_get_plugins('node_subpages', 'node_subpages_content');
}

/**
 * Get a particular subpage content plugin
 */
function _node_subpages_get_content_plugin($plugin_key) {
  // Make sure CTools is installed correctly
  if (!node_subpages_check_ctools_version()) {
    return array();
  }

  ctools_include('plugins');
  return ctools_get_plugins('node_subpages', 'node_subpages_content', $plugin_key);
}

/**
 * Implements hook_ctools_plugin_type().
 * Tell ctools about plugin types
 */
function node_subpages_ctools_plugin_type() {
  return array(
    'node_subpages_content' => array(
      'use hooks' => TRUE,
      'cache' => TRUE,
    ),
  );
}

/**
 * Implementation of hook_ctools_plugin_api().
 *
 * Tell CTools that we support the default_node_subpages_paths API.
 */
function node_subpages_ctools_plugin_api($owner, $api) {
  if ($owner == 'node_subpages' && $api == 'default_node_subpages_paths') {
    return array('version' => 1);
  }
}

/**
 * Export a single path
 */
function node_subpages_path_export($obj, $indent = '') {
  ctools_include('export');
  $output = ctools_export_object('node_subpages_path', $obj, $indent);
  return $output;
}






/******************************************************************************
 * Features functions
 */

/**
 * Implements hook_features_export_options().
 */
function node_subpages_features_export_options() {
  $subpages = db_select('node_subpages', 'ns')
    ->fields('ns')
    ->orderBy('node_type')
    ->orderBy('weight')
    ->execute();
  $node_types = node_type_get_types();
  $options = array();
  foreach ($subpages as $path) {
    $type = $node_types[$path->node_type]->name;
    $options[$path->machine_name] = "{$type}: {$path->subpath}";
  }
  return $options;
}


/**
 * Implements hook_features_export.
 */
function node_subpages_features_export($data, &$export, $module_name = '') {
  $pipe = array();

  // When adding subpages, make sure the content type is added as well
  foreach ($data as $machine_name) {
    $node_type = db_select('node_subpages', 'ns')
      ->fields('ns', array('node_type'))
      ->condition('machine_name', $machine_name)
      ->execute()
      ->fetchField();
    $pipe['node'][$node_type] = $node_type;
  }
  return $pipe;
}


/**
 * Implements hook_features_export_alter().
 * When a content type is added to a feature, add any subpages for that content
 * type.
 */
function node_subpages_features_export_alter(&$export, $module_name) {
  static $ctools_dep = '';
  if (isset($export['features']['node'])) {
    foreach ($export['features']['node'] as $node_type) {
      // Get all the subpages for this content type
      $subpages = db_select('node_subpages', 'ns')
        ->fields('ns')
        ->orderBy('weight')
        ->condition('node_type', $node_type)
        ->execute();

      // Loop over each subpage path and add to the components
      foreach ($subpages as $path) {
        // Add this subpath
        $export['features']['node_subpages'][$path->machine_name] = $path->machine_name;
        // Make sure this module is a dependency
        $export['dependencies']['node_subpages'] = 'node_subpages';

        // Determine the API version for this module
        if ($ctools_dep == '') {
          $node_sub_api_ver = node_subpages_ctools_plugin_api('node_subpages', 'default_node_subpages_paths');
          $ctools_dep = 'node_subpages:default_node_subpages_paths:' . $node_sub_api_ver['version'];
        }
        // Add the ctools dependency
        $export['features']['ctools'][$ctools_dep] = $ctools_dep;
      }
    }
  }
}
