$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' => '
', ); // 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' => '
', ); } } if ($content) { $content['#prefix'] = '

'; $content['#suffix'] = '

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