<?php

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

namespace PHPDish\Bundle\MediaBundle\DependencyInjection;

use Symfony\Component\DependencyInjection\ChildDefinition;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
use Symfony\Component\DependencyInjection\Loader;

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

        //加载服务配置
        $loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
        $loader->load('services.yml');

        //process imagine resolver
        $imagineResolver = $container->findDefinition('phpdish_media.imagine.resolver');
        $imagineResolver->replaceArgument('$webRootDir', $config['imagine_resolver']['web_root']);
        $imagineResolver->replaceArgument('$cachePrefix', $config['imagine_resolver']['cache_prefix']);
        $imagineResolver->replaceArgument('$baseUrl', $config['imagine_resolver']['cdn_host']);

        //process twig extension
        $container->findDefinition('phpdish_media.twig_extension')->replaceArgument(1, $config['imagine_resolver']['cdn_host']);
        // process media mapping
        $this->processMediaMapping($config, $container);

        //process oneup uploader
        $this->processOneupUploader($config, $container);
    }

    protected function processMediaMapping($config, ContainerBuilder $container)
    {
        $defaultAlias = empty($config['default_mapping']) ? key($config['mappings']) : $config['default_mapping'];

        foreach ($config['mappings'] as $alias => $mappingConfig) {
            $filesystemId = $mappingConfig['filesystem_service'];

            $fileManagerDefinition = new ChildDefinition('phpdish_media.file_manager.private');
            $fileManagerDefinition->replaceArgument(0, new Reference($filesystemId));
            $container->setDefinition('phpdish_media.file_manager.'.$alias, $fileManagerDefinition);

            //自定义配置namer
            if (!empty($mappingConfig['namer'])) {
                $container->setAlias('phpdish_media.file_namer.'.$alias, $mappingConfig['namer']);
            } else {
                $fileNamerDefinition = new ChildDefinition('phpdish_media.file_namer.private');
                $fileNamerDefinition->replaceArgument(0, new Reference($filesystemId));
                $container->setDefinition('phpdish_media.file_namer.'.$alias, $fileNamerDefinition);
            }

            $urlBuilderDefinition = new ChildDefinition('phpdish_media.url_builder.private');
            $urlBuilderDefinition->replaceArgument(1, $mappingConfig['base_url']);
            $container->setDefinition('phpdish_media.url_builder.'.$alias, $urlBuilderDefinition);

            $fileFactoryDefinition = new ChildDefinition('phpdish_media.file_factory.private');
            $fileFactoryDefinition->replaceArgument(0, new Reference('phpdish_media.file_namer.'.$alias));
            $container->setDefinition('phpdish_media.file_factory.'.$alias, $fileFactoryDefinition);

            $fileUploaderDefinition = new ChildDefinition('phpdish_media.file_uploader.private');
            $fileUploaderDefinition->replaceArgument(0, new Reference('phpdish_media.file_factory.'.$alias));
            $fileUploaderDefinition->replaceArgument(1, new Reference('phpdish_media.file_manager.'.$alias));
            $container->setDefinition('phpdish_media.file_uploader.'.$alias, $fileUploaderDefinition);

            $fileDownloaderDefinition = new ChildDefinition('phpdish_media.file_downloader.private');
            $fileDownloaderDefinition->replaceArgument(1, new Reference('phpdish_media.file_manager.'.$alias));
            $fileDownloaderDefinition->replaceArgument(2, new Reference('phpdish_media.file_factory.'.$alias));
            $container->setDefinition('phpdish_media.file_downloader.'.$alias, $fileDownloaderDefinition);
        }
        //为添加别名
        $container->addAliases([
            'phpdish_media.file_manager' => 'phpdish_media.file_manager.'.$defaultAlias,
            'phpdish_media.file_namer' => 'phpdish_media.file_namer.'.$defaultAlias,
            'phpdish_media.url_builder' => 'phpdish_media.url_builder.'.$defaultAlias,
            'phpdish_media.file_factory' => 'phpdish_media.file_factory.'.$defaultAlias,
            'phpdish_media.file_uploader' => 'phpdish_media.file_uploader.'.$defaultAlias,
            'phpdish_media.file_downloader' => 'phpdish_media.file_downloader.'.$defaultAlias,
        ]);
    }

    protected function processOneupUploader($config, ContainerBuilder $container)
    {
        foreach ($config['mappings'] as $alias => $mappingConfig) {
            $filesystemId = $mappingConfig['filesystem_service'];

            $definition = new ChildDefinition('phpdish_media.oneup_file_namer.private');
            $definition->replaceArgument(0, new Reference($filesystemId));
            $container->setDefinition('phpdish_media.oneup_file_namer.'.$alias, $definition);
        }
    }

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


<?php

namespace PHPDish\Bundle\CoreBundle\DataFixtures\ORM;

use Doctrine\Bundle\FixturesBundle\Fixture;
use Doctrine\Common\Persistence\ObjectManager;
use PHPDish\Bundle\WebBundle\Entity\FriendLink;
use PHPDish\Bundle\WebBundle\Service\FriendLinkManagerInterface;

class FriendLinkFixtures extends Fixture
{
    /**
     * {@inheritdoc}
     */
    public function load(ObjectManager $manager)
    {
        $friendLink = new FriendLink();
        $friendLink->setName('PHPDish')->setUrl('https://www.phpdish.com');
        $this->getFriendLinkManager()->saveFriendLink($friendLink);
    }

    public function getFriendLinkManager()
    {
        return $this->container->get('phpdish.manager.friend_link');
    }
}


<?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\Model\ThemeInterface;
use PHPDish\Bundle\ThemeBundle\Theming\ThemeManagerInterface;
use Symfony\Bundle\FrameworkBundle\CacheWarmer\TemplateFinder as BaseTemplateFinder;
use Symfony\Component\Finder\Finder;
use Symfony\Component\HttpKernel\Bundle\BundleInterface;
use Symfony\Component\HttpKernel\KernelInterface;
use Symfony\Component\Templating\TemplateNameParserInterface;
use Symfony\Component\Templating\TemplateReferenceInterface;

class TemplateFinder extends BaseTemplateFinder
{

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

    /**
     * @var KernelInterface
     */
    private $kernel;
    /**
     * @var TemplateNameParserInterface
     */
    private $parser;
    /**
     * @var string
     */
    private $rootDir;
    /**
     * @var array
     */
    private $templates;

    protected $themeManager;

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

    public function __construct(
        ThemeManagerInterface $themeManager,
        KernelInterface $kernel,
        TemplateNameParserInterface $parser,
        string $rootDir,
        ?string $themeDir = null
    )
    {
        parent::__construct($kernel, $parser, $rootDir);
        $this->themeManager = $themeManager;
        $this->kernel = $kernel;
        $this->parser = $parser;
        $this->rootDir = $rootDir;
        $this->themeDir =$themeDir;
    }

    /**
     * Find all the templates in the bundle and in the kernel Resources folder.
     *
     * @return TemplateReferenceInterface[]
     */
    public function findAllTemplates()
    {
        if (null !== $this->templates) {
            return $this->templates;
        }
        //All themes
        $this->themes = $this->themeManager->getThemes();

        $templates = array();

        foreach ($this->kernel->getBundles() as $bundle) {
            $templates = array_merge($templates, $this->findTemplatesInBundle($bundle));
        }

        $templates = array_merge($templates, $this->findTemplatesInFolder($this->rootDir.'/views'));

        return $this->templates = $templates;
    }

    /**
     * Find templates in the given bundle.
     *
     * @param BundleInterface $bundle The bundle where to look for templates
     *
     * @return TemplateReferenceInterface[]
     */
    private function findTemplatesInBundle(BundleInterface $bundle)
    {
        $name = $bundle->getName();
        $templates = array_merge(
            $this->findTemplatesInFolder($bundle->getPath().'/Resources/views'),
            $this->findTemplatesInFolder($this->rootDir.'/'.$name.'/views')
        );
        //追踪主题下的模板
        foreach ($this->themes as $theme) {
            $templates = array_merge($templates,
                $this->findTemplatesInFolder($this->themeDir.'/'.$name.'/views')
            );
        }
        $templates = array_unique($templates);

        foreach ($templates as $i => $template) {
            $templates[$i] = $template->set('bundle', $name);
        }

        return $templates;
    }

    /**
     * Find templates in the given directory.
     *
     * @param string $dir The folder where to look for templates
     *
     * @return TemplateReferenceInterface[]
     */
    private function findTemplatesInFolder($dir)
    {
        $templates = array();

        if (is_dir($dir)) {
            $finder = new Finder();
            foreach ($finder->files()->followLinks()->in($dir) as $file) {
                $template = $this->parser->parse($file->getRelativePathname());
                if (false !== $template) {
                    $templates[] = $template;
                }
            }
        }

        return $templates;
    }
}


