connector.module in Connector 6
Same filename and directory in other branches
Connector module
File
connector.moduleView source
<?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?
/**
* Implementation of hook_theme().
*/
function connector_theme() {
return array(
'connector_buttons' => array(
'arguments' => array(
'form' => NULL,
),
),
'connector_connections_list_tableselect' => array(
'arguments' => array(
'form' => NULL,
),
),
);
}
/**
* Implementation of hook_menu().
*/
function connector_menu() {
$items = array();
$items['user/%user_category/edit/connector'] = array(
'title' => 'Connections',
'page callback' => 'connector_user_settings',
'page arguments' => array(
1,
),
//TODO: Change the access check to a Connector specific permission?
'access callback' => 'user_edit_access',
'access arguments' => array(
1,
),
'type' => MENU_LOCAL_TASK,
'load arguments' => array(
'%map',
'%index',
),
'file' => 'connector.pages.inc',
);
return $items;
}
/**
* Implementation of hook_init().
*/
function connector_init() {
drupal_add_css(drupal_get_path('module', 'connector') . '/connector.css', 'module');
}
/**
* Implementation of hook_cron().
*/
function connector_cron() {
//TODO: If we don't have time to refresh all data - just remove the ones we didn't have time to refresh?
while (_connector_cron_time()) {
$result = db_query_range("SELECT uid, type, max_life FROM {connector_info} WHERE max_life < %d ORDER BY max_life ASC", array(
':time' => time(),
), 0, 20);
if ($info = db_fetch_object($result)) {
do {
_connector_information_update($info->uid, array(
$info->type => $info->max_life,
));
} while ($info = db_fetch_object($result));
}
else {
break;
}
}
}
/**
* Implementation of hook_block().
*/
function connector_block($op = 'list', $delta = 0) {
global $user;
if ($op == 'list') {
$block['one_click_block']['info'] = t('Connector');
return $block;
}
elseif ($op == 'view') {
switch ($delta) {
case 'one_click_block':
if (!$user->uid) {
return array(
'content' => drupal_get_form('connector_button_form'),
);
}
break;
}
}
}
/**
* Implementation of hook_user().
*/
function connector_user($op, &$edit, &$user, $category = NULL) {
switch ($op) {
case 'delete':
$connectors = _connector_get_connectors();
$connections = _connector_get_user_connections($user);
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_query('DELETE FROM {connector_info} WHERE uid = %d', array(
':uid' => $user->uid,
));
db_query('DELETE FROM {connector_user} WHERE uid = %d', array(
':uid' => $user->uid,
));
break;
case 'logout':
$connectors = _connector_get_connectors();
$connections = _connector_get_user_connections($user);
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);
}
}
}
break;
case 'categories':
return array(
array(
'name' => 'connector',
'title' => 'Connections',
'weight' => 3,
),
);
break;
}
}
/**
* Implementation of hook_realname().
*/
function connector_realname() {
return array(
'name' => 'Connector',
'types' => FALSE,
'fields' => FALSE,
'cache' => FALSE,
);
}
/**
* Implementation of hook_realname_make().
*/
function connector_realname_make($account) {
$info = _connector_information_fetch($account, array(
'real name' => TRUE,
));
if ($info['real name'] === FALSE) {
return NULL;
}
elseif (empty($info['real name'])) {
return t('Hidden name');
}
return $info['real name'];
}
/**
* Implementation of hook_views_api().
*/
function connector_views_api() {
return array(
'api' => 2,
'path' => drupal_get_path('module', 'connector') . '/includes',
);
}
function connector_button_form(&$form_state, $account = FALSE) {
$form = array(
'#theme' => 'connector_buttons',
);
$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 (isset($connector[$callback]) && is_callable($connector[$callback])) {
$form[$key] = array(
'#type' => 'submit',
'#value' => t('Connect with !title', array(
'!title' => $connector['title'],
)),
'#submit' => array(
$connector[$callback],
),
'connector' => array(
'#type' => 'value',
'#value' => $connector,
),
);
}
}
return $form;
}
function _connector_cron_time() {
static $time_limit;
if (!$time_limit) {
$max = ini_get('max_execution_time');
if (!$max) {
$max = 240;
}
$time_limit = time() + 0.15 * $max;
// However, check for left time, maybe some other cron processing already occured
$time_limit = min($time_limit, variable_get('cron_semaphore', time()) + $max);
}
return max($time_limit - time(), 0);
}
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 = 'connector' AND uid = %d", $uid);
while ($row = db_fetch_object($result)) {
$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_result(db_query("SELECT primary_connection FROM {connector_user} WHERE uid = %d", $uid));
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_query("UPDATE {connector_user} SET primary_connection = '%s' WHERE uid = %d", array(
':primary_connection' => $connection,
':uid' => $uid,
));
if (!db_affected_rows()) {
db_query("INSERT INTO {connector_user} (uid, primary_connection) VALUES (%d, '%s')", array(
':uid' => $uid,
':primary_connection' => $connection,
));
}
}
function _connector_log_in($connector_name, $cid = NULL) {
global $user;
if (user_is_logged_in()) {
return FALSE;
}
$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) {
if (variable_get('user_register', 1)) {
// Mostly copied from user_external_login_register - because it doesn't check user_register
// Register this new user.
$userinfo = array(
'name' => $authname,
'pass' => user_password(),
'init' => $authname,
'status' => variable_get('user_register', 1) == 1,
'access' => time(),
);
$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 {
db_query("INSERT INTO {authmap} (uid, authname, module) VALUES (%d, '%s','connector')", $new_account->uid, $authname);
_connector_set_primary_connection($new_account->uid, $authname);
_connector_information_update($new_account);
if ($new_account->status) {
$user = $new_account;
return TRUE;
}
else {
drupal_set_message(t('Your account is currently pending approval by the site administrator.'), 'warning');
if (isset($connector['logout callback']) && is_callable($connector['logout callback'])) {
call_user_func($connector['logout callback'], $connector, $connection->cid);
}
}
watchdog('user', 'New external user: %name using module %module.', array(
'%name' => $authname,
'%module' => 'connector',
), WATCHDOG_NOTICE, l(t('edit'), 'user/' . $new_account->uid . '/edit'));
}
}
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);
}
}
}
else {
//Log in user
if ($account->status) {
$result = user_external_login($account);
if ($result) {
return TRUE;
}
}
else {
drupal_set_message(t('Your account is currently pending approval by the site administrator.'), 'warning');
if (isset($connector['logout callback']) && is_callable($connector['logout callback'])) {
call_user_func($connector['logout callback'], $connector, $connection->cid);
}
}
}
}
return FALSE;
}
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;
}
else {
if (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) {
$result = (bool) db_query("INSERT INTO {authmap} (uid, authname, module) VALUES (%d, '%s','connector')", $uid, $authname);
if (!_connector_get_primary_connection($uid)) {
_connector_set_primary_connection($uid, $authname);
}
}
else {
$result = TRUE;
}
}
return $result;
}
function _connector_information_fetch($uid, $types = NULL, $update = TRUE, $reset = FALSE) {
//TODO: Use $types if more info is added for a user
static $cache;
if (is_object($uid)) {
$uid = $uid->uid;
}
if (empty($cache)) {
$cache = array();
}
if ($reset) {
unset($cache[$uid]);
return;
}
elseif (!isset($cache[$uid])) {
$result = db_result(db_query("SELECT value FROM {connector_info} WHERE uid = %d AND type = 'real name'", array(
':uid' => $uid,
)));
if ($result === FALSE && $update) {
_connector_information_update($uid, array(
'real name' => TRUE,
));
$result = db_result(db_query("SELECT value FROM {connector_info} WHERE uid = %d AND type = 'real name'", array(
':uid' => $uid,
)));
}
$cache[$uid] = array(
'real name' => $result,
);
}
return $cache[$uid];
}
function _connector_information_update($uid, $types = NULL) {
//TODO: Configure more types of information that we want fetched?
if (is_object($uid)) {
$uid = $uid->uid;
}
$connection = _connector_get_primary_connection($uid);
$connector = isset($connection) ? _connector_get_connectors($connection->connector) : FALSE;
if ($connector) {
if (isset($connector['information callback']) && is_callable($connector['information callback'])) {
$info = call_user_func($connector['information callback'], $connector, $connection->cid, $types);
}
$info = $info ? (array) $info : array();
foreach ((array) $types as $type => $value) {
if (!empty($value) && !array_key_exists($type, $info) && $type != 'avatar') {
$info[$type] = FALSE;
}
}
if (empty($types) || !empty($types['avatar'])) {
if (variable_get('user_pictures', 0) && isset($connector['avatar callback']) && is_callable($connector['avatar callback'])) {
$avatar = call_user_func($connector['avatar callback'], $connector, $connection->cid);
}
else {
$avatar = NULL;
}
}
if (!empty($connector['cache'])) {
$max_life = time() + $connector['cache'];
}
else {
//TODO: Make default cache time configurable?
$max_life = time() + 432000;
//5 days x 24 hours per day x 3600 seconds per hour = 432000
}
}
if (!empty($info)) {
if (array_key_exists('real name', $info)) {
$real_name = (object) array(
'uid' => $uid,
'type' => 'real name',
);
$existing = db_fetch_object(db_query("SELECT value, failure_level FROM {connector_info} WHERE uid = %d AND type = 'real name'", array(
':uid' => $uid,
)));
if ($info['real name'] === FALSE) {
$real_name->max_life = intval(time() + 30 * 60 * pow(5.6346264945, $existing->failure_level));
// 30 minutes * 60 seconds per minute * failure level 4 = delay of 3 weeks
if ($existing->failure_level < 4) {
$real_name->failure_level = $existing->failure_level + 1;
}
}
else {
$real_name->max_life = $max_life;
if ($existing && $existing->failure_level > 0) {
$real_name->failure_level = 0;
}
}
if ($info['real name'] !== FALSE || !empty($connector['invalidate old info'])) {
$real_name->value = empty($info['real name']) ? '' : $info['real name'];
//TODO: Empty should be saved as NULL?
}
if ($existing !== FALSE) {
if ($existing->value == $info['real name']) {
unset($real_name->value);
}
drupal_write_record('connector_info', $real_name, array(
'uid',
'type',
));
}
else {
drupal_write_record('connector_info', $real_name);
}
_connector_information_fetch($uid, NULL, FALSE, TRUE);
}
}
if (isset($avatar)) {
if ($avatar !== FALSE || !empty($connector['invalidate old info'])) {
$account = user_load($uid);
if (isset($account->picture) && $account->picture != $destination && file_exists($account->picture)) {
file_delete($account->picture);
}
}
$avatar_record = (object) array(
'uid' => $uid,
'type' => 'avatar',
);
$existing = db_fetch_object(db_query("SELECT failure_level FROM {connector_info} WHERE uid = %d AND type = 'avatar'", array(
':uid' => $uid,
)));
if ($avatar === FALSE) {
$avatar_record->max_life = time() + 30 * pow(5.6346264945, $existing->failure_level);
// Failure level 4 = delay of 3 weeks
if ($existing->failure_level < 4) {
$avatar_record->failure_level = $avatar_record->failure_level + 1;
}
}
elseif ($existing->failure_level > 0) {
$avatar_record->max_life = $max_life;
$avatar_record->failure_level = 0;
}
if (db_result(db_query("SELECT COUNT(*) FROM {connector_info} WHERE uid = %d AND type = 'avatar'", array(
':uid' => $uid,
)))) {
drupal_write_record('connector_info', $avatar_record, array(
'uid',
'type',
));
}
else {
drupal_write_record('connector_info', $avatar_record);
}
if (!empty($avatar)) {
$result = drupal_http_request($avatar);
if ($result->code != 200) {
watchdog('connector', 'Failed importing avatar for user @uid, code : @code', array(
'@uid' => $uid,
'@code' => $result->code,
));
}
else {
//Copied from file_save_data - needs to write the file before validating it
$temp = file_directory_temp();
// On Windows, tempnam() requires an absolute path, so we use realpath().
$tmp_file = tempnam(realpath($temp), 'file');
if (!($fp = fopen($tmp_file, 'wb'))) {
drupal_set_message(t('The file could not be created.'), 'error');
}
else {
fwrite($fp, $result->data);
fclose($fp);
$file = new stdClass();
$file->filename = file_munge_filename(trim(basename($tmp_file), '.'), 'jpg jpeg gif png', FALSE);
$file->filepath = $tmp_file;
$file->filemime = file_get_mimetype($tmp_file);
$file->filesize = filesize($tmp_file);
$errors = array();
$errors += file_validate_is_image($file);
$errors += file_validate_image_resolution($file, variable_get('user_picture_dimensions', '85x85'));
$errors += file_validate_size($file, variable_get('user_picture_file_size', '30') * 1024);
if (empty($errors)) {
$info = image_get_info($file->filepath);
$destination = file_create_path(variable_get('user_picture_path', 'pictures'));
file_check_directory($destination, FILE_CREATE_DIRECTORY);
if (file_copy($file, $destination . '/picture-' . $uid . '.' . $info['extension'], FILE_EXISTS_REPLACE)) {
user_save($account, array(
'picture' => $file->filepath,
));
}
}
file_delete($tmp_file);
}
}
}
}
}
function theme_connector_buttons($form) {
$output = '';
$buttons = array();
foreach ($form as $key => $value) {
if (drupal_substr($key, 0, 1) != '#') {
$buttons[$key] = $value;
}
}
$num_links = count($buttons);
$i = 1;
foreach ($buttons as $key => $value) {
$class = 'connector-' . str_replace('_', '-', $key);
if ($i == 1) {
$class .= ' first';
}
if ($i == $num_links) {
$class .= ' last';
}
$output .= '<li' . drupal_attributes(array(
'class' => $class,
)) . '>';
$output .= drupal_render($value);
$output .= '</li>';
$i += 1;
}
return '<ul class="connector-buttons">' . $output . '</ul>';
}
Functions
Name | Description |
---|---|
connector_block | Implementation of hook_block(). |
connector_button_form | |
connector_cron | Implementation of hook_cron(). |
connector_init | Implementation of hook_init(). |
connector_menu | Implementation of hook_menu(). |
connector_realname | Implementation of hook_realname(). |
connector_realname_make | Implementation of hook_realname_make(). |
connector_theme | Implementation of hook_theme(). |
connector_user | Implementation of hook_user(). |
connector_views_api | Implementation of hook_views_api(). |
theme_connector_buttons | |
_connector_add_connection | |
_connector_cron_time | |
_connector_get_connectors | |
_connector_get_primary_connection | |
_connector_get_user_connections | |
_connector_information_fetch | |
_connector_information_update | |
_connector_log_in | |
_connector_set_primary_connection |