<?php

/*
 * This file is part of the phpdish/phpdish
 *
 * (c) Slince <taosikai@yeah.net>
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

namespace PHPDish\Bundle\ForumBundle\Controller;

use Carbon\Carbon;
use Doctrine\Common\Collections\Criteria;
use PHPDish\Bundle\CmsBundle\Utility\StringManipulator;
use PHPDish\Bundle\ResourceBundle\Controller\ResourceController;
use PHPDish\Bundle\ForumBundle\Event\Events;
use PHPDish\Bundle\ForumBundle\Event\ReplyTopicEvent;
use PHPDish\Bundle\ForumBundle\Event\TopicEvent;
use PHPDish\Bundle\ForumBundle\Form\Type\TopicReplyType;
use PHPDish\Bundle\ForumBundle\Form\Type\TopicType;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;

class TopicController extends ResourceController
{
    use ManagerTrait;

    use \PHPDish\Bundle\UserBundle\Controller\ManagerTrait;

    /**
     * 话题列表.
     *
     * @Route("/", name="homepage")
     * @Route("/topics", name="topic")
     *
     * @param Request $request
     *
     * @return Response
     */
    public function indexAction(Request $request)
    {
        $manager = $this->getTopicManager();
        $criteria = Criteria::create();
        $criteria->orderBy(['isTop' => 'desc',  'lastCommentAt' => 'desc'])->where(Criteria::expr()->eq('enabled', true));

        $tab = $request->query->get('tab');
        if ($tab === 'following') {
            $this->denyAccessUnlessGranted('IS_AUTHENTICATED_REMEMBERED'); //关注的需要登录
            $topics = $manager->findFollowingThreadsTopics($this->getUser(), $request->query->getInt('page', 1));
        } else {
            if ($tab === 'recommend') {
                $criteria->andWhere(Criteria::expr()->eq('recommended', true));
            }
            $topics = $manager->findTopicsPager($criteria, $request->query->getInt('page', 1));
        }
//        return $this->render('@PHPDishWeb/Topic/index.html.twig', [
//            'topics' => $topics,
//        ]);
        return $this->render($this->configuration->getTemplate('Topic:index.html.twig'), [
            'topics' => $topics,
        ]);
    }

    /**
     * 创建话题.
     *
     * @Route("/topics/new", name="topic_add")
     *
     * @param Request $request
     *
     * @return Response
     */
    public function createAction(Request $request)
    {
        $this->denyAccessUnlessGranted('IS_AUTHENTICATED_REMEMBERED');
        $manager = $this->getTopicManager();
        $topic = $manager->createTopic($this->getUser());
        $form = $this->createForm(TopicType::class, $topic);
        $form->handleRequest($request);
        if ($form->isSubmitted() && $form->isValid()) {
            $manager->saveTopic($topic);
            //触发事件
            $this->get('event_dispatcher')->dispatch(Events::TOPIC_CREATED, new TopicEvent($topic));

            return $this->redirectToRoute('topic_view', [
                'id' => $topic->getId(),
            ]);
        }

        return $this->render($this->configuration->getTemplate('Topic:create.html.twig'), [
            'form' => $form->createView(),
            'threads' => $this->getThreadManager()->findEnabledThreads(10)
        ]);
    }

    /**
     * 查看话题.
     *
     * @Route("/topics/{id}", name="topic_view", requirements={"id": "\d+"}, methods={"GET"})
     *
     * @param int     $id
     * @param Request $request
     *
     * @return Response
     */
    public function viewAction($id, Request $request)
    {
        $topic = $this->getTopicManager()->findTopicById($id);
        if (!$topic || !$topic->isEnabled()) {
            throw $this->createNotFoundException();
        }
        $replies = $this->getReplyManager()->findTopicReplies(
            $topic,
            $request->query->getInt('page', 1)
        );

        $reply = $this->getReplyManager()->createReply($topic, $this->getUser());
        $form = $this->createForm(TopicReplyType::class, $reply);

        //SEO
        $seoPage = $this->get('sonata.seo.page');
        $summary = StringManipulator::stripLineBreak($topic->getSummary());
        $seoPage
            ->setTitle($topic->getTitle())
            ->removeMeta('name', 'keywords')
            ->addMeta('name', 'description', $summary)
            ->addMeta('property', 'og:title', $topic->getTitle())
            ->addMeta('property', 'og:type', 'article')
            ->addMeta('property', 'og:url',  $this->generateUrl('topic_view', ['id' => $topic->getId()], UrlGeneratorInterface::ABSOLUTE_URL))
            ->addMeta('property', 'og:description', $summary);

        return $this->render($this->configuration->getTemplate('Topic:view.html.twig'), [
            'topic' => $topic,
            'replies' => $replies,
            'form' => $form->createView(),
        ]);
    }

    /**
     * 修改话题.
     *
     * @Route("/topics/{id}/edit", name="topic_edit", requirements={"id": "\d+"})
     *
     * @param int     $id
     * @param Request $request
     *
     * @return Response
     */
    public function editAction($id, Request $request)
    {
        $topic = $this->getTopicManager()->findTopicById($id);
        if (!$topic) {
            throw $this->createNotFoundException();
        }
        $this->denyAccessUnlessGranted('edit', $topic);

        $form = $this->createForm(TopicType::class, $topic);
        $form->handleRequest($request);
        if ($form->isSubmitted() && $form->isValid()) {
            $this->getTopicManager()->saveTopic($topic);

            return $this->redirectToRoute('topic_view', [
                'id' => $topic->getId(),
            ]);
        }

        return $this->render($this->configuration->getTemplate('Topic:create.html.twig'), [
            'form' => $form->createView(),
            'topic' => $topic,
            'threads' => $this->getThreadManager()->findEnabledThreads(10)
        ]);
    }

    /**
     * 删除话题.
     *
     * @Route("/topics/{id}", name="topic_delete", requirements={"id": "\d+"}, methods={"DELETE"})
     *
     * @param int $id
     *
     * @return Response
     */
    public function deleteAction($id)
    {
        $topic = $this->getTopicManager()->findTopicById($id);
        if (!$topic || !$topic->isEnabled()) {
            throw $this->createNotFoundException();
        }
        $this->denyAccessUnlessGranted('edit', $topic);

        $this->getTopicManager()->blockTopic($topic);

        return $this->handleView($this->view([
            'result' => true,
        ]));
    }

    /**
     * 回复话题
     *
     * @Route("/topics/{id}/replies", name="topic_add_reply", methods={"POST"})
     *
     * @param int     $id
     * @param Request $request
     *
     * @return Response
     */
    public function replyTopicAction($id, Request $request)
    {
        $this->denyAccessUnlessGranted('IS_AUTHENTICATED_REMEMBERED');

        $topic = $this->getTopicManager()->findTopicById($id);
        $reply = $this->getReplyManager()->createReply($topic, $this->getUser());
        $form = $this->createForm(TopicReplyType::class, $reply);
        $form->handleRequest($request);
        $view = $this->view()->setFormat('json');
        if ($form->isSubmitted() && $form->isValid()) {
            $this->getReplyManager()->saveReply($reply);

            //触发事件
            $this->get('event_dispatcher')->dispatch(Events::TOPIC_REPLIED, new ReplyTopicEvent($topic, $reply));

            $view->setData(['reply' => $reply])
                ->getContext()
                ->setGroups(['Default']);
        } else {
            $view->setStatusCode(400)
                ->setData(array(
                    'form' => $form,
                ));
        }
        return $this->handleView($view);
    }

    /**
     * 切换话题推荐状态
     *
     * @Route("/topics/{id}/toggle_recommend", name="topic_toggle_recommend", requirements={"id": "\d+"})
     *
     * @param int $id
     *
     * @return Response
     */
    public function toggleRecommendAction($id)
    {
        $topic = $this->getTopicManager()->findTopicById($id);
        if (!$topic || !$topic->isEnabled()) {
            throw $this->createNotFoundException();
        }
        $this->denyAccessUnlessGranted('ROLE_ADMIN', $topic);
        $topic->setRecommended(!$topic->isRecommended());
        $manager = $this->getDoctrine()->getManager();
        $manager->persist($topic);
        $manager->flush();

        return $this->handleView($this->view([
            'is_recommended' => $topic->isRecommended(),
        ]));
    }

    /**
     * 切换置顶状态
     *
     * @Route("/topics/{id}/toggle_top", name="topic_toggle_top", requirements={"id": "\d+"})
     *
     * @param int $id
     *
     * @return Response
     */
    public function toggleTopAction($id)
    {
        $topic = $this->getTopicManager()->findTopicById($id);
        if (!$topic || !$topic->isEnabled()) {
            throw $this->createNotFoundException();
        }
        $this->denyAccessUnlessGranted('ROLE_ADMIN', $topic);
        $topic->setTop(!$topic->isTop());
        $manager = $this->getDoctrine()->getManager();
        $manager->persist($topic);
        $manager->flush();

        return $this->handleView($this->view([
            'is_top' => $topic->isTop(),
        ]));
    }

    /**
     * 切换点赞状态
     *
     * @Route("/topics/{id}/voters", name="topic_toggle_voter", methods={"POST"})
     */
    public function toggleVoterAction($id)
    {
        $this->denyAccessUnlessGranted('IS_AUTHENTICATED_REMEMBERED');

        $topic = $this->getTopicManager()->findTopicById($id);
        if (!$topic) {
            throw new \InvalidArgumentException($this->get('translator')->trans('topic.not_exists'));
        }
        if ($isVoted = $topic->isVotedBy($this->getUser())) {
            $this->getTopicManager()->removeVoter($topic, $this->getUser());
        } else {
            $this->getTopicManager()->addVoter($topic, $this->getUser());
        }
        return $this->json([
            'vote_count' => $topic->getVoteCount(),
            'is_voted' => !$isVoted
        ]);
    }

    /**
     * 用户的帖子.
     * @Route("/users/{username}", name="user_view")
     * @Route("/users/{username}/topics", name="user_topics")
     *
     * @param string  $username
     * @param Request $request
     *
     * @return Response
     */
    public function getUserTopicsAction($username, Request $request)
    {
        $user = $this->getUserManager()->findUserByName($username);
        $topics = $this->getTopicManager()->findUserTopics($user, $request->query->getInt('page', 1));

        return $this->render($this->configuration->getTemplate('Topic:user_topics.html.twig'), [
            'user' => $user,
            'topics' => $topics,
        ]);
    }

    /**
     * 今日热帖.
     * @param int|null $limit
     *
     * @return Response
     */
    public function todayHotTopicsAction($limit = null)
    {
        $date = Carbon::today()->modify('-100 days');
        $topics = $this->getTopicManager()->findHotTopics($date, $limit ?: 10);

        return $this->render($this->configuration->getTemplate('Topic:_today_hot.html.twig'), [
            'topics' => $topics,
        ]);
    }
}


<?php