<?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\ResourceBundle\DependencyInjection\AbstractExtension;
use Slince\YouzanPay\ApiContext;
use Symfony\Component\DependencyInjection\ChildDefinition;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
use Symfony\Component\DependencyInjection\Loader;

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

        $loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
        $loader->load('services.yml');

        if (isset($config['youzan'])) {
            $container->setDefinition('phpdish.payment_gateway.youzan.api_context', new Definition(ApiContext::class, [
                $config['youzan']['client_id'],
                $config['youzan']['client_secret'],
                $config['youzan']['kdt_id']
            ]));
            if ($container->hasDefinition('phpdish.payment_gateway.youzan')) {
                $youzanPayDef = $container->getDefinition('phpdish.payment_gateway.youzan');
                $youzanPayDef->replaceArgument(0, new Reference('phpdish.payment_gateway.youzan.api_context'));
            }
        }

        //注册到资源管理
        $this->registerResources($config['resources'], $container);
        $this->registerResourcesConfiguration($config['resources'], $config['templates'], $config['templates_namespace'], $container);
    }

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


<?php

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

namespace PHPDish\Bundle\PostBundle\EventListener;

use PHPDish\Bundle\PostBundle\Event\Events;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use PHPDish\Bundle\PostBundle\Event\CategoryPersistEvent;
use PHPDish\Bundle\ResourceBundle\AvatarGenerator\AvatarGeneratorInterface;

final class CategorySubscriber implements EventSubscriberInterface
{
    /**
     * @var AvatarGeneratorInterface
     */
    protected $avatarGenerator;

    public function __construct(
        AvatarGeneratorInterface $avatarGenerator
    ) {
        $this->avatarGenerator = $avatarGenerator;
    }

    /**
     * 专栏创建之前被触发
     * @param CategoryPersistEvent $event
     */
    public function onCategoryPersist(CategoryPersistEvent $event)
    {
        $category = $event->getCategory();
        $cover = $category->getCover();
        if (!$cover || $cover === '/avatar/user1.jpg') {
            $avatar = $this->avatarGenerator->generate($category->getName());
            $category->setCover($avatar->getKey());
        }
    }
    /**
     * {@inheritdoc}
     */
    public static function getSubscribedEvents()
    {
        return [
            Events::CATEGORY_PRE_PERSIST => 'onCategoryPersist'
        ];
    }
}


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

use PHPDish\Bundle\UserBundle\Service\UserManagerInterface;
use PHPDish\Component\Mention\AdapterInterface;
use Symfony\Component\Routing\RouterInterface;

class Adapter implements AdapterInterface
{
    /**
     * @var UserManagerInterface
     */
    protected $userManager;

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

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

    /**
     * {@inheritdoc}
     */
    public function findUsers($names)
    {
        return $this->userManager->findUsersByNames($names);
    }

    /**
     * {@inheritdoc}
     */
    public function createUserLink($user)
    {
        return sprintf('@%s ', $this->router->generate('user_view', [
            'username' => $user->getUsername(),
        ]), $user->getUsername(), $user->getUsername());
    }
}


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

use FOS\RestBundle\Context\Context;
use JMS\Serializer\SerializationContext;
use PHPDish\Bundle\ResourceBundle\Controller\ResourceConfigurationInterface;
use PHPDish\Bundle\ResourceBundle\Controller\ResourceController;
use PHPDish\Bundle\PaymentBundle\Form\Type\PaymentType;
use PHPDish\Bundle\PaymentBundle\Service\PaymentManagerInterface;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;

class PaymentController extends ResourceController
{
    use ManagerTrait;

    /**
     * @var PaymentManagerInterface
     */
    protected $paymentManager;

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

    /**
     * 创建交易
     * @Route("/payments", name="payment_add")
     * @Method("POST")
     * @param Request $request
     * @return Response
     */
    public function createAction(Request $request)
    {
        $this->denyAccessUnlessGranted('IS_AUTHENTICATED_REMEMBERED');
        $manager = $this->getPaymentManager();
        $payment = $manager->createPayment($this->getUser());
        $form = $this->createForm(PaymentType::class, $payment);
        $form->handleRequest($request);
        if ($form->isValid() && $form->isSubmitted()) {
            $manager->savePayment($payment);

            $qrCode = $manager->charge($payment);

            return $this->json([
                'payment' => $payment,
                'qrcode' => $qrCode
            ]);
        }
        throw new \InvalidArgumentException('Invalid request');
    }

    /**
     * 检查支付结果
     * @Route("/payments/result", name="payment_result")
     * @param Request $request
     * @return Response
     */
    public function getPaymentResultAction(Request $request)
    {
        $qrId = $request->query->get('qr_id');
        if (!$qrId) {
            throw new \InvalidArgumentException("Bad Request");
        }
        $result = $this->get('phpdish.payment_gateway.youzan')->checkQRStatus($qrId);
        if ($result) { //如果支付成功，则通知
            $this->getPaymentManager()->notifyPayment($qrId);
        }
        return $this->json([
            'result' => $result
        ]);
    }

    /**
     * 处理消息推送
     * @Route("/payments/notify", name="payment_notify")
     * @param Request $request
     * @return Response
     */
    public function notifyAction(Request $request)
    {
        $youZanPay = $this->get('phpdish.payment_gateway.youzan');
        try {
            $result = $youZanPay->verifyWebhook($request);
            $trade = $youZanPay->getTrade($result['id']);
            if ($trade) { //如果支付成功，则通知
                $this->getPaymentManager()->notifyPayment($trade->getQrId());
                $return = [
                    'result' => 'success'
                ];
            } else {
                $return = [
                    'result' => 'error',
                ];
            }
        } catch (\InvalidArgumentException $exception) {
            $return = [
                'result' => 'error',
                'message' => $exception->getMessage()
            ];
        }
        $this->get('monolog.logger.notes')->debug(print_r($return, true));
        $this->get('monolog.logger.notes')->debug($request->getContent());
        return $this->json($return);
    }

    /**
     * 处理消息推送
     * @Route("/wallet/withdraw", name="wallet_withdraw")
     * @param Request $request
     * @return Response
     */
    public function withdrawAction(Request $request)
    {
        $this->denyAccessUnlessGranted('IS_AUTHENTICATED_REMEMBERED');
        $walletManager = $this->getWalletManager();
        $wallet = $walletManager->getUserWallet($this->getUser());
        $history = $walletManager->withdraw($wallet,
            $request->request->get('amount'),
            $request->request->get('alipay_account')
        );
        $view = $this->view([
            'history' => $history
        ])->setContext((new Context())->setGroups(['Default']));
        return $this->handleView($view);
    }

    /**
     * 处理消息推送
     * @Route("/wallet", name="user_wallet")
     * @param Request $request
     * @return Response
     */
    public function getWalletAction(Request $request)
    {
        $this->denyAccessUnlessGranted('IS_AUTHENTICATED_REMEMBERED');
        $wallet = $this->getWalletManager()->getUserWallet($this->getUser());
        $histories = $this->getWalletManager()->findUserWalletHistories($wallet,
            $request->query->getInt('page', 1)
        );
        return $this->render($this->configuration->getTemplate('Wallet:index.html.twig'), [
            'wallet' => $wallet,
            'histories' => $histories
        ]);
    }

    /**
     * 检查支付结果
     * @Route("/payments/test", name="payment_test")
     * @return Response
     */
    public function testAction()
    {
        $this->getPaymentManager()->notifyPayment('6410820');
        return $this->json([
            'result' => 'success'
        ]);
    }
}


<?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\Collection;
use Doctrine\Common\Collections\Criteria;
use Money\Money;
use PHPDish\Bundle\CmsBundle\Model\AbstractTaxonomy;
use PHPDish\Bundle\ResourceBundle\Model\IdentifiableTrait;
use PHPDish\Bundle\UserBundle\Model\UserInterface;

class Category extends AbstractTaxonomy implements BookInterface
{
    use IdentifiableTrait;

    protected $cover;

    protected $recommended = false;

    protected $followerCount = 0;

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

    /**
     * @var PostInterface[]|Collection
     */
    protected $posts;

    /**
     * 订阅者.
     */
    protected $followers;

    /**
     * 创建人.
     */
    protected $creator;

    /**
     * 管理员.
     */
    protected $managers;

    /**
     * @var boolean
     */
    protected $isBook = false;

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

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

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

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

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

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

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

    /**
     * {@inheritdoc}
     */
    public function setCreator(UserInterface $creator)
    {
        $this->creator = $creator;

        return $this;
    }

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

    /**
     * {@inheritdoc}
     */
    public function addManager(UserInterface $user)
    {
        $this->managers[] = $user;

        return $this;
    }

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

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

    /**
     * {@inheritdoc}
     */
    public function setFollowers(array $followers)
    {
        $this->followers = $followers;

        return $this;
    }

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

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

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

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function isFollowedBy(UserInterface $user)
    {
        return $this->followers->contains($user);
    }

    /**
     * {@inheritdoc}
     */
    public function getSummary()
    {
        return $this->getPosts()->matching(Criteria::create()
            ->where(Criteria::expr()->isNull('parent'))
            ->andWhere(Criteria::expr()->eq('enabled', true))
        );
    }

    /**
     * {@inheritdoc}
     */
    public function isBelongsTo(UserInterface $user)
    {
        return $this->creator === $user;
    }

