Customising filter for ModelAdmin to support date range in SilverStripe - silverstripe

I am developing a SilverStripe project. I am now struggling with customizing the filter/ search for the ModelAdmin entities, https://silverstripe.org/learn/lessons/v4/introduction-to-modeladmin-1. I am trying to add a date range filter as follows.
As you can see there are from and to fields. I have a class called Property and I am trying to customize the search/ filter for the CMS as follow to support the date range filtering.
class Property extends DataObject
{
public function searchableFields()
{
return [
//other fields go here
'Created' => [
'filter' => 'GreaterThanOrEqualFilter',
'title' => 'From',
'field' => DateField::class
],
'Created' => [
'filter' => 'To',
'title' => 'Decision date until',
'field' => DateField::class
],
];
}
}
Only one field is added to the pop up because the array key is overridden. How can I configure it to have the two date fields to specify the date range for the search form?

It might not be relevant now but I bumped to this issue today and I tried your code, you are right, only one field is created I think because you are using single DateField::class. I tried to look for a module that creates a Date Range field and I only can find this one but it looks like it's a project specific.
In my case I have 2 date fields (created and ended), using your code I can get good results by tweaking it to something like this:
public function searchableFields()
{
return [
//other fields go here
'StartDate' => [
'filter' => 'GreaterThanOrEqualFilter',
'title' => 'From',
'field' => DateField::class
],
'EndDate' => [
'filter' => 'LessThanOrEqualFilter',
'title' => 'To',
'field' => DateField::class
],
];
}
Hope it helps someone.

Using this example DataObject create a custom update function updateAdminSearchFields...
app/src/Test/MyDataObject.php
namespace MyVendor\MyNamespace;
use SilverStripe\Forms\DateField;
use SilverStripe\ORM\DataObject;
class MyDataObject extends DataObject {
private static $db = [
'Title' => 'Varchar',
'MyDateTimeField' => 'DBDatetime'
];
private static $summary_fields = ['Title','MyDateTimeField'];
public function updateAdminSearchFields($fields) {
$fields->removeByName('MyDateTimeField');//needed as added in summary field
$fields->push(DateField::create('MyDateTimeField:GreaterThanOrEqual', 'MyDateTimeField (Start)'));
$fields->push(DateField::create('MyDateTimeField:LessThanOrEqual', 'MyDateTimeField (End)'));
}
}
Then create an extension that can link that to a ModelAdmin...
app/src/Test/MyAdminExtension.php
namespace MyVendor\MyNamespace;
use SilverStripe\ORM\DataExtension;
class MyAdminExtension extends DataExtension {
public function updateSearchContext($context) {
$class = $context->getQuery([])->dataClass();
if (method_exists($class, 'updateAdminSearchFields'))
(new $class)->updateAdminSearchFields($context->getFields());
return $context;
}
}
app/_config/mysite.yml
MyVendor\MyNamespace\MyAdmin:
extensions:
- MyVendor\MyNamespace\MyAdminExtension
Lastly on the ModelAdmin apply these filters...
app/src/Test/MyAdmin.php
namespace MyVendor\MyNamespace;
use SilverStripe\Admin\ModelAdmin;
class MyAdmin extends ModelAdmin {
private static $menu_title = 'MyAdmin';
private static $url_segment = 'myadmin';
private static $managed_models = [MyDataObject::class];
public function getList() {
$list = parent::getList();
if ($params = $this->getRequest()->requestVar('filter'))
if ($filters = $params[$this->sanitiseClassName($this->modelClass)])
return $list->filter($filters);
return $list;
}
}
This example is working on latest stable version 4.7.2

Related

How to Update Order custom field in Shopware 6 during order process?

I want to add a selector for date during order process in checkout. In which step should the custom field of an order be updated? And how is the custom field update done for order or other entity?
I want to add the fields as it is shown in official docs.
$this->customFieldSetRepository->create([
[
'name' => 'swag_example',
'customFields' => [
['name' => 'swag_example_size', 'type' => CustomFieldTypes::INT],
['name' => 'swag_example_color', 'type' => CustomFieldTypes::TEXT]
]
]
], $context);
Where exactly do you want to add that data during the order process?
One example would be from confirm to finish -> the cart is converted to an order.
You just need to create a Subscriber for that:
<?php
class OrderProcessSubscriber implements EventSubscriberInterface
{
public static function getSubscribedEvents()
{
return [
CartConvertedEvent::class => 'addCustomFieldsToConvertedCart',
];
}
public function addCustomFieldsToConvertedCart(CartConvertedEvent $event)
{
$convertedCart['customFields']['my_custom_field'] = 'test';
$event->setConvertedCart($convertedCart);
}
}
?>

