<?php
// $Id: content_profile.module,v 1.1.2.48 2010/04/07 15:09:17 fago Exp $

/**
 * @file
 * Marks content types as profiles.
 */


/**
 * Implementation of hook_init().
 */
function content_profile_init() {
  module_load_include('inc', 'content_profile', 'content_profile.theme_vars');
}

/**
 * Implementation of hook_ctools_plugin_directory().
 */
function content_profile_ctools_plugin_directory($module, $plugin) {
  if ($module == 'ctools' && $plugin == 'relationships') {
    return 'panels/' . $plugin;
  }
}

/**
 * Implementation of hook_menu().
 */
function content_profile_menu() {
  $items = array();

  //Register a path for each content profile type
  foreach (content_profile_get_types('names') as $type => $typename) {
    $items['admin/content/node-type/'. str_replace('_', '-', $type) .'/edit'] = array(
      'title' => 'Edit',
      'type' => MENU_DEFAULT_LOCAL_TASK,
    );
    $items['admin/content/node-type/'. str_replace('_', '-', $type) .'/profile'] = array(
      'title' => 'Content profile',
      'description' => 'Configure the display and management of this content profile.',
      'page callback' => 'drupal_get_form',
      'page arguments' => array('content_profile_admin_settings', $type),
      'access callback' => 'user_access',
      'access arguments' => array('administer nodes'),
      'type' => MENU_LOCAL_TASK,
      'weight' => 1,
    );
  }
  foreach (content_profile_get_types('names') as $type => $type_name) {
    $items['user/%user/profile/'. $type] = array(
      'title callback' => 'check_plain',
      'title' => drupal_ucfirst($type_name),
      'page callback' => 'content_profile_page_edit',
      'page arguments' => array($type, 1),
      'access callback' => 'content_profile_page_access',
      'access arguments' => array($type, 1),
      'weight' => content_profile_get_settings($type, 'weight'),
      'file' => 'node.pages.inc',
      'file path' => drupal_get_path('module', 'node'),
      'type' => content_profile_get_settings($type, 'edit_tab') == 'top' ? MENU_LOCAL_TASK : MENU_CALLBACK,
    );
  }
  return $items;
}

/**
 * Implementation of hook_menu_alter().
 * Take over menu items generated by the user module for our categories.
 */
function content_profile_menu_alter(&$items) {
  foreach (content_profile_get_types('names', 'edit_tab', 'sub') as $type => $type_name) {
    if (!empty($items['user/%user_category/edit/'. $type])) {
      $item = &$items['user/%user_category/edit/'. $type];
      $item = array(
        'page callback' => 'content_profile_page_edit',
        'page arguments' => array($type, 1),
        'access callback' => 'content_profile_page_access',
        'access arguments' => array($type, 1),
        'file' => 'node.pages.inc',
        'file path' => drupal_get_path('module', 'node'),
      ) + $item;
    }
  }
}


function content_profile_page_access($type, $account) {
  if ($node = content_profile_load($type, $account->uid)) {
    return node_access('update', $node);
  }
  // Else user may view the page when they are going to create their own profile
  // or have permission to create it for others.
  global $user;
  if ($user->uid == $account->uid || user_access('administer nodes') ){
    return node_access('create', $type);
  }
  return FALSE;
}

/**
 * Presents a node editing or adding form for the given content profile.
 */
function content_profile_page_edit($type, $account) {
  drupal_set_title(check_plain($account->name));
  $node = content_profile_load($type, $account->uid);
  if (!$node) {
    $node = array('uid' => $account->uid, 'name' => (isset($account->name) ? $account->name : ''), 'type' => $type, 'language' => '');
  }
  return drupal_get_form($type .'_node_form', $node);
}


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


/**
 * Menu callback; content profile settings.
 */