    /**
     * {@inheritdoc}
     */
    public function asBook()
    {
        $this->isBook = true;
    }

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

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

    public function getChargePrice()
    {
        return Money::CNY($this->charge);
    }

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

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function isCharging()
    {
        return $this->charge > 0;
    }
}


<?php

namespace PHPDish\Bundle\PostBundle\Controller;

use Doctrine\Common\Collections\Criteria;
use PHPDish\Bundle\ResourceBundle\Controller\ResourceController;
use PHPDish\Bundle\PostBundle\Event\CategoryCreateEvent;
use PHPDish\Bundle\PostBundle\Event\Events;
use PHPDish\Bundle\PostBundle\Form\Type\CategoryType;
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;
use Symfony\Component\Translation\TranslatorInterface;

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

    /**
     * @Route("/categories/new", name="category_add")
     *
     * @param Request $request
     *
     * @return Response
     */
    public function createAction(Request $request)
    {
        $this->denyAccessUnlessGranted('IS_AUTHENTICATED_REMEMBERED');
        $manager = $this->getCategoryManager();

        $category = $manager->createCategory($this->getUser());
        $form = $this->createForm(CategoryType::class, $category);
        $form->handleRequest($request);

        /**@var TranslatorInterface*/
        $translator = $this->get('translator');
        if (($number = $manager->getUserCategoriesNumber($this->getUser())) >= 2) {
            $this->addFlash('danger', $translator->trans('category.more_than_2_categories', [
                '%count%' => $number
            ]));
        }

        if ($form->isSubmitted() && $form->isValid()) {

            //触发专栏创建事件
            $event = new CategoryCreateEvent($category);
            $this->get('event_dispatcher')->dispatch(Events::CATEGORY_PRE_CREATED, $event);

            if ($event->isCreationAborted()) {
                $this->addFlash('success', $translator->trans('category.add_error'));
            } else {
                $manager->saveCategory($category);
                $this->addFlash('success', $translator->trans('category.add_success'));

                return $this->redirectToRoute('category_view', [
                    'slug' => $category->getSlug(),
                ]);
            }
        }

        return $this->render($this->configuration->getTemplate('Category:create.html.twig'), [
            'form' => $form->createView(),
            'hasManyCategories' => $number >= 2,
            'isBook' => false
        ]);
    }

    /**
     * 专栏详情.
     *
     * @Route("/categories/{slug}", name="category_view")
     *
     * @param string  $slug
     * @param Request $request
     *
     * @return Response
     */
    public function viewAction($slug, Request $request)
    {
        $category = $this->getCategoryManager()->findCategoryBySlug($slug);
        $criteria = Criteria::create()->where(Criteria::expr()->eq('category', $category->getId()))
            ->andWhere(Criteria::expr()->eq('enabled', true));
        if ($request->query->get('orderby') === 'hot') {
            $criteria->orderBy([
                'viewCount' => 'desc',
                'createdAt' => 'desc',
            ]);
        } else {
            $criteria->orderBy([
                'createdAt' => 'desc',
            ]);
        }
        $posts = $this->getPostManager()->findPostsPager($criteria, $request->query->getInt('page', 1));

        return $this->render($this->configuration->getTemplate('Category:view.html.twig'), [
            'category' => $category,
            'posts' => $posts,
        ]);
    }

    /**
     * 编辑专栏信息.
     *
     * @Route("/categories/{slug}/edit", name="category_edit")
     *
     * @param string  $slug
     * @param Request $request
     *
     * @return Response
     */
    public function editAction($slug, Request $request)
    {
        $manager = $this->getCategoryManager();
        $category = $manager->findCategoryBySlug($slug);
        if (!$category) {
            throw $this->createNotFoundException();
        }
        $this->denyAccessUnlessGranted('edit', $category);
        $form = $this->createForm(CategoryType::class, $category);
        $form->handleRequest($request);
        if ($form->isValid() && $form->isSubmitted()) {
            $manager->saveCategory($category);
            $this->addFlash('success', $this->get('translator')->trans('category.edit_success'));

            return $this->redirectToRoute('category_view', [
                'slug' => $category->getSlug(),
            ]);
        }

        return $this->render($this->configuration->getTemplate('Category:create.html.twig'), [
            'form' => $form->createView(),
            'category' => $category,
            'hasManyCategories' => false,
            'isBook' => false
        ]);
    }

    /**
     * 专栏的关注者.
     *
     * @Route("/categories/{slug}/followers", name="category_followers")
     * @Method("GET")
     *
     * @param string  $slug
     * @param Request $request
     *
     * @return Response
     */
    public function getFollowersAction($slug, Request $request)
    {
        $category = $this->getCategoryManager()->findCategoryBySlug($slug);
        $users = $this->getUserManager()->findCategoryFollowers($category, $request->query->getInt('page', 1));

        return $this->render($this->configuration->getTemplate('Category:followers.html.twig'), [
            'category' => $category,
            'users' => $users,
        ]);
    }

    /**
     * 关注话题.
     *
     * @Route("/categories/{slug}/followers", name="category_follow")
     * @Method("POST")
     *
     * @param string $slug
     *
     * @return Response
     */
    public function followAction($slug)
    {
        $this->denyAccessUnlessGranted('IS_AUTHENTICATED_REMEMBERED');
        $category = $this->getCategoryManager()->findCategoryBySlug($slug);

        if ($category->isCharging() && !$category->isBelongsTo($this->getUser())) { //收费专栏/电子书
            $qrCode = $this->getCategoryManager()->payForCategory($category, $this->getUser());
            $view = $this->view([
                'require_payment' => true,
                'message' => $this->get('translator')->trans('category.need_payment'),
                'qrcode' => $qrCode
            ]);
        } else {
            $this->getCategoryManager()->followCategory($category, $this->getUser());
            $view = $this->view([
                'follower_count' => $category->getFollowerCount(),
            ]);
        }

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

    /**
     * 取消关注话题.
     *
     * @Route("/categories/{slug}/followers", name="category_unfollow")
     * @Method("DELETE")
     *
     * @param string $slug
     *
     * @return Response
     */
    public function unFollowAction($slug)
    {
        $this->denyAccessUnlessGranted('IS_AUTHENTICATED_REMEMBERED');
        $category = $this->getCategoryManager()->findCategoryBySlug($slug);
        $this->getCategoryManager()->unFollowCategory($category, $this->getUser());
        $view = $this->view([
            'follower_count' => $category->getFollowerCount(),
        ]);

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

    /**
     * 获取用户的专栏.
     *
     * @param UserInterface $user
     *
     * @return Response
     */
    public function userCategoriesAction(UserInterface $user)
    {
        $categories = $this->getCategoryManager()->findUserCategories($user);

        return $this->render($this->configuration->getTemplate('Category:user_categories.html.twig'), [
            'categories' => $categories,
            '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\EventListener\Notification;

use PHPDish\Bundle\CoreBundle\Util\NotificationHelper;
use PHPDish\Bundle\ForumBundle\Event\Events;
use PHPDish\Bundle\ForumBundle\Event\ReplyMentionUserEvent;
use PHPDish\Bundle\ForumBundle\Event\ReplyTopicEvent;
use PHPDish\Bundle\ForumBundle\Event\VoteReplyEvent;
use PHPDish\Bundle\ForumBundle\Event\VoteTopicEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;

final class TopicSubscriber implements EventSubscriberInterface
{
    /**
     * @var NotificationHelper
     */
    protected $notificationHelper;

    public function __construct(NotificationHelper $notificationHelper)
    {
        $this->notificationHelper = $notificationHelper;
    }

    /**
     * {@inheritdoc}
     */
    public static function getSubscribedEvents()
    {
        return [
            Events::TOPIC_REPLIED => 'onTopicReplied',
            Events::TOPIC_VOTED => 'onTopicVoted',
            Events::REPLY_VOTED => 'onReplyVoted',
            Events::USER_MENTIONED_REPLY => 'onUserMentioned'
        ];
    }

    /**
     * 话题被回复时触发.
     *
     * @param ReplyTopicEvent $event
     */
    public function onTopicReplied(ReplyTopicEvent $event)
    {
        if ($event->getTopic()->getUser() === $event->getReply()->getUser()) {
            return;
        }
        $notification = $this->notificationHelper->createReplyTopicNotification($event->getTopic(), $event->getReply());

        $this->notificationHelper->sendNotification($event->getTopic()->getUser(), $notification);
    }

    /**
     * 话题被点赞时触发.
     *
     * @param VoteTopicEvent $event
     */
    public function onTopicVoted(VoteTopicEvent $event)
    {
        //自己点赞自己不用通知
        if ($event->getTopic()->getUser() === $event->getVoter()) {
            return;
        }
        $notification = $this->notificationHelper->createVoteTopicNotification($event->getTopic(), $event->getVoter());

        $this->notificationHelper->sendNotification($event->getTopic()->getUser(), $notification);
    }

    /**
     * 回复被点赞时触发.
     *
     * @param VoteReplyEvent $event
     */
    public function onReplyVoted(VoteReplyEvent $event)
    {
        //自己点赞自己不用通知
        if ($event->getReply()->getUser() === $event->getVoter()) {
            return;
        }
        $notification = $this->notificationHelper->createVoteReplyNotification(
            $event->getTopic(),
            $event->getReply(),
            $event->getVoter()
        );

        $this->notificationHelper->sendNotification($event->getReply()->getUser(), $notification);
    }

    /**
     * 当用户在话题回复中被提及.
     *
     * @param ReplyMentionUserEvent $event
     */
    public function onUserMentioned(ReplyMentionUserEvent $event)
    {
        //不能艾特自己, 不能艾特楼主，楼主本身就会收到消息
        $author = $event->getReply()->getUser();
        $maintainer = $event->getReply()->getTopic()->getUser();

        $participants = array_filter($event->getMentionedUsers(), function($user) use ($author, $maintainer){
            return $user !== $author && $user !== $maintainer;
        });
        $notification = $this->notificationHelper->createMentionUserInTopicNotification($event->getReply());

        $this->notificationHelper->sendNotification($participants, $notification);
    }

}


<?php

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

namespace PHPDish\Bundle\ThemeBundle\Theming;

class GenericThemeContext implements ThemeContextInterface
{
    /**
     * @var string
     */
    protected $currentThemeName;

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

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

    /**
     * @return string
     */
    public function getCurrentThemeName()
    {
        return $this->currentThemeName;
    }

    /**
     * {@inheritdoc}
     */
    public function getTheme()
    {
        if ($this->currentThemeName) {
            foreach ($this->themeFinder->find() as $theme) {
                if ($theme->getName() === $this->currentThemeName) {
                    return $theme;
                }
            }
            throw new \InvalidArgumentException(sprintf('The theme "%s" is not found', $this->currentThemeName));
        }
        return null;
    }
}


<?php

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

namespace PHPDish\Bundle\ThemeBundle\DependencyInjection;

use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;

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

        //polyfill to parameters
        $container->setParameter('phpdish_theme.theme_dir', $config['theme_directory']);

        $loader = new YamlFileLoader($container, new FileLocator(__DIR__ . '/../Resources/config'));
        $loader->load('theme.yml');
        $loader->load('templating.yml');

        //替换当前主题为
        if ($config['current_theme']) {
            $container->findDefinition('phpdish_theme.theme_context')->replaceArgument(0, $config['current_theme']);
        }

        //Theme finder
        $container->findDefinition('phpdish_theme.theme_finder')
            ->replaceArgument(0, $config['configuration_filename'])
            ->replaceArgument(1, $config['configuration_type'])
            ->replaceArgument(2, $config['theme_directory']);

        //Theme manager
        $container->findDefinition('phpdish_theme.theme_manager')
            ->replaceArgument(2, $config['namespaces']);
    }

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


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

use Doctrine\Common\Collections\Collection;
use PHPDish\Bundle\ResourceBundle\Model\DateTimeTrait;
use PHPDish\Bundle\ResourceBundle\Model\EnabledTrait;

abstract class AbstractTaxonomy implements TaxonomyInterface
{
    use DateTimeTrait, EnabledTrait;

    protected $name;

    protected $slug;

    protected $description;

    protected $postCount;

    /**
     * @var PostInterface[]|Collection
     */
    protected $posts;

    public function __toString()
    {
        return $this->getName();
    }

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

        return $this;
    }

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

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

        return $this;
    }

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

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

    /**
     * {@inheritdoc}
     */
    public function setSlug($slug)
    {
        $this->slug = $slug;
        return $this;
    }

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

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

    /**
     * {@inheritdoc}
     */
    public function addPostCount($count = 1)
    {
        $this->postCount += $count;
        $this->postCount = max($this->postCount, 0);
        return $this;
    }

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

    /**
     * {@inheritdoc}
     */
    public function addPost(PostInterface $post)
    {
        $this->posts[] = $post;
    }
}


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

use FOS\RestBundle\Context\Context;
use PHPDish\Bundle\CmsBundle\Utility\StringManipulator;
use PHPDish\Bundle\ResourceBundle\Controller\ResourceController;
use PHPDish\Bundle\PostBundle\Form\Type\BookType;
use PHPDish\Bundle\PostBundle\Form\Type\ChapterType;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Translation\TranslatorInterface;

class BookController extends ResourceController
{
    use ManagerTrait;

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

    /**
     * 创建电子书
     *
     * @Route("/books/new", name="book_add")
     *
     * @param Request $request
     *
     * @return Response
     */
    public function createAction(Request $request)
    {
        $this->denyAccessUnlessGranted('IS_AUTHENTICATED_REMEMBERED');
        $manager = $this->getBookManager();

        $book = $manager->createBook($this->getUser());
        $form = $this->createForm(BookType::class, $book);
        $form->handleRequest($request);

        /**@var TranslatorInterface*/
        $translator = $this->get('translator');
        if (($number = $manager->getUserBookNumber($this->getUser())) >= 5) {
            $this->addFlash('danger', $translator->trans('book.more_than_5_books', ['%count%' => $number]));
        }

        if ($form->isSubmitted() && $form->isValid()) {
            $manager->saveBook($book);
            $this->addFlash('success', $translator->trans('book.add_success'));

            return $this->redirectToRoute('book_view', [
                'slug' => $book->getSlug(),
            ]);
        }

        return $this->render($this->configuration->getTemplate('Category:create.html.twig'), [
            'form' => $form->createView(),
            'hasManyCategories' => $number >= 5,
            'isBook' => true
        ]);
    }

    /**
     * 查看电子书
     *
     * @Route("/books/{slug}", name="book_view", requirements={"slug": "[\w-]+"})
     * @param string $slug
     * @param Request $request
     * @return Response
     */
    public function viewAction($slug, Request $request)
    {
        $book = $this->getBookManager()->findBook($slug);
        return $this->render($this->configuration->getTemplate('Book:view.html.twig'), [
            'book' => $book,
        ]);
    }

    /**
     * 编辑电子书信息.
     *
     * @Route("/books/{slug}/edit", name="book_edit")
     *
     * @param string  $slug
     * @param Request $request
     *
     * @return Response
     */
    public function editAction($slug, Request $request)
    {
        $manager = $this->getBookManager();
        $book = $manager->findBook($slug);
        if (!$book) {
            throw $this->createNotFoundException();
        }
        $this->denyAccessUnlessGranted('edit', $book);
        $form = $this->createForm(BookType::class, $book);
        $form->handleRequest($request);
        if ($form->isValid() && $form->isSubmitted()) {
            $manager->saveBook($book);
            $this->addFlash('success', $this->get('translator')->trans('book.edit_success'));

            return $this->redirectToRoute('book_view', [
                'slug' => $book->getSlug(),
            ]);
        }

        return $this->render($this->configuration->getTemplate('Category:create.html.twig'), [
            'form' => $form->createView(),
            'category' => $book,
            'hasManyCategories' => false,
            'isBook' => true
        ]);
    }

    /**
     * 查看电子书目录
     *
     * @Route("/books/{slug}/summary", name="book_summary")
     * @Method("GET")
     * @param string $slug
     * @return Response
     */
    public function getSummaryAction($slug)
    {
        $book = $this->getBookManager()->findBook($slug);
        $chaptersTree = $this->getBookManager()->findBookChaptersTree($book);

        return $this->render($this->configuration->getTemplate('Book:summary.html.twig'), [
            'book' => $book,
            'chaptersTree' => $chaptersTree
        ]);
    }

    /**
     * 专栏的关注者.
     *
     * @Route("/books/{slug}/followers", name="book_followers")
     * @Method("GET")
     *
     * @param string $slug
     * @param Request $request
     *
     * @return Response
     */
    public function getFollowersAction($slug, Request $request)
    {
        $book = $this->getBookManager()->findBook($slug);
        $users = $this->getUserManager()->findCategoryFollowers($book, $request->query->getInt('page', 1));

        return $this->render($this->configuration->getTemplate('Book:followers.html.twig'), [
            'book' => $book,
            'users' => $users,
        ]);
    }

    /**
     * 查看电子书具体章节
     *
     * @Route("/books/{slug}/chapters/{chapterId}", name="book_read", requirements={"slug": "[\w-]+", "chapterId": "\d+"})
     * @param string $slug
     * @param int $chapterId
     * @param Request $request
     * @return Response
     */
    public function viewChapterAction($slug, $chapterId, Request $request)
    {
        $book = $this->getBookManager()->findBook($slug);
        $chapter = $this->getBookManager()->findChapter($chapterId);
        $chaptersTree = $this->getBookManager()->findBookChaptersTree($book);

        //更新阅读数
        $this->getPostManager()->increasePostViews($chapter);

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

        return $this->render($this->configuration->getTemplate('Book:read.html.twig'), [
            'book' => $book,
            'chapter' => $chapter,
            'chaptersTree' => $chaptersTree
        ]);
    }

    /**
     * 查看用户的电子书
     *
     * @Route("/users/{username}/books", name="user_books")
     *
     * @param string  $username
     * @param Request $request
     *
     * @return Response
     */
    public function getUserBooksAction($username, Request $request)
    {
        $user = $this->getUserManager()->findUserByName($username);
        $books = $this->getBookManager()->findUserBooks($user);
        return $this->render($this->configuration->getTemplate('Book:user_books.html.twig'), [
            'user' => $user,
            'books' => $books
        ]);
    }

    /**
     * 添加目录
     *
     * @Route("/books/{slug}/summary", name="book_add_summary")
     * @Method("POST")
     * @param string  $slug
     * @param Request $request
     *
     * @return Response
     */
    public function addSummaryAction($slug, Request $request)
    {
        $book = $this->getBookManager()->findBook($slug);
        $this->denyAccessUnlessGranted('edit', $book);
        $chapter = $this->getBookManager()->addBookChapter($book, $request->request->get('title'));
        return $this->handleView($this->view([
            'chapter' => $chapter
        ])->setContext((new Context())->setGroups(['Default'])));
    }

    /**
     * 修改目录
     *
     * @Route("/books/{slug}/summary/{id}/edit", name="book_edit_summary")
     * @Method("POST")
     * @param string  $slug
     * @param int  $id
     * @param Request $request
     *
     * @return Response
     */
    public function editSummaryAction($slug, $id, Request $request)
    {
        $book = $this->getBookManager()->findBook($slug);
        $this->denyAccessUnlessGranted('edit', $book);
        $chapter = $this->getBookManager()->findChapter($id);
        $chapter->setTitle($request->request->get('title'));
        $this->getPostManager()->savePost($chapter);
        return $this->handleView($this->view([
            'chapter' => $chapter
        ])->setContext((new Context())->setGroups(['Default'])));
    }

    /**
     * 添加章节
     *
     * @Route("/books/{slug}/chapters/new", name="book_add_chapter")
     * @param string  $slug
     * @param Request $request
     *
     * @return Response
     */
    public function addChapterAction($slug, Request $request)
    {
        $book = $this->getBookManager()->findBook($slug);
        $this->denyAccessUnlessGranted('edit', $book);

        //创建from
        $chapter = $this->getPostManager()->createPost($this->getUser());
        $chapter->setCategory($book);
        $defaultChapter = null;
        if ($defaultChapterId = $request->query->get('summary')) {
            $defaultChapter = $this->getBookManager()->findChapter($defaultChapterId);
        }
        $form = $this->createForm(ChapterType::class, $chapter, [
            'book' => $book,
            'default_character' => $defaultChapter
        ]);
        $form->handleRequest($request);
        if ($form->isSubmitted() && $form->isValid()) {
            /**@var TranslatorInterface*/
            $translator = $this->get('translator');

            if ($this->getPostManager()->savePost($chapter)) {
                $this->addFlash('success', $translator->trans('book.add_chapter_success'));
                return $this->redirectToRoute('book_summary', [
                    'slug' => $slug
                ]);
            } else {
                $this->addFlash('error', $translator->trans('book.add_chapter_error'));
            }
        }
        return $this->render($this->configuration->getTemplate('Book:create_chapter.html.twig'), [
            'form' => $form->createView()
        ]);
    }

    /**
     * 修改章节
     *
     * @Route("/books/{slug}/chapters/{id}/edit", name="book_edit_chapter")
     * @param string  $slug
     * @param int $id
     * @param Request $request
     *
     * @return Response
     */
    public function editChapterAction($slug, $id, Request $request)
    {
        $book = $this->getBookManager()->findBook($slug);
        $this->denyAccessUnlessGranted('edit', $book);

        //创建from
        $chapter = $this->getBookManager()->findChapter($id);
        $form = $this->createForm(ChapterType::class, $chapter, [
            'book' => $book,
        ]);
        $form->handleRequest($request);
        if ($form->isSubmitted() && $form->isValid()) {
            /**@var TranslatorInterface*/
            $translator = $this->get('translator');

            if ($this->getPostManager()->savePost($chapter)) {
                $this->addFlash('success', $translator->trans('book.edit_chapter_success'));
                return $this->redirectToRoute('book_summary', [
                    'slug' => $slug
                ]);
            } else {
                $this->addFlash('error', $translator->trans('book.edit_chapter_error'));
            }
        }
        return $this->render($this->configuration->getTemplate('Book:create_chapter.html.twig'), [
            'form' => $form->createView(),
            'chapter' => $chapter
        ]);
    }

    /**
     * 移动章节
     *
     * @Route("/books/{slug}/chapters/{id}/move", name="book_move_chapter")
     * @param string $slug
     * @param int $id
     * @param Request $request
     * @return Response
     */
    public function moveChapterAction($slug, $id, Request $request)
    {
        $book = $this->getBookManager()->findBook($slug);
        $this->denyAccessUnlessGranted('edit', $book);
        $chapter = $this->getBookManager()->findChapter($id);
        $this->getBookManager()->moveBookChapter($book, $chapter,
            $request->request->get('direction'),
            $request->request->get('step')
        );
        return $this->handleView($this->view([
            'chapter' => $chapter
        ])->setContext((new Context())->setGroups(['Default'])));
    }
}


<?php

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

namespace PHPDish\Bundle\ResourceBundle\DependencyInjection\Compiler;

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

/**
 * 注册所有的资源到服务对象
 */
class RegisterResourcePass implements CompilerPassInterface
{
    /**
     * {@inheritdoc}
     */
    public function process(ContainerBuilder $container)
    {
        if (
            !$container->hasParameter('phpdish.resources')
            || !$container->hasDefinition('phpdish_resource.resource_registry')
        ) {
            return;
        }
        $resourceRegistry = $container->findDefinition('phpdish_resource.resource_registry');
        $resourceRegistry->setArgument(0, $container->getParameter('phpdish.resources'));
    }
}


<?php

/*
 * This file is part of the DataGridBundle.
 *
 * (c) Abhoryo <abhoryo@free.fr>
 * (c) Stanislav Turza
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace PHPDish\Bundle\AdminBundle\Grid\Filter;

use Doctrine\ORM\Query\Expr\Comparison;

class InputFilter extends AbstractFilter
{
    /**
     * @var string
     */
    protected $value;

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

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

    /**
     * {@inheritdoc}
     */
    public function getComparison()
    {
        $this->assertComparable();
        return new Comparison($this->column->getName(), $this->operator, $this->value);
    }

    /**
     * {@inheritdoc}
     */
    public function shouldSkip()
    {
        return !$this->value;
    }

    /**
     * {@inheritdoc}
     */
    public function initialize($data)
    {
        $this->value = $data['value'];
        // input filter 允许自己调整operator
        if (isset($data['operator'])) {
            $this->operator = $data['operator'];
        }
    }
}


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

class ResourceConfiguration implements ResourceConfigurationInterface
{
    /**
     * @var array
     */
    protected $resource;

    protected $templates;

    protected $defaultTemplateNamespace;

    public function __construct($resource, $templates)
    {
        $this->resource = $resource;
        $this->templates = $templates;
    }

    /**
     * 获取模板
     *
     * @param string $key
     * @return string
     */
    public function getTemplate($key)
    {
        if (isset($this->templates[$key])) {
            return $this->templates[$key];
        }
        return $this->defaultTemplateNamespace . ':' . $key;
    }

    /**
     * 设置默认空间
     *
     * @param string $defaultTemplateNamespace
     */
    public function setDefaultTemplateNamespace($defaultTemplateNamespace)
    {
        $this->defaultTemplateNamespace = $defaultTemplateNamespace;
    }

    /**
     * 设置默认空间
     *
     * @param string $defaultTemplateNamespace
     */
    public function setDefaultTemplatesNamespaceIfNull($defaultTemplateNamespace)
    {
        $this->defaultTemplateNamespace || ($this->defaultTemplateNamespace = $defaultTemplateNamespace);
    }
}


<?php

namespace PHPDish\Bundle\WebBundle\Menu;

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

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

    /**
     * @var EventDispatcherInterface $eventDispatcher
     */
    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', 'nav navbar-nav');

        $menu->addChild('homepage', [
            'label' => 'menu.homepage',
            'route' => 'homepage',
        ]);

        $menu->addChild('Category', [
            'label' => 'menu.category',
            'route' => 'post',
        ]);

        $menu->addChild('Topic', [
            'label' => 'menu.ask_question',
            'route' => 'thread_view',
            'routeParameters' => [
                'slug' => 'question',
            ],
        ]);

        $menu->addChild('job', [
            'label' => 'menu.job',
            'route' => 'thread_view',
            'routeParameters' => [
                'slug' => 'job',
            ],
        ]);

        $github = $menu->addChild('GitHub', [
            'label' => 'GitHub',
            'uri' => 'https://github.com/slince/phpdish'
        ]);
        $github->setLinkAttribute('target', '_blank');

        $this->eventDispatcher->dispatch(Events::NAV_MENU_BUILT, new FilterMenuEvent($menu));

        return $menu;
    }
}