/*
 * This file is part of the phpdish/phpdish
 *
 * (c) Slince <taosikai@yeah.net>
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

namespace PHPDish\Bundle\AdminBundle\Twig;

final class Breadcrumb
{
    /**
     * @var array
     */
    protected $crumbs = [];

    /**
     * 添加一个新选项
     *
     * @param string $title
     * @param string $route
     * @param array $routeParameters
     */
    public function push($title, $route = null, $routeParameters = [])
    {
        $this->crumbs[] = [
            'title' => $title,
            'route' => $route,
            'routeParameters' => $routeParameters
        ];
    }

    /**
     * 追加一个新选项
     *
     * @param string $title
     * @param string $route
     * @param array $routeParameters
     */
    public function unshift($title, $route = null, $routeParameters = [])
    {
        array_unshift($this->crumbs, [
            'title' => $title,
            'route' => $route,
            'routeParameters' => $routeParameters
        ]);
    }

    /**
     * 选项数
     *
     * @return int
     */
    public function count()
    {
        return count($this->crumbs);
    }

    /**
     * 移除指定选项
     *
     * @param string $title
     */
    public function remove($title)
    {
        $this->crumbs = array_filter($this->crumbs, function($crumb) use ($title){
            return $crumb['title'] !== $title;
        });
    }

    public function all()
    {
        return $this->crumbs;
    }
}


<?php

namespace PHPDish\Bundle\CoreBundle\DataFixtures\ORM;

use Doctrine\Common\Persistence\ObjectManager;
use Doctrine\Bundle\FixturesBundle\Fixture;
use PHPDish\Bundle\PostBundle\Service\CategoryManagerInterface;

class CategoryFixtures extends Fixture
{
    /**
     * {@inheritdoc}
     */
    public function load(ObjectManager $manager)
    {
        $manager = $this->getCategoryManager();
        $category = $manager->createCategory($this->getReference('general-user'));
        $category->setName('小风的PHP研究所')
            ->setDescription('小风的PHP研究所')
            ->setSlug('php-labs');
        $manager->saveCategory($category);
        $this->addReference('general-category', $category);
    }

    /**
     * @return CategoryManagerInterface
     */
    protected function getCategoryManager()
    {
        return $this->container->get('phpdish.manager.category');
    }

    /**
     * {@inheritdoc}
     */
    public function getDependencies()
    {
        return [
            UserFixtures::class,
        ];
    }
}


<?php

/*
 * This file is part of the phpdish/phpdish
 *
 * (c) Slince <taosikai@yeah.net>
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

namespace PHPDish\Bundle\ResourceBundle\AvatarGenerator;

use GuzzleHttp\Psr7\Stream;
use Md\MDAvatars;
use PHPDish\Component\Media\Manager\FileManagerInterface;
use PHPDish\Component\Media\Model\File;
use PHPDish\Component\Media\Namer\NamerInterface;

class AvatarGenerator implements AvatarGeneratorInterface
{
    /**
     * @var FileManagerInterface
     */
    protected $fileManager;

    /**
     * @var NamerInterface
     */
    protected $namer;

    public function __construct(FileManagerInterface $fileManager, NamerInterface $namer)
    {
        $this->fileManager = $fileManager;
        $this->namer = $namer;
    }

    /**
     * {@inheritdoc}
     */
    public function generate($idString, $width = 256)
    {
        $avatar = new MDAvatars($idString, $width);
        $tmpFile = sys_get_temp_dir() . '/' . md5(uniqid('avatar'));
        $avatar->Save($tmpFile, $width);
        $content = new Stream(fopen($tmpFile, 'r+'));
        $file = new File($this->namer->transformWithExtension('png'), $content);
        $this->fileManager->upload($file);
        @unlink($tmpFile);
        return $file;
    }
}


<?php

namespace PHPDish\Bundle\AdminBundle\Controller;

use PHPDish\Bundle\AdminBundle\Form\Type\WithdrawType;
use PHPDish\Bundle\PaymentBundle\Controller\ManagerTrait;
use Sonata\AdminBundle\Controller\CRUDController as Controller;

class PaymentController extends Controller
{
    use ManagerTrait;

    /**
     * 处理提现
     * @param int $id
     * @return \Symfony\Component\HttpFoundation\Response
     */
    public function withdrawAction($id)
    {
        $form = $this->createForm(WithdrawType::class);
        $history = $this->admin->getSubject();
        $request = $this->getRequest();
        $form->handleRequest($request);
        if ($form->isSubmitted() && $form->isValid()) {
            $translator = $this->get('translator');
            $walletManager = $this->getWalletManager();
            $action = $form->getData()['action'];
            $note = $form->getData()['note'];
            if ($action === 'approve') {
                $walletManager->approveWithdraw($history, $note);
                $this->addFlash('sonata_flash_success', $translator->trans('payment.withdraw_approve'));
            } else {
                $walletManager->refuseWithdraw($history, $note);
                $this->addFlash('sonata_flash_success', $translator->trans('payment.withdraw_declined'));
            }
            return $this->redirect($this->admin->generateUrl('list', ['filter' => $this->admin->getFilterParameters()]));
        }
        $formView = $form->createView();
        return $this->renderWithExtraParams('PHPDishAdminBundle:Payment:handle_withdraw.html.twig', [
            'action' => 'withdraw',
            'form' => $formView,
            'history' => $history
        ], null);
    }
}


<?php

/*
 * This file is part of the phpdish/phpdish
 *
 * (c) Slince <taosikai@yeah.net>
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

namespace PHPDish\Bundle\AdminBundle\Grid\Source;

use Doctrine\ORM\EntityManager;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\QueryBuilder;
use Pagerfanta\Adapter\DoctrineORMAdapter;
use Pagerfanta\Pagerfanta;
use PHPDish\Bundle\AdminBundle\Grid\Column\ColumnInterface;
use PHPDish\Bundle\AdminBundle\Grid\GridInterface;

class ORM implements SourceInterface
{
    /**
     * @var EntityManager
     */
    protected $entityManager;

    /**
     * @var string
     */
    protected $entity;

    /**
     * @var string
     */
    protected $alias = 'a';

    /**
     * @var QueryBuilder
     */
    protected $queryBuilder;

    protected $initialized = false;

    /**
     * @var ClassMetadata
     */
    protected $metadata;

    /**
     * @var string
     */
    protected $class;

    public function __construct($entity, $alias = null)
    {
        $this->entity = $entity;
        $alias && $this->alias = $alias;
    }

    public function setEntityManager(EntityManager $entityManager)
    {
        $this->entityManager = $entityManager;
    }

    protected function initialize()
    {
        if ($this->initialized) {
            return;
        }
        $this->metadata = $this->entityManager->getClassMetadata($this->entity);
        $this->class = $this->metadata->getName();
    }

    protected function createQueryBuilder()
    {
        return $this->entityManager->createQueryBuilder()
            ->select($this->alias)
            ->from($this->class, $this->alias);
    }

    /**
     * {@inheritdoc}
     */
    public function loadSource($columns, $page = 1, $limit = GridInterface::MAX_RESULTS_NUM)
    {
        $this->initialize();
        $qb = $this->createQueryBuilder();
        //应用过滤条件
//        $this->applyFilters($qb, $columns);
        //应用排序
        $this->applyOrders($qb, $columns);

//        dump($qb->getDQL());
        dump($qb->getQuery()->getSQL());exit;
        $adapter = new DoctrineORMAdapter($qb->getQuery());
        $pagerfanta = new Pagerfanta($adapter);
        $pagerfanta->setCurrentPage($page)->setMaxPerPage($limit);
        return $pagerfanta;
    }

    /**
     * @param QueryBuilder $qb
     * @param ColumnInterface[] $columns
     */
    protected function applyOrders(QueryBuilder $qb, $columns)
    {
        foreach ($columns as $column) {
            if (!$column->isSortable()) {
                continue;
            }
            $qb->addOrderBy($column->getName(), $column->getOrder);
        }
    }

    /**
     * @param QueryBuilder $qb
     * @param ColumnInterface[] $columns
     */
    protected function applyFilters(QueryBuilder $qb, $columns)
    {
        $expr = $qb->expr()->andX();
        foreach ($columns as $column) {
            if (!$column->isFilterable()) {
                continue;
            }
            //应用该字段所有的filter
            $exprJunction = $qb->expr()->andX();
            foreach ($column->getFilters() as $filter) {
                if ($filter->shouldSkip()) { //没有设置初值的跳过
                    continue;
                }
                $exprJunction->add($filter->getComparison());
            }
            $expr->add($exprJunction);
        }
        $qb->where($expr);
    }
}


<?php

declare(strict_types=1);

namespace PHPDish\Bundle\PostBundle\Twig;

use Doctrine\Common\Collections\Collection;
use Doctrine\Common\Collections\Criteria;
use PHPDish\Bundle\ForumBundle\Model\ReplyInterface;
use PHPDish\Bundle\ForumBundle\Service\ReplyManagerInterface;
use PHPDish\Bundle\ForumBundle\Service\ThreadManagerInterface;
use PHPDish\Bundle\ForumBundle\Service\TopicManagerInterface;
use PHPDish\Bundle\PostBundle\Model\CommentInterface;
use PHPDish\Bundle\PostBundle\Service\CategoryManagerInterface;
use PHPDish\Bundle\PostBundle\Service\CommentManagerInterface;
use PHPDish\Bundle\PostBundle\Service\PostManagerInterface;

class PostExtension extends \Twig_Extension
{
    /**
     * @var CategoryManagerInterface
     */
    protected $categoryManager;

    /**
     * @var PostManagerInterface
     */
    protected $postManager;

    /**
     * @var CommentManagerInterface
     */
    protected $commentManager;

    public function __construct(
        CategoryManagerInterface $categoryManager,
        PostManagerInterface $postManager,
        CommentManagerInterface $commentManager
    )
    {
        $this->categoryManager = $categoryManager;
        $this->postManager = $postManager;
        $this->commentManager = $commentManager;
    }

    /**
     * {@inheritdoc}
     */
    public function getFunctions(): array
    {
        return [
            new \Twig_SimpleFunction('get_category', [$this->categoryManager, 'findCategoryById']),
            new \Twig_SimpleFunction('get_category_by_slug', [$this->categoryManager, 'findCategoryBySlug']),
            new \Twig_SimpleFunction('get_categories', [$this, 'getCategories']),
            new \Twig_SimpleFunction('get_categories_pager', [$this->categoryManager, 'findCategoriesPager']),

            new \Twig_SimpleFunction('get_post', [$this->postManager, 'findPostById']),
            new \Twig_SimpleFunction('get_posts', [$this, 'getPosts']),
            new \Twig_SimpleFunction('get_posts_pager', [$this->postManager, 'findPostsPager']),

            new \Twig_SimpleFunction('get_comment', [$this->commentManager, 'findCommentById']),
            new \Twig_SimpleFunction('get_comments', [$this, 'getComments']),
            new \Twig_SimpleFunction('get_comments_pager', [$this->commentManager, 'findCommentsPager']),
        ];
    }

    /**
     * 指定条件查找专栏
     *
     * @param array| $criteria
     * @param array $orderBy
     * @param int $limit
     * @return array
     */
    public function getCategories($criteria, array $orderBy = [], ?int $limit = null)
    {
        if ($criteria instanceof Criteria) {
            return $this->categoryManager->findCategories($criteria);
        } else {
            return $this->categoryManager->getCategoryRepository()->findBy($criteria, $orderBy, $limit);
        }
    }

    /**
     * 指定条件查找post
     *
     * @param array| $criteria
     * @param array $orderBy
     * @param int $limit
     * @return array
     */
    public function getPosts($criteria, array $orderBy = [], ?int $limit = null)
    {
        if ($criteria instanceof Criteria) {
            return $this->postManager->findPosts($criteria);
        } else {
            return $this->postManager->getPostRepository()->findBy($criteria, $orderBy, $limit);
        }
    }

