D9 Can't change field regions or enable fields in node displays UI (e.g. full, teaser, etc.). Can I enable fields programmatically? - field

I was handed this D9 website and I just trying to enable some fields in node displays (e.g. full, teaser, etc.) and to assign them to regions, but I can't do it. The loading gif goes on forever and when I click submit it goes back to disabled or the original there are a lot of fields on these node displays. This might be a cache problem. I can't figure it out on the backend.
Is there any way I can enable these fields programmatically or in the database?
How do I assign a region to the field in the code below?
/** #var \Drupal\Core\Entity\EntityDisplayRepositoryInterface $display_repository **/
$display_repository = \Drupal::service('entity_display.repository');
// Assign widget settings for the default form mode.
$display_repository->getFormDisplay('node', 'node_type')
->setComponent('field_name', [
'region' => 'content',
])
->save();
// Assign display settings for the 'default' and 'full' view modes.
$display_repository->getViewDisplay('node', 'node_type')
->setComponent('field_name', [
'label' => 'hidden',
'region' => 'content',
])
->save();
$display_repository->getViewDisplay('node', 'node_type', 'full')
->setComponent('field_name', [
'label' => 'hidden',
'type' => 'string_textfield',
])
->save();
If the above is the correct code, what function hook can I use to implement this?

The following worked for me:
module/module.routing.yml
module.fields:
path: '/module/fields'
defaults:
_controller: '\Drupal\module\Controller\ModuleController::fields'
_title: 'Enable fields in node after changing field type'
requirements:
_permission: 'access content'
module/src/Controller/ModuleController.php
namespace Drupal\module\Controller;
use Drupal\Core\Controller\ControllerBase;
/**
* Class ModuleController.
** #package Drupal\lotus\Controller
*/ class ModuleController extends ControllerBase
{
/**
* Fields.
* * #return string
* Return Fields string.
*/
public function fields()
{
$display_repository = \Drupal::service('entity_display.repository');
// Assign widget settings for the default form mode.
$display_repository->getFormDisplay('node', 'bundle')
->setComponent('field_name', [
'region' => 'content',
])
->save();
// Assign display settings for the 'default' and 'full' view modes.
$display_repository->getViewDisplay('node', 'bundle')
->setComponent('field_name', [
'region' => 'content',
])
->save();
$display_repository->getViewDisplay('node', 'bundle', 'full')
->setComponent('field_name', [
'region' => 'content',
])
->save();
return ['#type' => 'markup',
'#markup' => $this->t('Field field_name has been enabled!!'),];
}
}

Related

How to set selected option on choiceType sonata admin bundle

setting default/ selected option for choice field in symfony3 sonata admin bundle?
For example :
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
/**
* #inheritdoc
*/
public function configureFormFields(FormMapper $formMapper) {
parent::configureFormFields($formMapper);
$formMapper->add('type', ChoiceType::class, [
'label' => 'config.label_type',
'choices' => [
'config.label_permanent' => 'permanent',
'config.label_automatic' => 'automatic',
'config.label_temporary' => 'temporary'
],
'required' => false
]);
}
How to make the _permanent_ as selected value ?
This post doesn't help me out
setting default value in symfony2 sonata admin bundle
You can try with something like this:
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
/**
* #inheritdoc
*/
public function configureFormFields(FormMapper $formMapper) {
parent::configureFormFields($formMapper);
$subject = $this->getSubject();
if (null === $subject->getId()) {
$subject->setType('permanent');
}
$formMapper->add('type', ChoiceType::class, [
'label' => 'config.label_type',
'choices' => [
'config.label_permanent' => 'permanent',
'config.label_automatic' => 'automatic',
'config.label_temporary' => 'temporary'
],
'required' => false
]);
}

zf2 form disable select option(s)

Is it possible to disable the options in a select element?
I have a form with a select element that by default has a lot of options available. During form creation, depending on information retrieved from the database, i would like to disable certain options.
Some research came up with
$form->get('selectElement')->setAttribute("disabled", array(0, 1, 2));
...which should disable the first 3 options, but unfortunately does not.
You must use the setAttribute() method to set the attributes of your select element, not its options. For this, you should use setValueOptions():
$myOptions = $form->get('selectElement')->getValueOptions();
foreach ([0, 1, 2] as $value) {
$myOptions [$value]['disabled'] = true ;
}
$form->get('selectElement')->setValueOptions($myOptions);
$myOptionsmust be an array of options:
[
[
'label' => 'My first option',
'disabled' => false,
'value' => 1
],
[
'label' => '2nd option',
'disabled' => false,
'value' => 2
],
[
'label' => '3rd option disabled',
'disabled' => true,
'value' => 3
],
]