<?php

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

namespace PHPDish\Bundle\CoreBundle\Service;

use Doctrine\ORM\EntityManager;
use Doctrine\ORM\EntityRepository;
use PHPDish\Bundle\CoreBundle\Model\FriendLink;

class FriendLinkManager implements FriendLinkManagerInterface
{
    /**
     * @var EntityRepository
     */
    protected $friendLinkRepository;

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

    public function __construct(EntityManager $entityManager)
    {
        $this->entityManager = $entityManager;
        $this->friendLinkRepository = $this->entityManager
            ->getRepository(FriendLink::class);
    }

    /**
     * {@inheritdoc}
     */
    public function findEnabledFriendLinks($limit)
    {
        return $this->friendLinkRepository->findBy([], ['priority' => 'asc'], $limit);
    }

    /**
     * {@inheritdoc}
     */
    public function saveFriendLink(FriendLink $friendLink)
    {
        $this->entityManager->persist($friendLink);
        $this->entityManager->flush();
    }
}


<?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\Common\Collections\ArrayCollection;
use PHPDish\Bundle\AdminBundle\Grid\Column\ColumnInterface;
use PHPDish\Bundle\AdminBundle\Grid\Source\SourceInterface;
use Symfony\Component\HttpFoundation\Request;

class Grid implements GridInterface
{
    /**
     * @var Request
     */
    protected $request;