    /**
     * 查找评论
     *
     * @param array|Criteria $criteria
     * @param array $orderBy
     * @param int|null $limit
     * @return CommentInterface[]|Collection
     */
    public function getComments($criteria, array $orderBy = [], ?int $limit = null)
    {
        if ($criteria instanceof Criteria) {
            return $this->commentManager->findComments($criteria);
        } else {
            return $this->commentManager->getCommentRepository()->findBy($criteria, $orderBy, $limit);
        }
    }
}


<?php

/*
 * This file is part of the phpdish/phpdish
 *
 * (c) Slince <taosikai@yeah.net>
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

declare(strict_types=1);

namespace PHPDish\Component\Media\Manager;

use Gaufrette\Adapter\StreamFactory;
use Gaufrette\Filesystem;
use Gaufrette\StreamMode;
use PHPDish\Component\Media\Model\FileInterface;
use Psr\Http\Message\StreamInterface;

class FileManager implements FileManagerInterface
{
    /**
     * @var Filesystem
     */
    protected $filesystem;

    public function __construct(Filesystem $filesystem)
    {
        $this->filesystem = $filesystem;
    }

    /**
     * {@inheritdoc}
     */
    public function upload(FileInterface $file, $overwrite = true)
    {
        $body = $file->getContent();
        if ($body instanceof StreamInterface) { //流式写入
            $this->assertStreamingSupport(); //不支持流式读写抛出异常
            $stream = $this->filesystem->createStream($file->getKey());
            $stream->open(new StreamMode('w+'));
            while (!$body->eof()) {
                $stream->write($body->read(1024));
            }
            $stream->close(); //close the stream
        } else {
            $this->filesystem->write($file->getKey(), $body, $overwrite);
        }
    }

    /**
     * {@inheritdoc}
     */
    public function download(FileInterface $file, $streaming = true)
    {
        if ($streaming) {
            $this->assertStreamingSupport(); //不支持流式读写抛出异常
            $body = $this->filesystem->createStream($file->getKey());
        } else {
            $body = $this->filesystem->read($file->getKey());
        }
        $file->setContent($body);
    }

    /**
     * {@inheritdoc}
     */
    public function has($file)
    {
        $key = $file instanceof FileInterface ? $file->getKey() : $file;

        return $this->filesystem->has($key);
    }

    protected function assertStreamingSupport()
    {
        if (!$this->filesystem->getAdapter() instanceof StreamFactory) {
            throw new \InvalidArgumentException('The streaming io is not support for the adapter');
        }
    }
}


<?php

namespace PHPDish\Bundle\CoreBundle\Locale;

class LocaleManager
{
    /**
     * @var array
     */
    protected $enabledLocales = [];

    public function __construct(array $locales = [])
    {
        $this->enabledLocales = array_merge($this->enabledLocales, $locales);
    }

    /**
     * 添加支持的locale
     *
     * @param string $locale
     */
    public function addLocale($locale)
    {
        $this->enabledLocales[] = $locale;
    }

    /**
     * 添加多个支持的 locale
     *
     * @param array $locales
     */
    public function addLocales(array $locales)
    {
        $this->enabledLocales = array_merge($this->enabledLocales, $locales);
    }

    /**
     * 检查 locale 是否支持
     *
     * @param string $locale
     * @return bool
     */
    public function support($locale)
    {
        return in_array($locale, $this->enabledLocales);
    }

    /**
     * 所有支持的locale
     *
     * @return array
     */
    public function all()
    {
        return $this->enabledLocales;
    }

    /**
     * 移除 locale
     * @param string $locale
     */
    public function removeLocale($locale)
    {
        if (($key = array_search($this->enabledLocales, $locale)) !== false) {
            unset($this->enabledLocales[$key]);
        }
    }
}


<?php

/*
 * This file is part of the phpdish/phpdish
 *
 * (c) Slince <taosikai@yeah.net>
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

namespace PHPDish\Bundle\ResourceBundle\DependencyInjection;

use PHPDish\Bundle\ResourceBundle\Controller\ResourceConfigurationInterface;
use PHPDish\Bundle\ResourceBundle\Service\EntitySubscriberInterface;
use PHPDish\Bundle\ResourceBundle\Service\ServiceManagerInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;

class PHPDishResourceExtension extends Extension
{
    /**
     * {@inheritdoc}
     */
    public function load(array $configs, ContainerBuilder $container)
    {
        $configuration = new Configuration();
        $config = $this->processConfiguration($configuration, $configs);

        if ($config['templates_namespace']) {
            $container->setParameter('phpdish.templates_namespace', $config['templates_namespace']);
        }
        // load default services
        $loader = new YamlFileLoader($container, new FileLocator(__DIR__ . '/../Resources/config'));
        $loader->load('services.yml');

        //AutoConfigure
        $container->registerForAutoconfiguration(ResourceConfigurationInterface::class)
            ->addTag('phpdish.resource_configuration');

        $container->registerForAutoconfiguration(EntitySubscriberInterface::class)
            ->addTag('phpdish.entity_subscriber');

        $container->registerForAutoconfiguration(ServiceManagerInterface::class)
            ->addTag('phpdish.service_manager');
    }

    /**
     * {@inheritdoc}
     */
    public function getAlias()
    {
        return 'phpdish_resource';
    }
}


<?php

/*
 * This file is part of the phpdish/phpdish
 *
 * (c) Slince <taosikai@yeah.net>
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

namespace PHPDish\Bundle\ResourceBundle\DependencyInjection;

use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;

abstract class AbstractConfiguration
{
    /**
     * 添加模板模块配置
     *
     * @param ArrayNodeDefinition $rootNode
     */
    protected function addTemplatesSection(ArrayNodeDefinition $rootNode)
    {
        $rootNode
            ->children()
                ->scalarNode('templates_namespace')->defaultNull()->end()
                ->arrayNode('templates')
                    ->scalarPrototype()->end()
                ->end()
            ->end();
    }
}


<?php

/*
 * This file is part of the phpdish/phpdish
 *
 * (c) Slince <taosikai@yeah.net>
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

namespace PHPDish\Bundle\PostBundle\Service;

use Doctrine\Common\Collections\Criteria;
use Doctrine\ORM\EntityManagerInterface;
use PHPDish\Bundle\CoreBundle\Service\PaginatorTrait;
use PHPDish\Bundle\PostBundle\Event\Events;
use PHPDish\Bundle\PostBundle\Event\PostPersistEvent;
use PHPDish\Bundle\PostBundle\Event\VotePostEvent;
use PHPDish\Bundle\PostBundle\Model\CategoryInterface;
use PHPDish\Bundle\ResourceBundle\Service\ServiceManagerInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use PHPDish\Bundle\UserBundle\Model\UserInterface;
use PHPDish\Bundle\PostBundle\Model\PostInterface;
use Knp\Bundle\MarkdownBundle\MarkdownParserInterface;

class PostManager implements PostManagerInterface, ServiceManagerInterface
{
    use PaginatorTrait;

    /**
     * @var EventDispatcherInterface
     */
    protected $eventDispatcher;

    /**
     * @var EntityManagerInterface
     */
    protected $entityManager;

    /**
     * @var MarkdownParserInterface
     */
    protected $markdownParser;

    protected $postEntity;

    public function __construct(
        $postEntity,
        EntityManagerInterface $entityManager,
        EventDispatcherInterface $eventDispatcher,
        MarkdownParserInterface $markdownParser
    ) {
        $this->postEntity = $postEntity;
        $this->eventDispatcher = $eventDispatcher;
        $this->entityManager = $entityManager;
        $this->markdownParser = $markdownParser;
    }

    /**
     * {@inheritdoc}
     */
    public function createPost(UserInterface $user)
    {
        $post = new $this->postEntity;
        $post->setUser($user);

        return $post;
    }

    /**
     * {@inheritdoc}
     */
    public function savePost(PostInterface $post)
    {
        if (!$post->getId()) {
            $post->getCategory()->setPostCount($post->getCategory()->getPostCount() + 1);
        }
        //Transform markdown format body
        if ($post->getOriginalBody()) {
            $post->setBody($this->markdownParser->transformMarkdown($post->getOriginalBody()));
        }
        $event = new PostPersistEvent($post);
        $this->eventDispatcher->dispatch(Events::POST_PRE_PERSIST, $event);

        if ($event->isPersistenceAborted()) {
            return false;
        }
        $this->entityManager->persist($post);
        $this->entityManager->flush();

        return true;
    }

    /**
     * {@inheritdoc}
     */
    public function findPostById($id)
    {
        return $this->getPostRepository()
            ->find($id);
    }

    /**
     * {@inheritdoc}
     */
    public function findPosts(Criteria $criteria)
    {
        return $this->getPostRepository()->createQueryBuilder('p')
            ->addCriteria($criteria)
            ->getQuery()
            ->getResult();
    }

    /**
     * {@inheritdoc}
     */
    public function findPostsPager(Criteria $criteria, $page = 1, $limit = null)
    {
        $query = $this->getPostRepository()->createQueryBuilder('p')
            ->addCriteria($criteria)
            ->getQuery();

        return $this->createPaginator($query, $page, $limit);
    }

    /**
     * @return \Doctrine\ORM\QueryBuilder
     */
    protected function getPostsQb()
    {
        return $this->getPostRepository()->createQueryBuilder('p')
            ->addSelect('pu')
            ->join('p.user', 'pu');
    }

    /**
     * {@inheritdoc}
     */
    public function findUserEnabledPosts(UserInterface $user, $page = 1, $limit = null)
    {
        $qb = $this->getPostRepository()->createQueryBuilder('p')
            ->join('p.category', 'pc')->addSelect('pc')
            ->where('p.user = :user')->setParameter('user', $user)
            ->andWhere('p.enabled = :enabled')->setParameter('enabled', true)
            ->orderBy('p.createdAt', 'desc');

        return $this->createPaginator($qb->getQuery(), $page, $limit);
    }

    /**
     * {@inheritdoc}
     */
    public function findCategoryPosts(CategoryInterface $category, $page = 1, $limit = null)
    {
        $qb = $this->getPostRepository()->createQueryBuilder('p')
            ->join('p.user', 'pu')->addSelect('pu')
            ->where('p.category = :category')->setParameter('category', $category)
            ->andWhere('p.enabled = :enabled')->setParameter('enabled', true)
            ->orderBy('p.createdAt', 'desc');

        return $this->createPaginator($qb->getQuery(), $page, $limit);
    }

    /**
     * {@inheritdoc}
     */
    public function findLatestEnabledPosts($page, $limit = null)
    {
        $qb = $this->getPostRepository()->createQueryBuilder('p')
            ->join('p.user', 'pu')->addSelect('pu')
            ->join('p.category', 'pc')->addSelect('pc')
            ->where('p.enabled = :enabled')->setParameter('enabled', true)
            ->andWhere('p.body != :body')->setParameter('body', '')
            ->orderBy('p.createdAt', 'desc');

        return $this->createPaginator($qb->getQuery(), $page, $limit);
    }

    /**
     * {@inheritdoc}
     */
    public function blockPost(PostInterface $post)
    {
        $post->disable();
        $this->entityManager->persist($post);
        $this->entityManager->flush();
    }

    /**
     * {@inheritdoc}
     */
    public function increasePostViews(PostInterface $post, $views = 1)
    {
        $post->addViewCount($views);
        $this->entityManager->persist($post);
        $this->entityManager->flush();
    }

    /**
     * {@inheritdoc}
     */
    public function getUserPostCount(UserInterface $user, $ignoreEmptyPost = true)
    {
        $qb = $this->getPostRepository()->createQueryBuilder('p');
        $qb->select($qb->expr()->count('p'))
            ->where('p.enabled = :enabled')->setParameter('enabled', true)
            ->andWhere('p.user = :user')->setParameter('user', $user);
        if ($ignoreEmptyPost) {
            $qb->andWhere('p.originalBody is not null');
        }
        return $qb->getQuery()->getSingleScalarResult();
    }

    /**
     * {@inheritdoc}
     */
    public function addVoter(PostInterface $post, UserInterface $user)
    {
        $post->addVoter($user)
            ->addVoteCount();
        $this->entityManager->persist($post);
        $this->entityManager->flush();

        //触发事件
        $this->eventDispatcher->dispatch(Events::POST_VOTED,
            new VotePostEvent($post, $user)
        );
    }

    /**
     * {@inheritdoc}
     */
    public function removeVoter(PostInterface $post, UserInterface $user)
    {
        $post->removeVoter($user)
            ->addVoteCount(-1);
        $this->entityManager->persist($post);
        $this->entityManager->flush();
    }

    /**
     * {@inheritdoc}
     */
    public function getPostRepository()
    {
        return $this->entityManager->getRepository($this->postEntity);
    }

    /**
     * {@inheritdoc}
     */
    public static function getSubscribedEntities()
    {
        return [
            'postEntity' => PostInterface::class
        ];
    }
}


