<?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\Controller;

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

class UserController extends ResourceController
{
    use ManagerTrait;

    /**
     * 用户首页
     *
     * @param string $username
     *
     * @return Response
     */
    public function viewAction($username)
    {
        return $this->forward('PHPDishForumBundle:Topic:getUserTopics', [
            'username' => $username,
        ]);
    }

    /**
     * 最近注册的用户.
     *
     * @param int $limit
     *
     * @return Response
     */
    public function latestUsersAction($limit)
    {
        $users = $this->getUserManager()->findLatestUsers($limit, true);
        return $this->render($this->configuration->getTemplate('User:latest.html.twig'), [
            'users' => $users,
        ]);
    }

    /**
     * 用户关注的人.
     *
     * @param UserInterface $user
     *
     * @return Response
     */
    public function userFollowingAction(UserInterface $user)
    {
        $following = $this->getUserManager()->findUserFollowing($user, 1);

        return $this->render($this->configuration->getTemplate('User:user_grid.html.twig'), [
            'users' => $following,
        ]);
    }

    /**
     * 用户的粉丝.
     *
     * @param UserInterface $user
     *
     * @return Response
     */
    public function userFollowersAction(UserInterface $user)
    {
        $following = $this->getUserManager()->findUserFollowers($user, 1);

        return $this->render($this->configuration->getTemplate('User:user_grid.html.twig'), [
            'users' => $following,
        ]);
    }

    /**
     * 获取用户关注的人.
     *
     * @Route("/users/{username}/following", name="user_following")
     *
     * @param string  $username
     * @param Request $request
     *
     * @return Response
     */
    public function getUserFollowingAction($username, Request $request)
    {
        $manager = $this->getUserManager();
        $user = $manager->findUserByName($username);
        $following = $manager->findUserFollowing($user, $request->query->getInt('page', 1));

        return $this->render($this->configuration->getTemplate('User:user_following.html.twig'), [
            'user' => $user,
            'users' => $following,
        ]);
    }

    /**
     * @Route("/users/{username}/followers.{_format}", name="user_followers", defaults={"_format"="html"})
     * @Method("GET")
     *
     * @param string  $username
     * @param Request $request
     *
     * @return Response
     */
    public function getUserFollowersAction($username, Request $request)
    {
        $manager = $this->getUserManager();
        $user = $manager->findUserByName($username);
        $followers = $manager->findUserFollowers($user, $request->query->getInt('page', 1));

        $view = $this->view([
                'user' => $user,
                'followers' => $followers,
            ])->setTemplate($this->configuration->getTemplate('User:user_followers.html.twig'));

        return $this->handleView($view);
    }

    /**
     * @Route("/users/{username}/followers", name="follower_add")
     * @Method("POST")
     *
     * @param string $username
     *
     * @return Response
     */
    public function followAction($username)
    {
        $this->denyAccessUnlessGranted('IS_AUTHENTICATED_REMEMBERED');
        $manager = $this->getUserManager();
        $user = $manager->findUserByName($username);
        $view = $this->view();
        try {
            $manager->followUser($user, $this->getUser());
            $view->setStatusCode(static::HTTP_CREATED)->setData([
                'follower_count' => $user->getFollowerCount(),
            ]);
        } catch (\Exception $exception) {
            $view->setStatusCode(static::HTTP_BAD_REQUEST)
                ->setData([
                    'error' => $exception->getMessage(),
                ]);
        }

        return $this->handleView($view);
    }

    /**
     * @Route("/users/{username}/followers", name="follower_delete")
     * @Method("DELETE")
     *
     * @param string $username
     *
     * @return Response
     */
    public function unFollowAction($username)
    {
        $this->denyAccessUnlessGranted('IS_AUTHENTICATED_REMEMBERED');
        $manager = $this->getUserManager();
        $user = $manager->findUserByName($username);
        $view = $this->view();
        $manager->unFollowUser($user, $this->getUser());
        $view->setStatusCode(static::HTTP_OK)->setData([
            'follower_count' => $user->getFollowerCount(),
        ]);

        return $this->handleView($view);
    }

    /**
     * 用户积分
     *
     * @Route("/users/{username}/points", name="user_points")
     *
     * @param string $username
     * @param Request $request
     * @return Response
     */
    public function pointAction($username, Request $request)
    {
        $this->denyAccessUnlessGranted('IS_AUTHENTICATED_REMEMBERED');
        $manager = $this->getUserManager();
        $user = $manager->findUserByName($username);
        $histories = $this->getPointManager()->findPointHistories($user, $request->get('page', 1));
        return $this->render($this->configuration->getTemplate('User:point_history.html.twig'), [
            'histories' => $histories,
            'user' => $user,
        ]);
    }
}


<?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\DependencyInjection\Compiler;

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

class InjectAssetsPass implements CompilerPassInterface
{
    /**
     * {@inheritdoc}
     */
    public function process(ContainerBuilder $container)
    {
        //设置web assets package 为默认
        $container->setAlias('assets._default_package', 'assets._package_web');
        // 如果是url assets
        $this->processPackage($container, 'web');
        $this->processPackage($container, 'admin');
    }

    protected function processPackage(ContainerBuilder $container, $type)
    {
        $cdnHost = $container->getParameter('cdn.host.' . $type);
        if (strpos($cdnHost, 'http') === 0) {
            $version = $container->findDefinition('assets._package_' . $type)->getArgument(1);
            $package = new ChildDefinition('assets.url_package');
            $package
                ->setPublic(false)
                ->replaceArgument(0, $cdnHost)
                ->replaceArgument(1, $version);
        } else {
            $version = $container->findDefinition('assets._package_' . $type)->getArgument(1);
            $package = new ChildDefinition('assets.path_package');
            $package
                ->setPublic(false)
                ->replaceArgument(0, $cdnHost)
                ->replaceArgument(1, $version);
        }
        $container->setDefinition('assets._package_'.$type, $package);
    }
}


<?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\EventListener;

use Carbon\Carbon;
use Doctrine\Common\Persistence\Event\LifecycleEventArgs;
use FOS\UserBundle\Event\FormEvent;
use FOS\UserBundle\FOSUserEvents;
use PHPDish\Bundle\ResourceBundle\AvatarGenerator\AvatarGeneratorInterface;
use PHPDish\Bundle\UserBundle\Event\Events;
use PHPDish\Bundle\UserBundle\Event\UserEvent;
use PHPDish\Bundle\UserBundle\Model\UserInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\Routing\RouterInterface;

final class UserSubscriber implements EventSubscriberInterface
{
    /**
     * @var EventDispatcherInterface
     */
    protected $eventDispatcher;

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

    /**
     * @var AvatarGeneratorInterface
     */
    protected $avatarGenerator;

    public function __construct(
        EventDispatcherInterface $eventDispatcher,
        RouterInterface $router,
        AvatarGeneratorInterface $avatarGenerator
    ) {
        $this->eventDispatcher = $eventDispatcher;
        $this->router = $router;
        $this->avatarGenerator = $avatarGenerator;
    }

    /**
     * {@inheritdoc}
     */
    public static function getSubscribedEvents()
    {
        return [
            FOSUserEvents::REGISTRATION_SUCCESS => 'onRegisterSuccess',
            FOSUserEvents::RESETTING_RESET_SUCCESS => 'onResettingResetSuccess',
            FOSUserEvents::CHANGE_PASSWORD_SUCCESS => 'onChangePassword'
        ];
    }

    /**
     * 注册监听完成
     *
     * @param FormEvent $event
     */
    public function onRegisterSuccess(FormEvent $event)
    {
        $user = $event->getForm()->getData();
        $response = new RedirectResponse($this->router->generate('user_view', [
            'username' => $user->getUsername(),
        ]));
        $event->setResponse($response);
    }

    /**
     * 重置之后
     *
     * @param FormEvent $event
     */
    public function onResettingResetSuccess(FormEvent $event)
    {
        $response = new RedirectResponse($this->router->generate('setting_profile'));
        $event->setResponse($response);
    }

    /**
     * 密码修改成功触发
     *
     * @param FormEvent $event
     */
    public function onChangePassword(FormEvent $event)
    {
        $event->setResponse(new RedirectResponse(
            $this->router->generate('setting_change_password') //跳转回修改密码页面
        ));
    }

    /**
     * doctrine 事件，数据库写入
     *
     * @param UserInterface $user
     * @param LifecycleEventArgs $event
     */
    public function postPersist($user, LifecycleEventArgs $event)
    {
        $now = Carbon::now();
        $user->setCreatedAt($now)->setUpdatedAt($now);
        $user->setAvatar($this->avatarGenerator->generate($user->getUsername())->getKey()); //生成临时头像

        $this->eventDispatcher->dispatch(Events::USER_CREATED, new UserEvent($user));
    }
}


<?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\CmsBundle\BodyProcessor;

use Emojione\Emojione;
use Knp\Bundle\MarkdownBundle\MarkdownParserInterface;
use PHPDish\Component\Mention\MentionParserInterface;

class BodyProcessor implements BodyProcessorInterface
{
    /**
     * @var MarkdownParserInterface
     */
    protected $markdownParser;

    /**
     * @var MentionParserInterface
     */
    protected $mentionParser;

    /**
     * @var \HTMLPurifier
     */
    protected $htmlPurifier;

    public function __construct(
        MarkdownParserInterface $markdownParser,
        MentionParserInterface $mentionParser,
        \HTMLPurifier $htmlPurifier
    )
    {
        $this->markdownParser = $markdownParser;
        $this->mentionParser = $mentionParser;
        $this->htmlPurifier = $htmlPurifier;
    }

