You are here

content_profile.module in Content Profile 6

File

content_profile.module
View source
<?php

/**
 * @file
 * Marks content types as profiles.
 */
require_once dirname(__FILE__) . '/content_profile.theme_vars.inc';

/**
 * 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',
    );
  }

  // Imagecache presets to link to user profile page.
  if (module_exists('imagecache')) {
    foreach (imagecache_presets() as $preset) {
      $return['content_profile_formatter_' . $preset['presetname'] . '_userlink'] = array(
        'arguments' => array(
          'element' => NULL,
        ),
        'function' => 'theme_content_profile_imagecache_formatter_userlink',
        'file' => 'content_profile.theme.inc',
      );
    }
  }
  return $return;
}

/**
 * Implementation of hook_field_formatter_info().
 *
 * Imagecache formatters are named as $presetname_$style
 * $style is used to determine how the preset should be rendered.
 * If you are implementing custom imagecache formatters please treat _ as
 * reserved.
 *
 * These presets allow linking the image to the user profile page, rather than
 * the profile node.
 */
function content_profile_field_formatter_info() {
  $formatters = array();
  if (module_exists('imagecache')) {
    foreach (imagecache_presets() as $preset) {
      $formatters[$preset['presetname'] . '_userlink'] = array(
        'label' => t('@preset image linked to user profile', array(
          '@preset' => $preset['presetname'],
        )),
        'field types' => array(
          'image',
          'filefield',
        ),
      );
    }
  }
  return $formatters;
}

/**
 * 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',
    ),
  );
}

Functions

Namesort descending Description
content_profile_admin_settings Menu callback; content profile settings.
content_profile_admin_settings_submit
content_profile_content_profile_settings Implementation of hook_content_profile_settings().
content_profile_ctools_plugin_directory Implementation of hook_ctools_plugin_directory().
content_profile_field_formatter_info Implementation of hook_field_formatter_info().
content_profile_form_alter Implementation of hook_form_alter().
content_profile_get_settings Returns the content_profiles' settings.
content_profile_get_types 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.
content_profile_help Implementation of hook_help().
content_profile_load Loads the node, like node_load but makes sure the results are cached.
content_profile_menu Implementation of hook_menu().
content_profile_menu_alter Implementation of hook_menu_alter(). Take over menu items generated by the user module for our categories.
content_profile_nodeapi Implementation of hook_nodeapi().
content_profile_node_type Implementation of hook_node_type(). Rename or delete the settings variable if a type changes.
content_profile_pageroute_info Implementation of hook_pageroute_info() for Pageroute integration.
content_profile_page_access
content_profile_page_edit Presents a node editing or adding form for the given content profile.
content_profile_profile_exists Checks whether a node of this type exists already for the author
content_profile_settings_info Returns an array, which defines the available settings and their default value.
content_profile_set_settings Saves the content_profile settings of a content type.
content_profile_show_profiles Returns an array suitable for use with drupal_render, that shows all content_profiles as configured by the admin.
content_profile_simpletest Implementation of hook_simpletest().
content_profile_template_preprocess Adds $content_profile variable if we can find a $uid.
content_profile_theme Implementation of hook_theme().
content_profile_theme_registry_alter 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.
content_profile_user Implementation of hook_user().
content_profile_views_api Implementation of hook_views_api().
is_content_profile Determine if a given node is a content_profile.
_content_profile_node_delete 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…