<?php

/*
 * This file is part of the phpdish/phpdish
 *
 * (c) Slince <taosikai@yeah.net>
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

namespace PHPDish\Bundle\ThemeBundle\Twig;

use PHPDish\Bundle\ThemeBundle\Theming\ThemeManagerInterface;
use Symfony\Bundle\TwigBundle\Loader\FilesystemLoader as BaseFilesystemLoader;
use Twig\Error\LoaderError;

class FilesystemLoader extends BaseFilesystemLoader
{
    /**
     * @var ThemeManagerInterface
     */
    protected $themeManager;

    /**
     * 模板名称后缀
     * @var string
     */
    protected $templateLogicNameSuffix = '';

    /**
     * @param ThemeManagerInterface $themeManager
     */
    public function setThemeManager(ThemeManagerInterface $themeManager)
    {
        $this->themeManager = $themeManager;
        //当前主题后缀，防止动态切换主题导致模板位置索引错误
        if ($activeTheme = $themeManager->getCurrentTheme()) {
            $this->templateLogicNameSuffix = '|' . $activeTheme->getName();
        }
    }

    /**
     * {@inheritdoc}
     */
    public function findTemplate($template, $throw = true)
    {
        //补全模板名称后缀
        $logicalName = (string) $template . $this->templateLogicNameSuffix;

        if (isset($this->cache[$logicalName])) {
            return $this->cache[$logicalName];
        }

        $file = null;
        $previous = null;

        try {
            $templateReference  = $this->parser->parse($template);
            $file = $this->locator->locate($templateReference);
        } catch (\Exception $e) {
            $previous = $e;
            // for BC
            try {
                $file = parent::findTemplate((string) $template);
            } catch (LoaderError $e) {
                $previous = $e;
            }
        }

        if (false === $file || null === $file) {
            if ($throw) {
                throw new LoaderError(sprintf('Unable to find template "%s".', $logicalName), -1, null, $previous);
            }

            return false;
        }
        return $this->cache[$logicalName] = $file;
    }
}


<?php

/*
 * This file is part of the PHPDish package.
 *
 * (c) Tao <taosikai@yeah.net>
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

declare(strict_types=1);

namespace PHPDish\Bundle\CmsBundle\Twig;

use Doctrine\Common\Collections\Criteria;
use Emojione\Emojione;
use PHPDish\Bundle\CmsBundle\Utility\HtmlChunker;

class CmsExtension extends \Twig_Extension
{
    /**
     * @var HtmlChunker
     */
    protected $htmlChunker;

    public function __construct(HtmlChunker $htmlChunker)
    {
        $this->htmlChunker = $htmlChunker;
    }

    /**
     * {@inheritdoc}
     */
    public function getFilters(): array
    {
        return [
            new \Twig_SimpleFilter('html_safe_chunk', [$this, 'safeChunk']),
            new \Twig_SimpleFilter('emoji_short_to_unicode', [$this, 'emojiShortNameToUnicode']),
        ];
    }

    /**
     * {@inheritdoc}
     */
    public function getFunctions()
    {
        return [
            //Criteria
            new \Twig_SimpleFunction('criteria_create', [Criteria::class, 'create']),
            new \Twig_SimpleFunction('criteria_expr', [Criteria::class, 'expr'])
        ];
    }

    /**
     * 安全裁切 html
     * @param string $html
     * @param float $percent
     * @return string
     */
    public function safeChunk(string $html, float $percent): string
    {
        return $this->htmlChunker->chunk($html, $percent);
    }

    /**
     * emoji短语转换为unicode
     * @param string $string
     * @return string
     */
    public function emojiShortNameToUnicode(string $string) :string
    {
        return Emojione::getClient()->shortnameToUnicode($string);
    }
}


<?php

/*
 * This file is part of the phpdish/phpdish
 *
 * (c) Slince <taosikai@yeah.net>
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

namespace PHPDish\Bundle\UserBundle\Security\Core\User;

use Carbon\Carbon;
use FOS\UserBundle\Model\UserManagerInterface;
use HWI\Bundle\OAuthBundle\OAuth\Response\UserResponseInterface;
use HWI\Bundle\OAuthBundle\Security\Core\User\FOSUBUserProvider as BaseFOSUBProvider;
use PHPDish\Component\Media\Manager\FileDownloaderInterface;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\User\UserInterface;

class FOSUBUserProvider extends BaseFOSUBProvider
{
    /**
     * @var FileDownloaderInterface
     */
    protected $fileDownloader;

    /**
     * @var TokenStorageInterface
     */
    protected $tokenStorage;

    public function __construct(
        UserManagerInterface $userManager,
        TokenStorageInterface $tokenStorage,
        FileDownloaderInterface $fileDownloader,
        array $properties
    ) {
        $this->tokenStorage = $tokenStorage;
        $this->fileDownloader = $fileDownloader;
        parent::__construct($userManager, $properties);
    }

    /**
     * {@inheritdoc}
     */
    public function loadUserByOAuthUserResponse(UserResponseInterface $response)
    {
        //优先查找对应用户，否则使用当前登录用户，否则按照邮箱查找，否则创建新用户
        $username = $response->getUsername();
        $user = $this->userManager->findUserBy([$this->getProperty($response) => $username]) ?: $this->getAuthenticatedUser();
        if (!$user) {
            $user = $this->userManager->findUserByEmail($response->getEmail()) ?: $this->createNewUser($response);
        }

        $serviceName = $response->getResourceOwner()->getName();
        $accessTokenSetter = 'set'.ucfirst($serviceName).'AccessToken';
        $user->$accessTokenSetter($response->getAccessToken());
        $idSetter = 'set'.ucfirst($serviceName).'Id';
        $user->$idSetter($response->getUsername());

        return $user;
    }

    /**
     * 创建一个新用户.
     *
     * @param UserResponseInterface $response
     *
     * @return \PHPDish\Bundle\UserBundle\Model\UserInterface
     */
    protected function createNewUser(UserResponseInterface $response)
    {
        $user = $this->userManager->createUser()
            ->setEnabled(true)
            ->setUsername($this->generateUsername($response))
            ->setPassword('')
            ->setEmail($response->getEmail() ?: '')
            ->setCreatedAt($now = Carbon::now())
            ->setUpdatedAt($now);

        try {
            $avatar = $this->fileDownloader->download($response->getProfilePicture());
            $user->setAvatar($avatar->getKey());
        } catch (\Exception $exception) {
            $user->setAvatar('');
        }

        return $user;
    }

    /**
     * 生成用户名.
     *
     * @param UserResponseInterface $response
     *
     * @return string
     */
    protected function generateUsername(UserResponseInterface $response)
    {
        $username = $response->getNickname();

        return $this->userManager->findUserByUsername($username)
            ? $username.$response->getUsername()
            : $username;
    }

    /**
     * 获取当前登录的用户
     * @return UserInterface
     */
    protected function getAuthenticatedUser()
    {
        return ($token = $this->tokenStorage->getToken())
            ? ($token->getUser() instanceof UserInterface ? $token->getUser() : null)
            : null;
    }
}


<?php

namespace PHPDish\Bundle\CoreBundle\DependencyInjection\Compiler;

use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;

class AlgoliaPass implements CompilerPassInterface
{
    /**
     * {@inheritdoc}
     */
    public function process(ContainerBuilder $container)
    {
        if ($algoliaClient = $container->findDefinition('algolia_client')) {
            $algoliaClient->setArguments([
                '%algolia.application_id%',
                '%algolia.api_key%',
            ]);
        }
        //没有设置账号的不开启
        if (!$container->hasParameter('algolia.application_id') ||
            ($container->getParameter('algolia.application_id') === 'phpdish'
            && $container->getParameter('algolia.api_key') === 'phpdish')
        ) {
            $container->setAlias('search.engine', 'search.engine.null');
            $container->findDefinition('search.search_indexer_subscriber')
                ->replaceArgument(1, [])
                ->clearTags();
        }
    }
}


<?php

/*
 * This file is part of the phpdish/phpdish
 *
 * (c) Slince <taosikai@yeah.net>
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

namespace PHPDish\Bundle\ThemeBundle\Theming;

use PHPDish\Bundle\ThemeBundle\Model\ThemeInterface;

class ThemeManager implements ThemeManagerInterface
{
    /**
     * @var ThemeContextInterface
     */
    protected $themeContext;

    /**
     * @var ThemeFinderInterface
     */
    protected $themeFinder;

    /**
     * 当前主题
     *
     * @var ThemeInterface|null
     */
    protected $theme;

    /**
     * @var ThemeInterface[]
     */
    protected $themes;

    /**
     * @var array
     */
    protected $namespaces;

    public function __construct(
        ThemeContextInterface $themeContext,
        ThemeFinderInterface $themeFinder,
        $namespaces
    ) {
        $this->themeContext = $themeContext;
        $this->themeFinder = $themeFinder;
        $this->namespaces = $namespaces;
    }

    /**
     * @return array
     */
    public function getNamespaces()
    {
        return $this->namespaces;
    }

    /**
     * {@inheritdoc}
     */
    public function getCurrentTheme()
    {
        if ($this->theme === null) {
            $this->theme = $this->themeContext->getTheme();
        }
        return $this->theme;
    }

    /**
     * {@inheritdoc}
     */
    public function setCurrentTheme($theme)
    {
        $this->theme = $theme;
    }

    /**
     * {@inheritdoc}
     */
    public function getThemes()
    {
        if ($this->themes === null) {
            $this->themes = $this->themeFinder->find();
        }
        return $this->themes;
    }
}


<?php

