<?php
// $Id: noderelationships.module,v 1.1.2.24 2010/01/03 20:45:10 markuspetrux Exp $

/**
 * @file
 * This is the main script for the noderelationships module. It merely contains
 * the implementation of hooks invoked by Drupal core, CCK, Views, etc.
 * All common functions are externalized into several scripts that are included
 * on demand.
 */

/**
 * Back reference regions.
 */
define('NODERELATIONSHIPS_BACKREF_REGION_FIELD', 'field');
define('NODERELATIONSHIPS_BACKREF_REGION_PAGE', 'page');
define('NODERELATIONSHIPS_BACKREF_REGION_TAB', 'tab');

/**
 * Views related constants.
 */
define('NODERELATIONSHIPS_BACKREF_VIEW_NAME', 'noderelationships_backref');
define('NODERELATIONSHIPS_BACKREF_VIEW_TAG', 'noderelationships_backref');
define('NODERELATIONSHIPS_NODEREF_VIEW_NAME', 'noderelationships_noderef');
define('NODERELATIONSHIPS_NODEREF_VIEW_TAG', 'noderelationships_noderef');
define('NODERELATIONSHIPS_VIEW_PATH_PREFIX', 'noderelationships');

/**
 * Implementation of hook_theme().
 */
function noderelationships_theme() {
  module_load_include('inc', 'noderelationships', 'noderelationships.system');
  return _noderelationships_theme();
}

/**
 * Implementation of hook_menu().
 */
function noderelationships_menu() {
  module_load_include('inc', 'noderelationships', 'noderelationships.system');
  return _noderelationships_menu();
}

/**
 * Access callback for the node relationships tab.
 *
 * @see noderelationships_menu()
 */
function noderelationships_backref_access($referred_node) {
  // Deny access if the user does not have access to view the referred node.
  if (!node_access('view', $referred_node)) {
    return FALSE;
  }

  // Include common module functions.
  module_load_include('inc', 'noderelationships');

  // Obtain back reference settings for this type.
  $backref_settings = noderelationships_settings_load($referred_node->type, 'backref');

  // Deny access (meaning the tab is not visible) when the node type
  // does not have back references enabled for this region.
  if (empty($backref_settings['regions'][NODERELATIONSHIPS_BACKREF_REGION_TAB])) {
    return FALSE;
  }

  // Allow external modules deny access to the Relationships tab for this node.
  $access = (array) module_invoke_all('noderelationships_backref_access', $referred_node);
  foreach ($access as $value) {
    if ($value === FALSE) {
      return FALSE;
    }
  }

  // Ok, the user is allowed to view the node and this type has potential
  // back references, so we can grant access to the relationships tab.
  // @todo: Find out a way to hide the relationships tab if THIS node does
  // not have real back references. The problem here is that a series of
  // SELECT COUNT(*) queries would affect performance, so we would have to
  // use a summary field or something similar. But this information would
  // have to be updated when a nodereference is added, changed or removed
  // from the database.
  return TRUE;
}

/**
 * Implementation of hook_views_api().
 */
function noderelationships_views_api() {
  return array(
    'api' => 2,
    'path' => drupal_get_path('module', 'noderelationships') .'/views',
  );
}

/**
 * Implementation of hook_views_pre_view().
 *
 * @see noderelationships_backref_render_view()
 */
function noderelationships_views_pre_view(&$view, $display_id, &$view_args) {
  // Apply custom configuration to the given view.
  if ($view->tag == NODERELATIONSHIPS_BACKREF_VIEW_TAG) {
    module_load_include('inc', 'noderelationships');
    noderelationships_customize_backref_view($view, $display_id, $view_args);
  }
  elseif ($view->tag == NODERELATIONSHIPS_NODEREF_VIEW_TAG) {
    module_load_include('inc', 'noderelationships');
    noderelationships_customize_noderef_view($view, $display_id, $view_args);
  }
}

/**
 * Implementation of hook_views_pre_execute().
 *
 * @see noderelationships_customize_backref_view()
 */
function noderelationships_views_pre_execute(&$view) {
  if (!empty($view->noderelationships_breadcrumb)) {
    drupal_set_breadcrumb($view->noderelationships_breadcrumb);
  }
}

