EVOLUTION-NINJA
Edit File: connector.module
<?php /** * @file * Connector module */ //TODO: Make the user edit page more themable by other modules/themes - they should be able to rearrange or hide parts of it //TODO: Check whether 'invalidate old info' is set or not in hook_requirements()? //TODO: Enable user to remove itself/it's connection from the site? //TODO: React on disabling and uninstalling of another connector module //TODO: Show indication of the connection when logged in? //TODO: Is there really a need for a separate avatar callback? //TODO: Make it possible to upload an icon for a button? //TODO: Make it possible to disable default login, create account etc? //TODO: Always refresh info on log in? Because we will most certainly have access to the info then. Need to make sure not to refresh too often though //TODO: Show status of connections to at least the admin - do that on eg. the user's profile page //TODO: Should the backing of really be in this module? //TODO: Move syncing of info to a hook-system that everyone can hook into? //TODO: Provide more hooks! //TODO: Remove $uid from info callbacks? /** * Implements hook_theme(). */ function connector_theme() { return array( 'connector_buttons' => array( 'render element' => 'form', ), 'connector_connections_list_tableselect' => array( 'render element' => 'form', 'file' => 'connector.pages.inc', ), ); } /** * Implements hook_menu(). */ function connector_menu() { $items = array(); $items['user/%user/connections'] = array( 'title' => 'Connections', 'page callback' => 'connector_user_settings', 'page arguments' => array(1), 'access callback' => 'connector_user_settings_access_callback', 'access arguments' => array(1), 'type' => MENU_LOCAL_TASK, 'file' => 'connector.pages.inc', 'weight' => 2, ); $items['user/%user/connections/%/delete'] = array( 'title' => 'Remove connection', 'page callback' => 'drupal_get_form', 'page arguments' => array('connector_user_delete_form', 1, 3), 'access callback' => 'connector_user_settings_access_callback', 'access arguments' => array(1), 'file' => 'connector.pages.inc', ); $items['user/%user/connections/%/sync'] = array( 'title' => 'Sync profile with connection', 'page callback' => 'drupal_get_form', 'page arguments' => array('connector_user_sync_form', 1, 3), 'access callback' => 'connector_user_sync_access_callback', 'access arguments' => array(1), 'file' => 'connector.pages.inc', ); $items['connect/%'] = array( 'page callback' => 'connector_redirect_callback', 'page arguments' => array(1), 'access callback' => 'connector_redirect_access', 'access arguments' => array(1), ); return $items; } /** * Menu callback for the direct url. */ function connector_redirect_callback($connector_name) { global $user; if (!($connector = _connector_get_connectors($connector_name))) { return FALSE; } $callback = 'button callback'; if ($user && $user->uid != 0) { $callback = 'connect button callback'; } if (!is_callable($connector[$callback])) { return FALSE; } // We make sure there is a default destination to avoid redirect loops. if (!isset($_GET['destination'])) { $_GET['destination'] = '/'; } $action = 'default'; if (arg(2) && connector_actions(arg(2))) { $action = arg(2); } // this is normally a submit handler... we mimic that. $form = array(); $form_state = array(); $form_state['clicked_button']['connector']['#value'] = $connector; $form_state['values']['action'] = $action; $connector[$callback]($form, $form_state); } /** * Access callback for the direct url */ function connector_redirect_access($connector_name) { if (!($connector = _connector_get_connectors($connector_name))) { return FALSE; } return user_access('connect with ' . $connector['name']); } function connector_user_settings_access_callback($account) { return (bool) (user_edit_access($account) && user_access('access connections tab')); } function connector_user_sync_access_callback($account) { return (bool) (user_edit_access($account) && user_access('sync local profile with connections')); } /** * Implements hook_permission(). */ function connector_permission() { $perms = array( 'access connections tab' => array( 'title' => t('Access connections tab'), 'description' => t('Access the tab with connections to administer connections to external sites for a user.') ), 'sync local profile with connections' => array( 'title' => t('Sync local profile with connections'), 'description' => t('Sync a local profile with a connection.') ), ); $connectors = _connector_get_connectors(); foreach ($connectors as $connector) { $perms['connect with ' . $connector['name']] = array( 'title' => t('Connect with @title', array('@title' => $connector['title'])), 'description' => t('Allow user to make a connection with #title to login, register on this site or do stuff on #title in name of the user.', array('#title' => $connector['title'])), ); } return $perms; } /** * Implements hook_block_info(). */ function connector_block_info() { $block['one_click_block']['info'] = t('Connector'); return $block; } /** * Implements hook_block_view(). */ function connector_block_view($delta) { global $user; switch ($delta) { case 'one_click_block': if (!$user->uid) { $state = array('build_info' => array('args' => array())); $form = drupal_retrieve_form('connector_button_form', $state); if($form['#has_buttons']) { return array( 'content' => drupal_get_form('connector_button_form'), ); } } break; } } /** * Implements hook_user_cancel(). */ function connector_user_cancel($edit, $account, $method) { $connectors = _connector_get_connectors(); $connections = _connector_get_user_connections($account); foreach ($connections as $connection) { if (array_key_exists($connection->connector, $connectors)) { $connector = $connectors[$connection->connector]; if (isset($connector['delete callback']) && is_callable($connector['delete callback'])) { call_user_func($connector['delete callback'], $connector, $connection->cid); } } } db_delete('connector_user') ->condition('uid', $account->uid) ->execute(); db_delete('connector_info') ->condition('uid', $account->uid) ->execute(); } /** * Implements hook_user_logout(). */ function connector_user_logout($account) { $connectors = _connector_get_connectors(); $connections = _connector_get_user_connections($account); foreach ($connections as $connection) { if (array_key_exists($connection->connector, $connectors)) { $connector = $connectors[$connection->connector]; if (isset($connector['logout callback']) && is_callable($connector['logout callback'])) { call_user_func($connector['logout callback'], $connector, $connection->cid); } } } } /** * @todo Please document this function. * @see http://drupal.org/node/1354 */ function connector_button_form($form, &$form_state, $account = FALSE, $label = 'Connect with !title', $action = 'default') { $form = array( '#theme' => 'connector_buttons', '#has_buttons' => FALSE, ); $form['action'] = array( '#value' => $action, '#type' => 'value', ); $i = 0; $connectors = _connector_get_connectors(); if ($account && $account->uid != 0) { $callback = 'connect button callback'; } else { $callback = 'button callback'; } foreach ($connectors as $key => $connector) { if (user_access('connect with ' . $connector['name']) && isset($connector[$callback]) && is_callable($connector[$callback])) { $form[$key] = array( '#type' => 'submit', '#value' => t($label, array('!title' => $connector['title'])), '#submit' => array($connector[$callback]), 'connector' => array( '#type' => 'value', '#value' => $connector, ), ); $form['#has_buttons'] = TRUE; } } return $form; } function connector_actions($action_name) { static $connector_actions; if (!isset($connector_actions)) { $connector_actions = (array) module_invoke_all('connector_action'); foreach (array_keys($connector_actions) as $key) { if (!isset($connector_actions[$key]['name'])) { $connector_actions[$key]['name'] = $key; } } drupal_alter('connector_action', $connector_actions); } if ($action_name) { if (array_key_exists($action_name, $connector_actions)) { return $connector_actions[$action_name]; } else { return FALSE; } } return $connector_actions; } function connector_connector_action() { return array( 'default' => array( 'login callback' => '_connector_log_in', 'create account callback' => '_connector_create_account', 'add connection callback' => '_connector_add_connection', 'no external uid' => NULL, ), ); } function _connector_get_connectors($connector = NULL) { static $connectors; if (!isset($connectors)) { $connectors = (array) module_invoke_all('connector'); // Make sure all connectors has a reference to their own name foreach (array_keys($connectors) as $key) { if (!isset($connectors[$key]['name'])) { $connectors[$key]['name'] = $key; } } drupal_alter('connector', $connectors); } if ($connector) { if (array_key_exists($connector, $connectors)) { return $connectors[$connector]; } else { return FALSE; } } return $connectors; } function _connector_get_user_connections($uid) { $connectors = array(); if (is_object($uid)) { $uid = $uid->uid; } $result = db_query("SELECT authname FROM {authmap} WHERE module = :module AND uid = :uid", array(':module' => 'connector', ':uid' => $uid)); foreach ($result as $row) { $row = explode('__', $row->authname, 2); if (count($row) === 2) { $connectors[] = (object) array( 'connector' => $row[0], 'cid' => $row[1], ); } } return $connectors; } function _connector_get_primary_connection($uid) { $result = FALSE; if (is_object($uid)) { $uid = $uid->uid; } $primary_connection = db_query("SELECT primary_connection FROM {connector_user} WHERE uid = :uid", array(':uid' => $uid))->fetchField(); if ($primary_connection) { $row = explode('__', $primary_connection, 2); if (count($row) === 2) { $result = (object) array( 'connector' => $row[0], 'cid' => $row[1], ); } } return $result; } function _connector_set_primary_connection($uid, $connection) { if (is_object($uid)) { $uid = $uid->uid; } if (is_object($connection)) { $connection = ($connection->connector . '__' . $connection->cid); } db_delete('connector_user') ->condition('uid', $uid) ->execute(); db_insert('connector_user') ->fields(array( 'uid' => $uid, 'primary_connection' => $connection, )) ->execute(); } function _connector_log_in($connector_name, $cid = NULL, $consumer = NULL, $access_token = NULL, $request_token = NULL) { global $user; if (user_is_logged_in()) { return TRUE; } $connector = _connector_get_connectors($connector_name); if (!$connector) { return FALSE; } //Fetch connector ID if ($cid === NULL && isset($connector['id callback']) && is_callable($connector['id callback'])) { $cid = call_user_func($connector['id callback'], $connector); } if ($cid !== NULL) { $authname = $connector_name . '__' . $cid; $account = user_external_load($authname); if (!$account) { // Return NULL and not FALSE so that we know we didn't find a user. return NULL; } if ($account) { if ($account->status) { if (variable_get('user_email_verification', FALSE) && !$account->login) { // User still needs to verify the emailaddress. drupal_set_message(t('Your account is currently pending e-mail verification. You have receveid a email with further instructions. !link to start a new e-mail verification.', array('!link' => l('Request a new password', 'user/password'))), 'warning'); if (isset($connector['logout callback']) && is_callable($connector['logout callback'])) { call_user_func($connector['logout callback'], $connector, $connection->cid); } } else { //Log in user $form_state['uid'] = $account->uid; user_login_submit(array(), $form_state); return TRUE; } } else { drupal_set_message(t('Your account is currently pending approval by the site administrator.'), 'warning'); // why logout when account is pending? may be it is intentional. if (isset($connector['logout callback']) && is_callable($connector['logout callback'])) { call_user_func($connector['logout callback'], $connector, $connection->cid); } } } } return FALSE; } function _connector_create_account($connector_name, $cid = NULL, $consumer = NULL, $access_token = NULL, $request_token = NULL) { $connector = _connector_get_connectors($connector_name); if (!$connector) { return FALSE; } $authname = $connector_name . '__' . $cid; if (variable_get('user_register', 1)) { $userinfo = array( 'name' => $authname, 'pass' => user_password(), 'init' => $authname, 'status' => variable_get('user_register', 1) == 1, 'access' => REQUEST_TIME, ); // Try to assign values from connection. $info = array(); if (!empty($connector['information callback']) && is_callable($connector['information callback'])) { $info = $connector['information callback']($connector, $cid, array(), $access_token); } $allowed_fields = array('name', 'mail'); foreach ($info as $field) { if (isset($field['sync']) && $field['sync'] && in_array($field['sync'], $allowed_fields)) { $exists = db_select('users', 'u') ->fields('u') ->condition($field['sync'], $field['value']) ->execute() ->rowCount(); if ($exists < 1) { $userinfo[$field['sync']] = $field['value']; } } } $new_account = user_save('', $userinfo); // Terminate if an error occured during user_save(). if (!$new_account) { drupal_set_message(t("Error saving user account."), 'error'); } else { watchdog('user', 'New external user: %name using module %module.', array('%name' => $authname, '%module' => 'connector'), WATCHDOG_NOTICE, l(t('edit'), 'user/' . $new_account->uid . '/edit')); return $new_account; } } else { drupal_set_message(t('Only site administrators can create new user accounts.'), 'error'); if (isset($connector['logout callback']) && is_callable($connector['logout callback'])) { call_user_func($connector['logout callback'], $connector, $connection->cid); } } } function _connector_add_connection($connector_name, $cid = NULL, $uid = NULL) { global $user; $connector = _connector_get_connectors($connector_name); if (!$connector) { return FALSE; } $result = FALSE; if (empty($uid)) { $uid = $user->uid; } elseif (is_object($uid)) { $uid = $uid->uid; } //Fetch connector ID if ($cid === NULL && isset($connector['id callback']) && is_callable($connector['id callback'])) { $cid = call_user_func($connector['id callback'], $connector); } // Check that we have an external id to connect with if ($cid !== NULL) { $authname = $connector_name . '__' . $cid; $account = user_external_load($authname); // Check if the external id already connected to someone if ($account) { db_delete('authmap') ->condition('uid', $account->uid) ->condition('authname', $authname) ->execute(); } $result = (bool) db_insert('authmap') ->fields(array( 'uid' => $uid, 'authname' => $authname, 'module' => 'connector', )) ->execute(); if (!_connector_get_primary_connection($uid)) { _connector_set_primary_connection($uid, $authname); } } return $result; } /** * @todo Please document this function. * @see http://drupal.org/node/1354 */ function theme_connector_buttons($variables) { $form = $variables['form']; if (!$form['#has_buttons']) return NULL; $output = ''; $buttons = array(); foreach (element_children($form) as $key) { if ($form[$key]['#type'] == 'submit') { $class = str_replace('_', '-', $key); $form[$key]['#attributes']['class'][] = 'connector-button'; $form[$key]['#attributes']['class'][] = $class; $form[$key]['#prefix'] = '<span class="connector-button-wrapper ' .$class . '">'; $form[$key]['#suffix'] = '</span>'; } } return drupal_render_children($form); }