diff --git a/Admin/ProjectAdmin.php b/Admin/ProjectAdmin.php
index 4d6ed64..e8d7ff2 100644
--- a/Admin/ProjectAdmin.php
+++ b/Admin/ProjectAdmin.php
@@ -25,15 +25,26 @@ public function __construct($code, $class, $baseControllerName)
protected function configureFormFields(FormMapper $formMapper)
{
$formMapper
- ->add('name')
- ->add('slug')
+ ->with('General')
+ ->add('name')
+ ->add('slug')
+ ->add('description')
+ ->add('translations', 'project_translations', array(
+ 'by_reference' => false,
+ 'attr' => array(
+ 'class' => 'project-translations',
+ ),
+ 'locales' => array('uk', 'en')
+ ))
+
->add('url')
- ->add('description')
- ->add('imageFile', 'file', array('required' => false, 'data_class' => 'Symfony\Component\HttpFoundation\File\File'))
- ->add('date', 'date')
- ->add('categories')
- ->add('users')
- ->add('onFrontPage', 'checkbox', array('required' => false))
+ ->with('Options')
+ ->add('imageFile', 'file', array('required' => false, 'data_class' => 'Symfony\Component\HttpFoundation\File\File'))
+ ->add('date', 'date')
+ ->add('categories')
+ ->add('users')
+ ->add('onFrontPage', 'checkbox', array('required' => false))
+ ->end();
;
}
@@ -44,6 +55,9 @@ protected function configureListFields(ListMapper $listMapper)
->addIdentifier('slug')
->add('name')
->add('date')
+ ->add('translations', 'text', array(
+ 'template' => 'StfalconPortfolioBundle:ProjectAdmin:list_translations_field.html.twig'
+ ))
;
}
diff --git a/Controller/CategoryController.php b/Controller/CategoryController.php
index 6fd5af0..962e138 100644
--- a/Controller/CategoryController.php
+++ b/Controller/CategoryController.php
@@ -24,7 +24,7 @@ class CategoryController extends Controller
*
* @return array
* @Route(
- * "/portfolio/{slug}/{page}",
+ * "{slug}/{page}",
* name="portfolio_category_view",
* requirements={"page" = "\d+"},
* defaults={"page" = "1"}
diff --git a/Controller/ProjectController.php b/Controller/ProjectController.php
index 012e7cf..e687911 100644
--- a/Controller/ProjectController.php
+++ b/Controller/ProjectController.php
@@ -22,7 +22,7 @@ class ProjectController extends Controller
* @param string $projectSlug Slug of project
*
* @return array
- * @Route("/portfolio/{categorySlug}/{projectSlug}", name="portfolio_project_view")
+ * @Route("/{categorySlug}/{projectSlug}", name="portfolio_project_view")
* @Template()
*/
public function viewAction($categorySlug, $projectSlug)
diff --git a/Entity/Project.php b/Entity/Project.php
index 1783d98..173a0c3 100644
--- a/Entity/Project.php
+++ b/Entity/Project.php
@@ -3,12 +3,14 @@
namespace Stfalcon\Bundle\PortfolioBundle\Entity;
use Doctrine\Common\Collections\ArrayCollection;
+use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
+use Gedmo\Mapping\Annotation as Gedmo;
+use Gedmo\Translatable\Translatable;
+use Imagine;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\Validator\Constraints as Assert;
use Vich\UploaderBundle\Mapping\Annotation as Vich;
-use Gedmo\Mapping\Annotation as Gedmo;
-use Imagine;
/**
* Project entity
@@ -16,8 +18,9 @@
* @ORM\Table(name="portfolio_projects")
* @ORM\Entity(repositoryClass="Stfalcon\Bundle\PortfolioBundle\Repository\ProjectRepository")
* @Vich\Uploadable
+ * @Gedmo\TranslationEntity(class="Stfalcon\Bundle\PortfolioBundle\Entity\ProjectTranslation")
*/
-class Project
+class Project implements Translatable
{
/**
@@ -34,6 +37,7 @@ class Project
*
* @Assert\NotBlank()
* @Assert\MinLength(3)
+ * @Gedmo\Translatable(fallback=true)
* @ORM\Column(name="name", type="string", length=255)
*/
private $name;
@@ -52,6 +56,7 @@ class Project
*
* @Assert\NotBlank()
* @Assert\MinLength(10)
+ * @Gedmo\Translatable(fallback=true)
* @ORM\Column(name="description", type="text")
*/
private $description;
@@ -145,13 +150,35 @@ class Project
private $users;
/**
- * Initialization properties for new project entity
+ * @var ArrayCollection
*
- * @return void
+ * @ORM\OneToMany(targetEntity="ProjectTranslation", mappedBy="object", cascade={"persist", "remove"})
+ */
+ protected $translations;
+
+ /**
+ * @var string $locale
+ *
+ * Required for Translatable behaviour
+ * @Gedmo\Locale
+ */
+ protected $locale;
+
+ /**
+ * Initialization properties for new project entity
*/
public function __construct()
{
- $this->categories = new ArrayCollection();
+ $this->categories = new ArrayCollection();
+ $this->translations = new ArrayCollection();
+ }
+
+ /**
+ * @return string
+ */
+ public function __toString()
+ {
+ return $this->getName();
}
/**
@@ -189,7 +216,7 @@ public function addCategory(Category $category)
/**
* Set categories collection to project
*
- * @param \Doctrine\Common\Collections\Collection $categories Categories collection
+ * @param Collection $categories Categories collection
*
* @return void
*/
@@ -486,4 +513,39 @@ public function getImageFile()
{
return $this->imageFile;
}
+
+ /**
+ * @return ArrayCollection
+ */
+ public function getTranslations()
+ {
+ return $this->translations;
+ }
+
+ /**
+ * @param ProjectTranslation $projectTranslation
+ */
+ public function addTranslation(ProjectTranslation $projectTranslation)
+ {
+ if (!$this->translations->contains($projectTranslation)) {
+ $this->translations->add($projectTranslation);
+ $projectTranslation->setObject($this);
+ }
+ }
+
+ /**
+ * @param ProjectTranslation $projectTranslation
+ */
+ public function removeTranslation(ProjectTranslation $projectTranslation)
+ {
+ $this->translations->removeElement($projectTranslation);
+ }
+
+ /**
+ * @param Collection $translations
+ */
+ public function setTranslations(Collection $translations)
+ {
+ $this->translations = $translations;
+ }
}
\ No newline at end of file
diff --git a/Entity/ProjectTranslation.php b/Entity/ProjectTranslation.php
new file mode 100644
index 0000000..b4e6729
--- /dev/null
+++ b/Entity/ProjectTranslation.php
@@ -0,0 +1,43 @@
+setLocale($locale);
+ $this->setField($field);
+ $this->setContent($content);
+ }
+
+ /**
+ * @ORM\ManyToOne(targetEntity="Project", inversedBy="translations")
+ * @ORM\JoinColumn(name="object_id", referencedColumnName="id", onDelete="CASCADE")
+ */
+ protected $object;
+
+ function __toString()
+ {
+ return $this->getLocale();
+ }
+}
+
diff --git a/Form/TranslationLocaleType.php b/Form/TranslationLocaleType.php
new file mode 100644
index 0000000..fb626fb
--- /dev/null
+++ b/Form/TranslationLocaleType.php
@@ -0,0 +1,55 @@
+ $type) {
+ $builder->add($field, $type, array(
+ 'label' => ucfirst($field),
+ 'required' => false,
+ 'attr' => array(
+ 'class' => 'span5'
+ )
+ ));
+ }
+ }
+
+ /**
+ * @param OptionsResolverInterface $resolver
+ */
+ public function setDefaultOptions(OptionsResolverInterface $resolver)
+ {
+ $resolver->setDefaults(array(
+ 'error_bubbling' => true,
+ 'fields' => array(),
+ ));
+ }
+
+ /**
+ * Get form name
+ *
+ * @return string
+ */
+ public function getName()
+ {
+ return 'project_translation_locale';
+ }
+}
diff --git a/Form/TranslationsType.php b/Form/TranslationsType.php
new file mode 100644
index 0000000..fadb025
--- /dev/null
+++ b/Form/TranslationsType.php
@@ -0,0 +1,228 @@
+em = $em;
+ $this->annotationReader = $annotationReader;
+ $this->translatableListener = $translatableListener;
+ }
+
+ /**
+ * Builds the form.
+ *
+ * This method is called for each type in the hierarchy starting form the
+ * top most type. Type extensions can further modify the form.
+ *
+ * @see FormTypeExtensionInterface::buildForm()
+ *
+ * @param FormBuilderInterface $builder The form builder
+ * @param array $options The options
+ */
+ public function buildForm(FormBuilderInterface $builder, array $options)
+ {
+
+ $projectTranslationClass = $builder->getParent()->getDataClass();
+
+ $configuration = $this->translatableListener->getConfiguration($this->em, $projectTranslationClass);
+
+ $projectClass = $configuration['useObjectClass'];
+
+ $fields = array();
+ foreach ($configuration['fields'] as $field) {
+ $annotations = $this->annotationReader->getPropertyAnnotations(new \ReflectionProperty($projectClass, $field));
+ $mappingColumn = array_filter($annotations, function($item)
+ {
+ return $item instanceof \Doctrine\ORM\Mapping\Column;
+ });
+ $mappingColumnCurrent = current($mappingColumn);
+ // Convert field type
+ switch ($mappingColumnCurrent->type) {
+ case 'string':
+ $fields[$field] = 'text';
+ break;
+ case 'text':
+ $fields[$field] = 'textarea';
+ break;
+ }
+ }
+
+ // Build sub form for the each locale
+ foreach ($options['locales'] as $locale) {
+ $builder->add($locale, 'project_translation_locale', array(
+ 'fields' => $fields
+ ));
+ }
+
+ //Add event listeners
+ $this->preSetEventHandler($builder);
+
+ $this->bindEventHandler($builder, $configuration);
+ }
+
+ /**
+ * @param FormView $view
+ * @param FormInterface $form
+ * @param array $options
+ */
+ public function buildView(FormView $view, FormInterface $form, array $options)
+ {
+ $view->vars['locales'] = $options['locales'];
+ }
+
+ /**
+ * @param OptionsResolverInterface $resolver
+ */
+ public function setDefaultOptions(OptionsResolverInterface $resolver)
+ {
+ $resolver->setDefaults(array(
+ 'locales' => array()
+ ));
+ }
+
+ /**
+ * @return string
+ */
+ public function getName()
+ {
+ return 'project_translations';
+ }
+
+
+ /**
+ * Bind event handler
+ *
+ * @param FormBuilderInterface $builder
+ * @param array $configuration
+ */
+ private function bindEventHandler(FormBuilderInterface $builder, $configuration)
+ {
+ $builder->addEventListener(FormEvents::BIND, function(FormEvent $event) use ($builder, $configuration)
+ {
+ $form = $event->getForm();
+ $data = $event->getData();
+
+ if (is_array($data)) {
+ $data = new ArrayCollection();
+
+ } else {
+ // Remove new elements with wrong format
+ foreach ($data as $key => $d) {
+ if (!is_numeric($key)) {
+ $data->removeElement($d);
+ }
+ }
+ }
+
+ // Add/Update new elements with right format
+ $newData = new ArrayCollection();
+ foreach ($form->getChildren() as $translationsLocaleForm) {
+ $locale = $translationsLocaleForm->getName();
+ foreach ($translationsLocaleForm->getChildren() as $translation) {
+ $field = $translation->getName();
+ $content = $translation->getData();
+
+ $existingTranslationEntity = $data->filter(function($entity) use ($locale, $field)
+ {
+ return ($entity->getLocale() === $locale && $entity->getField() === $field);
+ })->first();
+
+ if ($existingTranslationEntity) {
+ $existingTranslationEntity->setContent($content);
+ $newData->add($existingTranslationEntity);
+ } else {
+ $translationEntity = new ProjectTranslation();
+ $translationEntity->setLocale($locale);
+ $translationEntity->setField($field);
+ $translationEntity->setContent($content);
+ $newData->add($translationEntity);
+ }
+ }
+ }
+
+ $event->setData($newData);
+ });
+ }
+
+ /**
+ * Pre-set event handler
+ *
+ * @param FormBuilderInterface $builder
+ */
+ private function preSetEventHandler(FormBuilderInterface $builder)
+ {
+ $builder->addEventListener(FormEvents::PRE_SET_DATA, function(FormEvent $event) use ($builder)
+ {
+ $form = $event->getForm();
+ $data = $event->getData();
+
+ if (is_null($data)) {
+ return;
+ }
+
+ // Sort by locales and fields
+ $dataLocale = array();
+ foreach ($data as $item) {
+ if (!isset($dataLocale[$item->getLocale()])) {
+ $dataLocale[$item->getLocale()] = new ArrayCollection();
+ }
+ $dataLocale[$item->getLocale()][$item->getField()] = $item;
+ }
+
+ foreach ($form->getChildren() as $translationFields) {
+ $locale = $translationFields->getName();
+ if (isset($dataLocale[$locale])) {
+ foreach ($translationFields as $translationField) {
+ $field = $translationField->getName();
+ if (isset($dataLocale[$locale][$field])) {
+ $translationField->setData($dataLocale[$locale][$field]->getContent());
+ }
+ }
+ }
+ }
+ });
+ }
+}
+
diff --git a/Resources/config/routing.yml b/Resources/config/routing.yml
index 5d9da99..2a91678 100644
--- a/Resources/config/routing.yml
+++ b/Resources/config/routing.yml
@@ -1,7 +1,8 @@
-_portfolio_category:
- resource: "@StfalconPortfolioBundle/Controller/CategoryController.php"
+_portfolio:
+ resource: "@StfalconPortfolioBundle/Controller"
type: annotation
-
-_portfolio_project:
- resource: "@StfalconPortfolioBundle/Controller/ProjectController.php"
- type: annotation
\ No newline at end of file
+ prefix: /{_locale}/portfolio
+ requirements:
+ _locale: ru|uk|en
+ defaults:
+ _locale: %locale%
\ No newline at end of file
diff --git a/Resources/config/services.xml b/Resources/config/services.xml
index 2242127..fffafd2 100644
--- a/Resources/config/services.xml
+++ b/Resources/config/services.xml
@@ -10,6 +10,9 @@
Stfalcon\Bundle\PortfolioBundle\Admin\ProjectAdmin
Stfalcon\Bundle\PortfolioBundle\Entity\Project
+
+ Stfalcon\Bundle\PortfolioBundle\Form\TranslationsType
+ Stfalcon\Bundle\PortfolioBundle\Form\TranslationLocaleType
@@ -31,6 +34,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Resources/views/ProjectAdmin/list_translations_field.html.twig b/Resources/views/ProjectAdmin/list_translations_field.html.twig
new file mode 100644
index 0000000..caa9ae7
--- /dev/null
+++ b/Resources/views/ProjectAdmin/list_translations_field.html.twig
@@ -0,0 +1,9 @@
+{% extends 'SonataAdminBundle:CRUD:base_list_field.html.twig' %}
+
+{%- block field %}
+ {% if value %}
+ {% for locale in value if locale.field == 'name' %}
+ {{ locale|upper }}
+ {% endfor %}
+ {% endif %}
+{% endblock -%}
diff --git a/Resources/views/public/admin.css b/Resources/views/public/admin.css
new file mode 100644
index 0000000..e69de29