PHPUnit ZF2 InputFilter with Custom Validator

I have the following InputFilter:
<?php
namespace Login\InputFilter;
use Zend\InputFilter\InputFilter;
/**
* Class Login
*
* #package Login\InputFilter
*/
class Login extends InputFilter
{
/**
* Construct
*/
public function __construct()
{
/**
* Password
*/
$this->add(
[
'name' => 'password',
'required' => true,
'filters' => [
[
'name' => 'stringtrim'
]
],
'validators' => [
[
'name' => 'stringlength',
'options' => [
'min' => '5',
'max' => '128'
],
'break_chain_on_failure' => true
],
[
'name' => 'regex',
'options' => [
'pattern' => '/^[^\\\' ]+$/'
],
'break_chain_on_failure' => true
]
]
]
);
}
/**
* Init
*/
public function init()
{
/**
* Employee ID
*/
$this->add(
[
'name' => 'employeeId',
'required' => true,
'filters' => [
[
'name' => 'stringtrim'
]
],
'validators' => [
[
'name' => 'stringlength',
'options' => [
'min' => '1',
'max' => '20'
],
'break_chain_on_failure' => true
],
[
'name' => 'digits',
'break_chain_on_failure' => true
],
[
'name' => 'Login\Validator\EmployeeId',
'break_chain_on_failure' => true
]
]
]
);
}
}
Attached to the employeeId is a custom validator I've created to check if the Employee ID actually exists in a database. It has a constructor for Doctrine Entity Manager. This works fine when testing via the web, so no worries there.
However now I would like to test via PHPUnit and I've created the following test:
<?php
namespace LoginTest\InputFilter;
use Login\InputFilter\Login;
/**
* Class LoginTest
*
* #package LoginTest\InputFilter
*/
class LoginTest extends \PHPUnit_Framework_TestCase
{
/**
* #var Login $inputFilter
*/
protected $inputFilter;
public function setUp()
{
$this->inputFilter = new Login();
$this->inputFilter->init();
parent::setUp();
}
public function testFormHasElements()
{
$inputs = $this->inputFilter->getInputs();
$this->assertArrayHasKey(
'employeeId',
$inputs
);
$this->assertArrayHasKey(
'password',
$inputs
);
}
}
When the test runs the following error is produced:
1) LoginTest\InputFilter\LoginTest::testFormHasElements
Argument 1 passed to Login\Validator\EmployeeId::__construct() must be an instance of Doctrine\ORM\EntityManager, none given, called in /vhosts/admin-application/vendor/zendframework/zendframework/library/Zend/ServiceManager/AbstractPluginManager.php on line 180 and defined
I'm not certain how I can get passed this particular error. I assume I need to use Mockery but I'm not certain.
The validator has a Factory which supplies the Doctrine Entity Manager from the Service Locator.
I am still very new to PHPUnit but I've been trying to do my research before asking here.
Any ideas?
You're getting this error because you directly instantiate you input filter and it isn't then aware of your custom validator factory.
In real application InputFilter is using Zend\Validator\ValidatorPluginManager for getting validators from service manager.
I see two ways how to solve this problem:
1.) You can setup real service manager from application configuration, like it's described in documentation and then pull the input filter from service manager:
$inputFilter = Bootstrap::getServiceManager()->get(\Login\InputFilter\Login::class); // change the service name if you have another
This solution is good if you want to write some kind of integration tests.
2.) You can mock your custom validator and inject into ValidatorPluginManager in setup method:
protected function setUp()
{
$validator = $this->getMockBuilder(\Login\Validator\EmployeeId::class)->getMock();
$inputFilter = new Login();
$inputFilter->getFactory()
->getDefaultValidatorChain()
->getPluginManager()
->setService(\Login\Validator\EmployeeId::class, $validator);
$inputFilter->init();
$this->inputFilter = $inputFilter;
parent::setUp();
}
This solution is good if you want to write unit tests for Login input filter.

