This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
version 2 as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
require_once ( AK_CLASSES . '/abstract/component.php' );
/**
* Class aocProfiles.
* Manages all about user profiles.
*
* @since 0.6
* @author Jordi Canals
* @package Alkivia
* @subpackage Community
*/
final class aocProfiles extends akComponentAbstract
{
/**
* Profiles component Statup.
* Sets all needed filters and actions to the WordPress API.
*
* @return void
*/
protected function moduleLoad ()
{
// Community home Page and user profile page.
if ( ! $this->getOption('disable_list') ) {
add_filter('aoc_alkivia_home', array($this, 'userList'));
}
add_filter('aoc_alkivia_page', array($this, 'profilesContent'));
// Rewrite Rules.
add_filter('generate_rewrite_rules', array($this, 'rewriteRules'));
add_filter('query_vars', array($this, 'rewriteVars'));
// IM Labels (Only works with WP 2.8 and above).
add_filter('user_aim_label', array($this, 'aimLabel'));
add_filter('user_yim_label', array($this, 'yimLabel'));
add_filter('user_jabber_label', array($this, 'jabberLabel'));
// Checks nickname uniqueness (Only works with WP 2.8 and above).
add_action('user_profile_update_errors', array($this, 'uniqueNickname'), 10, 3);
switch ( $this->getOption('author_link') ) {
case 1 :
add_filter('author_link', array($this, 'authorReplaceLink'), 15, 3);
add_filter('ak_chameleon_author', array($this, 'authorCreateLink')); // For chameleon theme
break;
case 2:
add_filter('the_author', array($this, 'authorCreateLink'));
add_filter('ak_chameleon_author', array($this, 'authorCreateLink')); // For chameleon theme
break;
}
if ( $this->getOption('comments_link') ) {
add_filter('get_comment_author_link', array($this, 'commentAuthorLink'));
}
if ( $this->getOption('last_posts') ) {
add_action('aoc_profile_data', array($this, 'latestUserPosts'));
}
if ( $this->getOption('last_comments') ) {
add_action('aoc_profile_data', array($this, 'recentUserComments'));
}
}
/**
* Returns component defaults.
* This defaults can differ from install time defaults.
* Here have to remember that array_merge is not recursive. So 'roles' is not changed item by item, all roles array is merged as is.
* This behavior is used to do not fill the roles and get in $settings['roles'] only the active items (those with value 1).
*
* @return void
*/
protected function defaultOptions ()
{
return array(
'show_email' => 0,
'show_aim' => 0,
'show_yahoo' => 0,
'show_jabber' => 0,
'show_firstname' => 0,
'show_lastname' => 0,
'roles' => array(
'administrator' => 0,
'editor' => 0,
'author' => 0,
'contributor' => 0,
'subscriber' => 0
),
'avatar_size' => 20,
'order_by' => 'display_name',
'order_dir' => 'ASC',
'per_page' => 20,
'author_link' => 0,
'comments_link' => 0,
'author_page' => 0,
'last_posts' => 0,
'last_comments' => 0,
'aim_label' => '',
'yim_label' => '',
'jabber_label' => '',
'disable_list' => 0,
'list_template' => 'default',
'profile_template' => 'default'
);
}
/**
* Component activation.
* We change the default settings that differ from defaults (as defaults already are set).
*
* @return void
*/
protected function componentActivate ()
{
$options = array(
'show_firstname' => 1,
'roles' => array(
'administrator' => 1,
'editor' => 1,
'author' => 1,
'contributor' => 1,
'subscriber' => 1
),
'author_page' => 1,
'last_posts' => 1,
'last_comments' => 1,
);
$this->mergeOptions($options);
}
/**
* Adds the Profiles menu to Alkivia.
*
* @hook action 'aoc_admin_menu'
* @return void
*/
function adminMenus ()
{
add_submenu_page( $this->slug, __('User Profiles', $this->PID), __('User Profiles', $this->PID), 'aoc_manage_settings', $this->slug . '-profiles', array($this, 'profileSettings'));
}
/**
* Selects which page type to output depending on the url query.
*
* @hook filter 'aoc_alkivia_page'
* @uses apply_filters() Calls the 'aoc_access_login' hooc on the login anchor.
*
* @param string $content Post Content as entered when editing the page.
* @return string User Profile info.
*/
function profilesContent ( $content )
{
$user_name = urldecode(get_query_var('user'));
if ( ! empty($user_name) ) {
$profile = $this->userProfile($user_name);
if ( $profile ) {
$content = $profile;
} else {
$content = '
' . __('User with this name not found.', $this->PID) . "
\n";
}
}
return $content;
}
/**
* Displays the a paged user list. This is the community home.
* Only users of configured roles are displayed.
*
* @hook filter 'aoc_alkivia_home'
*
* @param string $content Post Content as entered when editing the page.
* @return string The HTML formated user list added to $content.
*/
function userList( $content )
{
$page = (int) get_query_var('page');
if ( 0 == $page ) $page = 1;
$start = ($page - 1) * $this->getOption('per_page');
$order = array( 'by' => $this->getOption('order_by'), 'dir' => $this->getOption('order_dir'));
$users = $this->getUsersByRoles($this->getAllowedRoles(), $order, $start, $this->getOption('per_page'));
if ( ! $users ) {
// Normally occurs when a wrong page number has been requested.
return ''. __('No users found in this page.', $this->PID) ."
\n";
}
// Load and process the page template.
require_once ( AK_CLASSES . '/template.php');
$template = new akTemplate( aoc_template_paths() );
$template->textDomain($this->PID);
$template->assign('baselink', aoc_create_link('user'));
$template->assign('avatar_size', $this->getOption('avatar_size'));
$template->assignByRef('users', $users);
$total = $this->countUsersByRoles($this->getAllowedRoles());
$template->assign('pager', ak_pager($total, $this->getOption('per_page'), aoc_create_link('page'), $page));
$content .= $template->getDisplay('userlist-' . $this->getOption('list_template'), 'userlist-default');
return $content;
}
/**
* Display a User Profile page.
*
* @uses apply_filters() Calls the 'user_aim_label', 'user_yim_label' and 'user_jabber_label' hooks on their corresponding labels.
* @uses apply_filters() Calls the 'aoc_profile_header' on an empty string with user object as an extra param.
* @uses apply_filters() Calls the 'aoc_profile_footer' on an empty string with user object as an extra param.
* @uses do_action() Calls the 'aoc_profile_data' to get user additional data.
*
* @param string $name Login Name for the user.
* @return string|false The formated profile or false if the user cannot be shown.
*/
private function userProfile ( $name )
{
global $wpdb;
$user_login = sanitize_user( $name );
$out = '';
if ( empty($user_login) ) { // No user name provided
return false;
}
$user = get_userdatabylogin($user_login);
if ( ! $user ) { // User not found.
return false;
}
if ( ! $this->canShowUser($user) ) {
return false;
}
// Load and process the page template.
require_once ( AK_CLASSES . '/template.php');
$template = new akTemplate( aoc_template_paths() );
$template->textDomain($this->PID);
$template->assign('header', apply_filters('aoc_profile_header', '', $user));
$template->assign('footer', apply_filters('aoc_profile_footer', '', $user));
if ( is_user_logged_in() ) {
$cur_user = wp_get_current_user();
if ( $cur_user->user_login == $user->user_login ) {
$link = '' . __('Edit your profile', $this->PID) . '';
} elseif ( current_user_can('edit_users') ) {
$link = '' . __('Edit this user', $this->PID) . '';
}
$template->assign('edit_link', $link);
}
if ( $this->getOption('author_page') ) {
$template->assign('author_link', $this->authorPostsLink($user));
}
// Now, we set/unset the user data based on privacy settings.
unset($user->user_pass); // Just for security do not provide the password to templates.
// User full name.
if ( $this->getOption('show_firstname') && ! empty($user->first_name) ) {
$user->aoc_full_name = $user->first_name;
if ( $this->getOption('show_lastname') ) {
$user->aoc_full_name .= ' '. $user->last_name;
}
unset($user->first_name); unset($user->last_name);
unset($user->user_firstname); unset($user->user_lastname);
}
// Translated Role
$roles = ak_get_roles(true);
$user->aoc_translated_role = $roles[ak_get_user_role($user)];
$user->user_url = ( 'http://' == $user->user_url ) ? '' : $user->user_url;
// Email and IM addresses
if ( ! $this->getOption('show_email') ) {
unset($user->user_email);
}
if ( ! $this->getOption('show_aim') ) {
unset($user->aim);
}
if ( ! $this->getOption('show_yahoo') ) {
unset($user->yim);
}
if ( ! $this->getOption('show_jabber') ) {
unset($user->jabber);
}
// Pager links
$template->assign('prev_link', $this->pageNavigator($user, 'prev'));
$template->assign('next_link', $this->pageNavigator($user, 'next'));
// Get user data from action hooks.
$user->aoc_widgets = array();
do_action('aoc_profile_data', $user);
$template->assignByRef('user', $user);
return $template->getDisplay('profile-' . $this->getOption('profile_template'), 'profile-default');
}
/**
* Creates and returns previous and next user links.
* For paged user profiles.
*
* @param object $current Current user object.
* @param string $direction Direction for the link. Is 'prev' or 'next'.
* @return string Previous or next user link.
*/
private function pageNavigator ( $current, $direction = 'next' )
{
global $wpdb;
$roles = $this->getAllowedRoles();
$ord_by = $this->getOption('order_by');
$ord_dir = $this->getOption('order_dir');
$keys = array();
foreach ($roles as $value) {
$keys[] = "meta_value LIKE '%{$value}%'";
}
// Prepare the order field
switch ( $ord_by ) {
case 'user_registered':
$by = $current->user_registered;
break;
case 'user_login':
$by = $current->user_login;
break;
case 'ID':
$by = $current->ID;
break;
case 'display_name':
default:
$by = $current->display_name;
break;
}
// $ord_by contains the ordering field name.
if ( 'ASC' == $ord_dir ) {
$op = ( 'next' == $direction ) ? '>' : '<';
} else {
$op = ( 'next' == $direction ) ? '<' : '>';
}
$condition = "{$ord_by} {$op} '{$by}'";
$order_dir = ( '>' == $op ) ? 'ASC' : 'DESC';
// Compose the query
$query = "SELECT user_login, display_name FROM {$wpdb->usermeta} INNER JOIN {$wpdb->users} "
. "ON {$wpdb->usermeta}.user_id = {$wpdb->users}.id "
. "WHERE {$condition} AND meta_key='{$wpdb->prefix}capabilities' AND (". implode(' OR ', $keys) .") "
. "ORDER BY {$ord_by} {$order_dir} "
. "LIMIT 0,1;";
$user = $wpdb->get_row($query);
if ( empty($user) ) { // If no user, no link.
$link = '';
} else { // Create the base url for the link to user profile page.
$plink = aoc_create_link('user', urlencode($user->user_login));
$link = ( 'next' == $direction )
? ''. $user->display_name . ' »'
: '« '. $user->display_name . '';
}
return $link;
}
/**
* Return the roles that can be shown in Community
*
* @param $as_values Role names are returned as values insted as in keys with '1' or '0' as value.
* @return array The allowed roles.
*/
public function getAllowedRoles ( $as_values = true )
{
// return array_keys($this->getOption('roles'));
$roles = $this->cfg->getSetting($this->ID, 'roles');
if ( ! is_array($roles) ) {
$a = explode(',', $roles);
if ( $as_values ) {
$roles = $a;
} else {
$roles = array();
foreach ( $a as $role ) {
$roles[trim($role)] = 1;
}
}
} elseif ( $as_values ) {
$roles = array_keys($roles);
}
return $roles;
}
/**
* Creates an array as a paged list of users which have some roles.
*
* @param array|string $roles Roles names users need to have to be retrieved. Can be an string with only one role name.
* @param array $order Array for list ordering. [by] Field to order by, [dir] is ASC or DESC
* @param int $start User to start at.
* @param int $num Number of users per page.
* @return array Retrieved Users.
*/
private function getUsersByRoles ( $roles = '', $order = array(), $start = 0, $num = 20 )
{
global $wpdb;
if ( empty($roles) ) {
$roles = ak_get_roles();
}
if ( ! is_array($roles) ) {
$roles = array($roles);
}
if ( empty($order) ) {
$order['by'] = 'display_name';
$order['dir'] = 'ASC';
}
$keys = array();
foreach ($roles as $value) {
$keys[] = "meta_value LIKE '%{$value}%'";
}
$query = "SELECT {$wpdb->users}.* FROM {$wpdb->usermeta} INNER JOIN {$wpdb->users} "
. "ON {$wpdb->usermeta}.user_id = {$wpdb->users}.id "
. "WHERE meta_key='{$wpdb->prefix}capabilities' AND (". implode(' OR ', $keys) .") "
. "ORDER BY {$order['by']} {$order['dir']} "
. "LIMIT {$start},{$num};";
return $wpdb->get_results($query, 'ARRAY_A');
}
/**
* Counts the total users based on roles list.
*
* @param array|string $roles Roles names users to be counted from. Can be an string with only one role name.
* @return int Total users with any of the roles.
*/
private function countUsersByRoles ( $roles = '' )
{
global $wpdb;
if ( empty($roles) ) {
$roles = ak_get_roles();
}
if ( ! is_array($roles) ) {
$roles = array($roles);
}
$keys = array();
foreach ($roles as $value) {
$keys[] = "meta_value LIKE '%{$value}%'";
}
$query = "SELECT COUNT(*) FROM {$wpdb->usermeta} INNER JOIN {$wpdb->users} "
. "ON {$wpdb->usermeta}.user_id = {$wpdb->users}.id "
. "WHERE meta_key='{$wpdb->prefix}capabilities' AND (". implode(' OR ', $keys) .")";
return $wpdb->get_var($query);
}
/**
* Checks id a user is in an allowed role.
*
* @param int|object $user User ID or user object to check
* @return boolean If the user can be shown or not.
*/
public function canShowUser ( $user )
{
$roles = $this->getAllowedRoles();
$user_role = ak_get_user_role($user);
return ( in_array($user_role, $roles) ) ? true : false;
}
/**
* Creates the needed rewrite rules for user profiles.
*
* @hook filter 'generate_rewrite_rules'
* @param object $wp_rewrite Current rewrite rules. Received by ref.
* @return void
*/
function rewriteRules ( &$wp_rewrite )
{
$pid = ak_get_object($this->PID)->getOption('page_id');
$slug = basename(get_page_uri($pid));
$rules = array ( $slug . '/user/(.+)/?$' => 'index.php?page_id='. $pid .'&user='. $wp_rewrite->preg_index(1),
'(.+)/' . $slug . '/user/(.+)/?$' => 'index.php?page_id='. $pid .'&user=' . $wp_rewrite->preg_index(2),
$slug . '/page/(.+)/?' => 'index.php?page_id=' . $pid . '&page=' . $wp_rewrite->preg_index(1),
'(.+)/' . $slug . '/page/(.+)/?$' => 'index.php?page_id='. $pid .'&page=' . $wp_rewrite->preg_index(2) );
$wp_rewrite->rules = $rules + $wp_rewrite->rules;
}
/**
* Creates the query var to get a user profile page.
*
* @hook filter 'query_vars'
* @param array $vars Current WordPress query vars.
* @return array New Query vars.
*/
function rewriteVars ( $vars )
{
$vars[] = 'user';
$vars[] = 'page';
return $vars;
}
/**
* Creates the link to the author pages.
*
* @param $user Author object.
* @return string Link to author pages.
*/
private function authorPostsLink ( $user )
{
$count = get_usernumposts($user->ID);
$link = '';
if ( 0 < $count ) {
global $wp_rewrite;
$link = $wp_rewrite->get_author_permastruct();
if ( empty($link) ) {
$file = get_option('home') . '/';
$link = $file . '?author=' . $user->ID;
} else {
$link = str_replace('%author%', $user->user_login, $link);
$link = get_option('home') . trailingslashit($link);
}
}
return $link;
}
/**
* Filter to replace the link for author to his profile page.
* Only if the theme already sets the author links.
*
* @hook filter 'author_link'
* @param string $link Old author link
* @param int $auth_id Author user ID
* @param string $nicename Author nice name.
* @return string The link to the author profile.
*/
function authorReplaceLink ( $link, $auth_id, $nicename )
{
$auth = get_userdata($auth_id);
return aoc_profile_link($auth->user_login);
}
/**
* Filter to create a link for the author to his profile page.
* It does not adds the link to feeds, as author links on feeds arre not supported by RSS/Atom standards.
* This only can be used if currently the author has no links.
*
* @hook filter 'the_author'
* @return string The link to the author profile.
*/
function authorCreateLink ( $display_name )
{
if ( is_feed() ) {
return $display_name; // Feed standards do not allow links on dc:creator.
}
global $authordata;
$plink = aoc_profile_link($authordata->user_login);
$link = '' . $authordata->display_name . '';
return $link;
}
/**
* Filter to replace the comments author link.
* The link is only replaced if the user is registered and is from an allowed role.
*
* @hook filter 'get_comment_author_link'
* @param string $link Link to author home page generated by WordPress
* @return string Link to the user profile page if user is registered and from an allowed role.
*/
function commentAuthorLink ( $link )
{
global $comment;
if ( ! empty($comment->user_id) && $this->canShowUser($comment->user_id) ) {
$user = get_userdata($comment->user_id);
$link = '' . $user->display_name . '';
}
return $link;
}
/**
* Action to add the latest user comments to the profile page.
* Be careful, as this filter needs two parameters, the extra content and the user ID.
*
* @hook action 'aoc_profile_data'.
* @param object $user User Object.
* @return void.
*/
function latestUserPosts ( & $user )
{
global $wpdb;
$data = array();
$p = new WP_Query(array('showposts' => 5, 'what_to_show' => 'posts', 'nopaging' => 0, 'post_status' => 'publish', 'author' => $user->ID));
if ( $p->have_posts() ) {
$data['title'] = __('Recent user posts:', $this->PID);
while ( $p->have_posts() ) {
$p->the_post();
$data['content'][] = ''
. get_the_title() . '';
}
wp_reset_query(); // Restore global post data stomped by the_post().
}
$user->aoc_widgets['postlist'] = $data;
}
/**
* Action to add the latest comments user has made.
* Be careful, as this filter needs two parameters, the extra content and the user ID.
*
* @hook action 'aoc_profile_data'.
* @param object $user User Object.
* @return void.
*/
function recentUserComments ( & $user )
{
global $wpdb;
$data = array();
$comments = $wpdb->get_results("SELECT * FROM $wpdb->comments WHERE comment_approved = '1' AND user_id = '{$user->ID}' GROUP BY comment_post_ID ORDER BY comment_date_gmt DESC LIMIT 0,5");
if ( $comments ) {
$data['title'] = __('Latest comments on:', $this->PID);
foreach ( (array) $comments as $comment ) {
$data['content'][] = '' . get_the_title($comment->comment_post_ID) . '';
}
$user->aoc_widgets['commentlist'] = $data;
}
}
/**
* Filter to change the standard AIM label.
* Only works for WP 2.8 and above.
*
* @hook filter 'user_aim_label'
* @param $label Default label.
* @return string New label by profiles settings.
*/
function aimLabel ( $label )
{
$value = $this->getOption('aim_label');
return ( empty($value) ) ? $label : $value;
}
/**
* Filter to change the standard YIM label.
* Only works for WP 2.8 and above.
*
* @hook filter 'user_yim_label'
* @param $label Default label.
* @return string New label by profiles settings.
*/
function yimLabel ( $label )
{
$value = $this->getOption('yim_label');
return ( empty($value) ) ? $label : $value;
}
/**
* Filter to change the standard Jabber Label.
* Only Works for WP 2.8 and above.
*
* @hook filter 'user_jabber_label'
* @param $label Default label.
* @return string New label by profiles settings.
*/
function jabberLabel ( $label )
{
$value = $this->getOption('jabber_label');
return ( empty($value) ) ? $label : $value;
}
/**
* Filter to force unique nicknames when updating a user.
* Checks on different fields: user_login, user_nicename, display_name and usermeta->nickname.
* Only works on wordpress 2.8 and above due to the filter used.
*
* @hook action 'user_profile_update_errors'
* @since 0.7
* @param WP_Error $errors Errors object to add the new error if nickname exists.
* @param boolean $update True if updating an existing user. False if creating a new one.
* @param object $user User object with the form data.
* @return void
*/
function uniqueNickname ( & $errors, $update, & $user )
{
if ( ! $update ) {
return;
}
global $wpdb;
$user_id = $wpdb->get_var("SELECT user_id FROM {$wpdb->usermeta} WHERE meta_key='nickname' AND meta_value='{$user->nickname}' AND user_id <> {$user->ID};");
if ( ! $user_id ) {
$nicename = sanitize_title($user->nickname);
$login = sanitize_user($user->nickname, true);
$nick = $user->nickname;
$user_id = $wpdb->get_var("SELECT ID FROM {$wpdb->users} WHERE ID <> {$user->ID} AND ( user_login='{$nick}' OR user_login='{$login}' OR user_nicename='{$nicename}' OR user_nicename='{$nick}' OR display_name='{$nick}' );");
}
if ( $user_id ) {
$errors->add( 'nickname_exists', __('ERROR: This nickname is already in use, please choose another one.', $this->PID), array( 'form-field' => 'nickname' ) );
}
}
/**
* Loads settings page for User Profiles
*
* @hook add_submenu_page
* @return void
*/
function profileSettings() {
if ( ! current_user_can('aoc_manage_settings') ) { // Verify user permissions.
wp_die('' .__('What do you think you\'re doing?!?', $this->PID) . '');
}
global $wp_rewrite;
$wp_rewrite->flush_rules();
if ( 'POST' == $_SERVER['REQUEST_METHOD'] ) {
$this->saveAdminSettings();
}
require ( dirname(__FILE__) . '/admin.php');
}
/**
* Saves settings from admin form.
* TODO: Check settings with intval.
*
* @return void
*/
private function saveAdminSettings ()
{
check_admin_referer('alkivia-profile-settings');
if ( isset($_POST['action']) && 'update' == $_POST['action'] ) {
$options = stripslashes_deep($_POST['profiles']);
$this->setNewOptions($options);
ak_admin_notify();
} else { // Missing action
ak_admin_error(__('Bad form received.', $this->PID));
}
}
}