/*
 * This file is part of the phpdish/phpdish
 *
 * (c) Slince <taosikai@yeah.net>
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

namespace PHPDish\Bundle\AdminBundle\Grid\Column;

use PHPDish\Bundle\AdminBundle\Grid\Filter\FilterInterface;

class Column implements ColumnInterface
{
    protected $name;

    protected $type;

    protected $sortable = true;

    protected $filterable = true;

    /**
     * @var string
     */
    protected $operator;

    /**
     * @var FilterInterface[]
     */
    protected $filters;

    public function __construct($name, $type, $sortable = true)
    {
        $this->name = $name;
        $this->type = $type;
        $this->sortable = $sortable;
    }

    /**
     * {@inheritdoc}
     */
    public function getName()
    {
        return $this->name;
    }

    /**
     * {@inheritdoc}
     */
    public function getType()
    {
        return $this->type;
    }

    /**
     * {@inheritdoc}
     */
    public function isFilterable()
    {
        return $this->filterable;
    }

    /**
     * {@inheritdoc}
     */
    public function isSortable()
    {
        return $this->sortable;
    }

    /**
     * {@inheritdoc}
     */
    public function getFilters()
    {
        return $this->filters;
    }

    /**
     * {@inheritdoc}
     */
    public function addFilter(FilterInterface $filter)
    {
        $this->filters[] = $filter;
    }

    /**
     * {@inheritdoc}
     */
    public function getFilterByOperator($operator)
    {
        //如果只有一个filter，则operator可以忽略
        if ($operator === null && count($this->filters) === 1) {
            return $this->filters[0];
        }
        foreach ($this->filters as $filter) {
            if ($filter->getOperator() === $operator) {
                return $filter;
            }
        }
        return null;
    }
}


<?php

namespace PHPDish\Bundle\CoreBundle\Search\Algolia;

use Algolia\SearchBundle\IndexManagerInterface;
use Doctrine\ORM\EntityManagerInterface;
use Pagerfanta\Pagerfanta;
use PHPDish\Bundle\CoreBundle\Search\SearchProviderInterface;
use PHPDish\Bundle\ForumBundle\Model\Topic;
use PHPDish\Bundle\PostBundle\Model\Post;
use PHPDish\Bundle\UserBundle\Model\User;

class AlgoliaSearch implements SearchProviderInterface
{
    /**
     * @var IndexManagerInterface
     */
    protected $indexer;

    /**
     * @var EntityManagerInterface
     */
    protected $entityManager;

    public function __construct(IndexManagerInterface $indexer, EntityManagerInterface $entityManager)
    {
        $this->indexer = $indexer;
        $this->entityManager = $entityManager;
    }

    /**
     * {@inheritdoc}
     */
    public function queryUsers($term, array $options = [])
    {
        return $this->createPagerfanta($term, User::class, $options);
    }

    /**
     * {@inheritdoc}
     */
    public function queryPosts($term, array $options = [])
    {
        return $this->createPagerfanta($term, Post::class, $options);
    }

    /**
     * {@inheritdoc}
     */
    public function queryTopics($term, array $options = [])
    {
        return $this->createPagerfanta($term, Topic::class, $options);
    }

    /**
     * 创建分页
     *
     * @param string $keyword
     * @param string $entity
     * @param array $options
     * @return Pagerfanta
     */
    protected function createPagerfanta($keyword, $entity, $options)
    {
        $page = $options['page'] ?? 1;
        $length = $options['length'] ?? 10;
        unset($options['page']);
        unset($options['length']);

//        dump($page);
//        dump($length);exit;
        $adapter = new AlgoliaAdapter($keyword, $entity, $this->entityManager, $this->indexer, $options);
        $pagerfanta  = new Pagerfanta($adapter);
        $pagerfanta->setMaxPerPage($length)
            ->setCurrentPage($page);
        return $pagerfanta;
    }
}


<?php

/*
 * This file is part of the phpdish/phpdish
 *
 * (c) Slince <taosikai@yeah.net>
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

namespace PHPDish\Bundle\CoreBundle\EventListener;

use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
use Symfony\Component\Routing\Router;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;

final class KernelExceptionListener
{
    /**
     * 默认的状态码
     *
     * @var string
     */
    const DEFAULT_STATUS_CODE = Response::HTTP_BAD_REQUEST;

    /**
     * @var TokenStorageInterface
     */
    private $tokenStorage;

    /**
     * @var AuthorizationCheckerInterface
     */
    private $authorizationChecker;

    /**
     * @var Router
     */
    private $router;

    public function __construct(TokenStorageInterface $tokenStorage, AuthorizationCheckerInterface $authorizationChecker, Router $router)
    {
        $this->tokenStorage = $tokenStorage;
        $this->authorizationChecker = $authorizationChecker;
        $this->router = $router;
    }

    public function onKernelException(GetResponseForExceptionEvent $event)
    {
        return;
        //忽略子请求
        if (!$event->isMasterRequest()) {
            return;
        }
        //当前请求
        $request = $event->getRequest();
        $isRequestApi = $request->getRequestFormat() === 'json' || $request->isXmlHttpRequest();

        $filteredResponse = false;
        if ($isRequestApi) {
            $exception = $event->getException();
            $statusCode = $this->resolveStatusCode($exception);
            switch ($statusCode) {
                //403
                case Response::HTTP_FORBIDDEN:
                    $filteredResponse = $this->createForbiddenResponseForAPI();
                    break;

                // 404处理
                case Response::HTTP_NOT_FOUND:
                    $filteredResponse = $this->createNotFoundResponseForAPI($exception);
                    break;

                // 400处理
                case Response::HTTP_BAD_REQUEST:
                    $filteredResponse = $this->createBadRequestResponseForAPI($exception);
                    break;

                // 500处理
                case Response::HTTP_INTERNAL_SERVER_ERROR:
                    $filteredResponse = $this->createServerErrorResponseForAPI($exception);
                    break;
            }
        }
        $filteredResponse && $event->setResponse($filteredResponse);
    }

    /**
     * 计算该异常对应的错误码
     *
     * @param \Exception $exception
     *
     * @return int
     */
    private function resolveStatusCode(\Exception $exception)
    {
        if ($exception instanceof HttpExceptionInterface) {
            $statusCode = $exception->getStatusCode();
        } else {
            if (in_array($exception->getCode(), array_keys(Response::$statusTexts))) {
                $statusCode = $exception->getCode();
            } else {
                $statusCode = static::DEFAULT_STATUS_CODE;
            }
        }

        return $statusCode;
    }

    /**
     * 创建接口 403 response.
     *
     * @return JsonResponse
     */
    private function createForbiddenResponseForAPI()
    {
        if ($this->authorizationChecker->isGranted('IS_AUTHENTICATED_REMEMBERED')) {
            $response = new JsonResponse([
                'error' => '访问拒绝',
            ], Response::HTTP_FORBIDDEN);
        } else {
            $response = new JsonResponse([
                'error' => '请先登录',
                'redirect' => $this->router->generate('login'),
            ], Response::HTTP_FORBIDDEN);
        }

        return $response;
    }

    /**
     * 创建接口 404 response.
     *
     * @param \Exception $exception
     *
     * @return JsonResponse
     */
    private function createNotFoundResponseForAPI(\Exception $exception)
    {
        return new JsonResponse([
            'error' => $exception->getMessage() ?: '您所请求的资源不存在',
        ], Response::HTTP_NOT_FOUND);
    }

    /**
     * 创建接口 400 response.
     *
     * @param \Exception $exception
     *
     * @return JsonResponse
     */
    private function createBadRequestResponseForAPI(\Exception $exception)
    {
        return new JsonResponse([
            'error' => $exception->getMessage() ?: '错误请求',
        ], Response::HTTP_BAD_REQUEST);
    }

    /**
     * 创建接口 500 response.
     *
     * @param \Exception $exception
     *
     * @return JsonResponse
     */
    private function createServerErrorResponseForAPI(\Exception $exception)
    {
        return new JsonResponse([
            'error' => $exception->getMessage() ?: '服务器内部错误',
        ], Response::HTTP_INTERNAL_SERVER_ERROR);
    }
}


<?php

namespace PHPDish\Bundle\MediaBundle\Twig;

use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;

class MediaExtension extends \Twig_Extension
{
    /**
     * @var ContainerInterface
     */
    protected $container;

    protected $baseUrl;

    public function __construct(ContainerInterface $container, $baseUrl)
    {
        $this->container = $container;
        $this->baseUrl = $baseUrl;
    }

    /**
     * {@inheritdoc}
     */
    public function getFilters()
    {
        return [
            new \Twig_SimpleFilter('media_url_build', [$this, 'buildMediaUrl']),
            new \Twig_SimpleFilter('image_process', [$this, 'processBodyImage']),
        ];
    }

    /**
     * 处理内容中的图片地址
     *
     * @param string $body
     * @return string
     */
    public function processBodyImage($body)
    {
        return preg_replace_callback('#<img src="(.*)"#', function($matches){
            if (strpos('http', $matches[1]) === 0) {
                return $matches[0];
            }
            return str_replace($matches[1], $this->baseUrl . $matches[1], $matches[0]);
        }, $body);
    }

    /**
     * 构建媒体资源的访问链接
     * @param string $key
     * @param string $alias
     * @return string
     */
    public function buildMediaUrl($key, $alias = null)
    {
        $serviceId = $alias ? 'phpdish.media.url_builder.' .  $alias : 'phpdish.media.url_builder';
        try {
            $urlBuilder = $this->container->get($serviceId);
        } catch (ServiceNotFoundException $exception) {
            throw new \InvalidArgumentException(sprintf('Invalid Media Service Alias "%s"', $alias));
        }
        return $urlBuilder->build($key);
    }
}


<?php

/*
 * This file is part of the phpdish/phpdish
 *
 * (c) Slince <taosikai@yeah.net>
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

namespace PHPDish\Bundle\ThemeBundle\Templating;

use Symfony\Bundle\FrameworkBundle\Templating\TemplateReference;
use Symfony\Component\Templating\TemplateReferenceInterface;
use Symfony\Bundle\FrameworkBundle\Templating\TemplateNameParser as BaseTemplateNameParser;

/**
 * TemplateNameParser converts template names from the short notation
 * "@Bundle/Section/template.format.engine" to TemplateReferenceInterface instances.
 */
final class TemplateNameParser extends BaseTemplateNameParser
{

    /**
     * {@inheritdoc}
     */
    public function parse($name): TemplateReferenceInterface
    {
        if ($name instanceof TemplateReferenceInterface) {
            return $name;
        }

        if (isset($this->cache[$name])) {
            return $this->cache[$name];
        }

        //不符合twig别名模式的
        if (!preg_match('/^(?:@([^\/]*)|)(?:\/(.+))?\/(.+)\.([^\.]+)\.([^\.]+)$/', $name, $matches)) {
            return parent::parse($name);
        }

        $template = new TemplateReference(
            $matches[1] ? $matches[1] . 'Bundle' : '',
            $matches[2],
            $matches[3],
            $matches[4],
            $matches[5]
        );

        if ($template->get('bundle')) {
            try {
                $this->kernel->getBundle($template->get('bundle'));
            } catch (\Exception $e) {
                return parent::parse($name);
            }
        }

        return $this->cache[$name] = $template;
    }
}


<?php

