<?php
namespace AppBundle\Component\PageBuilder;
use AppBundle\Component\PageBuilder\Event\BlockLoadedEvent;
use AppBundle\Component\PageBuilder\Event\BlockLoadingEvent;
use AppBundle\Component\PageBuilder\Event\FormInitEvent;
use AppBundle\Component\PageBuilder\Event\FormLoadedEvent;
use AppBundle\Component\PageBuilder\Event\FormLoadingEvent;
use AppBundle\Component\PageBuilder\Event\OnCommitEvent;
use AppBundle\Component\PageBuilder\Event\PreValidateEvent;
use AppBundle\Component\PageBuilder\Type\BlockInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Validator\ConstraintViolationList;
class PageBuilder
{
public $container;
protected $entity = null;
protected $blocks = [];
protected $builder;
protected $engine;
protected $autoincrement = 0;
protected $blockTypes = [];
protected $data = [];
protected $errors = [];
public $translator;
protected $oldInputs = [];
protected $form = null;
public $themeDirectory = '::page-builder/themes';
public $theme = 'default';
public $mediaUsageTracker = null;
public $dispatcher = null;
public $sharedParams = [];
public $validationCallback = null;
protected $validationData = [];
public function __construct(ContainerInterface $container)
{
$this->container = $container;
$this->engine = $this->container->get('templating');
$this->dispatcher = new EventDispatcher();
$router = $container->get('router');
$mediaLibraryExists = (null !== $router->getRouteCollection()->get('admin_media_library_item'));
if($mediaLibraryExists){
$this->setSharedParam('media_library_enabled', true);
} else {
$this->setSharedParam('media_library_enabled', false);
}
}
public function duplicate()
{
$clone = clone $this;
$blocks1 = $this->getBlocks();
$blocks2 = [];
foreach($blocks1 as $block){
$blocks2[] = clone $block;
}
$clone->setBlocks($blocks2);
return $clone;
}
public function initOldInputs()
{
if(is_null($this->oldInputs)) {
$request = $this->container->get('request_stack')->getCurrentRequest();
$session = $request->getSession();
$this->oldInputs = $session->getFlashBag()->get('_form_inputs', []);
}
}
public function loadForm($class, $params = [])
{
$form = new $class($params);
$this->dispatcher
->dispatch(FormLoadingEvent::NAME, new FormInitEvent($form));
$initService = $this->getSharedParam('form_initializer', null);
$container = $this->getContainer();
if($container->has($initService)){
$initializer = $container->get($initService);
$initializer->init($form, $params);
}
$form->registerBlockData($this, $params);
$this->dispatcher
->dispatch(FormLoadingEvent::NAME, new FormLoadingEvent($form));
foreach($form->getRegisteredBlockDataList() as $name => $blockData){
$block = $this->createBlock($blockData['class'], $name, $blockData['options']);
$this->dispatcher->dispatch(BlockLoadingEvent::NAME, new BlockLoadingEvent($block));
$this->addBlock($block);
$this->dispatcher->dispatch(BlockLoadedEvent::NAME, new BlockLoadedEvent($block));
}
$this->form = $form;
$this->dispatcher
->dispatch(FormLoadedEvent::NAME, new FormLoadedEvent($form));
return $this;
}
public function getForm()
{
return $this->form;
}
public function getBlockType($name)
{
if(isset($this->blockTypes[$name])){
return $this->blockTypes[$name];
} else {
return null;
}
}
public function createBlock($class, $name, $options=[]){
/** @var BlockInterface $block */
$block = new $class($name, $options);
$block->setBuilder($this);
$block->setEngine($this->engine);
$block->init();
return $block;
}
// 初期表示するブロックを追加
public function addBlock($block){
$block->setBuilder($this);
$block->setEngine($this->engine);
$this->autoincrement++;
$block->setId($this->autoincrement);
$this->blocks[$block->getId()] = $block;
return $this;
}
/**
* 条件に一致するブロックを取得する
*
* @param array $conditions
* @return array
*/
public function getBlocks($conditions = [])
{
$blocks = $this->blocks;
$list = [];
foreach($blocks as $block){
$show = true;
foreach($conditions as $key => $value){
if($key === 'form_display_section' && $block->getFormDisplaySection() !== $value){
$show = false;
break;
}
if($key === 'content_display_section' && $block->getContentDisplaySection() !== $value){
$show = false;
break;
}
}
if($show){
$list[$block->getName()] = $block;
}
}
return $list;
}
/**
* ブロックをセットする
*
* @param $blocks
*/
public function setBlocks($blocks)
{
$this->blocks = $blocks;
}
/**
* 名前からブロックを探す
*
* @param $name
* @return null
*/
public function findBlockByName($name)
{
$blocks = $this->getBlocks();
foreach($blocks as $block){
if($name === $block->getName()){
return $block;
}
// グループであれば子も走査する
if($block->isGroup){
foreach($block->getChildren() as $child){
if($name === $child->getName()){
return $child;
}
}
}
}
return null;
}
public function findBlocksByFormDisplaySection($section)
{
}
public function getExtraBlocks(){
return $this->extraBlocks;
}
public function getBlockList()
{
$list = [];
foreach($this->getBlocks() as $block){
$list[$block->getName()] = $block;
}
return $list;
}
public function getEngine()
{
return $this->engine;
}
public function renderForm()
{
$content = '';
foreach($this->blocks as $block){
$content .= $block->renderForm();
}
return $content;
}
public function renderContent()
{
$content = '';
foreach($this->blocks as $block){
$content .= $block->renderContent();
}
return $content;
}
public function validate($inputs)
{
$validator = $this->container->get('validator');
$inputs = $this->optimizeInputs($inputs);
$form = $this->getForm();
$this->dispatcher->dispatch(PreValidateEvent::NAME, new PreValidateEvent($form, $inputs));
$blockList = $this->getBlockList();
$this->errors = [];
$violations = [];
$blockValues = [];
$blocks = [];
foreach ($blockList as $name => $block) {
$blocks[$block->getNamePrefix() . $name] = $block;
}
$blockList = $blocks;
foreach($blockList as $name => $block){
$blockValues[$name] = [];
}
foreach($inputs['blocks'] as $name => $values){
$parts = preg_split('/__/', $name, -1, \PREG_SPLIT_NO_EMPTY);
if(count($parts) === 2){
$blockName = $parts[0];
} else {
$blockName = $name;
}
if(!isset($blockList[$blockName])){
continue;
}
$blockValues[$name] = $values;
}
$this->setValidationData($inputs);
foreach($blockValues as $name => $values)
{
if(isset($blockList[$name])) {
$block = $blockList[$name];
} else {
continue;
}
$result = $block->validate($values);
if($result){
$violations[ $name ] = $result;
}
}
if(!is_null($this->validationCallback)){
$violations2 = $this->validationCallback->call($this, $blockValues);
if(is_array($violations2)){
$violations = array_merge($violations, $violations2);
}
}
$this->setValidationData([]);
$this->errors = $violations;
return $violations;
}
/**
* 送信された値を扱いやすい形に整形する
*
* @param $inputs
* @return array
*/
public function optimizeInputs($inputs){
$result = [
'fields' => [],
'blocks' => [],
];
foreach($inputs as $key => $value){
if(preg_match('/^__block_(.+?)$/', $key, $matches)){
$parts = preg_split('/__/', $matches[1], -1, \PREG_SPLIT_NO_EMPTY);
if(count($parts) === 3){
$section = $parts[0];
$name = $parts[1];
$uniqueCode = $parts[2];
if(!isset($result['blocks'][$section])) {
$result['blocks'][$section] = [];
}
$result['blocks'][$section][$section . '__' . $name . '__' . $uniqueCode] = $value;
} else {
$name = $matches[1];
$result['blocks'][$name] = $value;
}
} else {
$result['fields'][$key] = $value;
}
}
return $result;
}
public function hasErrors($field){
$this->initOldInputs();
return (!empty($this->errors[$field]));
}
public function getErrors($field=null)
{
if(is_null($field)){
return $this->errors;
} else if( isset($this->errors[$field]) ){
return $this->errors[$field];
} else {
return [];
}
}
/**
* @return int
*/
public function countAllErrors()
{
$errors = $this->errors;
$func = function($errors, $count = 0) use(&$func){
foreach($errors as $value){
if(is_array($value)){
$count = $func($value, $count);
} elseif($value instanceof ConstraintViolationList) {
$count += $value->count();
}
}
return $count;
};
$count = $func($errors);
return $count;
}
// 入力値を処理する
public function handleRequest(Request $request)
{
$inputs = $request->request->all();
$files = $this->optimizeInputs($request->files->all());
$session = $request->getSession();
$this->oldInputs = $inputs;
$flashBag = $session->getFlashBag();
$flashBag->set('_form_inputs', $inputs);
$oldInputs = $this->optimizeInputs($this->oldInputs);
foreach($this->blocks as $block){
// 変更前の値を退避
$block->stashValues();
$blockName = $block->getNamePrefix() . $block->getName();
if(isset($oldInputs['blocks'][$blockName])){
$block->applySubmittedValues($oldInputs['blocks'][$blockName]);
} else {
$block->resetValues();
}
if(isset($files['blocks'][$blockName])){
$block->applySubmittedFiles($files['blocks'][$blockName]);
} else {
$block->resetFiles();
}
}
// 入力値を検証
$violations = $this->validate($inputs);
return $violations;
}
/**
* 内容を確定させる
* @param string $mode
*/
public function commit($mode = OnCommitEvent::MODE_DEFAULT)
{
$this->dispatcher->dispatch(OnCommitEvent::NAME, new OnCommitEvent($this, $mode));
}
/**
* @return array
*/
public function getData()
{
$data = [];
foreach($this->blocks as $block){
$blockName = $block->getNamePrefix() . $block->getName();
if(!isset($data[$blockName])) $data[$blockName] = array();
// 返却値を展開する場合
if($block->isGroup){
$dataList = $block->getData();
if(!empty($dataList)){
foreach($dataList as $fieldName => $value){
$data[$fieldName] = $value;
}
}
} else {
$data[$blockName] = $block->getData();
}
}
return $data;
}
// データを各ブロックに割り当てる
public function applyData($values)
{
$blockValues = [];
$blocks = $this->getBlocks();
foreach($values as $field => $value){
foreach($blocks as $block){
$boundFields = $block->getBoundFields();
if(in_array($field, $boundFields)){
if($block->isGroup){
$blockValues[$block->getName()][$field] = $value;
} else {
$blockValues[$block->getName()] = $value;
}
}
}
}
foreach($blockValues as $blockName => $values){
if(isset($blocks[$blockName])){
$blocks[$blockName]->applyValues($values);
}
}
return $this;
}
public function readEntityData($entity){
if(!$entity){
return $this;
}
$class = get_class($entity);
$doctrine = $this->container->get('doctrine');
$em = $doctrine->getManager();
$metadata = $em->getClassMetadata($class);
$data = array();
foreach($metadata->fieldNames as $field => $property){
if(is_null($entity->{$property})){
$data[$property] = null;
} else {
$data[$property] = $entity->{$property};
}
}
// アソシエーション
foreach($metadata->associationMappings as $property => $mapping){
$data[$property] = $entity->{$property};
}
$this->applyData($data);
$this->entity = $entity;
return $this;
}
public function generateUniqueCode()
{
return sha1(uniqid(mt_rand(), true));
}
/**
* @return string
*/
public function getThemeDirectory()
{
return $this->themeDirectory;
}
/**
* @param string $themeDirectory
* @return $this
*/
public function setThemeDirectory($themeDirectory)
{
$this->themeDirectory = $themeDirectory;
return $this;
}
public function getTheme()
{
return $this->theme;
}
public function setTheme($theme)
{
$this->theme = $theme;
}
public function initMediaUsageTracker()
{
if($this->container->has('app.media_usage_tracker')){
$this->mediaUsageTracker = $this->container->get('app.media_usage_tracker')->create();
} else {
$this->mediaUsageTracker = null;
}
return $this;
}
public function getMediaUsageTracker()
{
return $this->mediaUsageTracker;
}
public function saveMediaUsages($entityName, $contentId)
{
$tracker = $this->getMediaUsageTracker();
if($tracker){
$tracker->save($entityName, $contentId);
}
return $this;
}
/**
* @param $eventName
* @param $listener
* @param int $priority
* @return $this
*/
public function addEventListener($eventName, $listener, $priority = 0)
{
$this->dispatcher->addListener($eventName, $listener, $priority);
return $this;
}
/**
* @param $eventName
* @param $listener
*/
public function removeEventListener($eventName, $listener)
{
$this->dispatcher->removeListener($eventName, $listener);
}
/**
* @param EventSubscriberInterface $subscriber
* @return $this
*/
public function addEventSubscriber(EventSubscriberInterface $subscriber)
{
$this->dispatcher->addSubscriber($subscriber);
return $this;
}
/**
* @return array
*/
public function getSharedParams(){
return $this->sharedParams;
}
/**
* @param $params
* @return $this
*/
public function setSharedParams($params){
$this->sharedParams = $params;
return $this;
}
/**
* @param $name
* @return bool
*/
public function hasSharedParam($name)
{
return isset($this->sharedParams[$name]);
}
/**
* @param $name
* @param null $default
* @return |null
*/
public function getSharedParam($name, $default=null)
{
if($this->hasSharedParam($name)){
return $this->sharedParams[$name];
} else {
return $default;
}
}
/**
* @param $name
* @param $value
* @return $this
*/
public function setSharedParam($name, $value)
{
$this->sharedParams[$name] = $value;
return $this;
}
/**
* @return ContainerInterface
*/
public function getContainer()
{
return $this->container;
}
/**
* @return null
*/
public function getEntity()
{
return $this->entity;
}
/**
* 他フィールドを参照するバリデーションのためのデータを取得
*
* @return array
*/
public function getValidationData()
{
return $this->validationData;
}
/**
* 他フィールドを参照するバリデーションのためのデータをセット
*
* @param $values
*/
public function setValidationData($values)
{
$this->validationData = $values;
}
}