function content_profile_admin_settings(&$form_state, $type) {
  $form_state['type'] = $type;

  $form['weight'] = array(
    '#type' => 'weight',
    '#title' => t("Weight"),
    '#default_value' => content_profile_get_settings($type, 'weight'),
    '#description' => t('The weight of content of this content type where ever they appear - this applies to the input form integration as well to the display integration.'),
    '#weight' => 5,
  );
  $form['display'] = array(
    '#type' => 'fieldset',
    '#title' => t('Display settings'),
    '#description' => t('Customize the display of this content profile.'),
    '#collapsible' => TRUE,
  );
  $form['display']['user_display'] = array(
    '#type' => 'radios',
    '#title' => t("User page display style"),
    '#default_value' => content_profile_get_settings($type, 'user_display'),
    '#options' => array(
      0 => t("Don't display this content profile on the user account page"),
      'link' => t('Display it as link to the profile content'),
      'full' => t('Display the full content'),
      'teaser' => t("Display the content's teaser"),
    ),
  );
  $form['display']['edit_link'] = array(
    '#type' => 'checkbox',
    '#title' => t("Include an edit link to the display"),
    '#default_value' => content_profile_get_settings($type, 'edit_link'),
  );
  $form['display']['add_link'] = array(
    '#type' => 'checkbox',
    '#title' => t("Show a link to the content profile creation page, if there is no profile."),
    '#default_value' => content_profile_get_settings($type, 'add_link'),
    '#description' => t("If selected and the user has no profile of this type yet, a link to add one is shown on the user page."),
  );
  $form['display']['edit_tab'] = array(
    '#type' => 'radios',
    '#title' => t("Profile edit tab"),
    '#default_value' => content_profile_get_settings($type, 'edit_tab'),
    '#options' => array(
      0 => t('None'),
      'top' => t("Show a tab at the user's page"),
      'sub' => t("Show a secondary tab below the user's edit tab"),
    ),
  );
  $form['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Submit'),
    '#weight' => 10,
  );
  return $form;
}

function content_profile_admin_settings_submit($form, &$form_state) {
  $settings = content_profile_get_settings($form_state['type']);
  foreach (content_profile_settings_info() as $setting => $default) {
    if (isset($form_state['values'][$setting])) {
      $settings[$setting] = $form_state['values'][$setting];
    }
  }
  content_profile_set_settings($form_state['type'], $settings);
  drupal_set_message('Your changes have been saved.');
  menu_rebuild();
}

/**
 * Determine if a given node is a content_profile.
 * @param $type
 *   The node object or the node's type
 */
function is_content_profile($type) {
  if (is_object($type)) {
    $type = $type->type;
  }
  return variable_get('content_profile_use_'. $type, FALSE);
}

/**
 * Builds a list of available content types that are marked as content_profiles,
 * and returns an array of content profile content types in the specified format.
 *
 * @param $op
 *   When set to 'types', content profile content types are returned
 *   as type objects. When set to 'names', only their type names are returned.
 * @param $setting
 *   If set, only content types that have this setting activated are returned.
 *   Leave it NULL to get all content profile types.
 * @param $value
 *   The value to compare the given setting too.
 */
function content_profile_get_types($op = 'types', $setting = NULL , $value = TRUE) {
  $types = array();

  foreach (node_get_types($op) as $type => $info) {
    if (is_content_profile($type) && (!isset($setting) || content_profile_get_settings($type, $setting) == $value)) {
      $types[$type] = $info;
    }
  }
  return $types;
}

/**
 * Implementation of hook_node_type().
 * Rename or delete the settings variable if a type changes.
 */
function content_profile_node_type($op, $info) {
  switch ($op) {
    case 'delete':
      variable_del('content_profile_use_'. $info->type);
      variable_del('content_profile_'. $info->type);
      break;
    case 'update':
      if (!empty($info->old_type) && $info->old_type != $info->type) {
        if (is_content_profile($info->old_type)) {
          $settings = variable_get('content_profile_'. $info->old_type, array());
          variable_del('content_profile_use_'. $info->old_type);
          variable_del('content_profile_'. $info->old_type);
          variable_set('content_profile_use_'. $info->type, 1);
          variable_set('content_profile_'. $info->type, $settings);
        }
      }
      break;
  }
}

/**
 * Implementation of hook_form_alter().
 */