How to modify Product in Silvershop (adding custom fields to $db)

I'm currently developing a shop using SilverShop. I want to add some specific fields to my products, such as what fabric my clothes are made of and an image. I know that we should not make these changes in the core SilverShop source code.
Should I extend the Product class in a new file such as app/src/ProductPage.php?
class Product extends Page implements Buyable
{
private static $db = [
'InternalItemID' => 'Varchar(30)', //ie SKU, ProductID etc (internal / existing recognition of product)
'Model' => 'Varchar(30)',
'BasePrice' => 'Currency(19,4)', // Base retail price the item is marked at.
//physical properties
// TODO: Move these to an extension (used in Variations as well)
'Weight' => 'Decimal(12,5)',
'Height' => 'Decimal(12,5)',
'Width' => 'Decimal(12,5)',
'Depth' => 'Decimal(12,5)',
'Featured' => 'Boolean',
'AllowPurchase' => 'Boolean',
'Popularity' => 'Float' //storage for CalculateProductPopularity task
];
...
Use DataExtension
For SilverStripe 4, it will be something like:
ProductExtension.php :
use SilverStripe\ORM\DataExtension;
use SilverStripe\Forms\FieldList;
use SilverStripe\Forms\TextField;
class ProductExtension extends DataExtension
{
private static $db = [
'NewField' => 'Varchar(255)'
];
public function updateCMSFields(FieldList $fields)
{
$fields->addFieldsToTab('Root.Main', TextField::create('NewField', 'This is new field'));
}
}
And, add the next lines to mysite.yml
SilverShop\Page\Product:
extensions:
- ProductExtension
dev/build and it's done

How to extend Silverstripe Blog module?

I'm looking a using Siverstripe's blog module for a project. The blog has most of the features I want, but as the site is mostly book focused I's like to add some fields to the blogpost table to hold book data (title, author, rating, etc. It seems like this should be relatively simple but I can't seem to get it to work. I've created the following extension PHP file:
namespace SilverStripe\Blog\Model;
use SilverStripe\Blog\Model\BlogPost;
use SilverStripe\ORM\DataExtension;
use SilverStripe\Forms\FieldList;
use SilverStripe\Forms\TextField;
use SilverStripe\Forms\TextareaField;
class BookDataExtension extends BlogPost
{
private static $db = [
'bookTitle' => 'Varchar',
'bookAuthor' => 'Varchar',
'bookSeries' => 'Varchar',
'bookISBN' => 'Varchar',
'bookSeriesNum' => 'Int',
'bookRating' => 'Decimal',
'bookCover' => 'Varchar'
];
}
And added the following to the mysite.yml file:
SilverStripe\Blog\BlogPost:
extensions:
- SilverStripe\Blog\BookDataExtension
I also tried adding the above to the config.yml file for the blog module itself. However, no matter what I try, when I rebuild the system it creates new table(s) for BookDataExtension rather than adding the fields to the BlogPost table. What am I doing wrong?
you subclassed BlogPost instead of plugging an extension to it, aka. extending it...
Your BlogPostExtension has to subclass DataExtension; it can be in your own namespace:
namespace MyProject\Extensions;
use SilverStripe\ORM\DataExtension;
class BookDataExtension extends DataExtension
{
private static $db = [
'bookTitle' => 'Varchar',
'bookAuthor' => 'Varchar',
'bookSeries' => 'Varchar',
'bookISBN' => 'Varchar',
'bookSeriesNum' => 'Int',
'bookRating' => 'Decimal',
'bookCover' => 'Varchar'
];
}
Then you can configure BlogPost to add your extension like you did before:
SilverStripe\Blog\BlogPost:
extensions:
- MyProject\Extensions\BookDataExtension
try this:
<?php
namespace {
use SilverStripe\ORM\DataExtension;
use SilverStripe\Forms\FieldList;
class BookDataExtension extends DataExtension {
private static $db = [
'db_field_example' => 'Varchar'
];
public function updateCMSFields(FieldList $fields) {
// Add fields here
}
}
}
add your extension to app/src/extensions/
and in your config:
SilverStripe\Blog\Model\BlogPost:
extensions:
- BookDataExtension

Silverstripe Elemental trouble after creating related dataobjects

Facts: SS 4.0.1, dnadesign/silverstripe-elemental 2.x-dev, php 7.1 and Zauberfisches Vagrant box Jessy Version 3
I made an Elemental Element that is a holder for single elements:
namespace R12page\Elements;
use DNADesign\Elemental\Models\BaseElement;
use SilverStripe\Forms\GridField\GridField;
use SilverStripe\Forms\GridField\GridFieldConfig_RecordEditor;
use SilverStripe\Versioned\Versioned;
use UndefinedOffset\SortableGridField\Forms\GridFieldSortableRows;
class ImageRasterElement extends BaseElement
{
private static $singular_name = 'Image Raster Element';
private static $plural_name = 'Images Raster Elements';
private static $description = 'Generates an Image Raster for multiple single Elements';
private static $table_name = 'ImageRasterElement';
private static $has_many = [
'SingleElements' => SingleElement::class
];
private static $extensions = [
Versioned::class . '.stagedversioned',
];
public function getType()
{
return 'ImageRasterElement';
}
public function getCMSFields()
{
$fields = parent::getCMSFields();
$singleElements = $this->SingleElements();
$singleElementGridConfig = GridFieldConfig_RecordEditor::create();
$singleElementGridConfig->addComponent(new GridFieldSortableRows('SortOrder'));
$singleElementGrid = GridField::create('SingleElements', 'Single Elements of this Page', $singleElements, $singleElementGridConfig);
$fields->addFieldsToTab('Root.Main', $singleElementGrid);
return $fields;
}
}
This Element has_many single Elements: they look like:
namespace R12page\Elements;
use R12page\Model\News;
use R12page\Model\People;
use R12page\Model\References;
use SilverStripe\ORM\DataObject;
use SilverStripe\Versioned\Versioned;
class SingleElement extends DataObject
{
private static $table_name = 'SingleElement';
private static $db = [
'Reference' => 'Boolean',
'People' => 'Boolean',
'News' => 'Boolean',
'SortOrder' => 'Int'
];
private static $has_one = [
'News' => News::class,
'People' => People::class,
'Reference' => References::class,
'ImageRasterElements' => ImageRasterElement::class
];
private static $summary_fields = [
'News.Headline' => 'News',
'People.Name' => 'People',
'Reference.Headline' => 'Reference'
];
private static $extensions = [
Versioned::class . '.stagedversioned',
];
}
Each Single Element has an has_one to a Data Object. The look like:
namespace R12page\Model;
use R12page\Elements\SingleElement;
use SilverStripe\ORM\DataObject;
use SilverStripe\Versioned\Versioned;
class News extends DataObject
{
private static $table_name = 'News';
private static $db = [
'Headline' => 'Varchar',
'SubHeadline' => 'Varchar',
'Date' => 'Date',
'DatePublished' => 'Date',
'Content' => 'HTMLText',
'IsActiv' => 'Boolean',
'DisplayOnHomePage' => 'Boolean'
];
private static $has_one = [
'Mobile' => Size::class,
'Screen' => Size::Class
];
private static $has_many = [
'SingleElements' => SingleElement::class
];
private static $owns = [
'SingleElements'
];
private static $summary_fields = [
'Headline' => 'Headline',
'Mobile.Title' => 'Mobile',
'Screen.Title' => 'Screen'
];
private static $extensions = [
Versioned::class . '.stagedversioned',
];
}
So far so good. This is what it looks like in the admin Area.
The strange thing is if I add a single Element an try to create it i get this:
When i refresh the page i can see the content of the page and i can save it with out any problems. When if use php_debug everything looks good. I also don't have and error messages in the console which i think, are related to the problem i am having . I just get those to warnings:
But i think the warnings should not be the problem. To be precise i get an error because a font is not loading.
Please help me debug this. I tried the hole day to get this to work. I can not identify the problem i am having.
This is the response i am getting back:
To me it looks okay.
This is the response from the browser networktap:
A view things i can confirm:
Elemental works on a clean install.
If i switch to live mode nothing changes.
If i try to use the content element that ships with elemental it has the same behavior.

Dynamically map manyToOne relations

I'm trying to create Symfony Bundle in which entities are defined which can be used in a one-to-many/many-to-one relationship without the needing to rewrite the mapping manually.
I do this by subscribing to the loadClassMetadata event and adding the mapping based on the Interfaces they implement. It is not as simple as using the ResolveTargetEntityListener because that will simply substitute an interface with the concrete class.
An example. I have a Address and a Customer entity. A Customer has many Addresses.
But another bundle may redefine the Customer (or a totally different Entity which can have multiple Addresses). For this reason the Customer implements the AddressableInterface. For ease of use I've implemented this interface in a trait.
In the subscriber I check if the class implements the AddressableInterface. If so it adds an OneToMany to the Address and an ManyToOne to the class which implements the AddressableInterface. (In this example the Customer class)
However this leaves the following error:
The association Entity\Customer#addresses refers to the owning side field Entity\Address#subject which does not exist.
But I setup to association both ways in my subscriber.
Below is the essence of my code.
namespace Entity;
class Address
{
public $subject;
}
namespace Entity;
class Customer implements AddressableInterface
{
use Traits/Addressable;
}
namespace Traits;
trait Addressable //Implements all methods from AddressableInterface
{
protected $addresses;
public function getAddresses()
{
return $this->addresses;
}
public function addAddress(AddressInterface $address)
{
$this->addresses->add($address);
}
public function removeAddress(AddressInterface $address)
{
$this->addresses->removeElement($address);
}
}
And the event subscriber
class DynamicAddressBindingSubscriber implements EventSubscriber
{
public function getSubscribedEvents()
{
return [Events::loadClassMetadata];
}
public function loadClassMetadata(LoadClassMetadataEventArgs $eventArgs)
{
$metadata = $eventArgs->getClassMetadata();
$class = $metadata->getReflectionClass();
if (!in_array(AddressableInterface::class, $class->getInterfaceNames())) {
return;
}
$factory = new \Doctrine\ORM\Mapping\ClassMetadataFactory;
$factory->setEntityManager($eventArgs->getEntityManager());
$addressMetadata = $factory->getMetadataFor(Address::class);
$addressMetadata->mapManyToOne(
[
"targetEntity" => $class->getName(),
"fieldName" => "subject",
"inversedBy" => "addresses"
]
);
$metadata->mapOneToMany(
[
'targetEntity' => Address::class,
'fieldName' => 'addresses',
'mappedBy' => 'subject'
]
);
}
}
I've looked at multiple examples and based most of my code on this article and the Doctrine Bundle source. But I'm stuck at this point because I have no idea why the association can't find the owing side.
Your address class doesn't have getter/setter for the subject field.
Another thing is that if you want to bind addresses to any class, you might prefer to make it a manyToMany relations. I do so with attachments like this:
$metadata->mapManyToMany([
'targetEntity' => '...\FilesBundle\Entity\Attachment',
'fieldName' => 'attachments',
'cascade' => array('persist'),
'joinTable' => array(
'name' => strtolower($namingStrategy->classToTableName($metadata->getName())) . '_attachment',
'joinColumns' => array(
array(
'name' => $namingStrategy->joinKeyColumnName($metadata->getName()),
'referencedColumnName' => $namingStrategy->referenceColumnName(),
'onDelete' => 'CASCADE',
'onUpdate' => 'CASCADE',
),
),
'inverseJoinColumns' => array(
array(
'name' => 'file_id',
'referencedColumnName' => $namingStrategy->referenceColumnName(),
'onDelete' => 'CASCADE',
'onUpdate' => 'CASCADE',
),
)
)
]);
where namingStrategy comes from the event:
$namingStrategy = $eventArgs
->getEntityManager()
->getConfiguration()
->getNamingStrategy()
;

Resources