139 lines
		
	
	
		
			5.2 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			139 lines
		
	
	
		
			5.2 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
<?php
 | 
						|
 | 
						|
declare(strict_types = 1);
 | 
						|
 | 
						|
// {{{ License
 | 
						|
// This file is part of GNU social - https://www.gnu.org/software/social
 | 
						|
//
 | 
						|
// GNU social is free software: you can redistribute it and/or modify
 | 
						|
// it under the terms of the GNU Affero General Public License as published by
 | 
						|
// the Free Software Foundation, either version 3 of the License, or
 | 
						|
// (at your option) any later version.
 | 
						|
//
 | 
						|
// GNU social is distributed in the hope that it will be useful,
 | 
						|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
						|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
						|
// GNU Affero General Public License for more details.
 | 
						|
//
 | 
						|
// You should have received a copy of the GNU Affero General Public License
 | 
						|
// along with GNU social.  If not, see <http://www.gnu.org/licenses/>.
 | 
						|
// }}}
 | 
						|
 | 
						|
namespace Plugin\TreeNotes;
 | 
						|
 | 
						|
use App\Core\Event;
 | 
						|
use App\Core\Modules\Plugin;
 | 
						|
use App\Entity\Note;
 | 
						|
use App\Util\Common;
 | 
						|
use App\Util\Formatting;
 | 
						|
use Symfony\Component\HttpFoundation\Request;
 | 
						|
 | 
						|
class TreeNotes extends Plugin
 | 
						|
{
 | 
						|
    /**
 | 
						|
     * Formatting notes without taking a direct reply out of context
 | 
						|
     * Show whole conversation in conversation related routes.
 | 
						|
     */
 | 
						|
    public function onFormatNoteList(array $notes_in, array &$notes_out, Request $request)
 | 
						|
    {
 | 
						|
        if (str_starts_with($request->get('_route'), 'conversation')) {
 | 
						|
            $parents   = $this->conversationFormat($notes_in);
 | 
						|
            $notes_out = $this->conversationFormatTree($parents, $notes_in);
 | 
						|
        } else {
 | 
						|
            $notes_out = $this->feedFormatTree($notes_in);
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Formats general Feed view, allowing users to see a Note and its direct replies.
 | 
						|
     * These replies are then, shown independently of parent note, making sure that every single Note is shown at least
 | 
						|
     * once to users.
 | 
						|
     *
 | 
						|
     * The list is transversed in reverse to prevent any parent Note from being processed twice. At the same time,
 | 
						|
     * this allows all direct replies to be rendered inside the same, respective, parent Note.
 | 
						|
     * Moreover, this implies the Entity\Note::getReplies() query will only be performed once, for every Note.
 | 
						|
     *
 | 
						|
     * @param array $notes The Note list to be formatted, each element has two keys: 'note' (parent/current note),
 | 
						|
     *                     and 'replies' (array of notes in the same format)
 | 
						|
     */
 | 
						|
    private function feedFormatTree(array $notes): array
 | 
						|
    {
 | 
						|
        $tree                = [];
 | 
						|
        $notes               = array_reverse($notes);
 | 
						|
        $max_replies_to_show = Common::config('plugin_tree_notes', 'feed_replies');
 | 
						|
        foreach ($notes as $note) {
 | 
						|
            if (!\is_null($children = $note->getReplies(limit: $max_replies_to_show))) {
 | 
						|
                $total_replies = $note->getRepliesCount();
 | 
						|
                $show_more     = $total_replies > $max_replies_to_show;
 | 
						|
                $notes         = array_filter($notes, fn (Note $n) => !\in_array($n, $children));
 | 
						|
 | 
						|
                $tree[] = [
 | 
						|
                    'note'      => $note,
 | 
						|
                    'replies'   => array_map(
 | 
						|
                        fn ($n) => [
 | 
						|
                            'note'          => $n,
 | 
						|
                            'replies'       => [], // We want only one depth level
 | 
						|
                            'show_more'     => ($n->getRepliesCount() > $max_replies_to_show),
 | 
						|
                            'total_replies' => $n->getRepliesCount(),
 | 
						|
                        ],
 | 
						|
                        $children,
 | 
						|
                    ),
 | 
						|
                    'total_replies' => $total_replies,
 | 
						|
                    'show_more'     => $show_more,
 | 
						|
                ];
 | 
						|
            } else {
 | 
						|
                $tree[] = ['note' => $note, 'replies' => [], 'show_more' => false];
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        return array_reverse($tree);
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Filters given Note list off any children, returning only initial Notes of a Conversation.
 | 
						|
     *
 | 
						|
     * @param array $notes_in Notes to be filtered
 | 
						|
     *
 | 
						|
     * @return array All initial Conversation Notes in given list
 | 
						|
     */
 | 
						|
    private function conversationFormat(array $notes_in): array
 | 
						|
    {
 | 
						|
        return array_filter($notes_in, static fn (Note $note) => \is_null($note->getReplyTo()));
 | 
						|
    }
 | 
						|
 | 
						|
    private function conversationFormatTree(array $parents, array $notes): array
 | 
						|
    {
 | 
						|
        $subtree = [];
 | 
						|
        foreach ($parents as $p) {
 | 
						|
            $subtree[] = $this->conversationFormatSubTree($p, $notes);
 | 
						|
        }
 | 
						|
 | 
						|
        return $subtree;
 | 
						|
    }
 | 
						|
 | 
						|
    private function conversationFormatSubTree(Note $parent, array $notes)
 | 
						|
    {
 | 
						|
        $children = array_filter($notes, fn (Note $note) => $note->getReplyTo() === $parent->getId());
 | 
						|
 | 
						|
        return [
 | 
						|
            'note'      => $parent,
 | 
						|
            'replies'   => $this->conversationFormatTree($children, $notes),
 | 
						|
            'show_more' => false, // It's always false, we're showing everyone
 | 
						|
        ];
 | 
						|
    }
 | 
						|
 | 
						|
    public function onAppendNoteBlock(Request $request, array $conversation, array &$res): bool
 | 
						|
    {
 | 
						|
        if (\array_key_exists('replies', $conversation)) {
 | 
						|
            $res[] = Formatting::twigRenderFile(
 | 
						|
                'tree_notes/note_replies_block.html.twig',
 | 
						|
                [
 | 
						|
                    'nickname'     => $conversation['note']->getActorNickname(),
 | 
						|
                    'conversation' => $conversation,
 | 
						|
                ],
 | 
						|
            );
 | 
						|
        }
 | 
						|
        return Event::next;
 | 
						|
    }
 | 
						|
}
 |