function content_profile_form_alter(&$form, $form_state, $form_id) {
  if ($form_id == 'node_type_form') {
    $form['content_profile'] = array(
      '#type' => 'fieldset',
      '#title' => t('Content Profile'),
      '#group' => 'additional_settings',
      '#collapsible' => TRUE,
      '#collapsed' => TRUE,
      '#weight' => 32,
    );
    $form['content_profile']['content_profile_use'] = array(
      '#type' => 'checkbox',
      '#title' => t('Use this content type as a content profile for users'),
      '#default_value' => variable_get('content_profile_use_'. $form['#node_type']->type, FALSE),
    );
  }
  elseif (isset($form['#node']) && $form['#node']->type .'_node_form' == $form_id && is_content_profile($form['#node'])) {
    // Customize the redirect target and buttons of our own node forms.
    if (arg(0) == 'user' && is_numeric(arg(1)) && arg(2) == 'edit' || arg(2) == 'profile') {
      $form['buttons']['preview']['#access'] = FALSE;
      $form['buttons']['delete']['#access'] = FALSE;
      $form['#redirect'] = arg(2) == 'profile' ? 'user/'. $form['#node']->uid : $_GET['q'];
    }
    // Set the author value - note that this works only for admins.
    if (!empty($_GET['uid']) && ($uid = intval($_GET['uid'])) && ($user = user_load($uid))) {
      $form['author']['name']['#default_value'] = $user->name;
    }
  }
}

/**
 * Implementation of hook_user().
 */
function content_profile_user($op, &$edit, &$account, $category = NULL) {
  global $user;

  switch ($op) {
    case 'categories':
      $data = array();
      foreach (content_profile_get_types('names', 'edit_tab', 'sub') as $type => $type_name) {
        $data[] = array(
          'name' => $type,
          'title' => drupal_ucfirst($type_name),
          'weight' => content_profile_get_settings($type, 'weight') + 1,
        );
      }
      return $data;

    case 'view':
      $account->content['content_profile'] = content_profile_show_profiles($account->uid);
      break;

    case 'delete':
      // Retrieve all profile nodes (in any language) for this user by issueing an SQL query.
      if ($types = content_profile_get_types()) {
        $condition = array_fill(0, count($types), "type = '%s'");
        $arguments = array_merge(array_keys($types), array($account->uid));

        $result = db_query("SELECT * FROM {node} WHERE (". implode(' OR ', $condition) .") AND uid = %d", $arguments);
        while ($node = db_fetch_object($result)) {
          _content_profile_node_delete($node);
        }
      }
      break;
  }
}

/**
 * The original node_delete() function uses node_load() to get the $node object.
 * Unfortunately, when a hook_user('delete') is called, node_load() doesn't
 * work anymore because the user has already been deleted, and node_load()
 * still expects the user to exist in the {user} table.
 *
 * So this is a modified copy of node_delete() that deletes a node without
 * calling node_load(), taking the full $node object (as retrieved by a simple
 * "SELECT * FROM {node}" query) instead of just the $nid.
 */
function _content_profile_node_delete($node) {
  // Copied over from node_load(), so that node_invoke('delete') gets
  // the fully extended node object, like modules would expect:

  if ($node->nid) {
    // Call the node specific callback (if any) and piggy-back the
    // results to the node or overwrite some values.
    if ($extra = node_invoke($node, 'load')) {
      foreach ($extra as $key => $value) {
        $node->$key = $value;
      }
    }
    if ($extra = node_invoke_nodeapi($node, 'load')) {
      foreach ($extra as $key => $value) {
        $node->$key = $value;
      }
    }
  }

  // Copied over from node_delete():

  db_query('DELETE FROM {node} WHERE nid = %d', $node->nid);
  db_query('DELETE FROM {node_revisions} WHERE nid = %d', $node->nid);

  // Call the node-specific callback (if any):
  node_invoke($node, 'delete');
  node_invoke_nodeapi($node, 'delete');

  // Clear the cache so an anonymous poster can see the node being deleted.
  cache_clear_all();

  // Remove this node from the search index if needed.
  if (function_exists('search_wipe')) {
    search_wipe($node->nid, 'node');
  }
  watchdog('content', '@type: deleted %title.', array('@type' => $node->type, '%title' => $node->title));
  drupal_set_message(t('@type %title has been deleted.', array('@type' => node_get_types('name', $node), '%title' => $node->title)));
}

/**
 * Implementation of hook_nodeapi().
 */