/*
 * This file is part of the phpdish/phpdish
 *
 * (c) Slince <taosikai@yeah.net>
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

namespace PHPDish\Bundle\CoreBundle;

use PHPDish\Bundle\CoreBundle\Application\Plugin\DependencyInjection\Compiler\RegisterPluginPass;
use PHPDish\Bundle\CoreBundle\DependencyInjection\Compiler\AlgoliaPass;
use PHPDish\Bundle\CoreBundle\DependencyInjection\Compiler\InjectAssetsPass;
use PHPDish\Bundle\CoreBundle\DependencyInjection\PHPDishCoreExtension;
use PHPDish\Bundle\ResourceBundle\AbstractBundle;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpKernel\Bundle\Bundle;

class PHPDishCoreBundle extends AbstractBundle
{
    /**
     * {@inheritdoc}
     */
    public function build(ContainerBuilder $container)
    {
        parent::build($container);
        $container->addCompilerPass(new AlgoliaPass());
        $container->addCompilerPass(new RegisterPluginPass());
        $container->addCompilerPass(new InjectAssetsPass());
    }

    /**
     * {@inheritdoc}
     */
    public function getContainerExtension()
    {
        if (null === $this->extension) {
            $this->extension = new PHPDishCoreExtension();
        }
        return $this->extension;
    }
}


<?php

/*
 * This file is part of the phpdish/phpdish
 *
 * (c) Slince <taosikai@yeah.net>
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

namespace PHPDish\Bundle\ForumBundle\Controller;

use PHPDish\Bundle\ResourceBundle\Controller\ResourceController;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;

class TopicReplyController extends ResourceController
{
    use ManagerTrait;

    use \PHPDish\Bundle\UserBundle\Controller\ManagerTrait;

    /**
     * 删除回复.
     *
     * @Route("/replies/{id}", name="topic_reply_delete", requirements={"id": "\d+"}, methods={"DELETE"})
     *
     * @param int $id
     *
     * @return Response
     */
    public function deleteAction($id)
    {
        $manager = $this->getReplyManager();
        $reply = $manager->findReplyById($id);
        if (!$reply) {
            throw $this->createNotFoundException();
        }
        $this->denyAccessUnlessGranted('edit', $reply);
        $manager->blockReply($reply);

        return $this->handleView($this->view([
            'result' => true,
        ]));
    }

    /**
     * 获取用户的回复
     *
     * @Route("/users/{username}/replies", name="user_replies")
     *
     * @param string  $username
     * @param Request $request
     *
     * @return Response
     */
    public function getUserRepliesAction($username, Request $request)
    {
        $user = $this->getUserManager()->findUserByName($username);
        $replies = $this->getReplyManager()
            ->findUserReplies($user, $request->query->getInt('page', 1));

        return $this->render($this->configuration->getTemplate('Topic:user_replies.html.twig'), [
            'user' => $user,
            'replies' => $replies,
        ]);
    }

    /**
     * 切换点赞状态
     *
     * @Route("/replies/{id}/voters", name="topic_reply_toggle_voter", methods={"POST"})
     *
     * @param int $id
     */
    public function toggleVoterAction($id)
    {
        $this->denyAccessUnlessGranted('IS_AUTHENTICATED_REMEMBERED');

        $reply = $this->getReplyManager()->findReplyById($id);
        if (!$reply) {
            throw new \InvalidArgumentException($this->get('translator')->trans('reply.not_exists'));
        }

        if ($isVoted = $reply->isVotedBy($this->getUser())) {
            $this->getReplyManager()->removeVoter($reply, $this->getUser());
        } else {
            $this->getReplyManager()->addVoter($reply, $this->getUser());
        }
        return $this->json([
            'vote_count' => $reply->getVoteCount(),
            'is_voted' => !$isVoted
        ]);
    }
}


<?php

/*
 * This file is part of the phpdish/phpdish
 *
 * (c) Slince <taosikai@yeah.net>
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

namespace PHPDish\Bundle\AdminBundle\Twig;

use APY\DataGridBundle\Twig\DataGridExtension as BaseDataGridExtension;
use Twig_Environment;

class DataGridExtension extends BaseDataGridExtension
{
    /**
     * 覆盖父级方法
     *
     * @param Twig_Environment $environment
     * @return \Twig_Template[]
     * @throws \Twig_Error_Loader
     * @throws \Twig_Error_Runtime
     * @throws \Twig_Error_Syntax
     */
    protected function getTemplates(Twig_Environment $environment)
    {
        if ($this->templates) {
            return $this->templates;
        }
        $this->templates[] = $environment->load($this->defaultTemplate);
        if ($this->theme) {
            if (!$this->theme instanceof \Twig_Template && !$this->theme instanceof \Twig_TemplateWrapper) {
                $template = $environment->load($this->theme);
            } else {
                $template = $this->theme;
            }
            $this->templates[] = $template;
        }
        return $this->templates;
    }
}


<?php

namespace PHPDish\Bundle\CoreBundle\Installer\Checker;

use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Filesystem\Exception\IOException;
use Symfony\Component\Filesystem\Filesystem;

class DirectoryChecker extends IOAwareChecker
{
    /**
     * @var Filesystem
     */
    protected $filesystem;

    /**
     * @var OutputInterface
     */
    protected $output;

    /**
     * @var string
     */
    protected $name;

    /**
     * @var string
     */
    protected $directory;

    public function __construct(Filesystem $filesystem)
    {
        $this->filesystem = $filesystem;
    }

    /**
     * {@inheritdoc}
     */
    public function check()
    {
        $this->ensureExists($this->directory);
        $this->ensureIsWritable($this->directory);

        return true;
    }

    /**
     * 检查目录是否可写.
     *
     * @param string $directory
     */
    public function ensureIsWritable($directory)
    {
        if (is_writable($directory)) {
            return;
        }
        try {
            $this->filesystem->chmod($directory, 0755);
            $this->output->writeln(sprintf('<comment>Changed "%s" permissions to 0755.</comment>', realpath($directory)));
        } catch (IOException $exception) {
            $this->output->writeln('');
            $this->output->writeln('<error>Cannot run command due to bad directory permissions (tried to change permissions to 0755).</error>');
            $this->output->writeln('');
            throw new \RuntimeException(sprintf(
                'Set "%s" writable and run command "<comment>%s</comment>"',
                realpath(dirname($directory)),
                $this->name
            ));
        }
    }

    /**
     * 检查目录是否存在.
     *
     * @param string $directory
     */
    public function ensureExists($directory)
    {
        if (is_dir($directory)) {
            return;
        }

        try {
            $this->filesystem->mkdir($directory, 0755);

            $this->output->writeln(sprintf('<comment>Created "%s" directory.</comment>', realpath($directory)));
        } catch (IOException $exception) {
            $this->output->writeln('');
            $this->output->writeln('<error>Cannot run command due to unexisting directory (tried to create it automatically, failed).</error>');
            $this->output->writeln('');

            throw new \RuntimeException(sprintf(
                'Create directory "%s" and run command "<comment>%s</comment>"',
                realpath($directory),
                $this->name
            ));
        }
    }

    /**
     * 设置目录.
     *
     * @param string $directory
     */
    public function setDirectory($directory)
    {
        $this->directory = $directory;
    }

    /**
     * 设置命令.
     *
     * @param string $name
     */
    public function setCommandName($name)
    {
        $this->name = $name;
    }
}


<?php

/*
 * This file is part of the phpdish/phpdish
 *
 * (c) Slince <taosikai@yeah.net>
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

namespace PHPDish\Bundle\AdminBundle\DataGrid;

use APY\DataGridBundle\Grid\Action\RowAction;
use APY\DataGridBundle\Grid\GridBuilder;
use APY\DataGridBundle\Grid\Source\Entity;
use PHPDish\Bundle\CoreBundle\Model\User;

class UserFactory extends AbstractGridFactory
{
    /**
     * {@inheritdoc}
     */
    public function factory()
    {
        $source = new Entity(self::getSourceClass());
        $gridBuilder = $this->gridFactory->createBuilder('grid', $source, [
            'persistence'  => true,
            'route'=> 'admin_user_index',
            'filterable'=> true,
            'sortable' => true,
            'max_per_page' => 20,
        ]);
        $this->addColumns($gridBuilder);
        $this->addFilters($gridBuilder);
        return $gridBuilder->getGrid();
    }

    protected function addFilters(GridBuilder $gridBuilder)
    {
        $gridBuilder->get('username')->setFilterable(true);
    }

    protected function addColumns(GridBuilder $gridBuilder)
    {
        $rowAction = new RowAction('user.action.show', 'admin_user_show');
        $gridBuilder
            ->add('id','number', [
                'primary' => 'true',
            ])
            ->add('username', 'text', [
                'title' => 'user.username'
            ])
            ->add('gender', 'boolean', [
                'title' => 'user.gender',
                'size' => -1
            ])
            ->add('createdAt', 'datetime', [
                'title' => 'user.created_at'
            ])
            ->add('lastLogin', 'datetime', [
                'title' => 'user.last_login'
            ])
            ->add('enabled', 'boolean', [
                'title' => 'user.enabled',
                'size' => -1
            ])
            ->addAction($rowAction);
    }

    /**
     * {@inheritdoc}
     */
    public static function getSourceClass()
    {
        return User::class;
    }
}


<?php

/*
 * This file is part of the phpdish/phpdish
 *
 * (c) Slince <taosikai@yeah.net>
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

namespace PHPDish\Bundle\PostBundle\EventListener;

use PHPDish\Bundle\PaymentBundle\Event\PaymentEvent;
use PHPDish\Bundle\PaymentBundle\Model\PaymentInterface;
use PHPDish\Bundle\PostBundle\Service\CategoryManagerInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;

final class PaymentSubscriber implements EventSubscriberInterface
{
    /**
     * @var CategoryManagerInterface
     */
    protected $categoryManager;

    public function __construct(
        CategoryManagerInterface $categoryManager
    )
    {
        $this->categoryManager = $categoryManager;
    }

    public static function getSubscribedEvents()
    {
        return [
            PaymentEvent::PAYMENT_PAID => 'onPaymentPaid'
        ];
    }

    /**
     * 交易完成，
     * 1. 增加付款人的订阅
     * 2. 增加专栏所属人的钱余额
     * @param PaymentEvent $event
     */
    public function onPaymentPaid(PaymentEvent $event)
    {
        $payment = $event->getPayment();
        //买书的交易或者订阅专栏的交易处理
        if (
            $payment->getType() === PaymentInterface::TYPE_FOLLOW_CATEGORY
            || $payment->getType() === PaymentInterface::TYPE_BUY_BOOK
        ) {
            $category = $this->categoryManager->findCategoryById($payment->getPayableId());
            if ($category) { //用户支付成功，增加订阅
                $this->categoryManager->followCategory($category, $payment->getUser());

                $this->categoryManager->addCategoryIncome($category->getCreator(), $category, $payment->getUser(), $payment->getAmount()); //增加收入
            }
        }
    }
}


<?php

