594 lines
17 KiB
PHP
594 lines
17 KiB
PHP
<?php
|
|
/**
|
|
*
|
|
* phpBB Studio - Advanced Points System. An extension for the phpBB Forum Software package.
|
|
*
|
|
* @copyright (c) 2019, phpBB Studio, https://www.phpbbstudio.com
|
|
* @license GNU General Public License, version 2 (GPL-2.0)
|
|
*
|
|
*/
|
|
|
|
namespace phpbbstudio\aps\actions;
|
|
|
|
/**
|
|
* phpBB Studio - Advanced Points System points actions manager.
|
|
*/
|
|
class manager
|
|
{
|
|
/** @var \phpbb\di\service_collection Array of action types from the service collection */
|
|
protected $actions;
|
|
|
|
/** @var \phpbbstudio\aps\points\distributor */
|
|
protected $distributor;
|
|
|
|
/** @var \phpbbstudio\aps\core\functions */
|
|
protected $functions;
|
|
|
|
/** @var \phpbb\language\language */
|
|
protected $lang;
|
|
|
|
/** @var \phpbb\log\log */
|
|
protected $log;
|
|
|
|
/** @var \phpbbstudio\aps\points\valuator */
|
|
protected $valuator;
|
|
|
|
/** @var \phpbb\user */
|
|
protected $user;
|
|
|
|
/** @var array Array of points fields for the triggered action types */
|
|
protected $fields;
|
|
|
|
/** @var array Array of action types that are triggered */
|
|
protected $types;
|
|
|
|
/** @var array Array of users that can receive points for the triggered action */
|
|
protected $users;
|
|
|
|
/** @var array Array of point values required for the triggered action types */
|
|
protected $values;
|
|
|
|
/**
|
|
* Constructor.
|
|
*
|
|
* @param \phpbb\di\service_collection $actions Array of action types from the service collection
|
|
* @param \phpbbstudio\aps\points\distributor $distributor APS Distributor object
|
|
* @param \phpbbstudio\aps\core\functions $functions APS Core functions
|
|
* @param \phpbb\language\language $lang Language object
|
|
* @param \phpbb\log\log $log phpBB Log object
|
|
* @param \phpbbstudio\aps\points\valuator $valuator APS Valuator
|
|
* @param \phpbb\user $user User object
|
|
* @return void
|
|
* @access public
|
|
*/
|
|
public function __construct($actions, \phpbbstudio\aps\points\distributor $distributor, \phpbbstudio\aps\core\functions $functions, \phpbb\language\language $lang, \phpbb\log\log $log, \phpbbstudio\aps\points\valuator $valuator, \phpbb\user $user)
|
|
{
|
|
$this->distributor = $distributor;
|
|
$this->functions = $functions;
|
|
$this->lang = $lang;
|
|
$this->log = $log;
|
|
$this->valuator = $valuator;
|
|
$this->user = $user;
|
|
|
|
$this->actions = $actions;
|
|
}
|
|
|
|
/**
|
|
* Get the APS Distributor object.
|
|
*
|
|
* @return \phpbbstudio\aps\points\distributor
|
|
* @access public
|
|
*/
|
|
public function get_distributor()
|
|
{
|
|
return $this->distributor;
|
|
}
|
|
|
|
/**
|
|
* Get the APS Core functions.
|
|
*
|
|
* @return \phpbbstudio\aps\core\functions
|
|
* @access public
|
|
*/
|
|
public function get_functions()
|
|
{
|
|
return $this->functions;
|
|
}
|
|
|
|
/**
|
|
* Get the APS Valuator.
|
|
*
|
|
* @return \phpbbstudio\aps\points\valuator
|
|
* @access public
|
|
*/
|
|
public function get_valuator()
|
|
{
|
|
return $this->valuator;
|
|
}
|
|
|
|
/**
|
|
* Get the localised points name.
|
|
*
|
|
* @return string The localised points name
|
|
* @access public
|
|
*/
|
|
public function get_name()
|
|
{
|
|
return $this->functions->get_name();
|
|
}
|
|
|
|
/**
|
|
* Clean an array from a listener, turns an object into an array.
|
|
*
|
|
* @param mixed $event The event to clean
|
|
* @return array The event data
|
|
* @access public
|
|
*/
|
|
public function clean_event($event)
|
|
{
|
|
if ($event instanceof \phpbb\event\data)
|
|
{
|
|
return (array) $event->get_data();
|
|
}
|
|
else
|
|
{
|
|
return (array) $event;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get all values for the provided key in an array.
|
|
*
|
|
* @param array $array The array to retrieve the values from
|
|
* @param string $key The keys of which to return the values
|
|
* @return array Array of unique integers
|
|
* @access public
|
|
*/
|
|
public function get_identifiers(array $array, $key)
|
|
{
|
|
return (array) array_map('intval', array_unique(array_filter(array_column($array, $key))));
|
|
}
|
|
|
|
/**
|
|
* Trigger a point action and calculate users' points.
|
|
*
|
|
* This is the "main" function for this extension.
|
|
*
|
|
* $action
|
|
* Calling this with an $action will trigger all action type which have their get_action() set to $action.
|
|
*
|
|
* $user_ids
|
|
* User identifiers are for the users who can receive points by this action, this user ($this->user) is
|
|
* automatically added to the list. If it was already present in the list, it's filtered out.
|
|
*
|
|
* $event
|
|
* An array with data that was available from the event (or any other occurrence) that triggered this action.
|
|
* For instance, phpBB's event object that is available in a listener. If indeed phpBB's event object is
|
|
* send it is automatically 'cleaned', which means the object is turned into an array.
|
|
*
|
|
* $forum_ids
|
|
* A list of forum identifiers for which the point values should be retrieved, as those values are necessary
|
|
* to require the amount of points for the users. If it's left empty it will assume that the triggered action
|
|
* is a "global" action, which means the forum_id = 0.
|
|
*
|
|
* @param string $action The action to trigger
|
|
* @param array|int $user_ids The user identifiers that can receive points
|
|
* @param array $event The event data
|
|
* @param array|int $forum_ids The forum identifiers (array or single value)
|
|
* @return void
|
|
* @access public
|
|
*/
|
|
public function trigger($action, $user_ids = [], $event = [], $forum_ids = 0)
|
|
{
|
|
// 1. Initialise arrays
|
|
$this->initialise_arrays();
|
|
|
|
// 2. Get action types
|
|
$this->get_types_and_fields($action);
|
|
|
|
// 3. Get values
|
|
$this->get_values($forum_ids);
|
|
|
|
// 4. Set users
|
|
$this->set_users($user_ids);
|
|
|
|
// 5. Calculate
|
|
$this->calculate($this->clean_event($event));
|
|
|
|
// 6. Distribute
|
|
$this->distribute();
|
|
}
|
|
|
|
/**
|
|
* Initialise the array fields used by this points manager.
|
|
*
|
|
* Has to be declared per each trigger() call, as otherwise types are carried over in chained calls.
|
|
*
|
|
* @return void
|
|
* @access protected
|
|
*/
|
|
protected function initialise_arrays()
|
|
{
|
|
// Array of points fields for the triggered action types
|
|
$this->fields = [0 => [], 1 => []];
|
|
|
|
// Array of action types that are triggered
|
|
$this->types = [];
|
|
|
|
// Array of users that can receive points for the triggered action
|
|
$this->users = [];
|
|
|
|
// Array of point values required for the triggered action types
|
|
$this->values = [];
|
|
}
|
|
|
|
/**
|
|
* Get all action types and their fields for the trigger action.
|
|
*
|
|
* $types
|
|
* While the $this->actions array holds ALL the registered action types,
|
|
* only a certain few are required. Only the required once are added to $this->types.
|
|
*
|
|
* $fields
|
|
* Each action type has an array of point value keys with a language string as value.
|
|
* Those keys are used for storing the points values set by the Administrator in the database.
|
|
* Therefore a list is generated with all the fields that need to be retrieved from the database.
|
|
*
|
|
* @param string $action The action that is triggered
|
|
* @return void
|
|
* @access protected
|
|
*/
|
|
protected function get_types_and_fields($action)
|
|
{
|
|
/** @var \phpbbstudio\aps\actions\type\action $type */
|
|
foreach ($this->actions as $name => $type)
|
|
{
|
|
// Only add action types that are listed under this $action
|
|
if ($type->get_action() === $action)
|
|
{
|
|
// Add this service to the action types
|
|
$this->types[$name] = $type;
|
|
|
|
// Get scope: 0 = local | 1 = global
|
|
$key = (int) $type->is_global();
|
|
|
|
// Get the type fields indexed by the scope
|
|
$this->fields[$key] = array_merge($type->get_fields(), $this->fields[$key]);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get all point values required by the triggered action types from the database.
|
|
*
|
|
* $values
|
|
* Get all point values from the database that are in the $fields array and
|
|
* have their forum identifier set to one provided in the $forum_ids array.
|
|
* The values array will contain all point values indexed by the forum identifier,
|
|
* if the fields are global, the forum identifier is set to 0.
|
|
*
|
|
* @param array|int $forum_ids The forum identifiers
|
|
* @return void
|
|
* @access protected
|
|
*/
|
|
protected function get_values($forum_ids)
|
|
{
|
|
// Create array filled with integers
|
|
$forum_ids = is_array($forum_ids) ? array_map('intval', $forum_ids) : [(int) $forum_ids];
|
|
|
|
// Make sure there are only unique and non-empty forum identifiers
|
|
$forum_ids = array_unique($forum_ids);
|
|
|
|
$this->values = $this->valuator->get_points($this->fields, $forum_ids);
|
|
}
|
|
|
|
/**
|
|
* Set all users available for receiving points by the triggered action.
|
|
*
|
|
* $user_ids
|
|
* The array of user identifiers provided from the place where the action is triggered.
|
|
* This user's ($this->user) identifier is automatically added.
|
|
*
|
|
* $users
|
|
* The array of users that are able to receive points, with a base array to make sure all keys are set,
|
|
* aswell as all the users' current points.
|
|
*
|
|
* @param array|int $user_ids The user identifiers
|
|
* @return void
|
|
* @access protected
|
|
*/
|
|
protected function set_users($user_ids)
|
|
{
|
|
// Create array filled with integers
|
|
$user_ids = is_array($user_ids) ? array_map('intval', $user_ids) : [(int) $user_ids];
|
|
|
|
// Make sure to include this user ($this->user)
|
|
$user_ids[] = (int) $this->user->data['user_id'];
|
|
|
|
// Make sure only unique users are set
|
|
$user_ids = array_unique(array_filter($user_ids));
|
|
|
|
// If there is only one user, that will be this user, so no need to query
|
|
if (count($user_ids) === 1)
|
|
{
|
|
// Build the base user array for this user
|
|
$this->users[(int) $this->user->data['user_id']] = $this->user_array($this->user->data['user_points']);
|
|
}
|
|
else
|
|
{
|
|
// Grab all the current point values for these users
|
|
$user_points = $this->valuator->users($user_ids);
|
|
|
|
foreach ($user_ids as $user_id)
|
|
{
|
|
if (isset($user_points[$user_id]))
|
|
{
|
|
// Build the base user arrays
|
|
$this->users[$user_id] = $this->user_array($user_points[$user_id]);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Lets make sure the anonymous user is never used
|
|
unset($this->users[ANONYMOUS]);
|
|
}
|
|
|
|
/**
|
|
* Let all the required action types calculate their user points.
|
|
*
|
|
* @param array $data Array of event data
|
|
* @return void
|
|
* @access protected
|
|
*/
|
|
protected function calculate(array $data)
|
|
{
|
|
/** @var \phpbbstudio\aps\actions\type\action $type */
|
|
foreach ($this->types as $type)
|
|
{
|
|
// Make the functions object available
|
|
$type->set_functions($this->functions);
|
|
|
|
// Set the users
|
|
$type->set_users($this->users);
|
|
|
|
// Check if APS is in Safe Mode
|
|
if ($this->functions->safe_mode())
|
|
{
|
|
// If so, catch any exceptions and log them
|
|
try
|
|
{
|
|
$type->calculate($data, $this->values);
|
|
}
|
|
catch (\Exception $e)
|
|
{
|
|
// Catch any error in the action type and log it!
|
|
$this->log->add('critical', $this->user->data['user_id'], $this->user->ip, 'LOG_ACP_APS_CALCULATION_ERROR', time(), [$e->getMessage(), $e->getFile(), $e->getLine(), $this->functions->get_name()]);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// If not, calculate and let the exceptions do their thing.
|
|
$type->calculate($data, $this->values);
|
|
}
|
|
|
|
// Iterate over all the users
|
|
foreach (array_keys($this->users) as $user_id)
|
|
{
|
|
// Get all received points for this user from this action type
|
|
$this->users[$user_id]['actions'][] = $type->get_points($user_id);
|
|
|
|
// Check for logs that need approving
|
|
if ($approve = $type->get_approve($user_id))
|
|
{
|
|
$this->users[$user_id]['approve'] = array_merge($this->users[$user_id]['approve'], $approve);
|
|
}
|
|
|
|
// Check for logs that need disapproving
|
|
if ($disapprove = $type->get_disapprove($user_id))
|
|
{
|
|
$this->users[$user_id]['disapprove'] = array_merge($this->users[$user_id]['disapprove'], $disapprove);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Distribute the points gained for all the users
|
|
*
|
|
* @return void
|
|
* @access protected
|
|
*/
|
|
protected function distribute()
|
|
{
|
|
// Iterate over all the users
|
|
foreach ($this->users as $user_id => $user_row)
|
|
{
|
|
// Iterate over all the action types
|
|
foreach ($user_row['actions'] as $actions)
|
|
{
|
|
// Iterate over the arrays added per action type
|
|
foreach ($actions as $action)
|
|
{
|
|
if ($action['approved'])
|
|
{
|
|
// Calculate the total points gained for this user
|
|
$this->functions->equate_reference($this->users[$user_id]['total'], $action['points']);
|
|
}
|
|
|
|
// Grab the post identifier, as we group the logs per post id
|
|
$post_id = (int) $action['post_id'];
|
|
|
|
// Set the logs for this user
|
|
$this->set_logs($user_id, $post_id, $action);
|
|
}
|
|
}
|
|
|
|
// And send it off: update user points and add log entries
|
|
$this->distributor->distribute(
|
|
$user_id,
|
|
$this->users[$user_id]['total'],
|
|
$this->users[$user_id]['logs'],
|
|
$this->users[$user_id]['current']
|
|
);
|
|
|
|
// Approve logs
|
|
if ($user_row['approve'])
|
|
{
|
|
$user_points = $this->functions->equate_points($this->users[$user_id]['total'], $this->users[$user_id]['current']);
|
|
|
|
$this->distributor->approve($user_id, array_unique(array_filter($user_row['approve'])), $user_points);
|
|
}
|
|
|
|
// Disapprove logs
|
|
if ($user_row['disapprove'])
|
|
{
|
|
$this->distributor->disapprove($user_id, array_unique(array_filter($user_row['disapprove'])));
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Set the log entries for this user and index per post identifier.
|
|
*
|
|
* @param int $user_id The user identifier
|
|
* @param int $post_id The post identifier
|
|
* @param array $row The log array
|
|
* @return void
|
|
* @access protected
|
|
*/
|
|
protected function set_logs($user_id, $post_id, array $row)
|
|
{
|
|
// Get the logs in a local variable for easier coding
|
|
$logs = $this->users[$user_id]['logs'];
|
|
|
|
// Filter out the empty values except the first key
|
|
if (empty($logs[$post_id]))
|
|
{
|
|
$first = array_splice($row['logs'], 0, 1);
|
|
$row['logs'] = array_filter($row['logs']);
|
|
$row['logs'] = $first + $row['logs'];
|
|
}
|
|
else
|
|
{
|
|
$row['logs'] = array_filter($row['logs']);
|
|
}
|
|
|
|
// If there are no logs entries yet under this post identifier
|
|
if (empty($logs[$post_id]))
|
|
{
|
|
$logs[$post_id] = [
|
|
'action' => (string) $this->main_log($row['logs']),
|
|
'actions' => (array) $row['logs'],
|
|
'approved' => (bool) $row['approved'],
|
|
'forum_id' => (int) $row['forum_id'],
|
|
'topic_id' => (int) $row['topic_id'],
|
|
'post_id' => (int) $row['post_id'],
|
|
'user_id' => (int) $user_id,
|
|
'reportee_id' => (int) $this->user->data['user_id'],
|
|
'reportee_ip' => (string) $this->user->ip,
|
|
'points_old' => (double) $this->users[$user_id]['current'],
|
|
'points_sum' => (double) $row['points'],
|
|
'points_new' => (double) $this->functions->equate_points($this->users[$user_id]['current'], $row['points']),
|
|
];
|
|
}
|
|
else
|
|
{
|
|
// Else there already exists log entries under this post identifier, so merge this one in
|
|
$this->merge_logs($logs[$post_id]['actions'], $row['logs']);
|
|
|
|
// Equate (by reference) the points gained ('sum') and the new total ('new').
|
|
$this->functions->equate_reference($logs[$post_id]['points_sum'], $row['points']);
|
|
$this->functions->equate_reference($logs[$post_id]['points_new'], $row['points']);
|
|
}
|
|
|
|
// Set the logs in the global variable again
|
|
$this->users[$user_id]['logs'] = $logs;
|
|
}
|
|
|
|
/**
|
|
* Get the "main" log entry, the first key of the array.
|
|
*
|
|
* @param array $logs The logs array
|
|
* @return string The main log entry string
|
|
* @access protected
|
|
*/
|
|
protected function main_log(array $logs)
|
|
{
|
|
reset($logs);
|
|
$action = key($logs);
|
|
|
|
return $action;
|
|
}
|
|
|
|
/**
|
|
* Merge a log entry into existing log entries.
|
|
*
|
|
* Log entries are language strings (key) with point values (value).
|
|
* array('APS_SOME_ACTION' => 5.00)
|
|
*
|
|
* If logs are merged, an array is created which has to be equated.
|
|
* array('APS_SOME_ACTION' => array(5.00, 2.00)
|
|
*
|
|
* @param array $logs The existing log entries
|
|
* @param array $array The log entry to merge in
|
|
* @return void Passed by reference
|
|
* @access protected
|
|
*/
|
|
protected function merge_logs(array &$logs, array $array)
|
|
{
|
|
// Merge the array in to the existing entries
|
|
$logs = array_merge_recursive($logs, $array);
|
|
|
|
// Iterate over the logged actions
|
|
foreach ($logs as $key => $value)
|
|
{
|
|
// If the logged action is no longer a single points value, equate it.
|
|
if (is_array($value))
|
|
{
|
|
$logs[$key] = $this->functions->equate_array($value);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Set up a base array for this user.
|
|
*
|
|
* 'current
|
|
* The user's current points
|
|
*
|
|
* 'actions'
|
|
* Array that will be filled with arrays added by all the action types.
|
|
*
|
|
* 'points'
|
|
* Array that will be filled with points added by all the action types.
|
|
*
|
|
* 'approve'
|
|
* Array that will be filled with post identifiers that need to be approved from the logs table.
|
|
*
|
|
* 'disapprove'
|
|
* Array that will be filled with post identifiers that need to be disapproved from the logs table.
|
|
*
|
|
* 'logs'
|
|
* Array of log entries that are going to be added for this user.
|
|
*
|
|
* 'total'
|
|
* The total points gained for this user, summing up all points per action type.
|
|
*
|
|
* @param double $points The user's current points
|
|
* @return array The user's base array
|
|
* @access protected
|
|
*/
|
|
protected function user_array($points)
|
|
{
|
|
return [
|
|
'current' => (double) $points,
|
|
'actions' => [],
|
|
'points' => [],
|
|
'approve' => [],
|
|
'disapprove' => [],
|
|
'logs' => [],
|
|
'total' => 0.00,
|
|
];
|
|
}
|
|
}
|