function content_profile_nodeapi(&$node, $op, $a3 = NULL, $a4 = NULL) {

  if ($op == 'prepare' && is_content_profile($node) && !isset($node->nid) && $node->uid && !user_access('administer nodes') && arg(0) != 'admin') {
    // Check if this nodetype already exists
    if ($nid = content_profile_profile_exists($node, $node->uid)) {
      // This node already exists, redirect to edit page
      drupal_goto('node/'. $nid .'/edit', 'destination=user/'. $node->uid);
    }
  }
  elseif ($op == 'validate' && is_content_profile($node) && user_access('administer nodes')) {
    $form = $a3;
    // Only validate if the user-name changed or we add a new node
    if (!empty($node->nid) && $form['author']['name']['#default_value'] == $node->name) {
      return;
    }
    //check whether the selected user has already a profile
    $uid = db_result(db_query("SELECT uid FROM {users} WHERE name = '%s'", $node->name));
    if ($uid && content_profile_profile_exists($node, $uid)) {
      form_set_error('name', t('This user already has a content profile of this type. You can only create one profile per user.'));
    }
  }
  elseif ($op == 'prepare translation' && is_content_profile($node->translation_source)) {
    // Make sure the translated profile belongs to the same user.
    $node->uid = $node->translation_source->uid;
    $node->name = $node->translation_source->name;
  }
}

/**
 * Checks whether a node of this type exists already for the author
 *
 * @param $node
 *   The node, which is to be created.
 * @param $uid
 *   The user to check for.
 * @return If a node exists, the node id, FALSE else.
 */
function content_profile_profile_exists($node, $uid) {
  $query = "SELECT n.nid AS nid FROM {node} n WHERE n.type = '%s' AND n.uid = %d ";
  if (module_exists('translation') && translation_supported_type($node->type)) {
    $query .= "AND n.language = '%s'";
  }
  return db_result(db_query($query, $node->type, $uid, $node->language));
}

/**
 * Returns the content_profiles' settings.
 * @param $type
 *   The content type to return settings for.
 * @param $return
 *   'all' or one of the content_profile_available_settings(),
 *   e.g. user_edit, register or weight.
 */
function content_profile_get_settings($type, $return = 'all') {
  $settings = variable_get('content_profile_'. $type, array());
  $settings += content_profile_settings_info();
  if ($return == 'all') {
    return $settings;
  }
  return $settings[$return];
}

/**
 * Saves the content_profile settings of a content type.
 */
function content_profile_set_settings($type, $settings) {
  variable_set('content_profile_'. $type, $settings);
}

/**
 * Returns an array, which defines the available settings
 * and their default value.
 */
function content_profile_settings_info() {
  return module_invoke_all('content_profile_settings');
}

/**
 * Implementation of hook_content_profile_settings().
 *
 * Defines content profile settings and their default value.
 */
function content_profile_content_profile_settings() {
  return array(
    'weight' => 0,
    'user_display' => 'full',
    'edit_link' => 0,
    'edit_tab' => 'sub',
    'add_link' => 1,
  );
}

/**
 * Loads the node, like node_load but makes sure the results are cached.
 *
 * @param $type
 *   The content profile's type.
 * @param $uid
 *   The profile owner's user id.
 * @param $lang
 *   Optional. If translation is enabled, the language of the profile to return.
 * @param $reset
 *   Optional. If set, the cache is reset.
 */
function content_profile_load($type, $uid, $lang = '', $reset = NULL) {
  static $cache = array();

  if (!isset($cache[$type][$uid][$lang]) || $reset) {
    $cache[$type][$uid][$lang] = FALSE;
    $params = array('type' => $type, 'uid' => $uid);
    if ($node = node_load($lang ? $params + array('language' => $lang) : $params, NULL, $reset)) {
      $cache[$type][$uid][$lang] = $node->nid;
    }
    return $node;
  }
  return !empty($cache[$type][$uid][$lang]) ? node_load($cache[$type][$uid][$lang]) : FALSE;
}

/**
 * Implementation of hook_help().
 *
 * Show node submission guidelines for content profile node forms.
 */
function content_profile_help($path, $arg) {
  if (preg_match('/user\/\%\/(profile|edit)\/(.*)/', $path, $matches)) {
    foreach (content_profile_get_types('names') as $type => $typename) {
      if ($type == $matches[2]) {
        $node = content_profile_load($type, $arg[1]);
        if ($node) {
          return node_help('node/%/edit', array(1 => $node->nid));
        }
        else {
          return node_help('node/add/'. $type, array('node', 'add', $type));
        }
      }
    }
  }
}