    /**
     * @var ColumnInterface[]|ArrayCollection
     */
    protected $columns = [];

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

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

    /**
     * @var Factory
     */
    protected $factory;

    /**
     * @var SourceInterface
     */
    protected $source;

    public function __construct(SourceInterface $source)
    {
        $this->source = $source;
        $this->columns = new ArrayCollection();
    }

    /**
     * @param Factory $factory
     */
    public function setFactory(Factory $factory)
    {
        $this->factory = $factory;
    }

    /**
     * {@inheritdoc}
     */
    public function handleRequest(Request $request)
    {
        $this->request = $request;
        return $this;
    }

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

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

    /**
     * {@inheritdoc}
     */
    public function addColumn($column, $type, $options = [])
    {
        if (!$column instanceof ColumnInterface) {
            $column = $this->factory->createColumn($column, $type, $options);
        }
        $this->columns[$column->getName()] = $column;
    }

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

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

    /**
     * {@inheritdoc}
     */
    public function initialize()
    {
        $this->loadEntities();  //加载数据
        $this->prepareRows(); //初始化rows
    }

    protected function prepareRows()
    {
        foreach ($this->entities as $entity) {
            $row = [];
            foreach ($this->columns as $column) {
                $row[$column->getName()] = $entity->getUsername();
            }
        }
    }

