Seditio Source
Root |
./othercms/xenForo 2.2.8/src/XF/Service/User/WarningPointsChange.php
<?php

namespace XF\Service\User;

use
XF\Entity\User;

use function
intval, strval;

class
WarningPointsChange extends \XF\Service\AbstractService
{
   
/**
     * @var User
     */
   
protected $user;

    public function
__construct(\XF\App $app, User $user)
    {
       
parent::__construct($app);

       
$this->user = $user;
    }

    public function
shiftPoints($amount, $fromWarningDelete = false)
    {
       
$this->setToPoints($this->user->warning_points + $amount, $fromWarningDelete);
    }

    public function
setToPoints($newPoints, $fromWarningDelete = false)
    {
       
$oldPoints = $this->user->warning_points;
       
$newPoints = max(0, intval($newPoints));

        if (
$oldPoints == $newPoints)
        {
            return;
        }

       
$this->db()->beginTransaction();

       
$this->user->warning_points = $newPoints;
       
$this->user->save(true, false);

        if (
$newPoints > $oldPoints)
        {
           
$this->processPointsIncrease($oldPoints, $newPoints);
        }
        else if (
$newPoints < $oldPoints)
        {
           
$this->processPointsDecrease($oldPoints, $newPoints, $fromWarningDelete);
        }

       
$this->db()->commit();
    }

    protected function
processPointsIncrease($oldPoints, $newPoints)
    {
       
$actions = $this->finder('XF:WarningAction')->order('points')->fetch();
        if (!
$actions->count())
        {
            return;
        }

       
/** @var \XF\Entity\WarningAction $action */
       
foreach ($actions AS $action)
        {
            if (
$action->points > $oldPoints && $action->points <= $newPoints)
            {
               
$this->applyWarningAction($action);
            }
        }
    }

    protected function
applyWarningAction(\XF\Entity\WarningAction $action)
    {
       
$permanent = ($action->action_length_type == 'permanent');
       
$endByPoints = ($action->action_length_type == 'points');

        if (
$permanent || $endByPoints)
        {
           
$actionEndDate = null;
        }
        else
        {
           
$actionEndDate = min(pow(2,32) - 1, strtotime("+{$action->action_length} {$action->action_length_type}"));
        }

       
$tempChangeKey = $action->getTempUserChangeKey();

        switch (
$action->action)
        {
            case
'ban':
               
/** @var \XF\Entity\UserBan $ban */
               
$ban = $this->user->Ban;
                if (
$endByPoints)
                {
                    if (
$ban)
                    {
                        if (!
$ban->end_date)
                        {
                           
// Permanent ban. If it was triggered, that should be with a lower point
                            // value, so use that as the trigger. If not triggered, it was manual already so do nothing.
                           
break;
                        }

                       
$minUnbanDate = $ban->end_date;
                    }
                    else
                    {
                       
$minUnbanDate = 0;
                    }

                    if (
$this->applyUserBan(0, true))
                    {
                       
$this->insertPointsActionTrigger($action, $minUnbanDate);
                    }
                }
                else
                {
                    if (
$ban)
                    {
                        if (!
$ban->end_date || ($actionEndDate && $ban->end_date > $actionEndDate))
                        {
                           
// already banned and the ban is longer than what would happen here so do nothing
                           
break;
                        }
                    }

                   
$this->applyUserBan($actionEndDate ?: 0, false);
                }
                break;

            case
'discourage':
               
/** @var \XF\Service\User\TempChange $changeService */
               
$changeService = $this->service('XF:User\TempChange');
               
$changeService->applyFieldChange(
                   
$this->user, $tempChangeKey, 'Option.is_discouraged', true, $actionEndDate
               
);

                if (
$endByPoints)
                {
                   
$this->insertPointsActionTrigger($action);
                }
                break;

            case
'groups':
               
$userGroupChangeKey = 'warning_action_' . $action->warning_action_id;

               
/** @var \XF\Service\User\TempChange $changeService */
               
$changeService = $this->service('XF:User\TempChange');
               
$changeService->applyGroupChange(
                   
$this->user, $tempChangeKey, $action->extra_user_group_ids, $userGroupChangeKey, $actionEndDate
               
);

                if (
$endByPoints)
                {
                   
$this->insertPointsActionTrigger($action);
                }
                break;
        }
    }

    protected function
insertPointsActionTrigger(\XF\Entity\WarningAction $action, $minUnbanDate = 0)
    {
       
$this->db()->insert('xf_warning_action_trigger', [
           
'warning_action_id' => $action->warning_action_id,
           
'user_id' => $this->user->user_id,
           
'action_date' => \XF::$time,
           
'trigger_points' => $action->points,
           
'action' => $action->action,
           
'min_unban_date' => $minUnbanDate
       
]);

        return
$this->db()->lastInsertId();
    }

    protected function
applyUserBan($endDate, $setTriggered)
    {
       
/** @var \XF\Entity\UserBan $ban */
       
$ban = $this->user->Ban;
        if (!
$ban)
        {
           
$reason = strval(\XF::phrase('warning_ban_reason'));

           
$ban = $this->user->getRelationOrDefault('Ban', false);
           
$ban->user_id = $this->user->user_id;
           
$ban->ban_user_id = 0;
           
$ban->user_reason = $reason;
        }

       
$ban->end_date = $endDate;
        if (
$setTriggered)
        {
           
$ban->triggered = true;
        }

        try
        {
           
$ban->save();
        }
        catch (\
Exception $e)
        {
            \
XF::logException($e, false, 'Ban could not be applied: ');
            return
null;
        }

        return
$ban;
    }

    protected function
processPointsDecrease($oldPoints, $newPoints, $fromWarningDelete = false)
    {
       
$triggers = $this->db()->fetchAllKeyed("
            SELECT *
            FROM xf_warning_action_trigger
            WHERE user_id = ?
            ORDER BY trigger_points DESC
        "
, 'action_trigger_id', $this->user->user_id);
        if (
$triggers)
        {
           
$remainingTriggers = $triggers;

            foreach (
$triggers AS $key => $trigger)
            {
                if (
$trigger['trigger_points'] > $newPoints)
                {
                    unset(
$remainingTriggers[$key]);
                   
$this->removeActionTrigger($trigger, $remainingTriggers);
                }
            }
        }

        if (
$fromWarningDelete)
        {
           
$actions = $this->finder('XF:WarningAction')->order('points', 'desc')->fetch();
           
/** @var \XF\Entity\WarningAction $action */
           
foreach ($actions AS $action)
            {
               
// If we're deleting, we need to undo warning action effects, even if they're time limited.
                // Points-based will be handled by the triggers so skip. Then only consider where we cross
                // the points threshold from the old (higher) to the new (lower) point values
               
if (
                   
$action->action_length_type == 'points'
                   
|| $action->points > $oldPoints // threshold above where we were
                   
|| $action->points <= $newPoints // we're still at/above the threshold
               
)
                {
                    continue;
                }

               
$this->removeWarningActionEffects($action);
            }
        }
    }

    protected function
removeActionTrigger(array $trigger, array $otherTriggers)
    {
       
$this->db()->beginTransaction();

       
$changed = $this->db()->delete(
           
'xf_warning_action_trigger', 'action_trigger_id = ?', $trigger['action_trigger_id']
        );
        if (!
$changed)
        {
           
$this->db()->rollback();
            return;
        }

        switch (
$trigger['action'])
        {
            case
'ban':
                if (
$trigger['min_unban_date'] && \XF::$time < $trigger['min_unban_date'])
                {
                   
// another ban trigger is still running
                   
break;
                }

               
$remove = true;
                foreach (
$otherTriggers AS $otherTrigger)
                {
                    if (
$otherTrigger['action'] == 'ban')
                    {
                       
$remove = false;
                        break;
                    }
                }
                if (!
$remove)
                {
                   
// there's still another ban trigger
                   
break;
                }

                if (
$this->user->Ban)
                {
                   
/** @var \XF\Entity\UserBan $ban */
                   
$ban = $this->user->Ban;
                    if (!
$ban->end_date && $ban->triggered)
                    {
                       
$ban->delete();
                    }
                }
                break;

            case
'discourage':
            case
'groups';
               
$tempChangeKey = 'warning_action_' . $trigger['warning_action_id'] . '_' . $trigger['action'];

               
/** @var \XF\Repository\UserChangeTemp $changeRepo */
               
$changeRepo = $this->repository('XF:UserChangeTemp');
               
$changeRepo->expireUserChangeByKey($this->user, $tempChangeKey);
                break;
        }

       
$this->db()->commit();
    }

    protected function
removeWarningActionEffects(\XF\Entity\WarningAction $action)
    {
        switch (
$action->action)
        {
            case
'ban':
                if (
$this->user->Ban)
                {
                   
/** @var \XF\Entity\UserBan $ban */
                   
$ban = $this->user->Ban;

                   
$isPermanent = ($action->action_length_type == 'permanent');
                   
$endByPoints = ($action->action_length_type == 'points');

                   
// we can only undo this ban if:
                    // - we inserted a permanent ban and this is one
                    // - we inserted a points-based ban and this is one
                    // - we inserted a time-based ban and this is one
                   
if (
                        (
$isPermanent && !$ban->end_date)
                        || (
$endByPoints && $ban->triggered)
                        || (!
$isPermanent && !$endByPoints && $ban->end_date && !$ban->triggered)
                    )
                    {
                       
$ban->delete();
                    }
                }
                break;

            case
'discourage':
            case
'groups';
               
$tempChangeKey = $action->getTempUserChangeKey();

               
/** @var \XF\Repository\UserChangeTemp $changeRepo */
               
$changeRepo = $this->repository('XF:UserChangeTemp');
               
$changeRepo->expireUserChangeByKey($this->user, $tempChangeKey);
                break;
        }
    }
}