/**
 * Returns an array suitable for use with drupal_render,
 * that shows all content_profiles as configured by the admin.
 */
function content_profile_show_profiles($uid) {
  global $user;

  $content = array();
  foreach (content_profile_get_types('names') as $type => $type_name) {
    $node = content_profile_load($type, $uid);

    if (($style = content_profile_get_settings($type, 'user_display')) && $node && node_access('view', $node)) {
      $content['content_profile_'. $type] = array(
        '#theme' => ($style == 'link') ? 'content_profile_display_link' : 'content_profile_display_view',
        '#edit_link' => content_profile_get_settings($type, 'edit_link'),
        '#uid' => $uid,
        '#style' => $style,
        '#content_type' => $type,
        '#weight' => content_profile_get_settings($type, 'weight'),
        '#suffix' => '<br />',
      );

      // Working around the bug described at http://drupal.org/node/302873
      module_load_include('inc', 'content_profile', 'content_profile.theme');
    }
    elseif (user_access('create '. $type .' content') && content_profile_get_settings($type, 'add_link') && !$node && ($uid == $user->uid || user_access('administer nodes'))) {
      $content['content_profile_'. $type] = array(
        '#admin' => $uid != $user->uid,
        '#theme' => 'content_profile_display_add_link',
        '#uid' => $uid,
        '#content_type' => $type,
        '#weight' => content_profile_get_settings($type, 'weight'),
        '#suffix' => '<br />',
      );
    }
  }
  if ($content) {
    $content['#prefix'] = '<p id="content-profile-view">';
    $content['#suffix'] = '</p>';
  }
  return $content;
}

/**
 * Implementation of hook_theme().
 */
function content_profile_theme() {
  $return = array(
    'content_profile_display_view' => array(
      'template'  => 'content_profile-display-view',
      'arguments' => array('element' => NULL),
      'file' => 'content_profile.theme.inc',
    ),
    'content_profile_display_add_link' => array(
      'file' => 'content_profile.theme.inc',
    ),
    'content_profile_display_link' => array(
      'file' => 'content_profile.theme.inc',
    ),
    'content_profile_display_tab_view' => array(
      'file' => 'content_profile.theme.inc',
    ),
    'content_profile_display_tab_edit' => array(
      'file' => 'content_profile.theme.inc',
    ),
  );
  if (module_exists('pageroute')) {
    $return['content_profile_pageroute_empty']  = array(
      'arguments' => array('type_name' => NULL),
    	'file' => 'content_profile.pageroute.inc',
    );
  }
  return $return;
}

/**
 * Implementation of hook_theme_registry_alter().
 * Adds our own preprocess functions to some templates. Further templates can be set in
 * $conf['content_profile_extra_templates'] in settings.php.
 */
function content_profile_theme_registry_alter(&$items) {
  $templates = array_merge(array(
    'author_pane',
    'comment',
    'node',
    'page',
    'search_result',
    'username',
    'user_profile',
    'user_signature',
  ), variable_get('content_profile_extra_templates', array()));

  foreach ($templates as $key) {
    if (isset($items[$key])) {
      $items[$key] += array('preprocess functions' => array());
      $items[$key]['preprocess functions'][] = 'content_profile_template_preprocess';
    }
  }
}

/**
 * Adds $content_profile variable if we can find a $uid.
 */
function content_profile_template_preprocess(&$variables, $hook) {
  // Search the uid
  foreach (array('account_id', 'uid', 'account', 'node', 'comment', 'user') as $name) {
    if (isset($variables[$name])) {
      $uid = is_object($variables[$name]) ? $variables[$name]->uid : $variables[$name];
      $variables['content_profile'] = new content_profile_theme_variables($uid);
      break;
    }
  }
}

/**
 * Implementation of hook_simpletest().
 */
function content_profile_simpletest() {
  // Scan through content_profile/tests directory for any .test files to tell SimpleTest module.
  $tests = file_scan_directory(drupal_get_path('module', 'content_profile') .'/tests', '\.test');
  return array_keys($tests);
}

/**
 * Implementation of hook_pageroute_info() for Pageroute integration.
 */
function content_profile_pageroute_info() {
  return array(
    'content_profile' => array(
      'viewprofile' => 'content_profile.pageroute',
      'editprofile' => 'content_profile.pageroute',
    )
  );
}