ZF2 Validator Context when using a Fieldset or Collection

Is it possible to pass the full form as the context to a validator?
I would like to create a conditional validator for element X in fieldset A which checks the value of element Y in a different fieldset B.
The problem is that the isValid function only receives the context for the fieldset it is in. This element X knows nothing about element Y.
All answers greatly received!
You can do this with collections and ZendCollectionInputFilter yeah.
There's not like loads of documentation for this, know the zend guys are sorting this out though (think the only mention of it is in http://framework.zend.com/apidoc/2.2/classes/Zend.InputFilter.CollectionInputFilter.html) but for now a resource that really helped me was this:
http://www.aronkerr.com/2013/11/zf2-form-collection-validation-unique.html
Very clever stuff once you get your head round these. Can't really give you much more help as your question isn't massively specific and there is no code for your form, fieldsets and input filters that you currently have implmented but hope this helps. If you get stuck at any point more than happy to run through more specific code
Let's say our fieldsets A and B belong to the form Sample. We need to add the validators from this parent form in order to access the context of this form when validating any child fieldsets:
<?php
namespace App\Form;
use Zend\Form\Form;
use Zend\InputFilter\InputFilterProviderInterface;
class Sample extends Form InputFilterProviderInterface
{
public function init()
{
$this->add([
'type' => 'App:Fieldset:A',
'name' => 'fieldsetA',
]);
$this->add([
'type' => 'App:Fieldset:B',
'name' => 'fieldsetB',
]);
$this->add([
'type' => 'submit',
'name' => 'submit',
'attributes' => [
'value' => 'Submit',
],
]);
}
public function getInputFilterSpecification()
{
return [
'fieldsetA' => [
'type' => 'InputFilter',
'X' => [
'required' => true,
'allow_empty' => true,
'continue_if_empty' => true,
'validators' => [
[
'name' => 'Callback',
'options' => [
'callback' => function ($value)
{
if ($this->data['fieldsetB']['Y'])
{
// do something
}
// do something else
},
],
],
],
],
],
];
}
}
Notice how we add validators to X in A from within Sample using the InputFilter type. Next we access $this->data directly and traverse it to get Y in B.

How can I add a violation to a collection?