    /**
     * {@inheritdoc}
     */
    public function process($body)
    {
        $body = $this->markdownParser->transformMarkdown($body);
        $body = $this->htmlPurifier->purify($body); //先过滤标签
        $parsedBody = $this->mentionParser->parse($body)->getParsedBody();
        return Emojione::getClient()->shortnameToUnicode($parsedBody);
    }

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

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


<?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\CoreBundle\Service\PaginatorTrait;
use PHPDish\Bundle\PaymentBundle\Model\PaymentInterface;
use PHPDish\Bundle\PaymentBundle\Model\WalletInterface;
use PHPDish\Bundle\PaymentBundle\Event\PaymentEvent;
use PHPDish\Bundle\PaymentBundle\Model\WalletHistoryInterface;
use PHPDish\Bundle\ResourceBundle\Service\ServiceManagerInterface;
use PHPDish\Bundle\UserBundle\Model\UserInterface;
use PHPDish\Bundle\UserBundle\Service\UserManagerInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\Routing\Router;
use Symfony\Component\Routing\RouterInterface;
use Symfony\Component\Translation\TranslatorInterface;

class WalletManager implements WalletManagerInterface, ServiceManagerInterface
{
    use PaginatorTrait;

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

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

    /**
     * @var UserManagerInterface
     */
    protected $userManager;

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

    /**
     * @var TranslatorInterface
     */
    protected $translator;

    protected $walletEntity;

    protected $paymentEntity;

    public function __construct(
        $walletEntity,
        $paymentEntity,
        EntityManagerInterface $entityManager,
        EventDispatcherInterface $eventDispatcher,
        RouterInterface $router,
        UserManagerInterface $userManager,
        TranslatorInterface $translator
    )
    {
        $this->walletEntity = $walletEntity;
        $this->paymentEntity = $paymentEntity;
        $this->entityManager = $entityManager;
        $this->eventDispatcher = $eventDispatcher;
        $this->router = $router;
        $this->userManager = $userManager;
        $this->translator = $translator;
    }

    /**
     * {@inheritdoc}
     */
    public function createWallet(UserInterface $user)
    {
        $wallet = new $this->walletEntity;
        $now = Carbon::now();
        $wallet->setUser($user)
            ->setCreatedAt($now)
            ->setUpdatedAt($now);
        return $wallet;
    }

    /**
     * {@inheritdoc}
     */
    public function saveWallet(WalletInterface $wallet)
    {
        $this->entityManager->persist($wallet);
        $this->entityManager->flush();
    }

    /**
     * {@inheritdoc}
     */
    public function addHistory(WalletInterface $wallet, WalletHistoryInterface $history)
    {
        $wallet->addHistory($history);
        //收入的话，增加钱包余额
        if ($history->isIncome()) {
            $wallet->setAmount(
                $wallet->getAmount() + $history->getAmount()
            );
        }
        $this->saveWallet($wallet);
    }

    /**
     * {@inheritdoc}
     */
    public function createHistory()
    {
        $history = new $this->paymentEntity;
        $now = Carbon::now();
        $history->setUpdatedAt($now)->setCreatedAt($now);
        return $history;
    }

    /**
     * {@inheritdoc}
     */
    public function getUserWallet(UserInterface $user)
    {
        $wallet = $this->getWalletRepository()->findOneBy([
            'user' => $user
        ]);
        if (!$wallet) {
            $wallet = $this->createWallet($user);
            $this->saveWallet($wallet);
        }
        $wallet->setUser($user);
        return $wallet;
    }

    /**
     * {@inheritdoc}
     */
    public function findUserWalletHistories(WalletInterface $wallet, $page, $limit = null)
    {
        $query = $this->getHistoryRepository()->createQueryBuilder('wh')
            ->where('wh.wallet = :wallet')->setParameter('wallet', $wallet)
            ->orderBy('wh.updatedAt', 'desc')
            ->getQuery();
        return $this->createPaginator($query, $page, $limit);
    }

    /**
     * {@inheritdoc}
     */
    public function withdraw(WalletInterface $wallet, $amount, $alipay)
    {
        $amount = intval($amount);
        if ($amount < PaymentInterface::WITHDRAW_MAX_AMOUNT) {
            throw new \LogicException($this->translator->trans('withdraw.amount_should_greater_than',
                PaymentInterface::WITHDRAW_MAX_AMOUNT)
            );
        }
        if ($wallet->getAmount() < $amount) {
            throw new \LogicException($this->translator->trans('withdraw.not_encough_balance'));
        }
        if (!$alipay) {
            throw new \LogicException($this->translator->trans('withdraw.need_provide_alipay'));
        }

        $history = $this->createHistory();
        $history->setType(PaymentInterface::TYPE_WITHDRAW)
            ->setAmount($amount)
            ->setDescription('')
            ->setStatus(PaymentInterface::STATUS_WAITING)
            ->setDescription($this->translator->trans('withdraw.to', ['%alipay%' => $alipay]))
            ->setUser($wallet->getUser());
        //钱包余额冻结
        $wallet->freeze($amount);
        $this->addHistory($wallet, $history);

        return $history;
    }

    /**
     * {@inheritdoc}
     */
    public function refuseWithdraw(WalletHistoryInterface $history, $reason = null)
    {
        $history->getWallet()->release($history->getAmount()); //钱包释放资本
        $history->setDescription(
            $history->getDescription() . "; " . $this->translator->trans('withdraw.decline_reason', [
                '%reason%' => $reason ?: $this->translator->trans('withdraw.no_reason')
            ])
        );
        $history->setStatus(PaymentInterface::STATUS_CLOSED);
        $this->entityManager->persist($history);
        $this->entityManager->flush();
        //提现拒绝之后
        $this->eventDispatcher->dispatch(PaymentEvent::WITHDRAW_DECLINED, new PaymentEvent($history));
    }

    /**
     * {@inheritdoc}
     */
    public function approveWithdraw(WalletHistoryInterface $history, $reason = null)
    {
        $wallet = $history->getWallet();
        $wallet->setFreezeAmount(
            $wallet->getFreezeAmount() - $history->getAmount()
        ); //减去冻结的金额
        $history->setDescription(
            $history->getDescription() . "; " . $this->translator->trans('withdraw.approve_reason', [
                '%reason%' => $reason ?: $this->translator->trans('withdraw.no_reason')
            ])
        );
        $history->setStatus(PaymentInterface::STATUS_OK);
        $this->entityManager->persist($history);
        $this->entityManager->flush();
        //提现
        $this->eventDispatcher->dispatch(PaymentEvent::WITHDRAW_APPROVED, new PaymentEvent($history));
    }

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

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

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

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


<?php

namespace PHPDish\Bundle\CoreBundle\Command;

use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\ConfirmationQuestion;
use Symfony\Component\Console\Style\SymfonyStyle;

class InstallDatabaseCommand extends AbstractInstallCommand
{
    /**
     * 需要执行的命令.
     *
     * @var array
     */
    protected $commands = [
        1 => [
            'command' => 'doctrine:database:create',
            'options' => [
                '--if-not-exists' => true,
                '--no-interaction' => true,
            ],
        ],
        3 => [
            'command' => 'doctrine:migrations:migrate',
            'options' => [
                '--no-interaction' => true,
            ],
        ],
//        5 => 'cache:clear',
    ];

    /**
     * {@inheritdoc}
     */
    protected function configure()
    {
        $this
            ->setName('phpdish:install:database')
            ->setDescription('Install PHPDish database.')
            ->setHelp(
                <<<EOT
The <info>%command.name%</info> command creates PHPDish database.
EOT
            );
    }

    /**
     * {@inheritdoc}
     */
    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $outputStyle = new SymfonyStyle($input, $output);
        $outputStyle->writeln('Creating PHPDish database.');

        //如果数据表存在，则询问是否重置
        if ($this->isDatabasePresent() && $this->isSchemaPresent()) {
            $outputStyle->writeln('It appears that your database already exists.');
            $outputStyle->writeln('<error>Warning! This action will erase your database.</error>');
            $questionHelper = $this->getHelper('question');
            $question = new ConfirmationQuestion('Would you like to reset it? (y/N) ', false);

            if ($questionHelper->ask($input, $output, $question)) {
                $this->commands[2] = [
                    'command' => 'doctrine:schema:drop',
                    'options' => [
                        '--force' => true,
                        '--full-database' => true,
                    ],
                ];
            }
        }

        //执行创建命令
        $this->createSchemaWithProgressbar($output);
        $outputStyle->newLine();
    }

    /**
     * 执行创建命令.
     *
     * @param OutputInterface $output
     */
    protected function createSchemaWithProgressbar(OutputInterface $output)
    {
        ksort($this->commands);
        $this->bulkRunCommands($this->commands, $output, true);
    }

    /**
     * 检查是否已经创建过数据库.
     *
     * @throws \Exception
     *
     * @return bool
     */
    protected function isDatabasePresent()
    {
        $connection = $this->getContainer()->get('doctrine.dbal.default_connection');
        $databaseName = $connection->getDatabase();

        try {
            return in_array($databaseName, $connection->getSchemaManager()->listDatabases());
        } catch (\Exception $exception) {
            $message = $exception->getMessage();
            $mysqlDatabaseError = false !== strpos($message, sprintf("Unknown database '%s'", $databaseName));
            $postgresDatabaseError = false !== strpos($message, sprintf('database "%s" does not exist', $databaseName));

            if ($mysqlDatabaseError || $postgresDatabaseError) {
                return false;
            }

            throw $exception;
        }
    }

    /**
     * 检查是否已经创建过表.
     *
     * @return bool
     */
    protected function isSchemaPresent()
    {
        $connection = $this->getContainer()->get('doctrine.dbal.default_connection');

        return count($connection->getSchemaManager()->listTables()) > 0;
    }
}


<?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;

use Symfony\Component\HttpKernel\Bundle\Bundle;

abstract class SimplePlugin extends Bundle implements SimplePluginInterface
{
    use PHPDishPluginTrait;

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

    /**
     * {@inheritdoc}
     */
    public function getServicesSource()
    {
        if (file_exists($this->getRootDir() . '/services.yml')) {
            return $this->getRootDir() . '/services.yml';
        }
        return false;
    }