/*
 * This file is part of the phpdish/phpdish
 *
 * (c) Slince <taosikai@yeah.net>
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

namespace PHPDish\Bundle\ForumBundle\Service;

use Carbon\Carbon;
use Doctrine\Common\Collections\Criteria;
use Doctrine\ORM\EntityManagerInterface;
use Overtrue\Pinyin\Pinyin;
use PHPDish\Bundle\CoreBundle\Service\PaginatorTrait;
use PHPDish\Bundle\ForumBundle\Model\ThreadInterface;
use PHPDish\Bundle\ResourceBundle\Service\ServiceManagerInterface;
use PHPDish\Bundle\UserBundle\Model\UserInterface;

class ThreadManager implements ThreadManagerInterface, ServiceManagerInterface
{

    use PaginatorTrait;

    /**
     * @var EntityManagerInterface
     */
    protected $entityManager;

    /**
     * @var Pinyin
     */
    protected $pinyin;

    protected $threadEntity;

    public function __construct($threadEntity, EntityManagerInterface $entityManager, Pinyin $pinyin)
    {
        $this->threadEntity = $threadEntity;
        $this->entityManager = $entityManager;
        $this->pinyin = $pinyin;
    }

    /**
     * {@inheritdoc}
     */
    public function findThreads(Criteria $criteria)
    {
        return $this->getThreadRepository()->createQueryBuilder('t')
            ->addCriteria($criteria)
            ->getQuery()
            ->getResult();
    }

    /**
     * {@inheritdoc}
     */
    public function findThreadsPager(Criteria $criteria, $page, $limit = null)
    {
        $qb = $this->getThreadRepository()->createQueryBuilder('t')
            ->addCriteria($criteria);
        return $this->createPaginator($qb->getQuery(), $page, $limit);
    }

    /**
     * {@inheritdoc}
     */
    public function findUserFollowingThreads(UserInterface $user, $page, $limit = null, Criteria $criteria = null)
    {
        $qb = $this->getThreadRepository()->createQueryBuilder('t')
            ->leftJoin('t.followers', 'f')
            ->where('f.id = :userId')
            ->setParameter('userId', $user);
        $criteria && $qb->addCriteria($criteria);
        return $this->createPaginator($qb->getQuery(), $page, $limit);
    }

    /**
     * {@inheritdoc}
     */
    public function findEnabledThreads($limit = null)
    {
        return $this->getThreadRepository()->findBy(['enabled' => true], [
            'followerCount' => 'desc'
        ], $limit);
    }

    /**
     * {@inheritdoc}
     */
    public function findThreadById($id)
    {
        return $this->getThreadRepository()->find($id);
    }

    /**
     * {@inheritdoc}
     */
    public function findThreadBySlug($slug)
    {
        return $this->getThreadRepository()->findOneBy(['slug' => $slug]);
    }

    /**
     * {@inheritdoc}
     */
    public function findThreadsByNames($names)
    {
        $qb = $this->getThreadRepository()->createQueryBuilder('t');
        return $qb->where($qb->expr()->in('t.name', $names))
            ->getQuery()
            ->getResult();
    }

    /**
     * {@inheritdoc}
     */
    public function createThread()
    {
        $thread = new $this->threadEntity;
        $thread->setCreatedAt(Carbon::now());
        return $thread;
    }

    /**
     * {@inheritdoc}
     */
    public function saveThread(ThreadInterface $thread)
    {
        $this->entityManager->persist($thread);
        $this->entityManager->flush();
    }

    /**
     * {@inheritdoc}
     */
    public function createThreadsByNames($names)
    {
        $threads = [];
        foreach ($names as $name) {
            $thread = $this->createThread();
            $thread->setName($name)
                ->setSlug($this->pinyin->permalink($name))
                ->setDescription($name);
            $threads[] = $thread;
        }
        return $threads;
    }

    /**
     * {@inheritdoc}
     */
    public function searchThreads($term)
    {
        $qb = $this->getThreadRepository()->createQueryBuilder('t');
        return $qb->where($qb->expr()->like('t.name', ':term'))
            ->orWhere($qb->expr()->like('t.description', ':term'))
            ->setParameter('term', "%{$term}%")
            ->setMaxResults(10)
            ->getQuery()
            ->getResult();
    }

    /**
     * {@inheritdoc}
     */
    public function followThread(ThreadInterface $thread, UserInterface $user)
    {
        $thread->addFollower($user);
        $thread->setFollowerCount($thread->getFollowerCount() + 1);
        $this->saveThread($thread);
    }

    /**
     * {@inheritdoc}
     */
    public function unFollowThread(ThreadInterface $thread, UserInterface $user)
    {
        $thread->removeFollower($user);
        $thread->setFollowerCount($thread->getFollowerCount() - 1 ?: 0);
        $this->saveThread($thread);
    }

    /**
     * {@inheritdoc}
     */
    public function getThreadRepository()
    {
        return $this->entityManager->getRepository($this->threadEntity);
    }

    /**
     * {@inheritdoc}
     */
    public static function getSubscribedEntities()
    {
        return [
            'threadEntity' => ThreadInterface::class
        ];
    }
}


<?php

namespace PHPDish\Bundle\CoreBundle\Installer\Checker;

use PHPDish\Bundle\CoreBundle\Installer\Requirement\PHPDishRequirements;
use Symfony\Component\Console\Helper\Table;

class RequirementsChecker extends IOAwareChecker
{
    /**
     * @var PHPDishRequirements
     */
    protected $requirements;

    public function __construct(PHPDishRequirements $requirements)
    {
        $this->requirements = $requirements;
    }

    /**
     * {@inheritdoc}
     */
    public function check()
    {
        $table = new Table($this->output);
        $fulfilled = true;
        foreach ($this->requirements as $requirement) {
            $row = [$requirement->getLabel()];
            if ($requirement->isFulfilled()) {
                $row[] = '<info>Yes</info>';
                $fulfilled = $fulfilled && true;
            } elseif ($requirement->isRequired()) {
                $row[] = '<error>Error</error>';
                $fulfilled = $fulfilled && false;
            } else {
                $row[] = '<comment>Warning</comment>';
                $fulfilled = $fulfilled && true;
            }
            $table->addRow($row);
        }
        $table->render();

        return $fulfilled;
    }
}


<?php

/*
 * This file is part of the phpdish/phpdish
 *
 * (c) Slince <taosikai@yeah.net>
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

namespace PHPDish\Bundle\ResourceBundle\DependencyInjection\Compiler;

use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;

/**
 * Resolves given target entities with container parameters.
 * Usable only with *doctrine/orm* driver.
 */
class DoctrineTargetEntitiesResolverPass implements CompilerPassInterface
{
    /**
     * {@inheritdoc}
     */
    public function process(ContainerBuilder $container)
    {
        try {
            $resources = $container->getParameter('phpdish.resources');
            $resolveTargetEntityListener = $container->findDefinition('doctrine.orm.listeners.resolve_target_entity');
        } catch (InvalidArgumentException $exception) {
            return;
        }

        $interfaces = $this->getInterfacesMapping($resources);
        foreach ($interfaces as $interface => $model) {
            $resolveTargetEntityListener->addMethodCall('addResolveTargetEntity', [
                $this->getInterface($container, $interface),
                $this->getClass($container, $model),
                [],
            ]);
        }

        if (!$resolveTargetEntityListener->hasTag('doctrine.event_listener')) {
            $resolveTargetEntityListener->addTag('doctrine.event_listener', ['event' => 'loadClassMetadata']);
        }
    }

    /**
     * @param array $resources
     *
     * @return array
     */
    private function getInterfacesMapping(array $resources): array
    {
        $interfaces = [];
        foreach ($resources as $alias => $resource) {
            foreach ($resource as $resourceItem) {
                $interfaces[$resourceItem['interface']] = $resourceItem['model'];
            }
        }
        return $interfaces;
    }

    /**
     * @param ContainerBuilder $container
     * @param string $key
     *
     * @return string
     *
     * @throws \InvalidArgumentException
     */
    private function getInterface(ContainerBuilder $container, string $key): string
    {
        if ($container->hasParameter($key)) {
            return $container->getParameter($key);
        }

        if (interface_exists($key) || class_exists($key)) {
            return $key;
        }

        throw new \InvalidArgumentException(
            sprintf('The interface %s does not exist.', $key)
        );
    }

    /**
     * @param ContainerBuilder $container
     * @param string $key
     *
     * @return string
     *
     * @throws \InvalidArgumentException
     */
    private function getClass(ContainerBuilder $container, $key): string
    {
        if ($container->hasParameter($key)) {
            return $container->getParameter($key);
        }

        if (class_exists($key)) {
            return $key;
        }

        throw new \InvalidArgumentException(
            sprintf('The class %s does not exist.', $key)
        );
    }
}


<?php

/*
 * This file is part of the phpdish/phpdish
 *
 * (c) Slince <taosikai@yeah.net>
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

namespace PHPDish\Bundle\PaymentBundle\Service;

use Carbon\Carbon;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityRepository;
use PHPDish\Bundle\PaymentBundle\Event\PaymentEvent;
use PHPDish\Bundle\PaymentBundle\Model\PaymentInterface;
use PHPDish\Bundle\ResourceBundle\Service\ServiceManagerInterface;
use PHPDish\Bundle\UserBundle\Model\UserInterface;
use Slince\YouzanPay\YouzanPay;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;

class PaymentManager implements PaymentManagerInterface, ServiceManagerInterface
{
    /**
     * @var EventDispatcherInterface
     */
    protected $eventDispatcher;

    /**
     * @var EntityManagerInterface
     */
    protected $entityManager;

    /**
     * @var YouzanPay
     */
    protected $youzanPay;

    /**
     * @var WalletManagerInterface
     */
    protected $walletManager;

    protected $paymentEntity;

    public function __construct(
        $paymentEntity,
        EntityManagerInterface $entityManager,
        EventDispatcherInterface $eventDispatcher,
        WalletManagerInterface $walletManager,
        YouzanPay $youzanPay
    )
    {
        $this->paymentEntity = $paymentEntity;
        $this->entityManager = $entityManager;
        $this->eventDispatcher = $eventDispatcher;
        $this->walletManager = $walletManager;
        $this->youzanPay = $youzanPay;
    }

    /**
     * {@inheritdoc}
     */
    public function createPayment(UserInterface $user = null)
    {
        $payment = new $this->paymentEntity;
        $now = Carbon::now();
        $payment->setUser($user)
            ->setStatus(PaymentInterface::STATUS_WAITING)
            ->setCreatedAt($now)
            ->setUpdatedAt($now);
        return $payment;
    }

    /**
     * {@inheritdoc}
     */
    public function savePayment(PaymentInterface $payment)
    {
        //如果没有钱包
        if (!$payment->getWallet()) {
            $payment->setWallet($this->walletManager->getUserWallet($payment->getUser()));
        }
        $this->entityManager->persist($payment);
        $this->entityManager->flush();
    }

    /**
     * {@inheritdoc}
     */
    public function charge(PaymentInterface $payment)
    {
        $qrCode = $this->youzanPay->charge([
            'name' => strip_tags($payment->getDescription()),
            'price' => $payment->getAmount(),
            'source' => $payment->getSerialNo()
        ]);
        $payment->setQrId($qrCode->getId());
        $this->savePayment($payment);
        return $qrCode;
    }

    /**
     * {@inheritdoc}
     */
    public function findPaymentByQrId($qrId)
    {
        return $this->getRepository()->findOneBy([
            'qrId' => $qrId
        ]);
    }

    /**
     * {@inheritdoc}
     */
    public function notifyPayment($qrId)
    {
        $payment = $this->findPaymentByQrId($qrId);
        if (!$payment) {
            return null;
        }
        //已经成功的不再通知
        if ($payment->getStatus() === PaymentInterface::STATUS_OK) {
            return null;
        }
        $payment->setStatus(PaymentInterface::STATUS_OK)
            ->setUpdatedAt(Carbon::now()); //交易状态

        $this->savePayment($payment);

        //触发交易完成事件
        $this->eventDispatcher->dispatch(PaymentEvent::PAYMENT_PAID, new PaymentEvent($payment));
        return $payment;
    }

    /**
     * @return EntityRepository
     */
    protected function getRepository()
    {
        return $this->entityManager->getRepository($this->paymentEntity);
    }

    /**
     * {@inheritdoc}
     */
    public static function getSubscribedEntities()
    {
        return [
            'paymentEntity' => PaymentInterface::class,
        ];
    }
}