    /**
     * {@inheritdoc}
     */
    protected function loadEntities()
    {
        if ($this->request) {
            if ($filters = $this->request->get('filters')) {
                $this->applyFilters($filters);
            }
        }
        $this->entities = $this->source->loadSource($this->columns);
    }

    /**
     * [
     *    'filters' => [
     *        ['column' => 'age', 'operator' => 'lt', 'value' => 18],
     *        ['column' => 'age', 'operator' => 'gt', 'value' => 5],
     *        ['column' => 'age', 'operator' => 'between', 'from' => 5, to => 18],
     *        'username' => 'slince',
     *    ]
     * ]
     * @param array $filterItems
     */
    protected function applyFilters(array $filterItems)
    {
        foreach ($filterItems as $columnName => $filterItem) {
            if (is_numeric($columnName)) {
                if (!is_array($filterItem)) {  //不是符合条件的filter格式，跳过
                    continue;
                }
                $columnName = $filterItem['column'];
            } else {
                //key-value 模式,只能是input filter
                $filterItem = [
                    'column' => $columnName,
                    'operator' => null,
                    'value' => $filterItem
                ];
            }

            //如果没有该列则跳过
            if (!$this->hasColumn($columnName)) {
                continue;
            }

            $column = $this->getColumn($columnName);
            if ($filter = $column->getFilterByOperator($filterItem['operator'])) {
                $filter->initialize($filterItem);
            }
        }
    }
}


<?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\Form\DataTransformer;

use Overtrue\Pinyin\Pinyin;
use PHPDish\Bundle\ForumBundle\Model\ThreadInterface;
use PHPDish\Bundle\ForumBundle\Service\ThreadManagerInterface;
use Symfony\Component\Form\DataTransformerInterface;
use Symfony\Component\Form\Exception\UnexpectedTypeException;

class StringToThreadsTransformer implements DataTransformerInterface
{
    /**
     * @var ThreadManagerInterface
     */
    protected $threadManager;

    public function __construct(ThreadManagerInterface $threadManager)
    {
        $this->threadManager = $threadManager;
    }

    /**
     * @param ThreadManagerInterface $threadManager
     */
    public function setThreadManager($threadManager)
    {
        $this->threadManager = $threadManager;
    }

    /**
     * {@inheritdoc}
     */
    public function transform($value)
    {
        if (null === $value) {
            return '';
        }
        $threadNames = [];
        foreach ($value as $thread) {
            if (!$thread instanceof ThreadInterface) {
                throw new UnexpectedTypeException($thread, ThreadInterface::class);
            }
            $threadNames[] = $thread->getName();
        }
        return implode(',', $threadNames);
    }

    /**
     * {@inheritdoc}
     */
    public function reverseTransform($value)
    {
        if (null === $value || '' === $value) {
            return null;
        }

        $threadNames = array_unique(array_filter(array_map('trim', explode(',', $value))));
        if (count($threadNames) === 0) {
            return null;
        }
        $threads =  $this->threadManager->findThreadsByNames($threadNames);
        $existingThreadNames = array_map(function(ThreadInterface $thread){
            return $thread->getName();
        }, $threads);

        //创建新的thread
        $nonExistThreads = $this->threadManager->createThreadsByNames(array_diff($threadNames, $existingThreadNames));
        return array_slice(array_merge($threads, $nonExistThreads), 0, 5); //只要五个
    }
}


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

use PHPDish\Bundle\CoreBundle\Application\Plugin\SimplePluginInterface;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\Config\Loader\DelegatingLoader;
use Symfony\Component\Config\Loader\LoaderResolver;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Loader;
use Symfony\Component\Finder\Finder;

class RegisterPluginPass implements CompilerPassInterface
{
    /**
     * @var ContainerBuilder
     */
    protected $container;

    /**
     * @var Definition
     */
    protected $routingLoaderDefinition;

    /**
     * {@inheritdoc}
     */
    public function process(ContainerBuilder $container)
    {
        $this->container = $container;

        $this->routingLoaderDefinition = $container->findDefinition('phpdish.plugin.route_loader');
        $this->translatorDefinition = $container->findDefinition('translator');

        //注册插件
        foreach ($container->getParameter('kernel.simple_plugins_metadata') as $plugin) {
            $this->install($plugin);
        }
    }

    /**
     * 注册插件
     *
     * @param array $pluginMetadata
     * @throws \Exception
     */
    public function install($pluginMetadata)
    {
        ////注册插件服务
        if ($pluginMetadata['servicesSource'] !== false) {
            $loader = $this->createContainerLoader($this->container,
                new FileLocator($pluginMetadata['path'])
            );
            $loader->load($pluginMetadata['servicesSource']);
        }

        //注册路由
        if ($pluginMetadata['routerSource'] !== false) {
            $this->routingLoaderDefinition->addMethodCall('addResource', [
                $pluginMetadata['routerSource']
            ]);
        }
    }

    protected function createContainerLoader(ContainerBuilder $container, FileLocator $fileLocator)
    {
        $resolver = new LoaderResolver([
            new Loader\YamlFileLoader($container, $fileLocator),
            new Loader\XmlFileLoader($container, $fileLocator),
        ]);
        return new DelegatingLoader($resolver);
    }
}


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

use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityRepository;
use PHPDish\Bundle\CoreBundle\Service\PaginatorTrait;
use PHPDish\Bundle\NotificationBundle\Model\NotificationInterface;
use PHPDish\Bundle\NotificationBundle\Model\NotificationMetadataInterface;
use PHPDish\Bundle\ResourceBundle\Service\ServiceManagerInterface;
use PHPDish\Bundle\UserBundle\Model\UserInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;

class NotificationManager implements NotificationManagerInterface, ServiceManagerInterface
{
    use PaginatorTrait;

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

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

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

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

    public function __construct(
        $notificationEntity,
        $metadataEntity,
        EntityManagerInterface $entityManager,
        EventDispatcherInterface $eventDispatcher
    ) {
        $this->notificationEntity = $notificationEntity;
        $this->metadataEntity = $metadataEntity;
        $this->entityManager = $entityManager;
        $this->eventDispatcher = $eventDispatcher;
    }

    /**
     * {@inheritdoc}
     */
    public function createNotification($subject, $message = null)
    {
        $notification = new $this->notificationEntity();
        $notification->setSubject($subject);
        $message && $notification->setMessage($message);
        return $notification;
    }

    /**
     * {@inheritdoc}
     */
    public function sendNotification($participants, NotificationInterface $notification, $send = false)
    {
        foreach ($participants as $participant) {
            $metadata = new $this->metadataEntity();
            $metadata->setParticipant($participant);
            $notification->addMetadata($metadata);
        }
        $this->entityManager->persist($notification);
        $send && $this->entityManager->flush();
    }

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