/**
 * Proxy function to preprocess the list views theme.
 */
function noderelationships_preprocess_views_ui_list_views(&$vars) {
  if (!isset($vars['views']) || !is_array($vars['views'])) {
    return;
  }
  foreach ($vars['views'] as $view) {
    if (!empty($view->tag) && in_array($view->tag, array(NODERELATIONSHIPS_BACKREF_VIEW_TAG, NODERELATIONSHIPS_NODEREF_VIEW_TAG))) {
      if (!empty($view->path)) {
        $view->path = t('Dynamically generated by the Node Relationships module.');
      }
      $view->description = t('This view is dynamically customized by the Node Relationships module.') .'<br />'. $view->description;
    }
  }
}

/**
 * Implementation of hook_field_info().
 *
 * Back reference fields:
 * - Are single-valued fields and cannot be shared (per type storage is a must).
 *   This is why these fields are created automatically. Note that these fields
 *   are hidden from the "Add new field" and "Add existing field" selectors in
 *   the "Manage fields" screen. See _noderelationships_cck_admin_forms_alter().
 * - Do not implement database columns and storage method is always per type.
 *   Hence, adding and removing back reference fields do not affect user data.
 * - Do not provide CCK to Views integration for back reference fields.
 * - Do not provide any widget in the node edit form.
 * - The widget is defined to manage its own multiple values, therefore these
 *   fields cannot be added to multigroups.
 * - Can be dragged from the "Manage fields" screen to any position in the node.
 * - The widget label can be changed at will from the "Manage fields" screen.
 * - The formatter simply renders a dynamically customized back reference view
 *   or the back references count.
 */
function noderelationships_field_info() {
  return array(
    'noderelationships_backref' => array(
      'label' => t('Back reference'),
      'description' => t('Display node back references.'),
    ),
  );
}

/**
 * Implementation of hook_field_settings().
 *
 * @see noderelationships_cck_backref_create()
 * @see _noderelationships_cck_admin_forms_alter()
 */
function noderelationships_field_settings($op, $field) {
  if ($op == 'save') {
    return array('referrer_type', 'referrer_field');
  }
  elseif ($op == 'views data') {
    return array();
  }
}

/**
 * Implementation of hook_field().
 */
function noderelationships_field($op, $node, $field, &$items, $teaser, $page) {
  if ($op == 'load') {
    // This is necessary so that we can use features such as
    // content_view_field() invoked by Panels integration, etc.
    return array($field['field_name'] => array(NULL));
  }
}

/**
 * Implementation of hook_content_is_empty().
 */
function noderelationships_content_is_empty($item, $field) {
  // Back reference fields are not stored in database, so tell CCK they are
  // always empty.
  return TRUE;
}

/**
 * Implementation of hook_widget_info().
 */
function noderelationships_widget_info() {
  return array(
    'noderelationships_backref' => array(
      'label' => t('Back reference'),
      'field types' => array('noderelationships_backref'),
      'multiple values' => CONTENT_HANDLE_MODULE,
    ),
  );
}

/**
 * Implementation of hook_widget().
 */
function noderelationships_widget(&$form, &$form_state, $field, $items, $delta = NULL) {
  // Back reference fields do not provide any widget in the node edit form.
  return array();
}

/**
 * Implementation of hook_field_formatter_info().
 *
 * @see theme_noderelationships_formatter_default()
 */
function noderelationships_field_formatter_info() {
  return array(
    'default' => array(
      'label' => t('Back references view'),
      'field types' => array('noderelationships_backref'),
      'multiple values' => CONTENT_HANDLE_MODULE,
    ),
    'count' => array(
      'label' => t('Back references count'),
      'field types' => array('noderelationships_backref'),
      'multiple values' => CONTENT_HANDLE_MODULE,
    ),
  );
}

/**
 * Alter variables used for content-admin-field-overview-form.tpl.php.
 */
function noderelationships_preprocess_content_field_overview_form(&$vars) {
  module_load_include('inc', 'noderelationships');
  module_load_include('inc', 'noderelationships', 'noderelationships.admin');
  _noderelationships_preprocess_content_field_overview_form($vars);
}

/**
 * Implementation of hook_content_extra_fields().
 */