    /**
     * {@inheritdoc}
     */
    public function getRouterResource()
    {
        if (file_exists($this->getRootDir() . '/routing.yml')) {
            return $this->getRootDir() . '/routing.yml';
        }
        return false;
    }

    /**
     * {@inheritdoc}
     */
    public function getRootDir()
    {
        if (null === $this->rootDir) {
            $r = new \ReflectionObject($this);
            $dir = $rootDir = dirname($r->getFileName());
            while (!file_exists($dir.'/composer.json')) {
                if ($dir === dirname($dir)) {
                    return $this->rootDir = $rootDir;
                }
                $dir = dirname($dir);
            }
            $this->rootDir = $dir;
        }

        return $this->rootDir;
    }
}


<?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 Doctrine\ORM\EntityRepository;
use PHPDish\Bundle\CoreBundle\Service\PaginatorTrait;
use PHPDish\Bundle\ForumBundle\Event\Events;
use PHPDish\Bundle\ForumBundle\Event\ReplyMentionUserEvent;
use PHPDish\Bundle\ForumBundle\Event\VoteReplyEvent;
use PHPDish\Bundle\ForumBundle\Model\ReplyInterface;
use PHPDish\Bundle\ForumBundle\Model\TopicInterface;
use PHPDish\Bundle\ResourceBundle\Service\ServiceManagerInterface;
use PHPDish\Bundle\UserBundle\Model\UserInterface;
use PHPDish\Bundle\CmsBundle\BodyProcessor\BodyProcessorInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;

class ReplyManager implements ReplyManagerInterface, ServiceManagerInterface
{
    use PaginatorTrait;
    /**
     * @var EntityManagerInterface
     */
    protected $entityManager;

    /**
     * @var BodyProcessorInterface
     */
    protected $bodyProcessor;

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

    protected $replyEntity;

    public function __construct(
        $replyEntity,
        EntityManagerInterface $entityManager,
        EventDispatcherInterface $eventDispatcher,
        BodyProcessorInterface $bodyProcessor
    ) {
        $this->replyEntity = $replyEntity;
        $this->entityManager = $entityManager;
        $this->eventDispatcher = $eventDispatcher;
        $this->bodyProcessor = $bodyProcessor;
    }

    /**
     * {@inheritdoc}
     */
    public function createReply(TopicInterface $topic, UserInterface $user = null)
    {
        $reply = new $this->replyEntity;
        $reply->setTopic($topic)
            ->setCreatedAt(Carbon::now());
        $user && $reply->setUser($user);

        return $reply;
    }

    /**
     * {@inheritdoc}
     */
    public function saveReply(ReplyInterface $reply)
    {
        $parsedBody = $this->bodyProcessor->process($reply->getOriginalBody());
        $reply->setUpdatedAt(Carbon::now())
            ->setBody($parsedBody);

        if ($new = !$reply->getId()) {
            $reply->getTopic()->addCommentCount(1)
                ->setLastCommentAt(Carbon::now())
                ->setLastCommentUser($reply->getUser());
        }

        $this->entityManager->persist($reply);
        $this->entityManager->flush();

        //如果是新生成的并且提及了用户则触发事件
        $mentionParser = $this->bodyProcessor->getMentionParser();
        $new && $mentionParser->getMentionedUsers() && $this->eventDispatcher->dispatch(Events::USER_MENTIONED_REPLY, new ReplyMentionUserEvent(
            $reply,
            $mentionParser->getMentionedUsers()
        ));

        return true;
    }

    /**
     * @return \Doctrine\ORM\QueryBuilder
     */
    protected function getRepliesQb()
    {
        return $this->getReplyRepository()->createQueryBuilder('r')
            ->addSelect('ru')
            ->join('r.user', 'ru');
    }

    /**
     * {@inheritdoc}
     */
    public function findReplies(Criteria $criteria)
    {
        return $this->getRepliesQb()
            ->addCriteria($criteria)
            ->getQuery()
            ->getResult();
    }

    /**
     * {@inheritdoc}
     */
    public function findRepliesPager(Criteria $criteria, $page, $limit = null)
    {
        $qb = $this->getRepliesQb()
            ->addCriteria($criteria);
        return $this->createPaginator($qb->getQuery(), $page, $limit);
    }

    /**
     * {@inheritdoc}
     */
    public function findTopicReplies(TopicInterface $topic, $page, $limit = null, Criteria $criteria = null)
    {
        $qb = $this->getRepliesQb()
            ->where('r.topic = :topic')->setParameter('topic', $topic)
            ->andWhere('r.enabled = :enabled')->setParameter('enabled', true);

        if ($criteria) {
            $qb->addCriteria($criteria);
        }
        return $this->createPaginator($qb->getQuery(), $page, $limit);
    }

    /**
     * {@inheritdoc}
     */
    public function findUserReplies(UserInterface $user, $page, $limit = null, Criteria $criteria = null)
    {
        $qb = $this->getReplyRepository()->createQueryBuilder('r')
            ->addSelect('rt')
            ->join('r.topic', 'rt')
            ->where('r.user = :user')->setParameter('user', $user)
            ->andWhere('r.enabled = :enabled')->setParameter('enabled', true)
            ->orderBy('r.createdAt', 'desc');

        if ($criteria) {
            $qb->addCriteria($criteria);
        }
        return $this->createPaginator($qb->getQuery(), $page, $limit);
    }

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

    /**
     * {@inheritdoc}
     */
    public function blockReply(ReplyInterface $reply)
    {
        $reply->disable();
        $topic = $reply->getTopic();
        $topic->addCommentCount(-1);
        $this->entityManager->persist($reply);
        $this->entityManager->flush();

        return true;
    }

    /**
     * {@inheritdoc}
     */
    public function replyTopic(UserInterface $user, TopicInterface $topic, $body)
    {
        $reply = $this->createReply($topic, $user);
        $reply->setOriginalBody($body);
        $this->saveReply($reply);
        return $reply;
    }

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

        //触发事件
        $event = new VoteReplyEvent($reply->getTopic(), $reply, $user);
        $this->eventDispatcher->dispatch(Events::REPLY_VOTED, $event);
    }

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

    /**
     * {@inheritdoc}
     */
    public function getReplyRepository(): EntityRepository
    {
        return $this->entityManager->getRepository($this->replyEntity);
    }

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


<?php

namespace PHPDish\Bundle\WebBundle\Controller;

use Pagerfanta\Pagerfanta;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;

class SearchController extends Controller
{
    const TYPE_POST = 'post';

    const TYPE_TOPIC = 'topic';

    const TYPE_USER = 'user';

    /**
     * 搜索页面
     *
     * @Route("/search", name="search")
     * @param Request $request
     * @return Response
     */
    public function searchAction(Request $request)
    {
        $keyword = $request->query->get('q');
        $type = $request->query->get('type', static::TYPE_TOPIC);
        $searchResult = $this->searcFrom($keyword, $type, [
            'length' => 10,
            'page' => $request->query->getInt('page', 1)
        ]);
        return $this->render('PHPDishWebBundle:Search:result.html.twig', [
            'searchResult' => $searchResult,
            'keyword' => $keyword
        ]);
    }

    /**
     * 从搜索服务获取搜索结果
     *
     * @param string $keyword
     * @param string $type
     * @param array $options
     * @return Pagerfanta
     */
    protected function searcFrom($keyword, $type, $options)
    {
        $searchService = $this->get('phpdish.searcher');

        if ($type === static::TYPE_TOPIC) {
            $searchResult = $searchService->queryTopics($keyword, $options);
        } elseif ($type === static::TYPE_USER) {
            $searchResult = $searchService->queryUsers($keyword, $options);
        } else {
            $searchResult = $searchService->queryPosts($keyword, $options);
        }

        return $searchResult;
    }
}


<?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\Namer;

use Carbon\Carbon;
use Gaufrette\Filesystem;
use PHPDish\Component\Media\ExtensionFinder;

class DatePathNamer implements NamerInterface
{
    /**
     * @var Filesystem
     */
    protected $filesystem;

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

    /**
     * {@inheritdoc}
     */
    public function transform(\SplFileInfo $file)
    {
        return $this->transformWithExtension($file->guessExtension());
    }

    /**
     * {@inheritdoc}
     */
    public function transformFromUrl($url)
    {
        $extension = ExtensionFinder::find($url);

        return $this->transformWithExtension(strrchr($url, $extension));
    }

    /**
     * {@inheritdoc}
     */
    public function transformWithExtension($extension)
    {
        $baseDir = Carbon::now()->format('Y/md');
        do {
            $path = "{$baseDir}/{$this->generateKey()}.".$extension;
        } while ($this->filesystem->has($path));

        return $path;
    }

    protected function generateKey()
    {
        return md5(uniqid((string) mt_rand(), true));
    }
}


<?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\DependencyInjection;

use PHPDish\Bundle\PaymentBundle\Model\Payment;
use PHPDish\Bundle\PaymentBundle\Model\PaymentInterface;
use PHPDish\Bundle\PaymentBundle\Model\Wallet;
use PHPDish\Bundle\PaymentBundle\Model\WalletInterface;
use PHPDish\Bundle\ResourceBundle\DependencyInjection\AbstractConfiguration;
use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;

class Configuration extends AbstractConfiguration implements ConfigurationInterface
{
    /**
     * {@inheritdoc}
     */
    public function getConfigTreeBuilder()
    {
        $treeBuilder = new TreeBuilder();
        $rootNode = $treeBuilder->root('phpdish_payment');

        $rootNode->children()
            ->arrayNode('youzan')
                ->children()
                    ->scalarNode('client_id')->cannotBeEmpty()->end()
                    ->scalarNode('client_secret')->cannotBeEmpty()->end()
                    ->scalarNode('kdt_id')->cannotBeEmpty()->end()
                ->end()
            ->end();

        $this->addResourcesSection($rootNode);
        $this->addTemplatesSection($rootNode);
        return $treeBuilder;
    }