    /**
     * {@inheritdoc}
     */
    public function findNotificationsPager(UserInterface $participant, $seen = null, $page, $limit = null)
    {
        $qb = $this->getNotificationQb($participant, $seen);
        return $this->createPaginator($qb->getQuery(), $page, $limit);
    }

    protected function getNotificationQb(UserInterface $participant, $seen = null)
    {
        $qb = $this->getRepository()->createQueryBuilder('n')
            ->addSelect('nm')
            ->innerJoin('n.metadata', 'nm')
            ->where('nm.participant = :participant')
            ->setParameter('participant', $participant);
        if ($seen !== null) {
            $qb->andWhere('nm.seen =:seen')->setParameter('seen', (boolean)$seen);
        }
        return $qb;
    }

    /**
     * {@inheritdoc}
     */
    public function findNotificationMetadataPager(UserInterface $participant, $seen, $page, $limit = null)
    {
        $qb = $this->getParticipantMetadataQb($participant, $seen);
        $qb->innerJoin('m.notification', 'mn')
            ->addSelect(['mn']);
        return $this->createPaginator($qb->getQuery(), $page, $limit);
    }

    /**
     * {@inheritdoc}
     */
    public function getNotificationCount(UserInterface $participant, $seen = null)
    {
        $qb = $this->getParticipantMetadataQb($participant, $seen);
        $qb->select($qb->expr()->count('m'));
        return $qb->getQuery()->getSingleScalarResult();
    }

    /**
     * 获取参与者metadata qb
     *
     * @param UserInterface $participant
     * @param boolean $seen
     * @return \Doctrine\ORM\QueryBuilder
     */
    protected function getParticipantMetadataQb(UserInterface $participant, $seen)
    {
        $qb = $this->getMetadataRepository()->createQueryBuilder('m')
            ->where('m.participant = :participant')
            ->setParameter('participant', $participant);
        if ($seen !== null) {
            $qb->andWhere('m.seen =:seen')->setParameter('seen', (boolean)$seen);
        }
        return $qb;
    }

    /**
     * {@inheritdoc}
     */
    public function markAllAsSeen(UserInterface $participant)
    {
        $notifications = $this->findNotificationMetadata($participant, false);
        foreach ($notifications as $notification) {
            $notification->setSeen(true);
        }
        $this->entityManager->flush();
    }

    /**
     * {@inheritdoc}
     */
    public function markAsSeen($notificationMeta)
    {
        foreach ($notificationMeta as $notificationMetadata) {
            $notificationMetadata->setSeen(true);
        }
        $this->entityManager->flush();
    }

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

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

    public static function getSubscribedEntities()
    {
        return [
            'notificationEntity' => NotificationInterface::class,
            'metadataEntity' => NotificationMetadataInterface::class,
        ];
    }
}


<?php

namespace PHPDish\Bundle\CoreBundle\Command;

use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
use Symfony\Component\Console\Helper\ProgressBar;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Output\NullOutput;
use Symfony\Component\Console\Output\OutputInterface;

abstract class AbstractInstallCommand extends ContainerAwareCommand
{
    /**
     * 文字logo.
     *
     * @var string
     */
    const PHPDISH_LOGO = <<<EOT

 _____   _   _   _____   _____   _   _____   _   _
|  _  \ | | | | |  _  \ |  _  \ | | /  ___/ | | | |
| |_| | | |_| | | |_| | | | | | | | | |___  | |_| |
|  ___/ |  _  | |  ___/ | | | | | | \___  \ |  _  |
| |     | | | | | |     | |_| | | |  ___| | | | | |
|_|     |_| |_| |_|     |_____/ |_| /_____/ |_| |_|

EOT;

    /**
     * 确认文件目录存在并且可读.
     *
     * @param string          $directory
     * @param OutputInterface $output
     */
    protected function ensureDirectoryWritableAndExists($directory, $output)
    {
        $checker = $this->getContainer()->get('phpdish.installer.directory_checker');
        $checker->setOutput($output);
        $checker->setDirectory($directory);
        $checker->check();
    }

    /**
     * Execute a command.
     *
     * @param string          $command
     * @param OutputInterface $output
     * @param array           $options Command options
     *
     * @return $this Self object
     */
    protected function executeCommand($command, OutputInterface $output = null, array $options = [])
    {
        if (is_null($output)) {
            $output = new NullOutput();
        }

        $options = array_merge($options, [
            'command' => $command,
        ]);

        $this
            ->getApplication()
            ->run(new ArrayInput($options), $output);

        return $this;
    }

    /**
     * 批量执行命令.
     *
     * @param array                $commands
     * @param OutputInterface|null $output
     * @param bool                 $renderProgressbar
     */
    protected function bulkRunCommands(array $commands, OutputInterface $output, $renderProgressbar = false)
    {
        if ($renderProgressbar) {
            $progressbar = new ProgressBar($output, count($commands));
        }
        foreach ($commands as $commandItem) {
            if (is_string($commandItem)) {
                $command = $commandItem;
                $options = [];
            } else {
                $command = $commandItem['command'];
                $options = $commandItem['options'];
            }
            $this->executeCommand($command, null, $options);
            $renderProgressbar && $progressbar->advance(1);
        }
        $renderProgressbar && $progressbar->finish();
    }
}


<?php

namespace PHPDish\Bundle\PostBundle\DependencyInjection;

use PHPDish\Bundle\PostBundle\Model\Category;
use PHPDish\Bundle\PostBundle\Model\CategoryInterface;
use PHPDish\Bundle\PostBundle\Model\Comment;
use PHPDish\Bundle\PostBundle\Model\CommentInterface;
use PHPDish\Bundle\PostBundle\Model\Post;
use PHPDish\Bundle\PostBundle\Model\PostInterface;
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;

/**
 * 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 extends AbstractConfiguration implements ConfigurationInterface
{
    /**
     * {@inheritdoc}
     */
    public function getConfigTreeBuilder()
    {
        $treeBuilder = new TreeBuilder();
        $rootNode = $treeBuilder->root('phpdish_post');

        $this->addResourcesSection($rootNode);
        $this->addTemplatesSection($rootNode);

        return $treeBuilder;
    }

    /**
     * {@inheritdoc}
     */
    public function addResourcesSection(ArrayNodeDefinition $rootNode)
    {
        $rootNode
            ->children()
                ->arrayNode('resources')
                    ->children()
                        ->arrayNode('post')
                            ->children()
                                ->scalarNode('interface')->defaultValue(PostInterface::class)->cannotBeEmpty()->end()
                                ->scalarNode('model')->defaultValue(Post::class)->cannotBeEmpty()->end()
                            ->end()
                        ->end()
                        ->arrayNode('comment')
                            ->children()
                                ->scalarNode('interface')->defaultValue(CommentInterface::class)->cannotBeEmpty()->end()
                                ->scalarNode('model')->defaultValue(Comment::class)->cannotBeEmpty()->end()
                            ->end()
                        ->end()
                        ->arrayNode('category')
                            ->children()
                                ->scalarNode('interface')->defaultValue(CategoryInterface::class)->cannotBeEmpty()->end()
                                ->scalarNode('model')->defaultValue(Category::class)->cannotBeEmpty()->end()
                            ->end()
                        ->end()
                    ->end()
                ->end()
            ->end();
    }
}


<?php

namespace PHPDish\Component\Pjax\EventListener;

use PHPDish\Component\Pjax\Helper\PjaxHelperInterface;
use Symfony\Component\DomCrawler\Crawler;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;

final class KernelResponseListener
{
    /**
     * @var PjaxHelperInterface
     */
    protected $pjaxHelper;

    public function __construct(PjaxHelperInterface $pjaxHelper)
    {
        $this->pjaxHelper = $pjaxHelper;
    }

    /**
     * 监听响应
     * @param FilterResponseEvent $event
     */
    public function onKernelResponse(FilterResponseEvent  $event)
    {
        if (!$this->pjaxHelper->isPjaxRequest($event->getRequest())) {
            return;
        }
        $this->filterResponse($event->getRequest(), $event->getResponse());
    }

    /**
     * 过滤 response
     * @param Request $request
     * @param Response $response
     */
    protected function filterResponse(Request $request, Response $response)
    {
        $crawler = new Crawler($response->getContent());
        $body = $this->fetchBody($crawler, $this->pjaxHelper->getContainer($request));
        $response->setContent($this->makeTitle($crawler) . $body);
    }

    /**
     * 生成 title
     * @param Crawler $crawler
     * @return string
     */
    protected function makeTitle(Crawler $crawler)
    {
        $pageTitle = $crawler->filter('head > title');
        if (!$pageTitle->count()) {
            return '';
        }
        return "<title>{$pageTitle->html()}</title>";
    }

    /**
     * 提取 body
     * @param Crawler $crawler
     * @param $container
     * @return string
     */
    protected function fetchBody(Crawler $crawler, $container)
    {
        $content = $crawler->filter($container);
        if ($content->count() == 0) {
            return '';
        }
        return $content->html();
    }
}


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

use Liip\ImagineBundle\Imagine\Cache\CacheManager as ImagineCacheManager;
use PHPDish\Component\Media\Model\ImageInterface;
use PHPDish\Component\Media\Model\MediaInterface;

