Seditio Source
Root |
./othercms/xenForo 2.2.8/src/XF/Service/Post/Copier.php
<?php

namespace XF\Service\Post;

use
XF\Entity\Post;
use
XF\Entity\Thread;

use function
intval, is_array;

class
Copier extends \XF\Service\AbstractService
{
   
/**
     * @var Thread
     */
   
protected $target;

    protected
$existingTarget = false;

    protected
$alert = false;
    protected
$alertReason = '';

    protected
$prefixId = null;

    protected
$log = true;

   
/**
     * @var Thread[]
     */
   
protected $sourceThreads = [];

   
/**
     * @var Post[]
     */
   
protected $sourcePosts = [];

    public function
__construct(\XF\App $app, Thread $target)
    {
       
parent::__construct($app);
       
$this->target = $target;
    }

    public function
getTarget()
    {
        return
$this->target;
    }

    public function
setExistingTarget($existing)
    {
       
$this->existingTarget = (bool)$existing;
    }

    public function
setLog($log)
    {
       
$this->log = (bool)$log;
    }

    public function
setSendAlert($alert, $reason = null)
    {
       
$this->alert = (bool)$alert;
        if (
$reason !== null)
        {
           
$this->alertReason = $reason;
        }
    }

    public function
setPrefix($prefixId)
    {
       
$this->prefixId = ($prefixId === null ? $prefixId : intval($prefixId));
    }

    public function
copy($sourcePostsRaw)
    {
        if (
$sourcePostsRaw instanceof \XF\Mvc\Entity\AbstractCollection)
        {
           
$sourcePostsRaw = $sourcePostsRaw->toArray();
        }
        else if (
$sourcePostsRaw instanceof Post)
        {
           
$sourcePostsRaw = [$sourcePostsRaw];
        }
        else if (!
is_array($sourcePostsRaw))
        {
            throw new \
InvalidArgumentException('Posts must be provided as collection, array or entity');
        }

        if (!
$sourcePostsRaw)
        {
            return
false;
        }

       
$db = $this->db();

       
/** @var Post[] $sourcePosts */
        /** @var Thread[] $sourceThreads */
       
$sourcePosts = [];
       
$sourceThreads = [];

        foreach (
$sourcePostsRaw AS $sourcePost)
        {
           
$sourcePost->setOption('log_moderator', false);
           
$sourcePosts[$sourcePost->post_id] = $sourcePost;

           
/** @var Thread $sourceThread */
           
$sourceThread = $sourcePost->Thread;
            if (!isset(
$sourceThreads[$sourceThread->thread_id]))
            {
               
$sourceThread->setOption('log_moderator', false);
               
$sourceThreads[$sourceThread->thread_id] = $sourceThread;
            }
        }

       
$sourcePosts = \XF\Util\Arr::columnSort($sourcePosts, 'post_date');

       
$this->sourceThreads = $sourceThreads;
       
$this->sourcePosts = $sourcePosts;

       
$target = $this->target;
       
$target->setOption('log_moderator', false);

        if (!
$target->thread_id)
        {
           
$firstPost = reset($sourcePosts);

           
$target->user_id = $firstPost->user_id;
           
$target->username = $firstPost->username;
           
$target->post_date = $firstPost->post_date;
        }

       
$db->beginTransaction();

       
$target->save();

       
$this->copyDataToTarget();
       
$this->updateTargetData();

        if (
$this->alert)
        {
           
$this->sendAlert();
        }

       
$this->finalActions();

       
$db->commit();

        return
true;
    }

    protected function
copyDataToTarget()
    {
       
$resetValues = $this->getResetPostData();

       
$position = 0;
       
$firstPost = $this->existingTarget ? $this->target->FirstPost : null;

       
// posts are sorted in date order
       
foreach ($this->sourcePosts AS $sourcePost)
        {
           
/** @var \XF\Entity\Post $newPost */
           
$newPost = $this->em()->create('XF:Post');

           
$values = $sourcePost->toArray(false);
            foreach (
$resetValues AS $key)
            {
                unset(
$values[$key]);
            }

           
$newPost->thread_id = $this->target->thread_id;
           
$newPost->bulkSet($values);

           
$newPost->position = $position;
            if (!
$firstPost)
            {
               
// first post is always visible, set $firstPost later
               
$this->target->discussion_state = $newPost->message_state;
               
$newPost->message_state = 'visible';
            }

           
$newPost->save();

            if (!
$firstPost)
            {
               
$firstPost = $newPost;

               
$this->target->fastUpdate('first_post_id', $newPost->post_id);
            }

           
$embedMetadata = $sourcePost->embed_metadata;
           
$newPost->embed_metadata = $this->updateEmbeds($sourcePost, $newPost, $embedMetadata ?: []);
           
$newPost->saveIfChanged();

            if (
$newPost->message_state == 'visible')
            {
               
$position++;
            }
        }
    }

    protected function
getResetPostData()
    {
        return  [
           
'post_id',
           
'thread_id',
           
'position',
           
'reaction_score',
           
'reactions',
           
'reaction_users',
           
'warning_id',
           
'warning_message',
           
'last_edit_date',
           
'last_edit_user_id',
           
'edit_count',
           
'vote_score',
           
'vote_count'
       
];
    }

    protected function
updateEmbeds(Post $sourcePost, Post $newPost, array $embedMetadata)
    {
       
$attachEmbed = $embedMetadata['attachments'] ?? [];

        foreach (
$sourcePost->Attachments AS $sourceAttachment)
        {
           
$newAttachment = $sourceAttachment->createDuplicate();
           
$newAttachment->content_type = 'post';
           
$newAttachment->content_id = $newPost->post_id;
           
$newAttachment->save();

           
$newPost->message = preg_replace(
               
'#(\[attach[^\]]*\])' . $sourceAttachment->attachment_id . '(\[/attach\])#i',
               
'${1}' . $newAttachment->attachment_id . '${2}',
               
$newPost->message
           
);

            if (isset(
$attachEmbed[$sourceAttachment->attachment_id]))
            {
                unset(
$attachEmbed[$sourceAttachment->attachment_id]);
               
$attachEmbed[$newAttachment->attachment_id] = $newAttachment->attachment_id;
            }
        }

        if (
$attachEmbed)
        {
           
$embedMetadata['attachments'] = $attachEmbed;
        }
        else
        {
            unset(
$embedMetadata['attachments']);
        }

        return
$embedMetadata;
    }

    protected function
updateTargetData()
    {
       
$target = $this->target;

        if (
$this->prefixId !== null)
        {
           
$target->prefix_id = $this->prefixId;
        }
       
$target->rebuildCounters();
       
$target->save();

       
$target->Forum->rebuildCounters();
       
$target->Forum->save();

       
/** @var \XF\Repository\Thread $threadRepo */
       
$threadRepo = $this->repository('XF:Thread');
       
$threadRepo->rebuildThreadPostPositions($target->thread_id);
       
$threadRepo->rebuildThreadUserPostCounters($target->thread_id);
    }

    protected function
sendAlert()
    {
       
$target = $this->target;

       
/** @var \XF\Repository\Post $postRepo */
       
$postRepo = $this->repository('XF:Post');

        foreach (
$this->sourcePosts AS $sourcePost)
        {
            if (
$sourcePost->Thread->discussion_state == 'visible'
               
&& $sourcePost->message_state == 'visible'
               
&& $sourcePost->user_id != \XF::visitor()->user_id
           
)
            {
               
$alertExtras = [
                   
'targetTitle' => $target->title,
                   
'targetLink' => $this->app->router('public')->buildLink('nopath:posts', $sourcePost)
                ];

               
$postRepo->sendModeratorActionAlert($sourcePost, 'copy', $this->alertReason, $alertExtras);
            }
        }
    }

    protected function
finalActions()
    {
       
$target = $this->target;
       
$postIds = array_keys($this->sourcePosts);

        if (
$this->log)
        {
           
$this->app->logger()->logModeratorAction('thread', $target, 'post_copy_target'  . ($this->existingTarget ? '_existing' : ''),
                [
'ids' => implode(', ', $postIds)]
            );

            foreach (
$this->sourceThreads AS $sourceThread)
            {
               
$this->app->logger()->logModeratorAction('thread', $sourceThread, 'post_copy_source', [
                   
'url' => $this->app->router('public')->buildLink('nopath:threads', $target),
                   
'title' => $target->title
               
]);
            }
        }
    }
}