mirror of
https://github.com/Mibew/mibew.git
synced 2025-02-12 18:41:08 +03:00
915 lines
28 KiB
PHP
915 lines
28 KiB
PHP
<?php
|
|
/*
|
|
* This file is a part of Mibew Messenger.
|
|
*
|
|
* Copyright 2005-2023 the original author or authors.
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
// Import namespaces and classes of the core
|
|
use Mibew\Database;
|
|
use Mibew\EventDispatcher\EventDispatcher;
|
|
use Mibew\EventDispatcher\Events;
|
|
use Mibew\Settings;
|
|
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
|
|
|
|
/**
|
|
* Name of the cookie to remember an operator
|
|
*/
|
|
define('REMEMBER_OPERATOR_COOKIE_NAME', 'mibew_operator');
|
|
|
|
/** Permissions constants */
|
|
|
|
/**
|
|
* Operator can administer Mibew Messenger installation
|
|
*/
|
|
define('CAN_ADMINISTRATE', 0);
|
|
|
|
/**
|
|
* Operator can take over threads
|
|
*/
|
|
define('CAN_TAKEOVER', 1);
|
|
|
|
/**
|
|
* Operator can view threads of other operators
|
|
*/
|
|
define('CAN_VIEWTHREADS', 2);
|
|
|
|
/**
|
|
* Operator can modify own profile
|
|
*/
|
|
define('CAN_MODIFYPROFILE', 3);
|
|
|
|
/**
|
|
* Operator can view system statistics
|
|
*/
|
|
define('CAN_VIEWSTATISTICS', 4);
|
|
|
|
|
|
/** End of permissions constants */
|
|
|
|
/**
|
|
* Map numerical permissions ids onto string names.
|
|
* @return array Associative array whose keys are numerical permission ids and
|
|
* values are string permission names.
|
|
*/
|
|
function permission_ids()
|
|
{
|
|
return array(
|
|
CAN_ADMINISTRATE => "admin",
|
|
CAN_VIEWSTATISTICS => "statistics",
|
|
CAN_TAKEOVER => "takeover",
|
|
CAN_VIEWTHREADS => "viewthreads",
|
|
CAN_MODIFYPROFILE => "modifyprofile",
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Map numerical permissions ids onto its descriptions.
|
|
*
|
|
* The descriptions are localized.
|
|
*
|
|
* @return array Array whose keys are numerical permission ids and values are
|
|
* localized permission descriptions.
|
|
*/
|
|
function permission_descriptions()
|
|
{
|
|
return array(
|
|
CAN_ADMINISTRATE => getlocal('System administration: settings, operators management, button generation'),
|
|
CAN_VIEWSTATISTICS => getlocal('Ability to view system statistics'),
|
|
CAN_TAKEOVER => getlocal('Take over chat thread'),
|
|
CAN_VIEWTHREADS => getlocal('View another operator\'s chat thread'),
|
|
CAN_MODIFYPROFILE => getlocal('Ability to modify profile'),
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Set new permissions to operator
|
|
* @param int $operator_id Operator ID
|
|
* @param int $perm New permissions value
|
|
*/
|
|
function update_operator_permissions($operator_id, $perm)
|
|
{
|
|
$operator = operator_by_id($operator_id);
|
|
$operator['iperm'] = $perm;
|
|
update_operator($operator);
|
|
}
|
|
|
|
function operator_by_login($login)
|
|
{
|
|
$db = Database::getInstance();
|
|
|
|
return $db->query(
|
|
"SELECT * FROM {operator} WHERE vclogin = ?",
|
|
array($login),
|
|
array('return_rows' => Database::RETURN_ONE_ROW)
|
|
);
|
|
}
|
|
|
|
function operator_by_email($mail)
|
|
{
|
|
$db = Database::getInstance();
|
|
|
|
return $db->query(
|
|
"SELECT * FROM {operator} WHERE vcemail = ?",
|
|
array($mail),
|
|
array('return_rows' => Database::RETURN_ONE_ROW)
|
|
);
|
|
}
|
|
|
|
function operator_by_id($id)
|
|
{
|
|
$db = Database::getInstance();
|
|
|
|
return $db->query(
|
|
"SELECT * FROM {operator} WHERE operatorid = ?",
|
|
array($id),
|
|
array('return_rows' => Database::RETURN_ONE_ROW)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Load operator info by specified operators code
|
|
*
|
|
* @param string $code Operators code
|
|
* @return array|boolean Operators info array or boolean false if there is no
|
|
* operator with specified code.
|
|
*/
|
|
function operator_by_code($code)
|
|
{
|
|
$db = Database::getInstance();
|
|
|
|
return $db->query(
|
|
"SELECT * FROM {operator} WHERE code = ?",
|
|
array($code),
|
|
array('return_rows' => Database::RETURN_ONE_ROW)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Get list of operators taking into account $options
|
|
* @param array $options Associative array of options. It can contains following
|
|
* keys:
|
|
* - 'sort': an associative array of sorting options.
|
|
* - 'isolated_operator_id': id of current operators. If it set - function
|
|
* would return only operators from adjacent groups.
|
|
*
|
|
* 'sort' array must contains two keys: 'by' and 'desc'.
|
|
* 'by' means the field by which operators would be sort and can take
|
|
* following values: 'commonname', 'localename', 'login', 'lastseen'. 'desc'
|
|
* means order in which operators would be sort. If it's 'true' operators
|
|
* would be sort in descending order and in ascending order overwise.
|
|
*
|
|
*/
|
|
function get_operators_list($options = array())
|
|
{
|
|
$db = Database::getInstance();
|
|
|
|
if (!empty($options['sort']) && isset($options['sort']['by']) && isset($options['sort']['desc'])) {
|
|
switch ($options['sort']['by']) {
|
|
case 'commonname':
|
|
$orderby = 'vccommonname';
|
|
break;
|
|
case 'localename':
|
|
$orderby = 'vclocalename';
|
|
break;
|
|
case 'lastseen':
|
|
$orderby = 'time';
|
|
break;
|
|
default:
|
|
$orderby = 'vclogin';
|
|
break;
|
|
}
|
|
$orderby = $orderby . ' ' . ($options['sort']['desc'] ? 'DESC' : 'ASC');
|
|
} else {
|
|
$orderby = "vclogin";
|
|
}
|
|
|
|
$query = "SELECT DISTINCT "
|
|
. "{operator}.operatorid, "
|
|
. "vclogin, "
|
|
. "vclocalename, "
|
|
. "vccommonname, "
|
|
. "code, "
|
|
. "istatus, "
|
|
. "idisabled, "
|
|
. "(:now - dtmlastvisited) AS time "
|
|
. "FROM {operator}"
|
|
. (empty($options['isolated_operator_id'])
|
|
? ""
|
|
: ", {operatortoopgroup} "
|
|
. "WHERE {operator}.operatorid = {operatortoopgroup}.operatorid "
|
|
. "AND {operatortoopgroup}.groupid IN ("
|
|
. "SELECT g.groupid FROM {opgroup} g, "
|
|
. "(SELECT {opgroup}.groupid, {opgroup}.parent FROM {opgroup}, {operatortoopgroup} "
|
|
. "WHERE {opgroup}.groupid = {operatortoopgroup}.groupid "
|
|
. "AND {operatortoopgroup}.operatorid = :operatorid) i "
|
|
. "WHERE g.groupid = i.parent "
|
|
. "OR g.parent = i.groupid "
|
|
. "OR (g.parent = i.parent AND g.parent IS NOT NULL) "
|
|
. "OR g.groupid = i.groupid "
|
|
. ")")
|
|
. " ORDER BY " . $orderby;
|
|
|
|
$values = array(
|
|
':now' => time(),
|
|
);
|
|
|
|
if (!empty($options['isolated_operator_id'])) {
|
|
$values[':operatorid'] = $options['isolated_operator_id'];
|
|
}
|
|
|
|
$operators = $db->query(
|
|
$query,
|
|
$values,
|
|
array('return_rows' => Database::RETURN_ALL_ROWS)
|
|
);
|
|
|
|
return $operators;
|
|
}
|
|
|
|
/*
|
|
* Get list of all operators
|
|
*
|
|
* @return array|null Operators list. Each its element contains (operatorid
|
|
* integer, vclogin string, vclocalename string, vccommonname string, istatus
|
|
* boolean, code string, idisabled integer, time integer)
|
|
*/
|
|
function operator_get_all()
|
|
{
|
|
$db = Database::getInstance();
|
|
|
|
return $operators = $db->query(
|
|
("SELECT operatorid, vclogin, vclocalename, vccommonname, istatus, "
|
|
. "code, idisabled, (:now - dtmlastvisited) AS time "
|
|
. "FROM {operator} ORDER BY vclogin"),
|
|
array(':now' => time()),
|
|
array('return_rows' => Database::RETURN_ALL_ROWS)
|
|
);
|
|
}
|
|
|
|
function operator_is_online($operator)
|
|
{
|
|
return $operator['time'] < Settings::get('online_timeout');
|
|
}
|
|
|
|
function operator_is_available($operator)
|
|
{
|
|
return ($operator['istatus'] == 0 && $operator['time'] < Settings::get('online_timeout'))
|
|
? "1"
|
|
: "";
|
|
}
|
|
|
|
function operator_is_away($operator)
|
|
{
|
|
return ($operator['istatus'] != 0 && $operator['time'] < Settings::get('online_timeout'))
|
|
? "1"
|
|
: "";
|
|
}
|
|
|
|
function operator_is_disabled($operator)
|
|
{
|
|
return $operator['idisabled'] == '1';
|
|
}
|
|
|
|
/**
|
|
* Update existing operator's info.
|
|
*
|
|
* Triggers {@link \Mibew\EventDispatcher\Events::OPERATOR_UPDATE} event.
|
|
*
|
|
* @param array $operator Associative array of operator's fields. This array
|
|
* must contain the following keys:
|
|
* - operatorid,
|
|
* - vclogin,
|
|
* - vcpassword,
|
|
* - vclocalename,
|
|
* - vccommonname,
|
|
* - vcemail,
|
|
* - dtmlastvisited,
|
|
* - istatus,
|
|
* - idisabled,
|
|
* - vcavatar,
|
|
* - iperm,
|
|
* - dtmrestore,
|
|
* - vcrestoretoken,
|
|
* - code
|
|
*
|
|
* @throws \InvalidArgumentException if not all operator's fields are in place.
|
|
*/
|
|
function update_operator($operator)
|
|
{
|
|
if (!check_operator_fields($operator)) {
|
|
throw new \InvalidArgumentException('Not all operator fields are specified');
|
|
}
|
|
|
|
// Get the original operator to trigger the "update" event later
|
|
$original_operator = operator_by_id($operator['operatorid']);
|
|
|
|
Database::getInstance()->query(
|
|
('UPDATE {operator} SET vclogin = :login, vcpassword=:password, '
|
|
. 'vclocalename = :local_name, vccommonname = :common_name, '
|
|
. 'vcemail = :email, dtmlastvisited = :last_visited, '
|
|
. 'istatus = :status, idisabled = :disabled, vcavatar = :avatar, '
|
|
. 'iperm = :permissions, dtmrestore = :restore_time, '
|
|
. 'vcrestoretoken = :restore_token, code = :code '
|
|
. 'WHERE operatorid = :id'),
|
|
array(
|
|
':id' => $operator['operatorid'],
|
|
':login' => $operator['vclogin'],
|
|
':password' => $operator['vcpassword'],
|
|
':local_name' => $operator['vclocalename'],
|
|
':common_name' => $operator['vccommonname'],
|
|
':email' => $operator['vcemail'],
|
|
':last_visited' => $operator['dtmlastvisited'],
|
|
':status' => $operator['istatus'],
|
|
':disabled' => $operator['idisabled'],
|
|
':avatar' => $operator['vcavatar'],
|
|
':permissions' => $operator['iperm'],
|
|
':restore_time' => $operator['dtmrestore'],
|
|
':restore_token' => $operator['vcrestoretoken'],
|
|
':code' => $operator['code'],
|
|
)
|
|
);
|
|
|
|
$args = array(
|
|
'operator' => $operator,
|
|
'original_operator' => $original_operator,
|
|
);
|
|
EventDispatcher::getInstance()->triggerEvent(Events::OPERATOR_UPDATE, $args);
|
|
}
|
|
|
|
function update_operator_avatar($operator_id, $avatar)
|
|
{
|
|
$operator = operator_by_id($operator_id);
|
|
$operator['vcavatar'] = $avatar;
|
|
update_operator($operator);
|
|
}
|
|
|
|
/**
|
|
* Create new operator
|
|
*
|
|
* Triggers {@link \Mibew\EventDispatcher\Events::OPERATOR_CREATE} event.
|
|
*
|
|
* @param string $login Operator's login
|
|
* @param string $email Operator's
|
|
* @param string $password Operator's password
|
|
* @param string $locale_name Operator's local name
|
|
* @param string $common_name Operator's international name
|
|
* @param string $avatar Operator's avatar
|
|
* @param string $code Operator's code which use to start chat with specified
|
|
* operator
|
|
* @return array Operator's array
|
|
*/
|
|
function create_operator(
|
|
$login,
|
|
$email,
|
|
$password,
|
|
$locale_name,
|
|
$common_name,
|
|
$avatar,
|
|
$code
|
|
) {
|
|
$db = Database::getInstance();
|
|
$db->query(
|
|
("INSERT INTO {operator} ("
|
|
. "vclogin, vcpassword, vclocalename, vccommonname, vcavatar, "
|
|
. "vcemail, code "
|
|
. ") VALUES ("
|
|
. ":login, :pass, :localename, :commonname, :avatar, "
|
|
. ":email, :code"
|
|
. ")"),
|
|
array(
|
|
':login' => $login,
|
|
':pass' => calculate_password_hash($login, $password),
|
|
':localename' => $locale_name,
|
|
':commonname' => $common_name,
|
|
':avatar' => $avatar,
|
|
':email' => $email,
|
|
':code' => $code,
|
|
)
|
|
);
|
|
|
|
$id = $db->insertedId();
|
|
$new_operator = $db->query(
|
|
"SELECT * FROM {operator} WHERE operatorid = ?",
|
|
array($id),
|
|
array('return_rows' => Database::RETURN_ONE_ROW)
|
|
);
|
|
|
|
$event = array('operator' => $new_operator);
|
|
EventDispatcher::getInstance()->triggerEvent(Events::OPERATOR_CREATE, $event);
|
|
|
|
return $new_operator;
|
|
}
|
|
|
|
/**
|
|
* Delete operator
|
|
*
|
|
* This function remove operator and associations with groups for this operator
|
|
* from datatabse.
|
|
* It triggers {@link \Mibew\EventDispatcher\Events::OPERATOR_DELETE} event.
|
|
*
|
|
* @param int $operator_id Operator ID
|
|
*/
|
|
function delete_operator($operator_id)
|
|
{
|
|
$db = Database::getInstance();
|
|
$db->query(
|
|
"DELETE FROM {operatortoopgroup} WHERE operatorid = ?",
|
|
array($operator_id)
|
|
);
|
|
$db->query(
|
|
"DELETE FROM {operator} WHERE operatorid = ?",
|
|
array($operator_id)
|
|
);
|
|
|
|
// Trigger 'operatorDelete' event
|
|
$dispatcher = EventDispatcher::getInstance();
|
|
$args = array('id' => $operator_id);
|
|
$dispatcher->triggerEvent(Events::OPERATOR_DELETE, $args);
|
|
}
|
|
|
|
/**
|
|
* Set current status of the operator('available' or 'away')
|
|
*
|
|
* @param int $operator_id Id of the operator
|
|
* @param int $istatus Operator status: '0' means 'available' and '1' means
|
|
* 'away'
|
|
*/
|
|
function notify_operator_alive($operator_id, $istatus)
|
|
{
|
|
$operator = operator_by_id($operator_id);
|
|
$operator['istatus'] = $istatus;
|
|
$operator['dtmlastvisited'] = time();
|
|
update_operator($operator);
|
|
}
|
|
|
|
/**
|
|
* Indicates if at least one operator of the group is online
|
|
* @param int $group_id Id of the group
|
|
* @return boolean true if the group have online operators and false otherwise
|
|
*/
|
|
function has_online_operators($group_id = "")
|
|
{
|
|
$db = Database::getInstance();
|
|
|
|
$query = "SELECT count(*) AS total, MIN(:now - dtmlastvisited) AS time "
|
|
. "FROM {operator}";
|
|
$values = array(':now' => time());
|
|
if ($group_id) {
|
|
$query .= ", {operatortoopgroup}, {opgroup} "
|
|
. "WHERE {opgroup}.groupid = {operatortoopgroup}.groupid "
|
|
. "AND ({opgroup}.groupid = :groupid OR {opgroup}.parent = :groupid) "
|
|
. "AND {operator}.operatorid = {operatortoopgroup}.operatorid "
|
|
. "AND istatus = 0";
|
|
$values[':groupid'] = $group_id;
|
|
} else {
|
|
if (Settings::get('enablegroups') == 1) {
|
|
// If the groups and prechat survey are enabled and a button was
|
|
// generated not for concrete group a user must select a group to
|
|
// chat with. All groups will be checked for online operators. If
|
|
// only operators, who do not related with groups, are online a user
|
|
// cannot complete prechat survey because there will be no online
|
|
// groups. The following code fixes this strange behaviour.
|
|
$query .= ", {operatortoopgroup} "
|
|
. "WHERE {operator}.operatorid = {operatortoopgroup}.operatorid "
|
|
. "AND istatus = 0";
|
|
} else {
|
|
$query .= " WHERE istatus = 0";
|
|
}
|
|
}
|
|
|
|
$row = $db->query(
|
|
$query,
|
|
$values,
|
|
array('return_rows' => Database::RETURN_ONE_ROW)
|
|
);
|
|
|
|
return ($row['time'] < Settings::get('online_timeout')) && ($row['total'] > 0);
|
|
}
|
|
|
|
/**
|
|
* Gets list of operators that are currently online.
|
|
*
|
|
* @param int|null $group_id ID of the group online operators should belong to
|
|
* or null to search within all groups.
|
|
* @return Array List of online operators. Each item of the list is an array
|
|
* with operators info. For details of its structure See description of
|
|
* {@link operator_by_id()} function's return value.
|
|
*/
|
|
function get_online_operators($group_id = null)
|
|
{
|
|
$db = Database::getInstance();
|
|
|
|
$groups = false;
|
|
if (!$group_id) {
|
|
// We should not care about groups. Just return all online operators.
|
|
$groups = $db->query(
|
|
('SELECT * FROM {operator} '
|
|
. 'WHERE istatus = 0 AND (:now - dtmlastvisited) < :timeout'),
|
|
array(
|
|
':now' => time(),
|
|
':timeout' => Settings::get('online_timeout'),
|
|
),
|
|
array('return_rows' => Database::RETURN_ALL_ROWS)
|
|
);
|
|
} else {
|
|
$groups = $db->query(
|
|
('SELECT o.* FROM {operator} o, {operatortoopgroup} r, {opgroup} g '
|
|
// The operator should be online
|
|
. 'WHERE o.istatus = 0 AND (:now - o.dtmlastvisited) < :timeout '
|
|
// And it should belong to the specified group or to one of
|
|
// its children.
|
|
. 'AND o.operatorid = r.operatorid '
|
|
. 'AND r.groupid = g.groupid '
|
|
. 'AND (g.groupid = :group_id OR g.parent = :group_id)'),
|
|
array(
|
|
':now' => time(),
|
|
':timeout' => Settings::get('online_timeout'),
|
|
':group_id' => $group_id,
|
|
),
|
|
array('return_rows' => Database::RETURN_ALL_ROWS)
|
|
);
|
|
}
|
|
|
|
return $groups ? $groups : array();
|
|
}
|
|
|
|
/**
|
|
* Indicates if operator online or not
|
|
*
|
|
* @param int $operator_id Id of the operator
|
|
* @return boolean true if operator is online and false otherwise
|
|
*/
|
|
function is_operator_online($operator_id)
|
|
{
|
|
$db = Database::getInstance();
|
|
$row = $db->query(
|
|
("SELECT count(*) AS total, "
|
|
. "MIN(:now - dtmlastvisited) AS time "
|
|
. "FROM {operator} WHERE operatorid = :operatorid"),
|
|
array(
|
|
':now' => time(),
|
|
':operatorid' => $operator_id,
|
|
),
|
|
array('return_rows' => Database::RETURN_ONE_ROW)
|
|
);
|
|
|
|
return ($row['time'] < Settings::get('online_timeout')) && ($row['total'] == 1);
|
|
}
|
|
|
|
/**
|
|
* Returns name of the operator. Choose between vclocalname and vccommonname
|
|
*
|
|
* @param array $operator Operator's array
|
|
* @return string Operator's name
|
|
*/
|
|
function get_operator_name($operator)
|
|
{
|
|
if (get_home_locale() == get_current_locale()) {
|
|
return $operator['vclocalename'];
|
|
} else {
|
|
return $operator['vccommonname'];
|
|
}
|
|
}
|
|
|
|
function setup_redirect_links(UrlGeneratorInterface $url_generator, $threadid, $operator, $token)
|
|
{
|
|
$result = array();
|
|
|
|
$operator_in_isolation = in_isolation($operator);
|
|
|
|
$list_options = $operator_in_isolation
|
|
? array('isolated_operator_id' => $operator['operatorid'])
|
|
: array();
|
|
$operators = get_operators_list($list_options);
|
|
$operators_count = count($operators);
|
|
|
|
$groups_count = 0;
|
|
$groups = array();
|
|
if (Settings::get('enablegroups') == "1") {
|
|
$groupslist = $operator_in_isolation
|
|
? get_groups_for_operator($operator, true)
|
|
: get_groups(true);
|
|
foreach ($groupslist as $group) {
|
|
if ($group['inumofagents'] == 0) {
|
|
continue;
|
|
}
|
|
$groups[] = $group;
|
|
}
|
|
$groups_count = count($groups);
|
|
}
|
|
|
|
$p = pagination_info(max($operators_count, $groups_count), 8);
|
|
$result['pagination'] = $p;
|
|
|
|
$operators = array_slice($operators, $p['start'], $p['end'] - $p['start']);
|
|
$groups = array_slice($groups, $p['start'], $p['end'] - $p['start']);
|
|
|
|
$agent_list = "";
|
|
$params = array('thread_id' => $threadid, 'token' => $token);
|
|
foreach ($operators as $agent) {
|
|
$params['nextAgent'] = $agent['operatorid'];
|
|
$status = $agent['time'] < Settings::get('online_timeout')
|
|
? ($agent['istatus'] == 0
|
|
? getlocal("(online)")
|
|
: getlocal("(away)"))
|
|
: "";
|
|
$agent_list .= "<li><a href=\"" . $url_generator->generate('chat_operator_redirect', $params)
|
|
. "\" title=\"" . get_operator_name($agent) . "\">"
|
|
. get_operator_name($agent)
|
|
. "</a> $status</li>";
|
|
}
|
|
$result['redirectToAgent'] = $agent_list;
|
|
|
|
$group_list = "";
|
|
if (Settings::get('enablegroups') == "1") {
|
|
$params = array('thread_id' => $threadid, 'token' => $token);
|
|
foreach ($groups as $group) {
|
|
$params['nextGroup'] = $group['groupid'];
|
|
$status = group_is_online($group)
|
|
? getlocal("(online)")
|
|
: (group_is_away($group) ? getlocal("(away)") : "");
|
|
$group_list .= "<li><a href=\"" . $url_generator->generate('chat_operator_redirect', $params)
|
|
. "\" title=\"" . get_group_name($group) . "\">"
|
|
. get_group_name($group)
|
|
. "</a> $status</li>";
|
|
}
|
|
}
|
|
$result['redirectToGroup'] = $group_list;
|
|
|
|
return $result;
|
|
}
|
|
|
|
function get_permission_list()
|
|
{
|
|
static $permission_list = array();
|
|
|
|
if (count($permission_list) == 0) {
|
|
$descriptions = permission_descriptions();
|
|
foreach (permission_ids() as $perm_code => $perm_id) {
|
|
$permission_list[] = array(
|
|
'id' => $perm_id,
|
|
'descr' => $descriptions[$perm_code],
|
|
);
|
|
}
|
|
}
|
|
|
|
return $permission_list;
|
|
}
|
|
|
|
function is_capable($perm, $operator)
|
|
{
|
|
$permissions = $operator && isset($operator['iperm']) ? $operator['iperm'] : 0;
|
|
|
|
return $perm >= 0 && $perm < 32 && ($permissions & (1 << $perm)) != 0;
|
|
}
|
|
|
|
function in_isolation($operator)
|
|
{
|
|
return !is_capable(CAN_ADMINISTRATE, $operator)
|
|
&& Settings::get('enablegroups')
|
|
&& Settings::get('enablegroupsisolation');
|
|
}
|
|
|
|
/**
|
|
* Prepare values to render page menu.
|
|
*
|
|
* @param array $operator An array with operators data.
|
|
* @param boolean $has_right Restricts access to menu items. If it equals to
|
|
* FALSE only "Home", "Visitors", and "Chat history" items will be displayed.
|
|
* Otherwise items set depends on operator's permissions and system settings.
|
|
* Default value is TRUE.
|
|
* @return array
|
|
*/
|
|
function prepare_menu($operator, $has_right = true)
|
|
{
|
|
$result = array();
|
|
|
|
$result['operator'] = get_operator_name($operator);
|
|
$result['isOnline'] = is_operator_online($operator['operatorid']);
|
|
if ($has_right) {
|
|
$result['showban'] = Settings::get('enableban') == "1";
|
|
$result['showstat'] = is_capable(CAN_VIEWSTATISTICS, $operator) && (Settings::get('enablestatistics') == "1");
|
|
$result['showadmin'] = is_capable(CAN_ADMINISTRATE, $operator);
|
|
$result['currentopid'] = $operator['operatorid'];
|
|
}
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Calculate hashed password value based upon operator's login and password
|
|
*
|
|
* By default function tries to make use of Blowfish encryption algorithm,
|
|
* with salted MD5 as a second possible choice, and unsalted MD5 as a fallback
|
|
* option
|
|
*
|
|
* @param string $login operator's login
|
|
* @param string $password Operator's password (as plain text)
|
|
*
|
|
* @return string hashed password value
|
|
*/
|
|
function calculate_password_hash($login, $password)
|
|
{
|
|
$hash = '*0';
|
|
if (CRYPT_BLOWFISH == 1) {
|
|
if (defined('PHP_VERSION_ID') && (PHP_VERSION_ID > 50306)) {
|
|
$hash = crypt($password, '$2y$08$' . generate_bf_salt($login));
|
|
} else {
|
|
$hash = crypt($password, '$2a$08$' . generate_bf_salt($login));
|
|
}
|
|
}
|
|
|
|
if ((CRYPT_MD5 == 1) && !strcmp($hash, '*0')) {
|
|
$hash = crypt($password, '$1$' . $login);
|
|
}
|
|
|
|
return strcmp($hash, '*0') ? $hash : md5($password);
|
|
}
|
|
|
|
/**
|
|
* Generates correct blowfish salt based a string.
|
|
*
|
|
* @param string $string A string which should be turned to blowfish salt.
|
|
* @return string Correct blowfish salt.
|
|
*/
|
|
function generate_bf_salt($string)
|
|
{
|
|
$result = '';
|
|
$bin = unpack('C*', md5($string, true));
|
|
for ($i = 0; $i < count($bin); $i++) {
|
|
$shift = 2 + ($i % 3) * 2;
|
|
$first = ($bin[$i + 1] >> $shift);
|
|
$second = ($bin[$i + 1] & bindec(str_repeat('1', $shift)));
|
|
switch ($shift) {
|
|
case 2:
|
|
$result .= bf_salt_character($first);
|
|
$tmp = $second;
|
|
break;
|
|
case 4:
|
|
$result .= bf_salt_character(($tmp << 4) | $first);
|
|
$tmp = $second;
|
|
break;
|
|
case 6:
|
|
$result .= bf_salt_character(($tmp << 2) | $first);
|
|
$result .= bf_salt_character($second);
|
|
break;
|
|
}
|
|
}
|
|
if ($shift == 2) {
|
|
$result .= bf_salt_character($second);
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Convert character code to a correct blowfish character.
|
|
*
|
|
* @param integer $num Character code.
|
|
* @return string Character that can be used in blowfish salt.
|
|
*/
|
|
function bf_salt_character($num)
|
|
{
|
|
if ($num > 63) {
|
|
return chr(46);
|
|
} elseif ($num < 12) {
|
|
return chr(46 + $num);
|
|
} elseif ($num < 38) {
|
|
return chr(53 + $num);
|
|
} else {
|
|
return chr(59 + $num);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Validate incoming hashed value to be the hashed value of operator's password
|
|
*
|
|
* @param string $login operator's login
|
|
* @param string $password Operator's password (as plain text)
|
|
* @param string $hash incoming hashed value
|
|
*
|
|
* @return boolean true if incoming value is the correct hashed value of
|
|
* operators' password and false otherwise
|
|
*/
|
|
function check_password_hash($login, $password, $hash)
|
|
{
|
|
if (preg_match('/^\$/', $hash)) {
|
|
return !strcmp(calculate_password_hash($login, $password), $hash);
|
|
} else {
|
|
return !strcmp(md5($password), $hash);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Updates set of groups the operator belongs to.
|
|
*
|
|
* Triggers {@link \Mibew\EventDispatcher\Events::GROUP_UPDATE_OPERATORS} event.
|
|
*
|
|
* @param int $operator_id ID of the operator.
|
|
* @param array $new_value List of operator's groups IDs.
|
|
*/
|
|
function update_operator_groups($operator_id, $new_value)
|
|
{
|
|
// Get difference of groups the operator belongs to before and after the
|
|
// update.
|
|
$original_groups = get_operator_group_ids($operator_id);
|
|
$groups_union = array_unique(array_merge($original_groups, $new_value));
|
|
$groups_intersect = array_intersect($original_groups, $new_value);
|
|
$updated_groups = array_diff($groups_union, $groups_intersect);
|
|
|
|
// Get members of all updated groups. It will be used to trigger the
|
|
// "update" event later.
|
|
$original_relations = array();
|
|
foreach ($updated_groups as $group_id) {
|
|
$original_relations[$group_id] = get_group_members($group_id);
|
|
}
|
|
|
|
// Update group members
|
|
$db = Database::getInstance();
|
|
$db->query(
|
|
"DELETE FROM {operatortoopgroup} WHERE operatorid = ?",
|
|
array($operator_id)
|
|
);
|
|
|
|
foreach ($new_value as $group_id) {
|
|
$db->query(
|
|
"INSERT INTO {operatortoopgroup} (groupid, operatorid) VALUES (?,?)",
|
|
array($group_id, $operator_id)
|
|
);
|
|
}
|
|
|
|
// Trigger the "update" event
|
|
foreach ($original_relations as $group_id => $operators) {
|
|
$args = array(
|
|
'group' => group_by_id($group_id),
|
|
'original_operators' => $operators,
|
|
'operators' => get_group_members($group_id),
|
|
);
|
|
EventDispatcher::getInstance()->triggerEvent(Events::GROUP_UPDATE_OPERATORS, $args);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Makes an operator disabled.
|
|
*
|
|
* @param int $operator_id ID of the operator to disable.
|
|
*/
|
|
function disable_operator($operator_id)
|
|
{
|
|
$operator = operator_by_id($operator_id);
|
|
$operator['idisabled'] = 1;
|
|
update_operator($operator);
|
|
}
|
|
|
|
/**
|
|
* Makes an operator enabled.
|
|
*
|
|
* @param int $operator_id ID of the operator to enable.
|
|
*/
|
|
function enable_operator($operator_id)
|
|
{
|
|
$operator = operator_by_id($operator_id);
|
|
$operator['idisabled'] = 0;
|
|
update_operator($operator);
|
|
}
|
|
|
|
/**
|
|
* Checks that operator array has all needed fields.
|
|
*
|
|
* @param array $operator Associative operator's array.
|
|
* @return bool Boolean true if all the fields are in place and false otherwise.
|
|
*/
|
|
function check_operator_fields($operator)
|
|
{
|
|
$obligatory_fields = array(
|
|
'operatorid',
|
|
'vclogin',
|
|
'vcpassword',
|
|
'vclocalename',
|
|
'vccommonname',
|
|
'vcemail',
|
|
'dtmlastvisited',
|
|
'istatus',
|
|
'idisabled',
|
|
'vcavatar',
|
|
'iperm',
|
|
'dtmrestore',
|
|
'vcrestoretoken',
|
|
'code',
|
|
);
|
|
|
|
return (count(array_diff($obligatory_fields, array_keys($operator))) == 0);
|
|
}
|