I want to make a twig template for my custom module that outputs Next and Previous article links.
In my .module file I have
<?php
/**
* #file
* Code for the nextprev module.
*/
function nextprev_theme($existing, $type, $theme, $path) {
return [
'nextprev_template' => [
'variables'=> [
'nextprev' => 'Some_value',
],
],
];
}
In my controller file I use
public function build() {
/**
* {#inheritdoc}
*/
$node = \Drupal::request()->attributes->get('node');
$created_time = $node->getCreatedTime();
$nextprevlinks .= $this->generateNext($created_time);
$nextprevlinks .= $this->generatePrevious($created_time);
$renderable = [
'#theme' => 'nextprev_template',
'#nextprev' => 'nextprevlinks',
];
$rendered = drupal_render($renderable);
}
}
I want to print out my $nextprevlinks in twig as {{ nextprev }}
I've made twig template within my module folder and it works, however I can't print out my {{ nextprev }} variable, it returns Null when I use kint.
I also added nextprev.routing.yml with:
nextprev.block:
path: /node
defaults:
_controller: Drupal\nextprev\Controller\NextPrevLinksBlock::build
requirements:
_permission: 'access content'
This must work:
$renderable = [
'#theme' => 'nextprev_template',
'#nextprev' => $nextprevlinks,
];
But I think, it coild be better, if you return renderable array from controller like this:
return [
'#theme' => 'nextprev_template',
'#nextprev' => $nextprevlinks,
];
Related
Context
I'm trying to change the form type of one field on one of the core entity: Business Unit
The default form field is TextField and I want to change it to ChoiceType.
Here is my custom field on Business Unit entity created with migration :
$table->addColumn('periodicite', 'string', [
'oro_options' => [
'extend' => ['owner' => ExtendScope::OWNER_CUSTOM],
'entity' => ['label' => 'Périodicité'],
],
]);
Issue
I've seen on the Oro documentation that entity_config.yml could solve my problem. I've tried to put these lines but it doesn't work :
entity_config:
business_unit:
entity:
items:
periodicite:
form:
type: Symfony\Component\Form\Extension\Core\Type\ChoiceType
options:
choices:
Mensuel: Mensuel
Trimestriel: Trimestriel
placeholder: false
required: true
label: "Périodicite"
I have also tried to create a new migration to change the field type on my custom field but it doesn't work
<?php
namespace Baltimore\Bundle\AppBundle\Migrations\Schema\v1_1;
use Doctrine\DBAL\Schema\Schema;
use Oro\Bundle\EntityConfigBundle\Migration\UpdateEntityConfigFieldValueQuery;
use Oro\Bundle\EntityExtendBundle\EntityConfig\ExtendScope;
use Oro\Bundle\EntityExtendBundle\Migration\Extension\ExtendExtension;
use Oro\Bundle\EntityExtendBundle\Migration\Extension\ExtendExtensionAwareInterface;
use Oro\Bundle\MigrationBundle\Migration\Migration;
use Oro\Bundle\MigrationBundle\Migration\QueryBag;
use Oro\Bundle\OrganizationBundle\Entity\BusinessUnit;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
class UpdateBusinessUnitField implements Migration, ExtendExtensionAwareInterface
{
/** #var ExtendExtension */
protected $extendExtension;
/**
* #inheritdoc
*/
public function setExtendExtension(ExtendExtension $extendExtension)
{
$this->extendExtension = $extendExtension;
}
public function up(Schema $schema, QueryBag $queries)
{
$queries->addQuery(
new UpdateEntityConfigFieldValueQuery(
BusinessUnit::class,
'periodicite',
'form',
'form_type',
ChoiceType::class
)
);
$queries->addQuery(
new UpdateEntityConfigFieldValueQuery(
BusinessUnit::class,
'periodicite',
'form',
'form_options',
[
'choices' => [
'Mensuel' => 'Mensuel',
'Trimestriel' => 'Trimestriel',
'Annuel' => 'Annuel',
],
]
)
);
}
}
I have found a solution with the changeColumn method in my migration file and it works like a charm.
By the way, these properties works also with the addColumn method.
public function up(Schema $schema, QueryBag $queries)
{
$table = $schema->getTable('oro_business_unit');
$table->changeColumn('periodicite', [
'oro_options' => [
'extend' => ['owner' => ExtendScope::OWNER_CUSTOM],
'entity' => ['label' => 'Périodicité'],
'form' => [
'form_type' => ChoiceType::class,
'form_options' => [
'choices' => [
'Mensuel' => 'Mensuel',
'Trimestriel' => 'Trimestriel',
'Semestriel' => 'Semestriel',
'Annuel' => 'Annuel'
]
]
],
],
]);
}
I don't know about the possibility to override entity config metadata using the YAML file. If there is - please share the documentation you used to implement it in the comments.
But for sure, you can manage the same using the schema migration, like in this example:
class UpdateOpportunityRelationFormType implements Migration
{
/**
* {#inheritdoc}
*/
public function up(Schema $schema, QueryBag $queries)
{
$queries->addQuery(
new UpdateEntityConfigFieldValueQuery(
Quote::class,
'opportunity',
'form',
'form_type',
OpportunitySelectType::class
)
);
$queries->addQuery(
new UpdateEntityConfigFieldValueQuery(
Quote::class,
'opportunity',
'form',
'form_options',
['attr' => ['readonly' => true]]
)
);
}
}
I have this configuration which allows me to create a pdf document in the CRUD, is there a way to add this code in the CRUD easyAdmin or link the CRUD of my EasyAdmin documentos to the CRUD of symfony.
I have problems creating the document in the EasyAdmin table
DocumentController.php
<?php
namespace App\Controller;
use App\Entity\Document;
use App\Form\DocumentType;
use App\Repository\DocumentRepository;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use App\Service\FileUploader;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
/**
* #IsGranted("ROLE_USER")
* #Route("/documents")
*/
class DocumentController extends AbstractController
{
/**
* #Route("/", name="document_index", methods={"GET"})
*/
public function index(DocumentRepository $documentRepository): Response
{
return $this->render('document/index.html.twig', [
'documents' => $documentRepository->findAll([], ['created_at' => 'desc']),
]);
}
/**
* #Route("/new", name="document_new", methods={"GET","POST"})
*/
public function new(Request $request, FileUploader $fileUploader): Response
{
$document = new Document();
$document->setCreatedAt(new \DateTime('now'));
$form = $this->createForm(DocumentType::class, $document);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$entityManager = $this->getDoctrine()->getManager();
$file = $form['fileDocument']->getData();
$originalFilename = pathinfo($file->getClientOriginalName(), PATHINFO_FILENAME);
// this is needed to safely include the file name as part of the URL
$fileName = transliterator_transliterate('Any-Latin; Latin-ASCII; [^A-Za-z0-9_] remove; Lower()', $originalFilename);
$fileName = md5(uniqid()) . '.' . $file->guessExtension();
$file->move(
$this->getParameter('brochures_directory'),
$fileName
);
$document->setFileDocument($fileName);
$entityManager->persist($document);
$entityManager->flush();
return $this->redirectToRoute('document_index', array('id' => $document->getId()));
}
return $this->render('document/new.html.twig', [
// 'document' => $document,
'form' => $form->createView(),
]);
}
/**
* #Route("/{id}", name="document_show", methods={"GET"})
*/
public function show(Document $document): Response
{
return $this->render('document/show.html.twig', [
'document' => $document,
]);
}
/**
* #Route("/{id}/edit", name="document_edit", methods={"GET","POST"})
*/
public function edit(Request $request, Document $document): Response
{
$form = $this->createForm(DocumentType::class, $document);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$file = $form['fileDocument']->getData();
$originalFilename = pathinfo($file->getClientOriginalName(), PATHINFO_FILENAME);
// this is needed to safely include the file name as part of the URL
$fileName = transliterator_transliterate('Any-Latin; Latin-ASCII; [^A-Za-z0-9_] remove; Lower()', $originalFilename);
$fileName = md5(uniqid()) . '.' . $file->guessExtension();
$file->move(
$this->getParameter('brochures_directory'),
$fileName
);
$document->setFileDocument($fileName);
$this->getDoctrine()->getManager()->flush();
return $this->redirectToRoute('document_index');
}
return $this->render('document/edit.html.twig', [
'document' => $document,
'form' => $form->createView(),
]);
}
/**
* #Route("/{id}", name="document_delete", methods={"DELETE"})
*/
public function delete(Request $request, Document $document): Response
{
if ($this->isCsrfTokenValid('delete' . $document->getId(), $request->request->get('_token'))) {
$entityManager = $this->getDoctrine()->getManager();
$entityManager->remove($document);
$entityManager->flush();
}
return $this->redirectToRoute('document_index');
}
}
DocumentCrudController Easy Admin
<?php
namespace App\Controller\Admin;
use App\Entity\Document;
use App\Entity\Publication;
use EasyCorp\Bundle\EasyAdminBundle\Config\Action;
use EasyCorp\Bundle\EasyAdminBundle\Config\Actions;
use EasyCorp\Bundle\EasyAdminBundle\Config\Crud;
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController;
use EasyCorp\Bundle\EasyAdminBundle\Field\DateTimeField;
use EasyCorp\Bundle\EasyAdminBundle\Field\IdField;
use EasyCorp\Bundle\EasyAdminBundle\Field\ImageField;
use EasyCorp\Bundle\EasyAdminBundle\Field\TextareaField;
use EasyCorp\Bundle\EasyAdminBundle\Field\TextField;
use Symfony\Component\Form\Extension\Core\Type\FileType;
use Symfony\Component\Validator\Constraints\File;
class DocumentCrudController extends AbstractCrudController
{
public static function getEntityFqcn(): string
{
return Document::class;
}
public function configureCrud(Crud $crud): Crud
{
return $crud
->setPageTitle(Crud::PAGE_INDEX, 'Liste de Documents')
;
}
public function configureFields(string $pageName): iterable
{
ImageField::new('fileDocument', 'Document PDF')->setFormType(FileType::class)
->setBasePath('docs');
return [
IdField::new('id')->onlyOnIndex(),
TextField::new('nomDocument', 'Titre'),
DateTimeField::new('created_at', 'Date de création'),
TextField::new('fileDocument', 'Document PDF')
->hideOnIndex()
->setFormType(FileType::class, [
'constraints' => [
new File([
'maxSize' => '1024k',
'mimeTypes' => [
'application/pdf',
'application/x-pdf',
],
'mimeTypesMessage' => 'Veuillez télécharger un document PDF valide',
])
],
]),
];
}
public function configureActions(Actions $actions): Actions
{
return $actions
->add(Crud::PAGE_INDEX, Action::DETAIL);
}
}
I don't know how I can implement the same configuration in easy admin.
Look Here this is what happens when i create a document from table EasyAdmin.
Thank you.
That's a little weird but you need to use the ImageField (https://symfony.com/bundles/EasyAdminBundle/current/fields/ImageField.html)
And in you CrudController:
public function configureFields(string $pageName): iterable{
return [
ImageField::new('pdf', 'Your PDF')
->setFormType(FileUploadType::class)
->setBasePath('documents/') //see documentation about ImageField to understand the difference beetwen setBasePath and setUploadDir
->setUploadDir('public/documents/')
->setColumns(6)
->hideOnIndex()
->setFormTypeOptions(['attr' => [
'accept' => 'application/pdf'
]
]),
];
}
See documentation about ImageField to understand the difference beetwen setBasePath and setUploadDir
----- EDIT ----
In your index page of CRUD, you can create a link for your file like this:
public function configureFields(string $pageName): iterable{
return [
ImageField::new('pdf', 'Your PDF')
->setFormType(FileUploadType::class)
->setBasePath('documents/') //see documentation about ImageField to understand the difference beetwen setBasePath and setUploadDir
->setUploadDir('public/documents/')
->setColumns(6)
->hideOnIndex()
->setFormTypeOptions(['attr' => [
'accept' => 'application/pdf'
]
]),
TextField::new('pdf')->setTemplatePath('admin/fields/document_link.html.twig')->onlyOnIndex(),
];
}
Your templates/admin/fields/document_link.html.twig :
{% if field.value %}
Download file
{% else %}
--
{% endif %}
I am trying to add 2 additional form fields to the Wishlist Share form where the user input will be rendered in the email. I have been able to add the fields to the form, but I am not sure how to add the user's input in the email twig template.
Here is how I have updated the form() function:
public function form(array $form, FormStateInterface $form_state) {
$form['#tree'] = TRUE;
$form['#attached']['library'][] = 'core/drupal.dialog.ajax';
// Workaround for core bug #2897377.
$form['#id'] = Html::getId($form_state->getBuildInfo()['form_id']);
$form['to'] = [
'#type' => 'email',
'#title' => $this->t('Recipient Email'),
'#required' => TRUE,
];
// COMBAK my edit
$form['sender_name'] = [
'#type' => 'textfield',
'#title' => $this->t('Your Name'),
'#required' => FALSE,
];
$form['sender_message'] = [
'#type' => 'textarea',
'#title' => $this->t('Your Message'),
'#required' => FALSE,
];
// COMBAK eo my edit
return $form;
}
/**
* {#inheritdoc}
*/
protected function actions(array $form, FormStateInterface $form_state) {
$actions['submit'] = [
'#type' => 'submit',
'#value' => $this->t('Send email'),
'#submit' => ['::submitForm'],
];
if ($this->isAjax()) {
$actions['submit']['#ajax']['callback'] = '::ajaxSubmit';
}
return $actions;
}
/**
* {#inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
/** #var \Drupal\commerce_wishlist\Entity\WishlistInterface $wishlist */
$wishlist = $this->entity;
$to = $form_state->getValue('to');
// COMBAK: my added vars
$sender_name = $form_state->getValue('sender_name');
$sender_message = $form_state->getValue('sender_message');
$this->wishlistShareMail->send($wishlist, $to, $sender_name, $sender_message);
$this->messenger()->addStatus($this->t('Shared the wishlist to #recipient.', [
'#recipient' => $to,
]));
$form_state->setRedirectUrl($wishlist->toUrl('user-form'));
}
This is the function that calls the mailHandler that I have updated:
public function send(WishlistInterface $wishlist, $to, $sender_name, $sender_message) {
$owner = $wishlist->getOwner();
$subject = $this->t('Check out my #site-name wishlist', [
'#site-name' => $this->configFactory->get('system.site')->get('name'),
]);
$body = [
'#theme' => 'commerce_wishlist_share_mail',
'#wishlist_entity' => $wishlist,
// COMBAK: my added vars
'#sender_name' => $sender_name,
'#sender_message' => $sender_message,
];
$params = [
'id' => 'wishlist_share',
'from' => $owner->getEmail(),
'wishlist' => $wishlist,
];
return $this->mailHandler->sendMail($to, $subject, $body, $params);
}
And this is the preprocees function provided by the commerce wishlist module:
function template_preprocess_commerce_wishlist_share_mail(array &$variables) {
/** #var Drupal\commerce_wishlist\Entity\WishlistInterface $wishlist */
$wishlist = $variables['wishlist_entity'];
$wishlist_url = $wishlist->toUrl('canonical', ['absolute' => TRUE]);
$variables['wishlist_url'] = $wishlist_url->toString();
// COMBAK: my added vars
//$sender_name = $variables['sender_name'];
}
And finally the twig template for the email itself:
{#
/**
* #file
* Template for the wishlist share email.
*
* Available variables:
* - wishlist_entity: The wishlist entity.
* - wishlist_url: The wishlist url.
*
* #ingroup themeable
*/
#}
<p>
{% trans %}Check out my wishlist!{% endtrans %}
</p>
<p>
{% trans %}I use my wishlist for keeping track of items I am interested in.{% endtrans %} <br>
{% trans %}To see the list in the store and buy items from it, click here.{% endtrans %}
</p>
<p>
{% trans %}Thanks for having a look!{% endtrans %}
</p>
I haven't been able to figure out how to access the variables I added to the body[] array in the twig template.
Any help would be greatly appreciated.
Thanks!
I want to access a specific product from a homepage :
{{ property.title }}
I specified the route in the controller
/**
* #Route("/property/{slug}-{id}", name="property.show", requirements={"slug": "[a-z0-9\-]*"})
* #param Property $property
* #return Response
*/
public function show(Property $property, string $slug): Response
{
if ($property->getSlug() !== $slug)
{
return $this->redirectToRoute('property.show', [
'id' => $property->getId(),
'slug' => $property->getSlug()
], 301);
}
$property = $this->repository->find($id);
return $this->render('property/show.html.twig', [
'property' => $property,
'current_menu' => 'properties'
]);
but when I click on the link, I get an error "No route found for "GET /my-first-property/property/-1" (from "http://localhost:8000/")"
The URL I'm trying to generate should be /property/my-first-property-1, I don't understand why it doesn't work.
I have been experiencing problems with embedding a controller that creates a form where you can upload files. When the controller is rendered in certain parts of the twig file, I get this error:
An exception has been thrown during the rendering of a template ("Expected argument of type "Symfony\Component\HttpFoundation\File\UploadedFile", "string" given").
This is strange since in other parts of the same twig file, the expected argument is given without problems. The problem seems to be another form in the same twig file that doesn't play nice with my embedded controller form.
The part that seems to cause the problem:
<div id="payment_checkout_form">
{% if cId and shippingRegionId %}
{% set savedPath =path('cart_set_shipping', {'store_id': webstore.id, 'shippingRegion': shippingRegionId,'cId':cId}) %}
{{ form_start(form, {'attr': {'id': 'form_checkout','data-url':savedPath}}) }}
{% else %}
{{ form_start(form, {'attr': {'id': 'form_checkout'}}) }}
{% endif %}
{{ render(url('passport')) }}
Relevent part of my PassportType:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('file', 'file', array('label' => false) , [
'multiple' => true,
'label' => '',
'attr' => [
'accept' => 'image/*',
'multiple' => 'multiple'
]
]
)
->add('confirm', 'submit');
}
public function configureOptions(OptionsResolver $resolver){
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\Passport',
));
}
Relevent part of my Passport entity:
/**
* #Assert\File(maxSize="6000000")
*/
private $file;
/**
* Sets file.
*
* #param Symfony\Component\HttpFoundation\File\UploadedFile $file
*/
public function setFile(UploadedFile $file = null) {
$this->file = $file;
}
Relevent part of my Passport controller
/**
* #Route("/passport", name="passport")
*/
public function createPassportAction(Request $request)
{
$request = $this->get('request_stack')->getMasterRequest();
$passport = new Passport();
$passport->setName('default');
$form = $this->createForm(new PassportType(), $passport);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$files = $request->files->get('passportPhoto');
if (!empty($files)) {
$this->uploadFile($files);
}
}
return $this->render('passport.html.twig', [
'form' => $form->createView(),
'isFormSubmitted' => $form->isSubmitted(),
'passportImages' => $this->getDoctrine()->getRepository('AppBundle\Entity\Passport')->findAll(),
]);
}
{{ render(url('passport')) }} is the embedded controller that renders the file upload form. If I put the{{ render(url('passport')) }} above the form_start of the other form everything works.
Answering my own question:
embedding a form inside another form like I'm trying to do in the question by using render is not possible. I fixed my problem by first removing the render call of my embedded passport form and making my passport type a sub type of the type that is used in the checkout form like this:
public function buildForm(FormBuilderInterface $builder, array $options){
$builder
...
->add('passport', new PassportType(), array(
'required' => true
))
...
}
I still wanted to have the controller of the passport part of my form to be in it's own file. To achieve this I called my passport controller inside of the checkout controller using the forward method like this:
$files = $request->files->get('order')['passport_id'];
$store_id = $request->attributes->get('store_id');
$this->forward('AppBundle\Controller\PassportController::uploadFile',
[ 'files' => $files, 'store_id' => $store_id ]);