    protected function addResourcesSection(ArrayNodeDefinition $rootNode)
    {
        $rootNode
            ->children()
                ->arrayNode('resources')
                    ->children()
                        ->arrayNode('payment')
                            ->children()
                                ->scalarNode('interface')->defaultValue(PaymentInterface::class)->cannotBeEmpty()->end()
                                ->scalarNode('model')->defaultValue(Payment::class)->cannotBeEmpty()->end()
                            ->end()
                        ->end()
                        ->arrayNode('wallet')
                            ->children()
                                ->scalarNode('interface')->defaultValue(WalletInterface::class)->cannotBeEmpty()->end()
                                ->scalarNode('model')->defaultValue(Wallet::class)->cannotBeEmpty()->end()
                            ->end()
                        ->end()
                    ->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\CmsBundle\Utility;

/**
 * Html Chunker
 */
class HtmlChunker
{
    /**
     * @var \HTMLPurifier
     */
    protected $HTMLPurifier;

    public function __construct(\HTMLPurifier $htmlPlurifier)
    {
        $this->HTMLPurifier = $htmlPlurifier;
    }

    /**
     * 无损截取html
     *
     * @param string $html
     * @param float $percent
     * @return string
     */
    public function chunk($html, $percent)
    {
        $totalLength = mb_strlen($html);
        $reservedLength = intval($totalLength * $percent);
        $chunkHtml = mb_substr($html, 0, $reservedLength, 'utf-8');
        $junkTagPattern = '#<img[^>]+$#';
        //如果有垃圾标签，则先补全垃圾标签
        if (preg_match($junkTagPattern, $chunkHtml)) {
            if (preg_match('#[^<]+(?=>)#U', mb_substr($html, $reservedLength), $matches)) {
                $chunkHtml .= $matches[0] . '>';
            }
        }
//        dump($chunkHtml);
//        dump($html);
        return $this->HTMLPurifier->purify($chunkHtml);
    }
}


<?php

namespace PHPDish\Bundle\PostBundle\Controller;

use FOS\RestBundle\Context\Context;
use PHPDish\Bundle\ResourceBundle\Controller\ResourceController;
use PHPDish\Bundle\PostBundle\Event\Events;
use PHPDish\Bundle\PostBundle\Event\PostCommentedEvent;
use PHPDish\Bundle\PostBundle\Form\Type\CommentType;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;

class CommentController extends ResourceController
{
    use \PHPDish\Bundle\UserBundle\Controller\ManagerTrait;
    use ManagerTrait;

    /**
     * 添加评论.
     *
     * @Route("/posts/{id}/comments", name="comment_add")
     * @Method("POST")
     *
     * @param int     $id
     * @param Request $request
     *
     * @return Response
     */
    public function addAction($id, Request $request)
    {
        $this->denyAccessUnlessGranted('IS_AUTHENTICATED_REMEMBERED');
        $post = $this->getPostManager()->findPostById($id);
        $comment = $this->getPostCommentManager()->createComment($post, $this->getUser());
        $form = $this->createForm(CommentType::class, $comment);
        $form->handleRequest($request);
        $view = $this->view();
        if ($form->isSubmitted() && $form->isValid()) {
            $this->getPostCommentManager()->saveComment($comment);

            //触发文章评论事件
            $this->get('event_dispatcher')->dispatch(Events::POST_COMMENTED, new PostCommentedEvent($post, $comment));

            $view->setData(['comment' => $comment])
                ->setStatusCode(static::HTTP_CREATED)
                ->setContext((new Context())->setGroups(['Default']));
        } else {
            $view->setData([ 'form' => $form])
                ->setStatusCode(static::HTTP_BAD_REQUEST);
        }

        return $this->handleView($view);
    }

    /**
     * 删除评论.
     *
     * @Route("/comments/{id}", name="comment_delete")
     * @Method("DELETE")
     *
     * @param int $id
     *
     * @return Response
     */
    public function deleteAction($id)
    {
        $manager = $this->getPostCommentManager();
        $comment = $manager->findCommentById($id);

        if (!$comment) {
            throw $this->createNotFoundException();
        }
        $this->denyAccessUnlessGranted('edit', $comment);

        $manager->blockComment($comment);

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

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

        $comment = $this->getPostCommentManager()->findCommentById($id);
        if (!$comment) {
            throw new \InvalidArgumentException($this->get('translator')->trans('comment.not_exists'));
        }
        if ($isVoted = $comment->isVotedBy($this->getUser())) {
            $this->getPostCommentManager()->removeVoter($comment, $this->getUser());
        } else {
            $this->getPostCommentManager()->addVoter($comment, $this->getUser());
        }
        return $this->json([
            'vote_count' => $comment->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\Grid;

use Doctrine\ORM\EntityManagerInterface;
use PHPDish\Bundle\AdminBundle\Grid\Column\Column;
use PHPDish\Bundle\AdminBundle\Grid\Column\ColumnInterface;
use PHPDish\Bundle\AdminBundle\Grid\Filter\InputFilter;
use PHPDish\Bundle\AdminBundle\Grid\Source\ORM;
use PHPDish\Bundle\AdminBundle\Grid\Source\SourceInterface;

class Factory
{
    /**
     * @var EntityManagerInterface
     */
    protected $entityManager;

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

    /**
     * 创建column
     *
     * @param string $name
     * @param string $type
     * @param array $options
     * @return ColumnInterface
     */
    public function createColumn($name, $type, $options = [])
    {
        $column = new Column($name, $type, $options['sortable'] ?? true);
        if ($options['filterable']) { //如果默认开启filter，则帮其生成一个默认的input filter
            $column->addFilter(new InputFilter(Operator::LIKE));
        }
        return $column;
    }

    /**
     * 创建 grid
     *
     * @param SourceInterface|string $source
     * @param string|null $alias
     * @return GridInterface
     */
    public function createGrid($source, $alias = null)
    {
        $source = $this->createSource($source, $alias);
        $grid = new Grid($source);
        $grid->setFactory($this);
        return $grid;
    }

    public function createSource($entity, $alias = null)
    {
        $source = new ORM($entity, $alias);
        $source->setEntityManager($this->entityManager);
        return $source;
    }
}


<?php

namespace PHPDish\Bundle\UserBundle\Twig;

use Doctrine\Common\Collections\Criteria;
use PHPDish\Bundle\UserBundle\Model\PointHistory;
use PHPDish\Bundle\UserBundle\Model\PointHistoryInterface;
use PHPDish\Bundle\UserBundle\Model\UserInterface;
use PHPDish\Bundle\UserBundle\Service\UserManagerInterface;

class UserExtension extends \Twig_Extension
{
    /**
     * @var UserManagerInterface
     */
    protected $userManager;

    public function __construct(UserManagerInterface $userManager)
    {
        $this->userManager = $userManager;
    }

    /**
     * {@inheritdoc}
     */
    public function getFunctions()
    {
        return [
            new \Twig_SimpleFunction('get_user', [$this->userManager, 'findUserById']),
            new \Twig_SimpleFunction('get_user_by_username', [$this->userManager, 'findUserByUsername']),
            new \Twig_SimpleFunction('get_user_by_email', [$this->userManager, 'findUserByEmail']),
            new \Twig_SimpleFunction('get_user_by_username_or_email', [$this->userManager, 'findUserByUsernameOrEmail']),
            new \Twig_SimpleFunction('get_users', [$this, 'getUsers']),
            new \Twig_SimpleFunction('get_users_pager', [$this->userManager, 'findUsersPager']),
            new \Twig_SimpleFunction('get_category_authors', [$this, 'getCategoryAuthors']),
        ];
    }

    /**
     * {@inheritdoc}
     */
    public function getFilters()
    {
        return [
            new \Twig_SimpleFilter('point_history_type', [$this, 'getPointHistoryLabel'])
        ];
    }

    /**
     * 获取专栏作者
     *
     * @param int $limit
     * @return UserInterface[]
     */
    public function getCategoryAuthors($limit = 10)
    {
        $qb = $this->userManager->getUserRepository()->createQueryBuilder('u');

        return $qb->select('distinct u')
            ->innerJoin('u.categories', 'c')
            ->where('c.postCount > :postCount')->setParameter('postCount', 0)
            ->setMaxResults($limit)
            ->orderBy('u.updatedAt', 'desc')
            ->getQuery()
            ->getResult();
    }

    /**
     * 指定条件查找用户
     *
     * @param array| $criteria
     * @param array $orderBy
     * @param int $limit
     * @return array
     */
    public function getUsers($criteria, array $orderBy = [], ?int $limit = null)
    {
        if ($criteria instanceof Criteria) {
            return $this->userManager->findUsersByCriteria($criteria);
        } else {
            return $this->userManager->getUserRepository()->findBy($criteria, $orderBy, $limit);
        }
    }

    /**
     * 获取积分历史类型
     *
     * @param PointHistoryInterface $history
     * @return string
     */
    public function getPointHistoryLabel(PointHistoryInterface $history)
    {
        static $labels = [
            PointHistory::TYPE_SIGN_IN => 'point.history.type.sign_in',
            PointHistory::TYPE_CHECK_IN => 'point.history.type.check_in',

            //资源
            PointHistory::TYPE_POST_TOPIC => 'point.history.type.post_topic',
            PointHistory::TYPE_POST_ARTICLE => 'point.history.type.post_article',
            // 资源被删除
            PointHistory::TYPE_REMOVE_TOPIC => 'point.history.type.remove_topic',
            PointHistory::TYPE_REMOVE_POST => 'point.history.type.remove_post',

            PointHistory::TYPE_POST_TOPIC_REPLY => 'point.history.type.post_topic_reply', //回复主题
            PointHistory::TYPE_TOPIC_REPLY => 'point.history.type.topic_reply', //主题被回复，

            PointHistory::TYPE_TOPIC_VOTED => 'point.history.type.topic_voted',
            PointHistory::TYPE_REPLY_VOTED => 'point.history.type.reply_voted',
            PointHistory::TYPE_AWARD => 'point.history.type.award',
        ];

        return $labels[$history->getType()] ?? 'point.history.type.unknown';
    }
}


<?php

namespace PHPDish\Bundle\CoreBundle\DataFixtures\ORM;

use Doctrine\Common\Persistence\ObjectManager;
use Doctrine\Bundle\FixturesBundle\Fixture;
use FOS\UserBundle\Model\UserManagerInterface;
use FOS\UserBundle\Util\UserManipulator;
use PHPDish\Bundle\ResourceBundle\AvatarGenerator\AvatarGeneratorInterface;

class UserFixtures extends Fixture
{
    /**
     * {@inheritdoc}
     */
    public function load(ObjectManager $manager)
    {
        $user1 = $this->getUserManipulator()->create('优雅的风', '123456', 'user1@phpdish.com', true, false);
        $user2 = $this->getUserManipulator()->create('风中的少年', '123456', 'user2@phpdish.com', true, false);

        $avatarGenerator = $this->getAvatarGenerator();
        $user1->setAvatar($avatarGenerator->generate('优雅的风')->getKey());
        $user2->setAvatar($avatarGenerator->generate('风中的少年')->getKey());

        $userManager = $this->getUserManager();
        $userManager->updateUser($user1);
        $userManager->updateUser($user2);
        $this->addReference('general-user', $user1);
    }

    /**
     * @return UserManagerInterface
     */
    protected function getUserManager()
    {
        return $this->container->get('fos_user.user_manager');
    }

    /**
     * @return UserManipulator
     */
    protected function getUserManipulator()
    {
        return $this->container->get('fos_user.util.user_manipulator');
    }

    /**
     * @return AvatarGeneratorInterface
     */
    protected function getAvatarGenerator()
    {
        return $this->container->get('phpdish.avatar_generator.user');
    }
}


<?php

namespace PHPDish\Bundle\CoreBundle\Service;

use Doctrine\ORM\Query;
use Pagerfanta\Pagerfanta;
use Pagerfanta\Adapter\DoctrineORMAdapter;

trait PaginatorTrait
{
    /**
     * @return int
     */
    public function getMaxPerPage()
    {
        return 10;
    }

    /**
     * 创建分页.
     *
     * @param Query    $query
     * @param int      $page
     * @param int|null $limit
     *
     * @return Pagerfanta
     */
    protected function createPaginator(Query $query, $page, $limit = null)
    {
        $paginator = new Pagerfanta(new DoctrineORMAdapter($query));
        $paginator->setCurrentPage($page);
        $paginator->setMaxPerPage($limit ?: $this->getMaxPerPage());

        return $paginator;
    }
}


<?php

namespace PHPDish\Bundle\CoreBundle\Command;

use RuntimeException;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

final class CheckRequirementsCommand extends AbstractInstallCommand
{
    /**
     * {@inheritdoc}
     */
    protected function configure()
    {
        $this
            ->setName('phpdish:install:check-requirements')
            ->setDescription('Checks if all PHPDish requirements are satisfied.')
            ->setHelp(
                <<<EOT
The <info>%command.name%</info> command checks system requirements.
EOT
            )
        ;
    }

    /**
     * {@inheritdoc}
     */
    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $checker = $this->getContainer()->get('phpdish.installer.requirements_checker');
        $checker->setOutput($output);
        $fulfilled = $checker->check($output);

        if (!$fulfilled) {
            throw new RuntimeException(
                'Some system requirements are not fulfilled. Please check output messages and fix them.'
            );
        }

        $output->writeln('<info>Success! Your system can run PHPDish properly.</info>');
    }
}


<?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 function GuzzleHttp\Psr7\stream_for;
use PHPDish\Component\Media\Model\File;
use PHPDish\Component\Media\Model\Image;
use PHPDish\Component\Media\Namer\NamerInterface;
use Symfony\Component\HttpFoundation\File\UploadedFile;

class FileFactory implements FileFactoryInterface
{
    /**
     * @var NamerInterface
     */
    protected $namer;

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

    /**
     * {@inheritdoc}
     */
    public function createFileFromUploadedFile(UploadedFile $uploadedFile)
    {
        $file = $this->isImage($uploadedFile) ? new Image() : new File();
        $file->setExtension($uploadedFile->guessExtension())
            ->setSize($uploadedFile->getSize())
            ->setContentType($uploadedFile->getMimeType())
            ->setContent(stream_for(fopen($uploadedFile->getRealPath(), 'r')))
            ->setKey($this->namer->transform($uploadedFile));

        return $file;
    }

    /**
     * 判断上传的文件是否是图片.
     *
     * @param UploadedFile $uploadedFile
     *
     * @return bool
     */
    public function isImage(UploadedFile $uploadedFile)
    {
        $contentType = $uploadedFile->getMimeType();

        return strpos($contentType, 'image') !== false;
    }

    /**
     * {@inheritdoc}
     */
    public function createFileByKey($key)
    {
        $file = new File($key);
        return $file;
    }

    /**
     * {@inheritdoc}
     */
    public function createFileWithExtension($extension)
    {
        $key = $this->namer->transformWithExtension($extension);
        return $this->createFileByKey($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\Model;

use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Money\Money;
use PHPDish\Bundle\ResourceBundle\Model\DateTimeTrait;
use PHPDish\Bundle\ResourceBundle\Model\IdentifiableTrait;
use PHPDish\Bundle\UserBundle\Model\UserAwareTrait;

class Wallet implements WalletInterface
{
    use IdentifiableTrait, UserAwareTrait, DateTimeTrait;

    /**
     * 余额，单位分
     * @var int
     */
    protected $amount = 0;

    /**
     * 冻结余额，单位分
     * @var int
     */
    protected $freezeAmount = 0;

    /**
     * @var WalletHistoryInterface[]|Collection
     */
    protected $histories;

    public function __construct()
    {
        $this->histories = new ArrayCollection();
    }

    /**
     * @return int
     */
    public function getAmount()
    {
        return $this->amount;
    }

    /**
     * @param int $amount
     * @return Wallet
     */
    public function setAmount($amount)
    {
        $this->amount = $amount;

        return $this;
    }

    /**
     * @return int
     */
    public function getFreezeAmount()
    {
        return $this->freezeAmount;
    }

    /**
     * @param int $freezeAmount
     * @return Wallet
     */
    public function setFreezeAmount($freezeAmount)
    {
        $this->freezeAmount = $freezeAmount;

        return $this;
    }

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

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

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function addHistory(WalletHistoryInterface $history)
    {
        $history->setWallet($this);
        $this->histories[] = $history;
    }

    /**
     * {@inheritdoc}
     */
    public function getPrice()
    {
        return Money::CNY($this->amount);
    }

    /**
     * {@inheritdoc}
     */
    public function getFreezePrice()
    {
        return Money::CNY($this->freezeAmount);
    }

    /**
     * {@inheritdoc}
     */
    public function freeze($amount)
    {
        if ($this->amount < $amount) {
            throw new \LogicException('Not enough balance');
        }
        $this->amount -= $amount;
        $this->freezeAmount += $amount;
        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function release($amount)
    {
        if ($this->freezeAmount < $amount) {
            throw new \LogicException('Not enough frozen balance');
        }
        $this->amount += $amount;
        $this->freezeAmount -= $amount;
        return $this;
    }
}


<?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\CacheWarmer;

use PHPDish\Bundle\ThemeBundle\Theming\ThemeManagerInterface;
use Symfony\Component\Filesystem\Filesystem;
use Symfony\Bundle\FrameworkBundle\CacheWarmer\TemplateFinderInterface;
use Symfony\Bundle\FrameworkBundle\CacheWarmer\TemplatePathsCacheWarmer as BaseTemplatePathsCacheWarmer;
use Symfony\Bundle\FrameworkBundle\Templating\Loader\TemplateLocator;
use Symfony\Component\Templating\TemplateReferenceInterface;

class TemplatePathsCacheWarmer extends BaseTemplatePathsCacheWarmer
{

    protected $themeManager;

    public function __construct(
        ThemeManagerInterface $themeManager,
        TemplateFinderInterface $finder,
        TemplateLocator $locator
    ) {
        parent::__construct($finder, $locator);
        $this->themeManager = $themeManager;
    }

    /**
     * {@inheritdoc}
     */
    public function warmUp($cacheDir)
    {

        /** @var TemplateReferenceInterface[] $allTemplates */
        $allTemplates = $this->finder->findAllTemplates();
        $filesystem = new Filesystem();
        $templates = array();

        // 不用主题
        $themes = $this->themeManager->getThemes();
        array_unshift($themes, null);
        foreach ($themes as $theme) {
            $keySuffix = $theme ? '|' . $theme->getName() : '';
            foreach ($allTemplates as $template) {
                $this->themeManager->setCurrentTheme($theme); //覆盖当前主题
                $key = $template->getLogicalName() . $keySuffix;
                $templates[$key]
                    = rtrim($filesystem->makePathRelative($this->locator->locate($template), $cacheDir), '/');
            }
        }

        $templates = str_replace("' => '", "' => __DIR__.'/", var_export($templates, true));
        $this->writeCacheFile($cacheDir.'/templates.php', sprintf("<?php return %s;\n", $templates));
    }

    /**
     * {@inheritdoc}
     */
    public function isOptional()
    {
        return true;
    }
}


<?php

namespace PHPDish\Bundle\CoreBundle\Command\Proxy;

use Sylius\Bundle\ThemeBundle\Command\ListCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

class ListThemeCommand extends Command
{
    /**
     * @var ListCommand
     */
    protected $rawCommand;

    /**
     * {@inheritdoc}
     */
    public function configure()
    {
        $this->setName('phpdish:theme:list')
            ->setDescription('Shows list of detected themes.');
    }

    /**
     * {@inheritdoc}
     */
    public function execute(InputInterface $input, OutputInterface $output)
    {
        $this->rawCommand = $this->getApplication()->get('sylius:theme:list');
        $arguments = array(
            'command' => 'sylius:theme:list',
        );
        $greetInput = new ArrayInput($arguments);
        $this->rawCommand->run($greetInput, $output);
    }

    /**
     * {@inheritdoc}
     */
    public function getAliases()
    {
        return ['theme:list'];
    }
}


<?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\Service;

use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityRepository;
use PHPDish\Bundle\CoreBundle\Service\PaginatorTrait;
use PHPDish\Bundle\ResourceBundle\Service\ServiceManagerInterface;
use PHPDish\Bundle\UserBundle\Model\PointHistoryInterface;
use PHPDish\Bundle\UserBundle\Model\UserInterface;

class PointManager implements PointManagerInterface, ServiceManagerInterface
{
    use PaginatorTrait;

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

    protected $pointHistoryEntity;

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

    /**
     * @return EntityRepository
     */
    public function getPointHistoryRepository()
    {
        return $this->entityManager->getRepository($this->pointHistoryEntity);
    }

    /**
     * {@inheritdoc}
     */
    public function createPointHistory(UserInterface $user, $amount, $type = null)
    {
        $history = new $this->pointHistoryEntity;
        $history->setUser($user)
            ->setAmount($amount)
            ->setType($type);
        return $history;
    }

    /**
     * {@inheritdoc}
     */
    public function savePointHistory(PointHistoryInterface $history)
    {
        $this->entityManager->persist($history);
        $this->entityManager->flush();
    }

    /**
     * {@inheritdoc}
     */
    public function findPointHistories(UserInterface $user, $page, $limit = null)
    {
        $query = $this->getPointHistoryRepository()->createQueryBuilder('p')
            ->where('p.user = :user')->setParameter('user', $user)
            ->getQuery();

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

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


<?php

use PHPDish\Bundle\CoreBundle\Application\Kernel;
use Symfony\Component\Config\Loader\LoaderInterface;

class AppKernel extends Kernel
{
    /**
     * {@inheritdoc}
     */
    public function registerBundles()
    {
        $bundles = [
            new EasyCorp\Bundle\EasyAdminBundle\EasyAdminBundle(), //正式版将会废弃
            new Sonata\AdminBundle\SonataAdminBundle(),
            new Sonata\DoctrineORMAdminBundle\SonataDoctrineORMAdminBundle(),
            new PHPDish\Bundle\AdminBundle\PHPDishAdminBundle(),
            new PHPDish\Bundle\WebBundle\PHPDishWebBundle(),
        ];

        // 注册插件
        $plugins = [

        ];

        return array_merge(parent::registerBundles(), $bundles, $plugins);
    }

    /**
     * {@inheritdoc}
     */
    public function getRootDir()
    {
        return __DIR__;
    }

    /**
     * {@inheritdoc}
     */
    public function getCacheDir()
    {
        return dirname(__DIR__).'/var/cache/'.$this->getEnvironment();
    }

    /**
     * {@inheritdoc}
     */
    public function getLogDir()
    {
        return dirname(__DIR__).'/var/logs';
    }

    /**
     * {@inheritdoc}
     */
    public function registerContainerConfiguration(LoaderInterface $loader)
    {
        $loader->load($this->getRootDir().'/config/config_'.$this->getEnvironment().'.yml');
    }
}


<?php

declare(strict_types=1);

namespace PHPDish\Bundle\CoreBundle\Theme\Context;

use Sylius\Bundle\ThemeBundle\Context\ThemeContextInterface;
use Sylius\Bundle\ThemeBundle\Model\ThemeInterface;
use Sylius\Bundle\ThemeBundle\Repository\ThemeRepositoryInterface;

class GenericThemeContext implements ThemeContextInterface
{
    /**
     * @var ThemeRepositoryInterface
     */
    protected $themeRepository;

    /**
     * 当前主题名
     * @var string
     */
    protected $currentThemeName;

    public function __construct(ThemeRepositoryInterface $themeRepository, ?string $currentThemeName)
    {
        $this->themeRepository = $themeRepository;
        $this->currentThemeName = $currentThemeName;
    }

    /**
     * {@inheritdoc}
     */
    public function getTheme(): ?ThemeInterface
    {
        return $this->currentThemeName ?
            $this->themeRepository->findOneByName($this->currentThemeName) :
            null;
    }
}


<?php

namespace PHPDish\Bundle\PostBundle\Form\Type;

use PHPDish\Bundle\PostBundle\Service\CategoryManagerInterface;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;

class PostType extends AbstractType
{
    /**
     * @var CategoryManagerInterface
     */
    protected $categoryManager;

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

    /**
     * {@inheritdoc}
     */
    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setRequired('user');
    }

    /**
     * {@inheritdoc}
     */
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('title', TextType::class, [
                'label' => 'form.post.title',
            ])
            ->add('category', EntityType::class, [
                'class' => 'PHPDishPostBundle:Category',
                'choice_label' => 'name',
                'choices' => $this->getCurrentUserCategories($options),
            ])
            ->add('originalBody', TextareaType::class, [
                'label' => 'form.post.body',
            ]);
    }

    /**
     * 获取当前用户的专栏.
     *
     * @return \PHPDish\Bundle\PostBundle\Model\CategoryInterface[]
     */
    protected function getCurrentUserCategories($options)
    {
        if (!isset($options['user'])) {
            throw new \InvalidArgumentException('You musts provide the option "user"');
        }

        return $this->categoryManager->findUserCategories($options['user']);
    }
}


<?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;

class CachedPluginFinder implements PluginFinderInterface
{
    /**
     * @var PluginFinderInterface
     */
    protected $decoratedFinder;

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

    /**
     * @var boolean
     */
    protected $cached;

    /**
     * @var array
     */
    protected $plugins = [];

    public function __construct($cacheFile, PluginFinderInterface $finder)
    {
        if (file_exists($cacheFile)) {
            $this->cached = true;
            $this->cache = include_once $cacheFile;
        } else {
            $this->cached = false;
        }
        $this->decoratedFinder = $finder;
    }

    /**
     * {@inheritdoc}
     */
    public function findAll()
    {
        if ($this->plugins) {
            return $this->plugins;
        }

        if ($this->cached) {
            foreach ($this->cache as $pluginItem) {
                if (class_exists($pluginItem['class'])) {
                    $this->plugins[] = new $pluginItem['class'];
                }
            }
        } else {
            $this->plugins = $this->decoratedFinder->findAll();
        }
        return $this->plugins;
    }
}


<?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\DependencyInjection\Compiler;

use PHPDish\Bundle\ThemeBundle\Twig\FilesystemLoader;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;

class ThemePass implements CompilerPassInterface
{
    /**
     * {@inheritdoc}
     */
    public function process(ContainerBuilder $container)
    {
        //覆盖目标版定位器
        $container->setAlias('templating.locator', 'phpdish_theme.templating_locator');
        //覆盖文件路径缓存预热器
        $container->setAlias('templating.cache_warmer.template_paths', 'phpdish_theme.templating.cache_warmer.template_paths');
        //覆盖twig的loader
        $container->findDefinition('twig.loader.filesystem')
            ->setClass(FilesystemLoader::class)
            ->replaceArgument(0, new Reference('phpdish_theme.templating_locator'))
            ->replaceArgument(1, new Reference('phpdish_theme.templating.name_parser'))
            ->addMethodCall('setThemeManager', [new Reference('phpdish_theme.theme_manager')]);
    }
}


<?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\NotificationBundle\Controller;

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

class NotificationController extends ResourceController
{
    /**
     * @var NotificationManagerInterface
     */
    protected $notificationManager;

    public function __construct(ResourceConfigurationInterface $configuration, NotificationManagerInterface $notificationManager)
    {
        parent::__construct($configuration);
        $this->notificationManager = $notificationManager;
    }

    /**
     * 当前用户的通知.
     *
     * @Route("/notifications", name="notifications")
     *
     * @param Request $request
     *
     * @return Response
     */
    public function index(Request $request)
    {
        $this->denyAccessUnlessGranted('IS_AUTHENTICATED_REMEMBERED');
        $meta = $this->notificationManager->findNotificationMetadataPager($this->getUser(),null,
            $request->query->getInt('page', 1)
        );
        //将当前页置为已读
        $this->notificationManager->markAsSeen($meta->getCurrentPageResults());
        return $this->render($this->configuration->getTemplate('Notification:index.html.twig'), [
            'notificationMeta' => $meta,
        ]);
    }
}


<?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\Model;

use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Criteria;
use PHPDish\Bundle\CmsBundle\Model\AbstractPost;
use PHPDish\Bundle\ResourceBundle\Model\IdentifiableTrait;
use PHPDish\Bundle\ResourceBundle\Model\TreeTrait;

class Post extends AbstractPost implements ChapterInterface
{
    use IdentifiableTrait,
        TreeTrait;

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

    /**
     * @var bool
     */
    protected $recommended = false;

    /**
     * @var CategoryInterface
     */
    protected $category;

    /**
     * Constructor.
     */
    public function __construct()
    {
        $this->children = new ArrayCollection();
    }

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

        return $this;
    }

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

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

    /**
     * {@inheritdoc}
     */
    public function setCategory(CategoryInterface $category)
    {
        $this->category = $category;

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function getChildren()
    {
        return $this->children->matching(Criteria::create()->where(Criteria::expr()->eq('enabled', true)));
    }

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

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


<?php

namespace PHPDish\Bundle\CoreBundle\DependencyInjection;

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

/**
 * This is the class that validates and merges configuration from your app/config files.
 *
 * To learn more see {@link http://symfony.com/doc/current/cookbook/bundles/configuration.html}
 */
class Configuration implements ConfigurationInterface
{
    /**
     * {@inheritdoc}
     */
    public function getConfigTreeBuilder()
    {
        $treeBuilder = new TreeBuilder();
        $rootNode = $treeBuilder->root('phpdish_core');

        $this->addAssetsSection($rootNode);

        return $treeBuilder;
    }

    protected function addAssetsSection(ArrayNodeDefinition $rootNode)
    {
        $rootNode
            ->children()
                ->arrayNode('assets')
                    ->isRequired()
                    ->children()
                        // 管理员的资源设置
                        ->arrayNode('admin')
                            ->children()
                                ->scalarNode('cdn_host')->defaultNull()->end()
                            ->end()
                        ->end()
                        // 前端的资源设置
                        ->arrayNode('web')
                            ->children()
                                ->scalarNode('cdn_host')->defaultNull()->end()
                            ->end()
                        ->end()
                    ->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\ResourceBundle;

use PHPDish\Bundle\ResourceBundle\Controller\ResourceConfigurationInterface;
use PHPDish\Bundle\ResourceBundle\DependencyInjection\Compiler\DoctrineTargetEntitiesResolverPass;
use PHPDish\Bundle\ResourceBundle\DependencyInjection\Compiler\InjectResourceConfigurationPass;
use PHPDish\Bundle\ResourceBundle\DependencyInjection\Compiler\InjectServiceManager;
use PHPDish\Bundle\ResourceBundle\DependencyInjection\Compiler\RegisterResourcePass;
use PHPDish\Bundle\ResourceBundle\DependencyInjection\PHPDishResourceExtension;
use Symfony\Component\DependencyInjection\Compiler\PassConfig;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpKernel\Bundle\Bundle;

class PHPDishResourceBundle extends Bundle
{

    /**
     * {@inheritdoc}
     */
    public function build(ContainerBuilder $container)
    {
        parent::build($container);
        $container->addCompilerPass(new DoctrineTargetEntitiesResolverPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, 1);
        $container->addCompilerPass(new RegisterResourcePass());
        $container->addCompilerPass(new InjectResourceConfigurationPass());
        $container->addCompilerPass(new InjectServiceManager());
    }

    /**
     * {@inheritdoc}
     */
    public function getContainerExtension()
    {
        if (null === $this->extension) {
            $this->extension = new PHPDishResourceExtension();
        }
        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\UserBundle\Security\Core\Encoder;

use Symfony\Component\Security\Core\Encoder\MessageDigestPasswordEncoder;
use Symfony\Component\Security\Core\Encoder\PasswordEncoderInterface;

class GenericPasswordEncoder implements PasswordEncoderInterface
{
    /**
     * 简单加密，MD5
     * @var string
     */
    const DIFFICULTY_SIMPLE = 'simple';

    /**
     * 高级加密
     * @var string
     */
    const DIFFICULTY_STRONG = 'strong';

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

    /**
     * @var MessageDigestPasswordEncoder
     */
    protected $messageDigestPasswordEncoder;

    public function __construct($difficulty)
    {
        $this->difficulty = $difficulty;
        $this->messageDigestPasswordEncoder = static::createEncoder($this->difficulty);
    }

    /**
     * {@inheritdoc}
     */
    public function encodePassword($raw, $salt)
    {
        if ($this->difficulty === static::DIFFICULTY_SIMPLE) {
            $salt = null;
        }
        return $this->messageDigestPasswordEncoder->encodePassword($raw, $salt);
    }

    /**
     * {@inheritdoc}
     */
    public function isPasswordValid($encoded, $raw, $salt)
    {
        if ($this->difficulty === static::DIFFICULTY_SIMPLE) {
            $salt = null;
        }
        return $this->messageDigestPasswordEncoder->isPasswordValid($encoded, $raw, $salt);
    }

    protected static function createEncoder($difficulty)
    {
        if ($difficulty === static::DIFFICULTY_SIMPLE) {
            $encoder = new MessageDigestPasswordEncoder('md5', false, 0);
        } else {
            $encoder = new MessageDigestPasswordEncoder('sha512', true, 2);
        }
        return $encoder;
    }
}


<?php

namespace PHPDish\Bundle\CoreBundle\DataFixtures\ORM;

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

class PostFixtures extends Fixture
{
    /**
     * {@inheritdoc}
     */
    public function load(ObjectManager $manager)
    {
        $manager = $this->getCategoryManager();
        $post = $manager->createPost($this->getReference('general-user'));

        $body = <<<EOT
此次基准测试只是简单测算一下node与php在冒泡排序方面的时间损耗情况，基本思想是使用冒泡排序各自运算100次之后求出平均值；冒泡排序算法摘自网上，测试代码如下：

## 代码

JavaScript:

```javascript
function sort(arr){
    var n=arr.length; //获取数组的长度，即有n个数在排序
    var temp=null; //定义一个临时变量，交换数据用
    for(var i=0; i<n-1; i++){ //外层循环n-1次
        for(var j=0; j<n-1-i; j++){ //内层每次循环n-1-i次，每次循环完，都能从剩下的数当中找出一个最大的放在n-1-i的位置
            if(arr[j]>arr[j+1]){ //如果a[j]>a[j+1]则交换位置
                temp=arr[j];
                arr[j]=arr[j+1];
                arr[j+1]=temp;
            }
        }
    }
    return arr; //返回排好序的数组
}

const array = [49, 38, 65, 97, 76, 13, 27,49, 38, 65, 97, 76, 13, 27];
const startTime = new Date();
for (let i = 0; i <= 99; i++) {
    const arr = sort(array);
}
console.log((new Date() - startTime) / 1000  / 100);
```

PHP:

```php
function bubble_sort(\$array)
{
    \$count = count(\$array);
    if (\$count <= 0) return false;
    for (\$i = 0; \$i < \$count; \$i++) {
        for (\$j = \$count - 1; \$j > \$i; \$j--) {
            //如果后一个元素小于前一个，则调换位置
            if (\$array[\$j] < \$array[\$j - 1]) {
                \$tmp = \$array[\$j];
                \$array[\$j] = \$array[\$j - 1];
                \$array[\$j - 1] = \$tmp;
            }
        }
    }
    return \$array;
}

\$array = [49, 38, 65, 97, 76, 13, 27,49, 38, 65, 97, 76, 13, 27];
\$startTime = microtime(true);
for (\$i = 0; \$i <= 99; \$i++) {
    \$arr = bubble_sort(\$array);
}
echo number_format((microtime(true) - \$startTime) / 100, 10);
```
注意：为了测试php5并没有采用php7的强调语法类型

## 测试结果

如图：

PHP5.6
![clipboard.png](https://sfault-image.b0.upaiyun.com/149/676/1496767840-59c89c789d4b7_articlex)

Node7.9:

![clipboard.png](https://sfault-image.b0.upaiyun.com/249/881/2498819097-59c89cb282dee_articlex)

PHP7.1

![clipboard.png](https://sfault-image.b0.upaiyun.com/378/208/3782083225-59c89d1b4f96b_articlex)

## 结论

三次测算的结果分别是：

| 环境 | 时间(s)|
| --- | --- |
|php5.6 | 0.0000207901|
|node7.9 | 0.0005099999999999999|
|php7.1 | 0.0000101209|

测试机器

![clipboard.png](https://sfault-image.b0.upaiyun.com/311/584/3115849489-59c89dbd5178b_articlex)

从本次测试的结果来看在执行速度上 PHP7.1 > PHP5.6 > Node7.9;
EOT;

        $post->setCategory($this->getReference('general-category'))
            ->setTitle('一个简单的NodeJs与PHP的benchmark')
            ->setOriginalBody($body);

        $manager->savePost($post);
    }

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

    /**
     * {@inheritdoc}
     */
    public function getDependencies()
    {
        return [
            UserFixtures::class,
            CategoryFixtures::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\CoreBundle\EventListener;

use Doctrine\ORM\Event\LifecycleEventArgs;
use PHPDish\Bundle\ForumBundle\Model\TopicInterface;
use PHPDish\Bundle\ForumBundle\Service\TopicManagerInterface;
use PHPDish\Bundle\UserBundle\Model\UserInterface;
use PHPDish\Bundle\UserBundle\Service\UserManagerInterface;

final class TopicListener
{
    /**
     * @var TopicManagerInterface
     */
    protected $topicManager;

    /**
     * @var UserManagerInterface
     */
    protected $userManager;

    public function __construct(
        TopicManagerInterface $topicManager,
        UserManagerInterface $userManager
    )
    {
        $this->topicManager = $topicManager;
        $this->userManager = $userManager;
    }

    /**
     * 生成新数据之前
     * @param TopicInterface $topic
     * @param LifecycleEventArgs $event
     */
    public function postPersist(TopicInterface $topic, LifecycleEventArgs $event)
    {
        $user = $topic->getUser();
        $this->handleUserTopicCount($user);
    }

    /**
     * 处理用户的话题数量
     * @param UserInterface $user
     */
    protected function handleUserTopicCount(UserInterface $user)
    {
        $count = $this->topicManager->getUserTopicCount($user);
        $user->setTopicCount($count);
        $this->userManager->saveUser($user);
    }
}


<?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 Carbon\Carbon;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\EntityManagerInterface;
use PHPDish\Bundle\PostBundle\Model\BookInterface;
use PHPDish\Bundle\PostBundle\Model\ChapterInterface;
use PHPDish\Bundle\UserBundle\Model\UserInterface;

class BookManager implements BookManagerInterface
{
    /**
     * @var EntityManagerInterface
     */
    protected $entityManager;

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

    /**
     * @var CategoryManagerInterface
     */
    protected $categoryManager;

    public function __construct(
        EntityManagerInterface $entityManager,
        PostManagerInterface $postManager,
        CategoryManagerInterface $categoryManager
    ) {
        $this->entityManager = $entityManager;
        $this->postManager = $postManager;
        $this->categoryManager = $categoryManager;
        //增加树形处理能力
        $entityManager->getConfiguration()
            ->addCustomHydrationMode('tree', 'Gedmo\Tree\Hydrator\ORM\TreeObjectHydrator');
    }

    /**
     * {@inheritdoc}
     */
    public function findBook($slug)
    {
        $category = $this->categoryManager->findCategoryBySlug($slug);
        if (!$category || !$category->isBook()) {
            throw new \InvalidArgumentException('The book is not exists');
        }
        return $category;
    }

    /**
     * {@inheritdoc}
     */
    public function findBookChaptersTree(BookInterface $book)
    {
        $repo = $this->postManager->getPostRepository();

        return $repo->createQueryBuilder('p')->where('p.category = :book')
            ->andWhere('p.enabled = :enabled')->setParameter('enabled', true)
            ->setParameter('book', $book)
            ->orderBy('p.level, p.left', 'ASC')
            ->getQuery()
            ->setHint(\Doctrine\ORM\Query::HINT_INCLUDE_META_COLUMNS, true)
            ->getResult('tree');
    }

    /**
     * {@inheritdoc}
     */
    public function findUserBooks(UserInterface $user)
    {
        $qb = $this->createGetUserBooksQueryBuilder($user);
        return $qb->getQuery()->getResult();
    }

    /**
     * {@inheritdoc}
     */
    public function getUserBookNumber(UserInterface $user)
    {
        $qb = $this->createGetUserBooksQueryBuilder($user);

        return (int)$qb->select($qb->expr()->count('c'))
            ->getQuery()
            ->getSingleScalarResult();
    }

    protected function createGetUserBooksQueryBuilder($user)
    {
        return $this->categoryManager->createGetUserCategoriesQueryBuilder($user)
            ->andWhere('c.isBook = :isBook')->setParameter('isBook', true);
    }

    /**
     * {@inheritdoc}
     */
    public function createBook(UserInterface $user)
    {
        $book = $this->categoryManager->createCategory($user);
        $book->asBook();
        return $book;
    }

    /**
     * {@inheritdoc}
     */
    public function saveBook(BookInterface $book)
    {
        $this->categoryManager->saveCategory($book);
    }

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

    /**
     * {@inheritdoc}
     */
    public function addBookChapter(BookInterface $book, $chapter)
    {
        if (is_string($chapter)) {
            $title = $chapter;
            $chapter = $this->postManager->createPost($book->getCreator());
            $chapter->setTitle($title);
        }
        $chapter->setUpdatedAt(Carbon::now())
            ->setCategory($book);
        $this->postManager->savePost($chapter);
        return $chapter;
    }

    /**
     * {@inheritdoc}
     */
    public function moveBookChapter(BookInterface $book, ChapterInterface $chapter, $direction, $step)
    {
        $func = $direction === static::MOVE_DIRECTION_UP ? 'moveUp' : 'moveDown';

        $this->postManager->getPostRepository()->$func($chapter, $step);

//        $this->entityManager->clear();
    }
}


<?php

namespace PHPDish\Bundle\UserBundle\Menu;

use Knp\Menu\FactoryInterface;
use PHPDish\Bundle\UserBundle\Event\Events;
use PHPDish\Bundle\WebBundle\Event\FilterMenuEvent;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;

final class SettingMenuBuilder
{
    /**
     * @var FactoryInterface
     */
    private $factory;

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

    public function __construct(FactoryInterface $factory, EventDispatcherInterface $eventDispatcher)
    {
        $this->factory = $factory;
        $this->eventDispatcher = $eventDispatcher;
    }

    /**
     * 创建用户的左侧菜单.
     *
     * @param array $options
     *
     * @return \Knp\Menu\ItemInterface
     */
    public function createMenu(array $options = [])
    {
        $menu = $this->factory->createItem('root');
        $menu->setChildrenAttribute('class', 'list-group vertical-menu');

        $menu->addChild('Profile', [
                'label' => 'menu.profile',
                'route' => 'setting_profile',
            ])
            ->setAttribute('class', 'list-group-item if i-envelope-o');

        $menu->addChild('Change Password', [
                'label' => 'menu.change_password',
                'route' => 'setting_change_password',
            ])
            ->setAttribute('class', 'list-group-item if i-password');

        $menu->addChild('Social Binding', [
                'label' => 'menu.social_binding',
                'route' => 'setting_social_binding',
            ])
            ->setAttribute('class', 'list-group-item if i-sync');

        //触发事件
        $this->eventDispatcher->dispatch(Events::SETTING_MENU_BUILT, new FilterMenuEvent($menu));
        return $menu;
    }
}


<?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\Cache;

use PHPDish\Bundle\CoreBundle\Application\Plugin\Finder\PluginFinderInterface;
use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmer;

class PluginPathsCacheWarmer extends CacheWarmer
{
    /**
     * @var PluginFinderInterface
     */
    protected $pluginFinder;

    public function __construct(PluginFinderInterface $pluginFinder)
    {
        $this->pluginFinder = $pluginFinder;
    }

    /**
     * {@inheritdoc}
     */
    public function warmUp($cacheDir)
    {
        $plugins = $this->pluginFinder->findAll();
        $processed = [];
        foreach ($plugins as $plugin) {
            $processed[] = [
                'class' => get_class($plugin),
                'path' => $plugin->getRootDir()
            ];
        }
        $var = var_export($processed, true);
        $export = <<<EOT
<?php
return $var;
EOT;

        $this->writeCacheFile($cacheDir . '/phpdish_plugins.php', $export);
    }

    /**
     * {@inheritdoc}
     */
    public function isOptional()
    {
        return true;
    }
}


<?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\Model;

use Carbon\Carbon;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use FOS\MessageBundle\Model\ParticipantInterface;
use PHPDish\Bundle\ResourceBundle\Model\DateTimeTrait;
use FOS\UserBundle\Model\User as BaseUser;

class User extends BaseUser implements UserInterface, ParticipantInterface
{
    use DateTimeTrait;

    /**
     * @var int
     */
    protected $gender = 0;

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

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

    /**
     * @var string
     */
    protected $followingCount = 0;

    /**
     * @var int
     */
    protected $followerCount = 0;

    /**
     * 个人资料.
     *
     * @var ProfileInterface[]|Collection
     */
    protected $profile;

    /**
     * 关注我的用户.
     *
     * @var ArrayCollection|UserInterface[]
     */
    protected $followers;

    /**
     * 我关注的用户.
     *
     * @var ArrayCollection|UserInterface[]
     */
    protected $following;

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

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

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

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

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

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

    /**
     * @var int
     */
    protected $point = 0;

    /**
     * @var PointHistoryInterface[]|Collection
     */
    protected $pointHistories;

    public function __construct()
    {
        parent::__construct();
        //关注我的
        $this->followers = new ArrayCollection();
        //我关注的
        $this->following = new ArrayCollection();
        //创建时间
        $this->setCreatedAt(Carbon::now());
        //更新时间
        $this->setUpdatedAt(Carbon::now());
    }

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

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

    /**
     * Set gender.
     *
     * @param int $gender
     *
     * @return User
     */
    public function setGender($gender)
    {
        $this->gender = $gender;

        return $this;
    }

    /**
     * Get gender.
     *
     * @return int
     */
    public function getGender()
    {
        return $this->gender;
    }

    /**
     * {@inheritdoc}
     */
    public function isWomen()
    {
        return $this->gender == static::GENDER_WOMEN;
    }

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

        return $this;
    }

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

    /**
     * {@inheritdoc}
     */
    public function setFollowerCount($count)
    {
        $this->followerCount = $count;

        return $this;
    }

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

    /**
     * 检查用户是否被某个用户关注.
     *
     * @param UserInterface $user
     *
     * @return bool
     */
    public function isFollowedBy(UserInterface $user)
    {
        return $this->followers->contains($user);
    }

    /**
     * Set description.
     *
     * @param string $description
     *
     * @return User
     */
    public function setDescription($description)
    {
        $this->description = $description;

        return $this;
    }

    /**
     * Get description.
     *
     * @return string
     */
    public function getDescription()
    {
        return $this->description;
    }

    /**
     * {@inheritdoc}
     */
    public function addFollower(UserInterface $follower)
    {
        $this->followers[] = $follower;

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function removeFollower(UserInterface $follower)
    {
        $this->followers->removeElement($follower);
    }

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

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

    /**
     * {@inheritdoc}
     */
    public function getAvatar()
    {
        return $this->avatar ?: 'user1.jpg';
    }

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

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function enable()
    {
        $this->setEnabled(true);
    }

    /**
     * {@inheritdoc}
     */
    public function disable()
    {
        $this->setEnabled(false);
    }

    /**
     * {@inheritdoc}
     */
    public function getProfile()
    {
        return $this->profile->first();
    }

    /**
     * {@inheritdoc}
     */
    public function setProfile(ProfileInterface $profile)
    {
        $this->profile->clear();
        $profile->setUser($this);
        $this->profile[] = $profile;

        return $this;
    }

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

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

        return $this;
    }

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

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

        return $this;
    }

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

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

        return $this;
    }

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

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

        return $this;
    }

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

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

        return $this;
    }

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

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

        return $this;
    }

    /**
     * @return int
     */
    public function getPoint()
    {
        return $this->point;
    }

    /**
     * @param int $point
     * @return User
     */
    public function addPoint(int $point)
    {
        $this->point += $point;
        $this->point = max($this->point, 0);
        return $this;
    }
}


<?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\Controller;

use PHPDish\Bundle\CoreBundle\Model\User;
use Symfony\Component\Routing\Annotation\Route;

class UserController extends AdminController
{
    /**
     * 用户管理列表
     *
     * @Route("/users", name="admin_user_index")
     */
    public function indexAction()
    {
        $this->breadcrumb->push('admin.user.index');
        $grid = $this->gridFactory->get(User::class);
        return $this->render('PHPDishAdminBundle:User:index.html.twig', [
            'grid' => $grid
        ]);
    }

    /**
     * 用户详情
     *
     * @Route("/users/show", name="admin_user_show")
     */
    public function showAction()
    {
    }
}