class GeneralBuilder implements UrlBuilderInterface
{
    /**
     * 基本url.
     *
     * @var string
     */
    protected $baseUrl;

    protected $imagineCacheManager;

    public function __construct(ImagineCacheManager $cacheManager, $baseUrl)
    {
        $this->imagineCacheManager = $cacheManager;
        if (strpos($baseUrl, 'http://') !== false || strpos($baseUrl, 'https://') !== false ) {
            $this->baseUrl = rtrim($baseUrl, '/').'/';
        } else {
            $this->baseUrl = '/'.trim($baseUrl, '/').'/';
        }
    }

    /**
     * {@inheritdoc}
     */
    public function build($media)
    {
        $key = $media instanceof MediaInterface ? $media->getKey() : $media;
        return "{$this->baseUrl}{$key}";
    }

    /**
     * {@inheritdoc}
     */
    public function buildImageResizeUrl(ImageInterface $image, $filter, $runtimeConfig = [])
    {
        return $this->imagineCacheManager->getBrowserPath($image->getKey(), $filter, $runtimeConfig);
    }
}


<?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\Form\Type\ChangeUserProfileType;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Translation\TranslatorInterface;

class SettingController extends ResourceController
{
    use ManagerTrait;

    /**
     * 修改资料.
     *
     * @Route("/settings", name="setting_profile")
     *
     * @param Request $request
     *
     * @return Response
     */
    public function editProfileAction(Request $request)
    {
        $this->denyAccessUnlessGranted('IS_AUTHENTICATED_REMEMBERED');
        $user = $this->getUser();
        $form = $this->createForm(ChangeUserProfileType::class, $user);
        $form->handleRequest($request);
        if ($form->isSubmitted() && $form->isValid()) {
            $this->getUserManager()->saveUser($user);
            /** @var TranslatorInterface */
            $translator = $this->get('translator');
            $this->addFlash('success', $translator->trans('profile.edit.success'));

            return $this->redirectToRoute('setting_profile');
        }

        return $this->render($this->configuration->getTemplate('Setting:profile.html.twig'), [
            'form' => $form->createView(),
        ]);
    }

    /**
     * 绑定社交账户.
     *
     * @Route("/settings/social-binding", name="setting_social_binding")
     *
     * @param Request $request
     *
     * @return Response
     */
    public function bindSocialSiteAction(Request $request)
    {
        $this->denyAccessUnlessGranted('IS_AUTHENTICATED_REMEMBERED');

        return $this->render($this->configuration->getTemplate('Setting:bind_social.html.twig'));
    }
}


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

class ResourceRegistry
{
    /**
     * [
     *     'phpdish_user' => [
     *         'user' => [
     *               'interface' => '',
     *               'model' => ''
     *         ]
     *     ]
     * ]
     * @var array
     */
    protected $resources;

    public function __construct($resources = [])
    {
        $this->resources = $resources;
    }

    /**
     * 合并资源
     *
     * @param string $alias
     * @param array $resources
     */
    public function mergeResources($alias, $resources)
    {
        $this->resources = array_merge($this->resources, [$alias => $resources]);
    }

    /**
     * 获取接口和实现的映射
     *
     * @return array
     */
    public function getInterfacesMapping()
    {
        $interfaces = [];
        foreach ($this->resources as $alias => $resource) {
            foreach ($resource as $resourceItem) {
                $interfaces[$resourceItem['interface']] = $resourceItem['model'];
            }
        }
        return $interfaces;
    }

    /**
     * 根据model查找
     * @param string $model
     * @return array|null
     */
    public function getResourceItemByModel($model)
    {
        foreach ($this->resources as $alias => $resource) {
            foreach ($resource as $resourceItem) {
                if ($resourceItem['model'] === $model) {
                    return $resourceItem;
                }
            }
        }
        return null;
    }
}


<?php

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

namespace PHPDish\Bundle\AdminBundle\Grid\Twig;

use PHPDish\Bundle\AdminBundle\Grid\GridInterface;
use Twig\Environment;
use Twig\Extension\AbstractExtension;
use Twig\TemplateWrapper;

class GridExtension extends AbstractExtension
{
    /**
     * @var Environment
     */
    protected $twig;

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

    /**
     * @var TemplateWrapper[]
     */
    protected $templates = [];

    public function __construct(Environment $twig, $baseTemplate)
    {
        $this->twig = $twig;
        $this->baseTemplate = $baseTemplate;
    }

    public function getFunctions()
    {
        return [
            new \Twig_SimpleFunction('grid', [$this, 'grid'])
        ];
    }

    /**
     * 渲染一个grid
     *
     * @param GridInterface $grid
     * @param null $theme
     * @return string
     */
    public function grid(GridInterface $grid, $theme =  null)
    {
        $this->initializeTemplate($theme);
        $grid->initialize();
        return $this->renderBlock('grid', [
            'grid' => $grid
        ]);
    }

    /**
     * 是否有block
     *
     * @param string $name
     * @return bool
     */
    protected function hasBlock($name)
    {
        foreach ($this->templates as $template) {
            if ($template->hasBlock($name)) {
                return true;
            }
        }
        return false;
    }

    protected function renderBlock($name, $context = [])
    {
        foreach ($this->templates as $template) {
            if (!$template->hasBlock($name)) {
                continue;
            }
            return $template->renderBlock($name, $context);
        }
        throw new \InvalidArgumentException(sprintf('The block "%s" is not found', $name));
    }

    protected function initializeTemplate($theme)
    {
        if ($this->templates) {
            return;
        }
        if ($theme) {
            $themeTemplate = $this->twig->load($theme);
            $this->templates[] = $themeTemplate;
        }
        $this->templates[] = $this->twig->load($this->baseTemplate);
    }
}


<?php

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

namespace PHPDish\Bundle\ThemeBundle\Theming;

use PHPDish\Bundle\ThemeBundle\Model\Theme;
use PHPDish\Bundle\ThemeBundle\Model\ThemeAuthor;
use PHPDish\Bundle\ThemeBundle\Model\ThemeInterface;
use PHPDish\Bundle\ThemeBundle\Model\ThemeScreenshot;
use Symfony\Component\Finder\Finder;
use Symfony\Component\Finder\SplFileInfo;

class ThemeFinder implements ThemeFinderInterface
{
    /**
     * composer.json
     *
     * @var string
     */
    protected $filename;

    /**
     * phpdish-theme
     *
     * @var string
     */
    protected $type;

    protected $directory;

    public function __construct($filename, $type, $directory)
    {
        $this->filename = $filename;
        $this->type = $type;
        $this->directory = $directory;
    }

    /**
     * 查找指定目录下的所有主题
     *
     * @return ThemeInterface[]
     */
    public function find()
    {
        $finder = new Finder();
        $finder->in($this->directory)->name($this->filename)->ignoreUnreadableDirs();
        $themes = [];
        foreach ($finder as $file) {
            $theme = $this->hydrateTheme($file);
            if ($theme === false) {
                continue;
            }
            $themes[] = $theme;
        }
        return $themes;
    }

    /**
     * @param SplFileInfo $file
     * @return bool|Theme
     */
    protected function hydrateTheme(SplFileInfo $file)
    {
        $configuration = json_decode($file->getContents(), true);

//        var_dump(!isset($configuration['type']) || $configuration['type'] !== $this->type);
        if (!isset($configuration['type']) || $configuration['type'] !== $this->type) {
            return false;
        }
        $theme = new Theme($configuration['name'] ?? 'Unknown', $file->getPath());
        $theme->setDescription($configuration['description'] ?? null);
        $theme->setAuthors($this->convertAuthorsArraysToAuthorsObjects($configuration['authors'] ?? []));
        $theme->setScreenshots($this->convertScreenshotsArraysToScreenshotsObjects($configuration['screenshots'] ?? []));
        return $theme;
    }

    /**
     * @param array $authorsArrays
     *
     * @return array|ThemeAuthor[]
     */
    private function convertAuthorsArraysToAuthorsObjects(array $authorsArrays): array
    {
        return array_map(function (array $authorArray) {
            $author = new ThemeAuthor();

            $author->setName($authorArray['name'] ?? null);
            $author->setEmail($authorArray['email'] ?? null);
            $author->setHomepage($authorArray['homepage'] ?? null);
            $author->setRole($authorArray['role'] ?? null);

            return $author;
        }, $authorsArrays);
    }

    /**
     * @param array $screenshotsArrays
     *
     * @return array|ThemeScreenshot[]
     */
    private function convertScreenshotsArraysToScreenshotsObjects(array $screenshotsArrays): array
    {
        return array_map(function (array $screenshotArray) {
            if (!array_key_exists('path', $screenshotArray)) {
                throw new \InvalidArgumentException('Screenshot path is required.');
            }

            $themeScreenshot = new ThemeScreenshot($screenshotArray['path']);
            $themeScreenshot->setTitle($screenshotArray['title'] ?? null);
            $themeScreenshot->setDescription($screenshotArray['description'] ?? null);
            return $themeScreenshot;

        }, $screenshotsArrays);
    }
}