function noderelationships_content_extra_fields($nodetype) {
  $extra = array();

  // Include common module functions.
  module_load_include('inc', 'noderelationships');

  // Obtain back reference settings for this type.
  $backref_settings = noderelationships_settings_load($nodetype, 'backref');
  $backref_regions = $backref_settings['regions'];

  if (!empty($backref_regions[NODERELATIONSHIPS_BACKREF_REGION_PAGE])) {
    $type_names = array();
    foreach (array_keys($backref_regions[NODERELATIONSHIPS_BACKREF_REGION_PAGE]) as $relation_key) {
      list($type_name, $field_name) = explode(':', $relation_key);
      if (!isset($type_names[$type_name])) {
        $type_names[$type_name] = noderelationships_get_localized_content_type_name($type_name);
      }
    }

    // Sort types alphabetically.
    uksort($type_names, '_noderelationships_sort_strncmp');

    $extra['noderelationships'] = array(
      'label' => t('Node relationships'),
      'description' => t('Back reference views for the following types: @types.', array('@types' => implode(', ', $type_names))),
      'weight' => noderelationships_get_element_weight($nodetype),
    );
  }

  return $extra;
}

/**
 * Implementation of hook_content_fieldapi().
 */
function noderelationships_content_fieldapi($op, $field) {
  // Watch for changes in nodereference fields.
  if ($op != 'read instance' && $field['type'] == 'nodereference') {
    module_load_include('inc', 'noderelationships');

    if ($op == 'delete instance') {
      // Delete relations related to this field instance.
      $args = array($field['type_name'], $field['field_name']);
      noderelationships_settings_delete("relation_type = 'noderef' AND type_name = '%s' AND field_name = '%s'", $args);
      noderelationships_settings_delete("relation_type = 'backref' AND related_type = '%s' AND field_name = '%s'", $args);
    }

    // Clear cached information about relationships (all operations).
    noderelationships_cache_clear();
  }
}

/**
 * Implementation of hook_node_type().
 */
function noderelationships_node_type($op, $info) {
  module_load_include('inc', 'noderelationships');

  if ($op == 'update') {
    // Deal with content type name changes.
    if (!empty($info->old_type) && $info->old_type != $info->type) {
      noderelationships_settings_rename_type($info->old_type, $info->type);
    }
  }
  elseif ($op == 'delete') {
    // Delete relationship settings related to the content type being deleted.
    noderelationships_settings_delete("type_name = '%s' OR related_type = '%s'", array($info->type, $info->type));
  }

  // Clear cached information about relationships (all operations).
  noderelationships_cache_clear();
}

/**
 * Implementation of hook_nodeapi().
 */
function noderelationships_nodeapi(&$node, $op, $teaser = NULL, $page = NULL) {
  if ($op == 'view') {
    if (!$teaser && $page && $node->build_mode == NODE_BUILD_NORMAL) {
      // Include common module functions.
      module_load_include('inc', 'noderelationships');

      // Obtain back reference settings for this type.
      $backref_settings = noderelationships_settings_load($node->type, 'backref');
      $backref_regions = $backref_settings['regions'];

      // Process the node only if we have back reference settings for this region.
      if (!empty($backref_regions[NODERELATIONSHIPS_BACKREF_REGION_PAGE])) {
        module_load_include('inc', 'noderelationships', 'noderelationships.pages');
        _noderelationships_nodeapi_view($node, $backref_regions[NODERELATIONSHIPS_BACKREF_REGION_PAGE]);
      }
    }
  }
  elseif ($op == 'prepare translation') {
    // Here we can assume translation module is enabled, translation for this
    // content type is enabled and the current user is allowed to translate.

    // Include common module functions.
    module_load_include('inc', 'noderelationships');

    // Obtain settings for nodereference extras for this type.
    $noderef_settings = noderelationships_settings_load($node->type, 'noderef');

    // Proceed only if the "Translate and reference" feature is enabled for
    // any field in this content type.
    if (!empty($noderef_settings['translate_and_reference'])) {
      module_load_include('inc', 'noderelationships', 'noderelationships.translation');
      _noderelationships_nodeapi_prepare_translation($node, $noderef_settings);
    }
  }
}

/**
 * Implementation of hook_form_alter().
 */
