<?php
/**
* This file is part of b2evolution - {@link http://b2evolution.net/}
* See also {@link https://github.com/b2evolution/b2evolution}.
*
* @license GNU GPL v2 - {@link http://b2evolution.net/about/gnu-gpl-license}
*
* @copyright (c)2009-2016 by Francois Planque - {@link http://fplanque.com/}
* Parts of this file are copyright (c)2009 by The Evo Factory - {@link http://www.evofactory.com/}.
*
* Released under GNU GPL License - {@link http://b2evolution.net/about/gnu-gpl-license}
*
* @package messaging
*/
if( !defined('EVO_CONFIG_LOADED') ) die( 'Please, do not access this page directly.' );
/**
* Minimum PHP version required for messaging module to function properly
*/
$required_php_version[ 'messaging' ] = '5.6';
/**
* Minimum MYSQL version required for messaging module to function properly
*/
$required_mysql_version[ 'messaging' ] = '5.1';
/**
* Aliases for table names:
*
* (You should not need to change them.
* If you want to have multiple b2evo installations in a single database you should
* change {@link $tableprefix} in _basic_config.php)
*/
$db_config['aliases']['T_messaging__thread'] = $tableprefix.'messaging__thread';
$db_config['aliases']['T_messaging__message'] = $tableprefix.'messaging__message';
$db_config['aliases']['T_messaging__prerendering'] = $tableprefix.'messaging__prerendering';
$db_config['aliases']['T_messaging__threadstatus'] = $tableprefix.'messaging__threadstatus';
$db_config['aliases']['T_messaging__contact'] = $tableprefix.'messaging__contact';
$db_config['aliases']['T_messaging__contact_groups'] = $tableprefix.'messaging__contact_groups';
$db_config['aliases']['T_messaging__contact_groupusers'] = $tableprefix.'messaging__contact_groupusers';
/**
* Controller mappings.
*
* For each controller name, we associate a controller file to be found in /inc/ .
* The advantage of this indirection is that it is easy to reorganize the controllers into
* subdirectories by modules. It is also easy to deactivate some controllers if you don't
* want to provide this functionality on a given installation.
*
* Note: while the controller mappings might more or less follow the menu structure, we do not merge
* the two tables since we could, at any time, decide to make a skin with a different menu structure.
* The controllers however would most likely remain the same.
*
* @global array
*/
$ctrl_mappings['messages'] = 'messaging/messages.ctrl.php';
$ctrl_mappings['threads'] = 'messaging/threads.ctrl.php';
$ctrl_mappings['contacts'] = 'messaging/contacts.ctrl.php';
$ctrl_mappings['msgsettings'] = 'messaging/msg_settings.ctrl.php';
$ctrl_mappings['abuse'] = 'messaging/abuse.ctrl.php';
/**
* Get the MessageCache
*
* @return MessageCache
*/
function & get_MessageCache()
{
global $MessageCache;
if( ! isset( $MessageCache ) )
{ // Cache doesn't exist yet:
load_class( 'messaging/model/_message.class.php', 'Message' );
$MessageCache = new DataObjectCache( 'Message', false, 'T_messaging__message', 'msg_', 'msg_ID' );
}
return $MessageCache;
}
/**
* Get the MessagePrerenderingCache
*
* @return MessagePrerenderingCache
*/
function & get_MessagePrerenderingCache()
{
global $MessagePrerenderingCache;
if( ! isset( $MessagePrerenderingCache ) )
{ // Cache doesn't exist yet:
$MessagePrerenderingCache = array();
}
return $MessagePrerenderingCache;
}
/**
* Get the ThreadCache
*
* @return ThreadCache
*/
function & get_ThreadCache()
{
global $ThreadCache;
if( ! isset( $ThreadCache ) )
{ // Cache doesn't exist yet:
load_class( 'messaging/model/_thread.class.php', 'Thread' );
$ThreadCache = new DataObjectCache( 'Thread', false, 'T_messaging__thread', 'thrd_', 'thrd_ID', 'thrd_title' );
}
return $ThreadCache;
}
/**
* messaging_Module definition
*/
class messaging_Module extends Module
{
/**
* Do the initializations. Called from in _main.inc.php.
* This is typically where classes matching DB tables for this module are registered/loaded.
*
* Note: this should only load/register things that are going to be needed application wide,
* for example: for constructing menus.
* Anything that is needed only in a specific controller should be loaded only there.
* Anything that is needed only in a specific view should be loaded only there.
*/
function init()
{
$this->check_required_php_version( 'messaging' );
load_funcs( 'messaging/model/_messaging.funcs.php' );
}
/**
* Get default module permissions
*
* #param integer Group ID
* @return array
*/
function get_default_group_permissions( $grp_ID )
{
switch( $grp_ID )
{
case 1: // Administrators group ID equals 1
global $install_test_features;
$perm_messaging = ! empty( $install_test_features ) ? 'abuse' : 'delete';
$max_new_threads = ''; // empty = no limit
break;
case 2: // Moderators group equals 2
case 3: // Editors group ID equals 3
$perm_messaging = 'write'; // Messaging permissions
$max_new_threads = '10'; // Maximum number of new threads per day
break;
case 4: // Normal users group ID equals 4
$perm_messaging = 'write';
$max_new_threads = '5';
break;
case 5: // Misbehaving/Suspect users (group ID 5) have permission by default:
$perm_messaging = 'write';
$max_new_threads = '1';
break;
case 6: // Spammers/restricted Users
default: // Other groups
$perm_messaging = 'reply';
$max_new_threads = '1';
break;
}
// We can return as many default permissions as we want:
// e.g. array ( permission_name => permission_value, ... , ... )
return $permissions = array( 'perm_messaging' => $perm_messaging, 'max_new_threads' => $max_new_threads );
}
/**
* Get available group permissions
*
* @return array
*/
function get_available_group_permissions()
{
// 'label' is used in the group form as label for radio buttons group
// 'user_func' is used to check user permission. This function should be defined in module initializer.
// 'group_func' is used to check group permission. This function should be defined in module initializer.
// 'perm_block' group form block where this permissions will be displayed. Now available, the following blocks: additional, system
// 'options' is permission options
$permissions = array(
'perm_messaging' => array(
'label' => T_('Messages'),
'user_func' => 'check_messaging_user_perm',
'group_func' => 'check_messaging_group_perm',
'perm_block' => 'additional',
'options' => array(
// format: array( radio_button_value, radio_button_label, radio_button_note )
array( 'none', T_( 'No Access' ), '' ),
array( 'reply', T_( 'Read & Send messages to people in contacts list only (except for blocked contacts)' ), '' ),
array( 'write', T_( 'Read & Send messages to anyone (except for blocked contacts)' ), '' ),
array( 'delete', T_( 'Read, Send & Delete any messages (including for blocked contacts)' ), '' ),
array( 'abuse', T_( 'Abuse Management' ), '' ) ) ),
'max_new_threads' => array(
'label' => T_( 'Maximum number of new threads per day' ),
'group_func' => 'get_group_settings',
'perm_block' => 'additional',
'perm_type' => 'text_input',
'note' => T_( 'Leave empty for no limit' ),
'maxlength' => 5 ),
);
// We can return as many permissions as we want.
// In other words, one module can return many pluggable permissions.
return $permissions;
}
/**
* Check a permission for the user. ( see 'user_func' in get_available_group_permissions() function )
*
* @param string Requested permission level
* @param string Permission value
* @param mixed Permission target (blog ID, array of cat IDs...)
* @return boolean True on success (permission is granted), false if permission is not granted
*/
function check_messaging_user_perm( $permlevel, $permvalue, $permtarget )
{
global $current_User;
if( $permtarget > 0 )
{ // Check user permission for current thread
$ThreadCache = & get_ThreadCache();
$Thread = & $ThreadCache->get_by_ID( $permtarget, false );
if( $Thread === false || ! $Thread->check_thread_recipient( $current_User->ID ) )
{
return false;
}
}
return true;
}
/**
* Check a permission for the group. ( see 'group_func' in get_available_group_permissions() function )
*
* @param string Requested permission level
* @param string Permission value
* @param mixed Permission target (blog ID, array of cat IDs...)
* @return boolean True on success (permission is granted), false if permission is not granted
*/
function check_messaging_group_perm( $permlevel, $permvalue, $permtarget )
{
$perm = false;
switch ( $permvalue )
{
case 'abuse':
// Abuse Management & Read, Send & Delete any messages (able to send messages even for blocked contacts)
if( $permlevel == 'abuse' )
{
$perm = true;
break;
}
case 'delete':
// Read, Send & Delete any messages (able to send messages even for blocked contacts)
if( $permlevel == 'delete' )
{ // User can ask for delete perm...
$perm = true;
break;
}
case 'write':
// Read & Send messages to anyone (except for blocked contacts)
if( $permlevel == 'write' )
{
$perm = true;
break;
}
case 'reply':
// Read & Send messages to people in contacts list only (except for blocked contacts)
if( $permlevel == 'reply' )
{
$perm = true;
break;
}
}
return $perm;
}
/**
* Get pluggable group settings value
*/
function get_group_settings( $permname, $permvalue, $permtarget )
{
return $permvalue;
}
/**
* Build the evobar menu
*/
function build_evobar_menu()
{
global $DB;
global $topleft_Menu, $topright_Menu;
global $admin_url;
$left_entries = array();
$right_entries = array();
if( check_user_perm( 'perm_messaging', 'reply' ) )
{
if( ! empty( $topleft_Menu->_menus['entries']['site']['entries'] ) )
{
$left_entries['messaging'] = array(
'text' => T_('Messages').'…',
'href' => $admin_url.'?ctrl=threads',
);
}
$messages_url = get_dispctrl_url( 'threads' );
$contacts_url = get_dispctrl_url( 'contacts' );
if( ! empty( $messages_url ) || ! empty( $contacts_url ) )
{
$right_entries[] = array( 'separator' => true );
}
if( ! empty( $messages_url ) )
{ // Display this menu item only when url is available to current user
$right_entries['messages'] = array(
'text' => T_('Messages'),
'href' => $messages_url,
'class' => 'evo_messages_link',
);
}
if( ! empty( $contacts_url ) )
{ // Display this menu item only when url is available to current user
$right_entries['contacts'] = array(
'text' => T_('Contacts'),
'href' => $contacts_url,
'class' => 'evo_messages_link',
);
}
// Count unread messages for current user
$unread_messages_count = get_unread_messages_count();
if( $unread_messages_count > 0 )
{
$right_entries['messages']['text'] = T_('Messages').' <span class="badge badge-important">'.$unread_messages_count.'</span>';
}
}
$topleft_Menu->add_menu_entries( 'site', $left_entries );
$topright_Menu->insert_menu_entries_after( array( 'userprefs', 'name' ), $right_entries );
}
/**
* Builds the 3rd half of the menu. This is the one with the configuration features
*
* At some point this might be displayed differently than the 1st half.
*/
function build_menu_3()
{
global $admin_url;
/**
* @var AdminUI_general
*/
global $AdminUI;
if( ! check_user_perm( 'admin', 'restricted' ) )
{
return;
}
if( check_user_perm( 'perm_messaging', 'reply' ) )
{ // Permission to view messaging:
// Count unread messages for current user
$unread_messages_count = get_unread_messages_count();
if( $unread_messages_count > 0 )
{
$messages_counter = ' <span class="badge badge-important">'.$unread_messages_count.'</span>';
}
else
{
$messages_counter = '';
}
$AdminUI->add_menu_entries( NULL, array(
'messaging' => array(
'text' => T_('Messages').$messages_counter,
'title' => T_('Messages'),
'href' => $admin_url.'?ctrl=threads',
'entries' => get_messaging_sub_entries( true )
),
), 'users' );
}
}
/**
* Get the messaging module cron jobs
*
* @see Module::get_cron_jobs()
*/
function get_cron_jobs()
{
return array(
'send-unread-messages-reminders' => array(
'name' => T_('Send reminders about unread messages'),
'help' => '#',
'ctrl' => 'cron/jobs/_unread_message_reminder.job.php',
'params' => NULL,
)
);
}
/**
* Handle messaging module htsrv actions
*/
function handle_htsrv_action()
{
global $current_User, $Collection, $Blog, $Session, $Messages;
// Init objects we want to work on.
$action = param_action( true, true );
$disp = param( 'disp', '/^[a-z0-9\-_]+$/', 'threads' );
// Check that this action request is not a CSRF hacked request:
$Session->assert_received_crumb( 'messaging_'.$disp );
// Load classes
load_class( 'messaging/model/_thread.class.php', 'Thread' );
load_class( 'messaging/model/_message.class.php', 'Message' );
if( !is_logged_in() )
{ // user must be logged in
debug_die( 'User must be logged in to proceed with messaging updates!' );
}
// Check permission:
check_user_perm( 'perm_messaging', 'reply', true );
// set where to redirect
$redirect_to = param( 'redirect_to', 'url', NULL );
if( empty( $redirect_to ) )
{
if( isset( $Blog ) )
{
$redirect_to = url_add_param( $Blog->gen_baseurl(), 'disp='.$disp );
}
else
{
$redirect_to = url_add_param( $baseurl, 'disp='.$disp );
}
}
if( ( $disp != 'contacts' ) && ( $thrd_ID = param( 'thrd_ID', 'integer', '', true ) ) )
{ // Load thread from cache:
$ThreadCache = & get_ThreadCache();
if( ( $edited_Thread = & $ThreadCache->get_by_ID( $thrd_ID, false ) ) === false )
{ // Thread doesn't exists with this ID
unset( $edited_Thread );
forget_param( 'thrd_ID' );
$Messages->add( T_('The private conversation you are trying to access does not exist any longer.'), 'error' );
$action = 'nil';
}
}
switch( $disp )
{
// threads action
case 'threads':
if( $action != 'create' && $action != 'preview' )
{ // Make sure we got a thrd_ID:
param( 'thrd_ID', 'integer', true );
}
switch( $action )
{
case 'create': // create thread
case 'preview': // preview message
// Stop a request from the blocked IP addresses or Domains
antispam_block_request();
// check if create new thread is allowed
if( check_create_thread_limit() )
{ // max new threads limit reached, don't allow to create new thread
debug_die( 'Invalid request, new conversation limit already reached!' );
}
$creating_success = create_new_thread();
if( !$creating_success || $action == 'preview' )
{ // unsuccessful new thread creation OR preview mode
global $edited_Thread, $edited_Message, $thrd_recipients, $thrd_recipients_array;
$redirect_to .= '&action=new';
// save new message and thread params into the Session to not lose the content
$unsaved_message_params = array();
$unsaved_message_params[ 'action' ] = $action;
$unsaved_message_params[ 'subject' ] = $edited_Thread->title;
$unsaved_message_params[ 'message' ] = $edited_Message->text;
$unsaved_message_params[ 'message_original' ] = $edited_Message->original_text;
$unsaved_message_params[ 'renderers' ] = $edited_Message->get_renderers_validated();
$unsaved_message_params[ 'thrdtype' ] = param( 'thrdtype', 'string', 'individual' ); // alternative: discussion
$unsaved_message_params[ 'thrd_recipients' ] = $thrd_recipients;
$unsaved_message_params[ 'thrd_recipients_array' ] = $thrd_recipients_array;
$unsaved_message_params[ 'creating_success' ] = $creating_success;
save_message_params_to_session( $unsaved_message_params );
}
break;
case 'delete': // delete thread
// Check permission:
check_user_perm( 'perm_messaging', 'delete', true );
$confirmed = param( 'confirmed', 'integer', 0 );
if( $confirmed )
{
$msg = sprintf( T_('Thread «%s» deleted.'), $edited_Thread->dget('title') );
$edited_Thread->dbdelete();
unset( $edited_Thread );
forget_param( 'thrd_ID' );
$Messages->add( $msg, 'success' );
}
else
{
$delete_url = get_htsrv_url().'action.php?mname=messaging&thrd_ID='.$edited_Thread->ID.'&action=delete&confirmed=1&redirect_to='.rawurlencode( $redirect_to ).'&'.url_crumb( 'messaging_threads' );
$ok_button = '<a href="'.$delete_url.'" class="btn btn-danger">'.T_( 'I am sure!' ).'</a>';
$cancel_button = '<a href="'.$redirect_to.'" class="btn btn-default">CANCEL</a>';
$msg = sprintf( T_( 'You are about to delete all messages in the conversation «%s».' ), $edited_Thread->dget('title') );
$msg .= '<br />'.T_( 'This CANNOT be undone!').'<br />'.T_( 'Are you sure?' ).'<br /><br />'.$ok_button."\t".$cancel_button;
$Messages->add( $msg, 'error' );
}
break;
case 'leave': // user wants to leave the thread
leave_thread( $edited_Thread->ID, $current_User->ID, false );
$Messages->add( sprintf( T_( 'You have successfuly left the «%s» conversation!' ), $edited_Thread->get( 'title' ) ), 'success' );
break;
case 'close': // close the thread
case 'close_and_block': // close the thread and block contact
leave_thread( $edited_Thread->ID, $current_User->ID, true );
// user has closed this conversation because there was only one other user involved
$Messages->add( sprintf( T_( 'You have successfuly closed the «%s» conversation!' ), $edited_Thread->get( 'title' ) ), 'success' );
if( $action == 'close_and_block' )
{ // user also wants to block contact with the other user involved in this thread
$block_user_ID = param( 'block_ID', 'integer', true );
$UserCache = & get_UserCache();
$blocked_User = $UserCache->get_by_ID( $block_user_ID );
set_contact_blocked( $block_user_ID, true );
$Messages->add( sprintf( T_( '«%s» was blocked.' ), $blocked_User->get( 'login' ) ), 'success' );
}
break;
}
break; // break from threads action switch
// contacts action
case 'contacts':
$user_ID = param( 'user_ID', 'string', true );
if( ( $action != 'block' ) && ( $action != 'unblock' ) )
{ // only block or unblock is valid
debug_die( "Invalid action param" );
}
if( set_contact_blocked( $user_ID, ( ( $action == 'block' ) ? 1 : 0 ) ) )
{
if( $action == 'block' )
{
$Messages->add( T_('You have blocked this user from contacting you.'), 'success' );
}
else
{
$Messages->add( T_('You have unblocked this user so he can contact you again.'), 'success' );
}
}
$redirect_to = str_replace( '&', '&', $redirect_to );
break;
// messages action
case 'messages':
switch( $action )
{
case 'create': // create new message
// Stop a request from the blocked IP addresses or Domains
antispam_block_request();
create_new_message( $thrd_ID );
break;
case 'preview': // create new message
// Stop a request from the blocked IP addresses or Domains
antispam_block_request();
global $edited_Message;
$creating_success = create_new_message( $thrd_ID, 'preview' );
// save new message and thread params into the Session to not lose the content
$unsaved_message_params = array();
$unsaved_message_params[ 'action' ] = $action;
$unsaved_message_params[ 'message' ] = $edited_Message->text;
$unsaved_message_params[ 'message_original' ] = $edited_Message->original_text;
$unsaved_message_params[ 'renderers' ] = $edited_Message->get_renderers_validated();
$unsaved_message_params[ 'creating_success' ] = $creating_success;
save_message_params_to_session( $unsaved_message_params );
break;
case 'delete': // delete message
// Check permission:
check_user_perm( 'perm_messaging', 'delete', true );
$msg_ID = param( 'msg_ID', 'integer', true );
$MessageCache = & get_MessageCache();
if( ($edited_Message = & $MessageCache->get_by_ID( $msg_ID, false )) === false )
{
$Messages->add( sprintf( T_('Requested «%s» object does not exist any longer.'), T_('Message') ), 'error' );
break;
}
$confirmed = param( 'confirmed', 'integer', 0 );
if( $confirmed )
{ // delete message
$edited_Message->dbdelete();
unset( $edited_Message );
$Messages->add( T_('Message deleted.'), 'success' );
}
else
{
$delete_url = get_htsrv_url().'action.php?mname=messaging&disp=messages&thrd_ID='.$thrd_ID.'&msg_ID='.$msg_ID.'&action=delete&confirmed=1';
if( ! empty( $Blog ) )
{ // Add blog ID to correctly redirect after deleting:
$delete_url .= '&blog='.$Blog->ID;
}
$delete_url = url_add_param( $delete_url, 'redirect_to='.rawurlencode( $redirect_to ), '&' ).'&'.url_crumb( 'messaging_messages' );
$ok_button = '<a href="'.$delete_url.'" class="btn btn-danger">'.T_('I am sure!').'</a>';
$cancel_button = '<a href="'.$redirect_to.'" class="btn btn-default">'.T_('CANCEL').'</a>';
$msg = T_('You are about to delete this message. ').'<br /> '.T_('This CANNOT be undone!').'<br />'.T_( 'Are you sure?' ).'<br /><br />'.$ok_button."\t".$cancel_button;
$Messages->add( $msg, 'error' );
}
break;
}
break;
}
header_redirect( $redirect_to ); // Will save $Messages into Session
}
/**
* Get contacts list params
*/
function get_contacts_list_params()
{
global $module_contacts_list_params;
if( !isset( $module_contacts_list_params ) )
{ // Initialize this array first time
$module_contacts_list_params = array();
}
$module_contacts_list_params['title_selected'] = T_('Send a message to all selected contacts');
$module_contacts_list_params['title_group'] = T_('Send a message to all %d contacts in the «%s» group');
$module_contacts_list_params['recipients_link'] = get_dispctrl_url( 'threads', 'action=new' );
}
}
$messaging_Module = new messaging_Module();
?>