<?php

/*
 * This file is part of the phpdish/phpdish
 *
 * (c) Slince <taosikai@yeah.net>
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

namespace PHPDish\Bundle\ThemeBundle\Templating\Loader;

use PHPDish\Bundle\ThemeBundle\Model\ThemeInterface;
use PHPDish\Bundle\ThemeBundle\Theming\ThemeManagerInterface;
use Symfony\Component\HttpKernel\Config\FileLocator as BaseFileLocator;
use Symfony\Component\HttpKernel\KernelInterface;

class FileLocator extends BaseFileLocator
{
    /**
     * @var ThemeManagerInterface
     */
    protected $themeManager;

    /**
     * @var ThemeInterface
     */
    protected $currentTheme;

    /**
     * @var KernelInterface
     */
    protected $_kernel;

    public function setThemeManager(ThemeManagerInterface $themeManager)
    {
        $this->themeManager = $themeManager;
        $this->currentTheme = $this->themeManager->getCurrentTheme();
        //将当前主题的路径添加入paths
        if ($this->currentTheme) {
            $this->paths[] = $this->currentTheme->getPath();
        }
    }

    /**
     * @param mixed $kernel
     */
    public function setKernel($kernel)
    {
        $this->_kernel = $kernel;
    }

    /**
     * {@inheritdoc}
     */
    public function locate($file, $currentPath = null, $first = true)
    {
        if ($this->currentTheme && $file[0] === '@') {
            $bundleName = substr($file, 1);
            if (false !== strpos($bundleName, '/')) {
                list($bundleName, $path) = explode('/', $bundleName, 2);
            }
            if (in_array($bundleName, $this->themeManager->getNamespaces())) {
                try {
                    $filePath = $this->_kernel->locateResource($file,
                        $this->currentTheme->getPath(), $first);
                    return $filePath;
                } catch (\Exception $exception) {
                    //Ignore if not found in theme.
                }
            }
        }
        return parent::locate($file, $currentPath);
    }
}


<?php

/*
 * This file is part of the phpdish/phpdish
 *
 * (c) Slince <taosikai@yeah.net>
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

declare(strict_types=1);

namespace PHPDish\Bundle\CoreBundle\Application\Plugin\Finder;

use PHPDish\Bundle\CoreBundle\Application\Plugin\SimplePluginInterface;

class PluginFinder implements PluginFinderInterface
{
    /**
     * @var SimplePluginInterface[]
     */
    protected $plugins = [];

    /**
     * @var string
     */
    protected $installedJson;

    public function __construct($projectDir)
    {
        $this->installedJson = $projectDir . '/vendor/composer/installed.json';
    }

    /**
     * {@inheritdoc}
     */
    public function findAll()
    {
        if (!file_exists($this->installedJson)) {
            return [];
        }
        $installed = \GuzzleHttp\json_decode(file_get_contents($this->installedJson), true);
        foreach ($installed as $package) {
            if (
                !isset($package['type'])
                || $package['type'] !== 'phpdish-plugin'
                || !isset($package['extra']['phpdish']['class'])
                || !class_exists($package['extra']['phpdish']['class'])
            ) {
                continue;
            }
            $pluginClass = $package['extra']['phpdish']['class'];
            $this->plugins[] = $this->initializePlugin($pluginClass);
        }
        return $this->plugins;
    }

    protected function initializePlugin($pluginClass)
    {
        return new $pluginClass;
    }
}


<?php

namespace PHPDish\Bundle\CoreBundle\DataFixtures\ORM;

use Carbon\Carbon;
use Doctrine\Common\Persistence\ObjectManager;
use Doctrine\Bundle\FixturesBundle\Fixture;
use PHPDish\Bundle\ForumBundle\Model\Thread;

class ThreadFixtures extends Fixture
{
    /**
     * {@inheritdoc}
     */
    public function load(ObjectManager $manager)
    {
        $job = new Thread();
        $job->setCreatedAt(Carbon::now())
            ->setUpdatedAt(Carbon::now())
            ->setEnabled(true)
            ->setName('招聘')
            ->setSlug('job')
            ->setDescription('招聘节点');

        $question = new Thread();
        $question->setCreatedAt(Carbon::now())
            ->setUpdatedAt(Carbon::now())
            ->setEnabled(true)
            ->setName('问答')
            ->setSlug('question')
            ->setDescription('问答节点');

        $share = new Thread();
        $share->setCreatedAt(Carbon::now())
            ->setUpdatedAt(Carbon::now())
            ->setEnabled(true)
            ->setName('创造与分享')
            ->setSlug('creation-share')
            ->setDescription('创造与分享');

        $manager->persist($job);
        $manager->persist($question);
        $manager->persist($share);
        $manager->flush();

        $this->addReference('thread-share', $share);
    }
}


<?php

/*
 * This file is part of the phpdish/phpdish
 *
 * (c) Slince <taosikai@yeah.net>
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

namespace PHPDish\Bundle\MediaBundle\Controller;

use PHPDish\Component\Media\Model\Image;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;

class MediaController extends Controller
{
    const UPLOAD_FIELD_NAME = 'file';

    /**
     * 附件上传接口.
     *
     * @Route("/uploads", name="upload")
     *
     * @param Request $request
     *
     * @return Response
     */
    public function upload(Request $request)
    {
        $uploadedFile = $request->files->get(static::UPLOAD_FIELD_NAME);
        if (is_null($uploadedFile)) {
            throw new \InvalidArgumentException('Bad arguments');
        }

        list($uploader, $urlBuilder) = $this->getUploaderAndBuilder($request);

        $file = $uploader->upload($uploadedFile);
        $response = [
            'key' => $file->getKey(),
            'path' => $urlBuilder->build($file)
        ];
        if ($file instanceof Image) {
            $response['thumb'] = $urlBuilder->buildImageResizeUrl($file, 'middle_square');
        }
        $file->getContent()->close();
        return $this->json($response);
    }

    /**
     * 获取上传处理器和url构建
     * @param Request $request
     * @return array
     */
    protected function getUploaderAndBuilder(Request $request)
    {
        if ($configKey = $request->headers->get('media_mapping')) {
            $uploader = $this->get('phpdish_media.file_uploader.' . $configKey);
            $urlBuilder = $this->get('phpdish_media.url_builder.' . $configKey);
        } else {
            $uploader = $this->get('phpdish_media.file_uploader');
            $urlBuilder = $this->get('phpdish_media.url_builder');
        }
        return [$uploader, $urlBuilder];
    }
}


<?php

/*
 * This file is part of the phpdish/phpdish
 *
 * (c) Slince <taosikai@yeah.net>
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

namespace PHPDish\Bundle\PaymentBundle\Money;

use Money\Currencies;
use Money\Currency;
use Money\Formatter\IntlMoneyFormatter;
use Money\Money;

class MoneyFormatter
{
    /**
     * @var Currencies
     */
    protected $currencies;

    protected $formatter;

    public function __construct()
    {
        $this->currencies = new Currencies\ISOCurrencies();
        $numberFormatter = new \NumberFormatter('zh_CN', \NumberFormatter::CURRENCY);
        $this->formatter = new IntlMoneyFormatter($numberFormatter, $this->currencies);
    }

    /**
     * 输出价格表达式
     *
     * @param int|Money $money
     * @return string
     */
    public function format($money)
    {
        if (!$money instanceof Money) {
            $money = new Money($money, new Currency('RMB'));
        }
        return $this->formatter->format($money);
    }
}


<?php

/*
 * This file is part of the phpdish/phpdish
 *
 * (c) Slince <taosikai@yeah.net>
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

declare(strict_types=1);

namespace PHPDish\Bundle\CoreBundle\DependencyInjection\Compiler;

use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;

class RegisterPluginListenerPass implements CompilerPassInterface
{
    /**
     * {@inheritdoc}
     */
    public function process(ContainerBuilder $container)
    {
        $listenerRegistry = $container->getParameter('phpdish.plugin.listener_registry');
        $eventDispatcherDefinition = $container->findDefinition('event_dispatcher');

        foreach ($listenerRegistry as $index => $listener) {
            if (is_array($listener)) {
                list($eventName, $listener, $priority) = $listener;

                if (is_array($listener)) {
                    list($class, $method) = $listener;
                } else {
                    $class = $listener;
                    $method = '__invoke';
                }

                $id = 'phpdish.plugin.listener_' . $index;
                $listenerDefinition = new Definition($class);
                $listenerDefinition->setAutowired(true);
                $container->setDefinition($id, $listenerDefinition);

                $eventDispatcherDefinition->addMethodCall('addListener', [
                    $eventName,
                    [new ServiceClosureArgument(new Reference($id)), $method],
                    $priority
                ]);

            } else {
                $id = 'phpdish.plugin.subscriber_' . $index;
                $listenerDefinition = new Definition($listener);
                $listenerDefinition->setAutowired(true);
                $container->setDefinition($id, $listenerDefinition);

                $eventDispatcherDefinition->addMethodCall('addSubscriber', [
                    new Reference($id)
                ]);
            }
        }

        $container->getParameterBag()->remove('phpdish.plugin.listener_registry');
    }
}


<?php

/*
 * This file is part of the phpdish/phpdish
 *
 * (c) Slince <taosikai@yeah.net>
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

namespace PHPDish\Bundle\AdminBundle\DataGrid;

use APY\DataGridBundle\Grid\Grid;
use APY\DataGridBundle\Grid\Source\Entity;
use Doctrine\ORM\EntityManagerInterface;
use PHPDish\Bundle\ResourceBundle\Metadata\ResourceRegistry;
use Symfony\Component\HttpFoundation\RequestStack;

class GridFactoryAggregate
{
    /**
     * @var ResourceRegistry
     */
    protected $resourceRegistry;

    /**
     * @var EntityManager
     */
    protected $entityManager;

    /**
     * @var RequestStack
     */
    protected $requestStack;

    /**
     * [
     *     'PHPDish\Bundle\UserBundle\UserInterface' => 'phpdish_admin.source_factory.user'
     * ]
     * @var GridFactoryInterface[]
     */
    protected $factory = [];

    /**
     * @var Grid
     */
    protected $grid;

    public function __construct(
        ResourceRegistry $resourceRegistry,
        EntityManagerInterface $entityManager,
        RequestStack $requestStack,
        Grid $grid
    ) {
        $this->resourceRegistry = $resourceRegistry;
        $this->entityManager = $entityManager;
        $this->requestStack = $requestStack;
        $this->grid = $grid;
    }

    /**
     * 获取grid source
     *
     * @param string $sourceClass
     * @return Grid
     * @throws \ReflectionException
     */
    public function get($sourceClass)
    {
        if (isset($this->factory[$sourceClass])) {
            $factory = $this->factory[$sourceClass];
            $grid = $factory->factory();
        } else {
            $reflection = new \ReflectionClass($sourceClass);
            if (!$reflection->isInstantiable()) {
                throw new \InvalidArgumentException(sprintf('The "%s" cannot be instantiated'));
            }
            $source = new Entity($sourceClass);
            $this->grid->setSource($source);
            $grid = $this->grid;
        }
        //handle request
//        $grid->handleRequest($this->requestStack->getCurrentRequest());
//        dump($grid->getColumns());
        return $grid;
    }

    public function addFactory($sourceClass, $factory)
    {
        $this->factory[$sourceClass] = $factory;
    }
}