function noderelationships_form_alter(&$form, $form_state, $form_id) {
  // Alter CCK administration forms.
  if (in_array($form_id, array('content_field_edit_form', 'content_field_overview_form'))) {
    // Delegate form manipulation to external script.
    module_load_include('inc', 'noderelationships');
    module_load_include('inc', 'noderelationships', 'noderelationships.admin');
    _noderelationships_cck_admin_forms_alter($form, $form_state, $form_id);
    return;
  }

  // Alter views ui / settings forms.
  if (strpos($form_id, 'views_ui_edit_') === 0 && isset($form_state['view']) && isset($form_state['view']->tag)) {
    if ($form_state['view']->tag == NODERELATIONSHIPS_BACKREF_VIEW_TAG || $form_state['view']->tag == NODERELATIONSHIPS_NODEREF_VIEW_TAG) {
      // Delegate form manipulation to external script.
      module_load_include('inc', 'noderelationships');
      module_load_include('inc', 'noderelationships', 'noderelationships.admin');
      _noderelationships_views_ui_form_alter($form, $form_state, $form_id, $form_state['view']->tag);
    }
    return;
  }

  // Alter views ui / clone view form.
  if ($form_id == 'views_ui_add_form' && isset($form['tag'])) {
    if ($form['tag']['#default_value'] == NODERELATIONSHIPS_BACKREF_VIEW_TAG || $form['tag']['#default_value'] == NODERELATIONSHIPS_NODEREF_VIEW_TAG) {
      // Delegate form manipulation to external script.
      module_load_include('inc', 'noderelationships');
      module_load_include('inc', 'noderelationships', 'noderelationships.admin');
      _noderelationships_views_ui_form_alter($form, $form_state, $form_id, $form['tag']['#default_value']);
    }
    return;
  }

  // Alter node form.
  if (isset($form['type']) && isset($form['#node']) && $form['type']['#value'] .'_node_form' == $form_id) {
    $type_name = $form['#node']->type;

    // Include common module functions.
    module_load_include('inc', 'noderelationships');

    // Obtain settings for nodereference extras for this type.
    $noderef_settings = noderelationships_settings_load($type_name, 'noderef');

    // Parent node processing only when we have settings for nodereference extras.
    if (!empty($noderef_settings['search_and_reference_view']) || !empty($noderef_settings['create_and_reference']) || (!empty($noderef_settings['translate_and_reference']) && noderelationships_translation_supported_type($type_name))) {
      module_load_include('inc', 'noderelationships', 'noderelationships.pages');
      _noderelationships_parent_node_form_alter($form, $form_state, $type_name, $noderef_settings);
    }

    // Child node processing when the page request has been flagged to do so
    // from noderelationships_noderef_page().
    if (!empty($_GET['noderelationships_referrer_type']) && !empty($_GET['noderelationships_field_name'])) {
      module_load_include('inc', 'noderelationships', 'noderelationships.pages');
      _noderelationships_child_node_form_alter($form, $_GET['noderelationships_referrer_type'], $_GET['noderelationships_field_name']);
    }
    return;
  }
}

/**
 * Implementation of hook_init().
 *
 * @see noderelationships_noderef_page_create().
 */
function noderelationships_init() {
  // Enable the Modal Frame API for the child window.
  if (!empty($_GET['noderelationships_referrer_type']) && !empty($_GET['noderelationships_field_name'])) {
    modalframe_child_js();
  }
}

/**
 * Proxy function to invoke node form after_build and pre_render callbacks,
 * because the file where the real handler is implemented might not be included
 * yet when the form is processed and invokes the callback.
 */
function _noderelationships_parent_node_form_scanner_proxy($form) {
  module_load_include('inc', 'noderelationships');
  module_load_include('inc', 'noderelationships', 'noderelationships.pages');
  return _noderelationships_parent_node_form_scanner($form);
}

/**
 * Proxy function to invoke node form after_build and pre_render callbacks,
 * because the file where the real handler is implemented might not be included
 * yet when the form is processed and invokes the callback.
 */
function _noderelationships_child_node_form_pre_render_proxy($form) {
  module_load_include('inc', 'noderelationships');
  module_load_include('inc', 'noderelationships', 'noderelationships.pages');
  return _noderelationships_child_node_form_pre_render($form);
}