My form looks like this:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$factory = $builder->getFormFactory();
$builder->add('name');
$builder->add('description');
$builder->add('manufacturers', null, array(
'required' => false
));
$builder->add('departments', 'collection', array(
'type' => new Department
));
}
I have a class validator on the entity the form represents which calls:
if (!$valid) {
$this->context->addViolationAtSubPath('departments', $constraint->message);
}
Which will only add a 'global' error to the form, not an error at the sub path. I assume this is because departments is a collection embedding another FormType.
If I changed departments to one of the other fields it works fine.
How can I get this error to appear in the right place? I assume it would work fine if my error was on a single entity within the collection, and thus rendered in the child form, but my criteria is that the violation occur if none of the entities in the collection are marked as active, thus it needs to be at the parent level.
By default, forms have the option "error_bubbling" set to true, which causes the behavior you just described. You can turn off this option for individual forms if you want them to keep their errors.
$builder->add('departments', 'collection', array(
'type' => new Department,
'error_bubbling' => false,
));
I have been wrestling with this issue in Symfony 3.3, where I wished to validate an entire collection, but pass the error to the appropriate collection element/field. The collection is added to the form thus:
$form->add('grades', CollectionType::class,
[
'label' => 'student.grades.label',
'allow_add' => true,
'allow_delete' => true,
'entry_type' => StudentGradeType::class,
'attr' => [
'class' => 'gradeList',
'help' => 'student.grades.help',
],
'entry_options' => [
'systemYear' => $form->getConfig()->getOption('systemYear'),
],
'constraints' => [
new Grades(),
],
]
);
The StudentGradeType is:
<?php
namespace Busybee\Management\GradeBundle\Form;
use Busybee\Core\CalendarBundle\Entity\Grade;
use Busybee\Core\SecurityBundle\Form\DataTransformer\EntityToStringTransformer;
use Busybee\Core\TemplateBundle\Type\SettingChoiceType;
use Busybee\Management\GradeBundle\Entity\StudentGrade;
use Busybee\People\StudentBundle\Entity\Student;
use Doctrine\Common\Persistence\ObjectManager;
use Doctrine\ORM\EntityRepository;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class StudentGradeType extends AbstractType
{
/**
* #var ObjectManager
*/
private $om;
/**
* StaffType constructor.
*
* #param ObjectManager $om
*/
public function __construct(ObjectManager $om)
{
$this->om = $om;
}
/**
* {#inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('status', SettingChoiceType::class,
[
'setting_name' => 'student.enrolment.status',
'label' => 'grades.label.status',
'placeholder' => 'grades.placeholder.status',
'attr' => [
'help' => 'grades.help.status',
],
]
)
->add('student', HiddenType::class)
->add('grade', EntityType::class,
[
'class' => Grade::class,
'choice_label' => 'gradeYear',
'query_builder' => function (EntityRepository $er) {
return $er->createQueryBuilder('g')
->orderBy('g.year', 'DESC')
->addOrderBy('g.sequence', 'ASC');
},
'placeholder' => 'grades.placeholder.grade',
'label' => 'grades.label.grade',
'attr' => [
'help' => 'grades.help.grade',
],
]
);
$builder->get('student')->addModelTransformer(new EntityToStringTransformer($this->om, Student::class));
}
/**
* {#inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver
->setDefaults(
[
'data_class' => StudentGrade::class,
'translation_domain' => 'BusybeeStudentBundle',
'systemYear' => null,
'error_bubbling' => true,
]
);
}
/**
* {#inheritdoc}
*/
public function getBlockPrefix()
{
return 'grade_by_student';
}
}
and the validator looks like:
namespace Busybee\Management\GradeBundle\Validator\Constraints;
use Busybee\Core\CalendarBundle\Entity\Year;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
class GradesValidator extends ConstraintValidator
{
public function validate($value, Constraint $constraint)
{
if (empty($value))
return;
$current = 0;
$year = [];
foreach ($value->toArray() as $q=>$grade)
{
if (empty($grade->getStudent()) || empty($grade->getGrade()))
{
$this->context->buildViolation('student.grades.empty')
->addViolation();
return $value;
}
if ($grade->getStatus() === 'Current')
{
$current++;
if ($current > 1)
{
$this->context->buildViolation('student.grades.current')
->atPath('['.strval($q).']') // could do a single atPath with a value of "[".strval($q)."].status"
->atPath('status') // full path = children['grades'].data[1].status
->addViolation();
return $value;
}
}
$gy = $grade->getGradeYear();
if (! is_null($gy))
{
$year[$gy] = empty($year[$gy]) ? 1 : $year[$gy] + 1 ;
if ($year[$gy] > 1)
{
$this->context->buildViolation('student.grades.year')
->atPath('['.strval($q).']')
->atPath('grade')
->addViolation();
return $value;
}
}
}
}
}
This results in the error being added to the field in the element of the collection as per the attach image.
Craig
I have a case very similar. I have a CollectionType with a Custom Form (with DataTransformers inside, etc...), i need check one by one the elements and mark what of them is wrong and print it on the view.
I make that solution at the ConstraintValidator (my custom validator):
The validator must target to CLASS_CONSTRAINT to work or the propertyPath doesnt work.
public function validate($value, Constraint $constraint) {
/** #var Form $form */
$form = $this->context->getRoot();
$studentsForm = $form->get("students"); //CollectionType's name in the root Type
$rootPath = $studentsForm->getPropertyPath()->getElement(0);
/** #var Form $studentForm */
foreach($studentsForm as $studentForm){
//Iterate over the items in the collection type
$studentPath = $studentForm->getPropertyPath()->getElement(0);
//Get the data typed on the item (in my case, it use an DataTransformer and i can get an User object from the child TextType)
/** #var User $user */
$user = $studentForm->getData();
//Validate your data
$email = $user->getEmail();
$user = $userRepository->findByEmailAndCentro($email, $centro);
if(!$user){
//If your data is wrong build the violation from the propertyPath getted from the item Type
$this->context->buildViolation($constraint->message)
->atPath($rootPath)
->atPath(sprintf("[%s]", $studentPath))
->atPath("email") //That last is the name property on the item Type
->addViolation();
}
}
}
Just i validate agains the form elements in the collection and build the violation using the propertyPath from the item in the collection that is wrong.

Resources