display titles from many_many relation in GridField - Silverstripe - silverstripe

How can I display the titles from a many_many relation in a GridField Summary?
I tried it with RelationName.Title but the result was only an empty field

there should be a couple solutions:
defining $summary_fields on the DataObject that is linked:
private static $summary_fields = array(
'YourFieldName',
'AnotherField'
);
or with the GridFieldConfig on the Page/DataObject that defines the relation:
$config->getComponentByType('GridFieldDataColumns')->setDisplayFields(array(
'FieldName' => 'GridFieldColumnName',
'AnotherFieldName' => 'AnotherGridFieldColumnName',
));
$config being your GridFieldConfig instance used by GridField.
EDIT
for more advanced formatting/control over the data included in the GridField, you can use setFieldFormatting:
$config->getComponentByType('GridFieldDataColumns')->setDisplayFields(array(
'TeamLink' => 'Edit teams'
));
$config->getComponentByType('GridFieldDataColumns')->setFieldFormatting(array(
'TeamLink' => function($value, $item)
{
// $item here would be a TeamMember instance
// since the GridField displays TeamMembers
$links = 'No teams';
$teamModelAdminClass = 'TeamModelAdmin'; //change to your classname
$teams = $item->Teams(); // get the teams
if ( $teams->count() > 0 )
{
$links = '';
$teamClass = $teams->dataClass;
$teamAdminURL = Config::inst()->get($teamModelAdminClass, 'url_segment');
$teamEditAdminURL = 'admin/'.$teamAdminURL.'/'.$teamClass.'/EditForm/field/'.$teamClass.'/item/';
foreach($teams as $team)
{
$links .= 'Edit '.$team->Title.'<br/>';
}
}
return $links;
}
));
Here setFieldFormatting will output edit links to all teams that a TeamMember is part of in a TeamLink column defined by setDisplayFields (might not be the best example, but hope you get the idea, although not tested).

colymba's answer already said most of it, but in addition you can also specify a method in $summary_fields. This allows you to display image thumbnails in a GridField or as you need it, piece together your own string from the titles of a many_many relation.
class TeamMember extends DataObject {
private static $db = array(
'Title' => 'Text',
'Birthday' => 'Date',
);
private static $has_one = array(
'Photo' => 'Image'
);
private static $has_many = array(
'Teams' => 'Team'
);
private static $summary_fields = array(
'PhotoThumbnail',
'Title',
'Birthday',
'TeamsAsString',
);
public function getPhotoThumbnail() {
// display a thumbnail of the Image from the has_one relation
return $this->Photo() ? $this->Photo()->CroppedImage(50,50) : '';
}
public function getTeamsAsString() {
if (!$this->Teams()) {
return 'not in any Team';
}
return implode(', ', $this->Teams()->Column('Title'));
// or if one field is not enough for you, you can use a foreach loop:
// $teamsArray= array();
// foreach ($this->Teams() as $team) {
// $teamsArray[] = "{$team->ID} {$team->Title}";
// }
// return implode(', ', $teamsArray);
}
}
alternative you can, as colymba pointed out, also use setDisplayFields to use different fields on different grids

Related

Symfony 5 - Display id and slug in category url and find it by id when slug is updated

My category url contains both id and slug like https://myapp.com/category/56-category-name (id field = 56 and slug field = category-name in the DB), when updating category name the slug field in DB is updated but the id still the same.
I would like to display my category whatever the slug provided that the id is correct and of course display the correct url. In this case SEO still correct i think.
Here my show method :
/**
* #Route("/category/{id}-{slug}", name="category_show", requirements={"id"="\d+"})
*/
public function show(CategoryRepository $categoryRepository, $slug, $id): Response
{
$category = $categoryRepository->find($id);
if($category->getSlug() !== $slug) {
return $this->redirectToRoute('category_show', [
'id' => $id,
'slug' => $category->getSlug()
]);
}
return $this->render('category/show.html.twig', [
'category' => $category
]);
}
It works if the given id exists in DB, othewise i got an error Call to a member function getSlug() on null. I can throw a NotFoundException, but i think this method use many times queries.
So please help me doing these properly with more flexibility and performance.
In case I would like to display the category in the post url as well like https://myapp.com/56-category-name/23-post-title how to go about it ??
Thanks in advance
Use findOneBy() and check if the result is null
/**
* #Route("/category/{id}-{slug}", name="category_show", requirements={"id"="\d+"})
* #Route("/{id}-{slug}/{idArticle}-{postSlug}", name="article_show", requirements={"idArticle"="\d+"})
*/
public function show(CategoryRepository $categoryRepository, $slug, $id, $idArticle = false, $postSlug = false ): Response
{
$category = $categoryRepository->findOneBy(['id'=>$id]);
if(is_null($category)){
throw new NotFoundHttpException(); //404, nothing found
}else{
//Category found.
if($idArticle){ // https://myapp.com/56-category-name/23-post-title
//Article route, put your logic here
}else{ //https://myapp.com/category/56-category-name
// Category route, logic here
if($category->getSlug() !== $slug) {
return $this->redirectToRoute('category_show', [
'id' => $id,
'slug' => $category->getSlug()
]);
}
return $this->render('category/show.html.twig', [
'category' => $category
]);
}
}
}
here is what i found good for my app :
in my CategoryController.php i create, the show method :
/**
* #Route("/{id}-{slug}", name="category_show", requirements={"id"="\d+"})
*/
public function show(CategoryRepository $categoryRepository, $slug = null, $id): Response
{
$category = $categoryRepository->find($id);
if (!$category) {
throw new NotFoundHttpException();
}
if($category->getSlug() !== $slug) {
return $this->redirectToRoute('category_show', [
'id' => $id,
'slug' => $category->getSlug()
]);
}
return $this->render('category/show.html.twig', [
'category' => $category
]);
}
in my index template to display the category_show link :
show
in my ArticleController.php i create, the show method :
/**
* #Route("/{category}/{id}-{slug}", name="article_show", requirements={"id"="\d+"})
*/
public function show(ArticleRepository $articleRepository, $slug = null, $id, $category = null): Response
{
$article = $articleRepository->find($id);
if (!$article) {
throw new NotFoundHttpException();
}
$cat = $article->getCategory()->getId() . '-' . $article->getCategory()->getSlug();
if($article->getSlug() !== $slug || $cat !== $category) {
return $this->redirectToRoute('article_show', [
'id' => $id,
'slug' => $article->getSlug(),
'category' => $cat
]);
}
return $this->render('article/show.html.twig', [
'article' => $article,
]);
}
in my index template to display the article_show link :
show
For me that solves my problem. But still, if we can improve the process, I'm a buyer.
Thanks

How to execute a Drupal-view in a custom module and print the output (as html-markup)?

I want to print the output from a simple view in my custom module. But is doesnt work. I have tried many options from forums and stackoverflow. All of them print "array" instead of html-markup.
My controller:
class DefaultController extends ControllerBase {
public function myfunc1() {
$view = Views::getView('myfirstview');
$view->setDisplay('page_1');
$view->preExecute();
$view->execute();
// $myresults = $view->preview(); = array
// $myresults = $view->render(); = array
$myresults = $view->result; // = array
return array(
'#title' => 'Hello World!',
'#markup' => $myresults,
);
}
}
How can I print the result/output of a view programmatically?
I dont want to make it without "embed view" , because I want to set some exposed filters later.
get the view
$corresponding_view = Views::getView('myfirstview');
set exposed filters
$arguments = your arguments (exposed filter value);
$corresponding_view->setArguments($arguments);
embed your view
return [
'#type' => 'view',
'#name' => 'myfirstview',
'#view' => $corresponding_view,
'#display_id' => 'page_1',
'#embed' => TRUE,
];

Silverstripe Framework Model Admin, Data Object Relations and OptionsetField saving into the Database

I am building an online shop. I am trying to implement a has_one : has_many relationship for DataObjects am managing using ModelAdmin with the use of radio buttons (OptionsetField in Silverstripe), but I have 2 problems.
The relations values are not getting saved into the database when I click save in the CMS.
The state does not persist so that when I log into the CMS next time I can see which radio button I selected last time.
Next is my code
---- Model Admin ----
<?php
class ProductAdmin extends ModelAdmin {
private static $menu_title = 'Store Manager';
private static $url_segment = 'StoreManager';
private static $managed_models = array (
'Product'=>array('title'=>'All Store Products'),
'ProductCategory'=>array('title'=>'Product Categories'),
'ProductSubcategory'=>array('title' => 'Product Sub Categories')
);
public $showImportForm = false;
}
---- Category ----
<?php
class ProductCategory extends DataObject {
private static $db = array (
'Title' => 'Varchar',
);
/**
* This relation links the Category to the Menu Items e.g. Category BoysClothing will be related to Boys * Menu Item.
*/
private static $has_one = array(
'Page' => 'Page'
);
private static $has_many = array (
'Subcategories' => 'ProductSubcategory'
);
public function getCMSFields(){
$fields = FieldList::create(TabSet::create('Root'));
$fields->addFieldsToTab('Root.Main', array(
TextField::create('Title', 'Name of Category'),
OptionsetField::create(
'Page', //name
'Page this category belongs to', //title
SiteTree::get()->filter(array('ShowInMenus' => '1'))->map('ID', 'Title'),
1
)
)//close array of fields
);//end adding fields to main tab
return $fields;
}
}
---- Product Sub Category ----
<?php
class ProductSubcategory extends DataObject {
private static $db = array (
'Title' => 'Varchar',
);
/**
* This relation links the Sub Category to the Category e.g. Category Khakis will be related to Boys
* Category
*
*/
private static $has_one = array(
'Category' => 'ProductCategory'
);//will lead to creation of column BoysPageID on the table BoysCategory
private static $has_many = array (
'Products' => 'Product'
);
public function getCMSFields(){
$fields = FieldList::create(TabSet::create('Root'));
$fields->addFieldsToTab('Root.Main', array(
TextField::create('Title', 'Name of Sub Category'),
DropdownField::create(
'Category',
'Category this subcategory belongs to',
ProductCategory::get()->map('ID', 'Title')
)
)//close array of fields
);//end adding fields to main tab
return $fields;
}
}//end class
---- Product ----
<?php
class Product extends DataObject{
private static $db = array (
'Title' => 'Varchar(255)',
'Price' => 'Varchar',
'Colors' => 'Varchar',
'Sizes' => 'Varchar',
'FeaturedOnHomepage' => 'Boolean'
);
private static $has_one = array (
'PrimaryPhoto' => 'Image',
'ProductSubcategory' => 'ProductSubcategory'
);
static $summary_fields = array (
'Title' => 'Name',
'Price' => 'Price',
'FeaturedOnHomepage.Nice' => 'Featured?',
'Thumbnail' => 'Picture'
);
public function getCMSFields(){
$fields = FieldList::create(TabSet::create('Root'));
$fields->addFieldsToTab('Root.Main', array(
TextField::create('Title', 'Name of product'),
TextField::create('Price', 'Price of product'),
TextField::create('Colors', 'Available Colors'), //Make this a checkbox set or multiselect drop down
TextField::create('Sizes', 'Available Sizes'), //Make this a checkbox set or multiselect drop down
CheckboxField::create('FeaturedOnHomepage', 'Feature On HomePage'),
CheckboxSetField::create(
'ProductSubcategory',
'Sub categories this product belongs to',
ProductSubcategory::get()->map('ID', 'Title')
),
$primary_photo = UploadField::create('PrimaryPhoto', 'Product Primary Photo')
)//close array of fields
);//end adding fields to main tab
//Add other photos related to this image in a deifferent tab
$other_product_photos = UploadField::create('ProductImages', 'Product Photo');
$fields->addFieldsToTab('Root.Other Photos', $other_product_photos);
//limit image extensions
$primary_photo->getValidator()->setAllowedExtensions(array('jpg', 'jpeg', 'png', 'gif'));
$other_product_photos->getValidator()->setAllowedExtensions(array('jpg', 'jpeg', 'png', 'gif'));
//set Folders
$primary_photo->setFolderName('ProductPhotos');
$other_product_photos->setFolderName('ProductPhotos');
//return
return $fields;
}
public function Link(){
$sview_link = "prods/sview/";
return $sview_link.$this->ID;
}
public function getThumbnail(){
if($this->PrimaryPhoto()->ID){
return $this->PrimaryPhoto()->setWidth(80);
}else{
return 'No Image';
}
}
}//close class Product
As Page is a has_one relation you need to add the suffix ID to the formfield (yes, that's annoying for beginners).
So
OptionsetField::create(
'PageID', //name
'Page this category belongs to', //title
SiteTree::get()->filter(array('ShowInMenus' => '1'))->map('ID', 'Title'),
1
)
should work. The same for the Category has_one in SubCategory.
You already stated in your code
//will lead to creation of column BoysPageID on the table BoysCategory
that's cause the has_one relation Foo is saved in DB as FooID, so we need to add the ID manually to the formfield.

SilverStripe and Display Logic fails with certain field types

I'm working on a SilverStripe 3.1.8 site. I have a DataExtension that defines some fields, and I'm trying to use Display Logic to modify the behavior of the CMS:
private static $db = array(
'Enabled' => 'Boolean',
'Title' => 'Text'
);
private static $has_one = array(
'Link' => 'SiteTree',
'Image' => 'Image'
);
/* this is a DataExtension */
public function updateCMSFields(FieldList $fields) {
$fields->addFieldsToTab('Root.Other', array(
CheckboxField::create('Enabled'),
TextField::create('Title', "Title")->displayIf('Enabled')->isChecked()->end(),
TreeDropdownField::create("LinkID", "Linked page", 'SiteTree')->displayIf('Enabled')->isChecked()->end(),
UploadField::create('Image', "Image")->displayIf('Enabled')->isChecked()->end()
));
}
When I check or uncheck the "Enabled" checkbox, the other three fields should appear or disappear correspondingly. Unfortunately, only the TextField does, the TreeDropdownField and UploadField are always shown.
Any ideas why Display Logic fails with these two field types, and how to solve it?
Thank you!
It appears that UploadField and DropdownField fall under the category of Dealing with non-standard forms and you need to wrap them in a DisplayLogicWrapper.
See the last 2 lines of the addFieldsToTab input array.
private static $db = array(
'Enabled' => 'Boolean',
'Title' => 'Text'
);
private static $has_one = array(
'Link' => 'SiteTree',
'Image' => 'Image'
);
/* this is a DataExtension */
public function updateCMSFields(FieldList $fields) {
$fields->addFieldsToTab('Root.Other', array(
CheckboxField::create('Enabled'),
TextField::create('Title', "Title")->displayIf('Enabled')->isChecked()->end(),
DisplayLogicWrapper::create(TreeDropdownField::create("LinkID", "Linked page", 'SiteTree'))->displayIf('Enabled')->isChecked()->end(),
DisplayLogicWrapper::create(UploadField::create('Image', "Image"))->displayIf('Enabled')->isChecked()->end()
));
}

Sonata admin form collection

How to limit the number of embedded form with the type "sonata_type_collection" ?
$formMapper->add('phones', 'sonata_type_collection',
array(
'required' => true,
'by_reference' => false,
'label' => 'Phones',
),
array(
'edit' => 'inline',
'inline' => 'table'
)
I would like limit to last five phones, I found only this solution for now, limit the display in the template twig "edit_orm_one_to_many", but i don't like that.
I found a solution by rewriting the edit action in the controller,
such in the documentation sonataAdminBundle I created my admin controller class:
class ContactAdminController extends Controller
{
public function editAction($id = null)
{
// the key used to lookup the template
$templateKey = 'edit';
$em = $this->getDoctrine()->getEntityManager();
$id = $this->get('request')->get($this->admin->getIdParameter());
// $object = $this->admin->getObject($id);
// My custom method to reduce the queries number
$object = $em->getRepository('GestionBundle:Contact')->findOneAllJoin($id);
if (!$object)
{
throw new NotFoundHttpException(sprintf('unable to find the object with id : %s', $id));
}
if (false === $this->admin->isGranted('EDIT', $object))
{
throw new AccessDeniedException();
}
$this->admin->setSubject($object);
/** #var $form \Symfony\Component\Form\Form */
$form = $this->admin->getForm();
$form->setData($object);
// Trick is here ###############################################
// Method to find the X last phones for this Contact (x = limit)
// And set the data in form
$phones = $em->getRepository('GestionBundle:Phone')->findLastByContact($object, 5);
$form['phones']->setData($phones);
// #############################################################
if ($this->get('request')->getMethod() == 'POST')
{
$form->bindRequest($this->get('request'));
$isFormValid = $form->isValid();
// persist if the form was valid and if in preview mode the preview was approved
if ($isFormValid && (!$this->isInPreviewMode() || $this->isPreviewApproved()))
{
$this->admin->update($object);
$this->get('session')->setFlash('sonata_flash_success', 'flash_edit_success');
if ($this->isXmlHttpRequest())
{
return $this->renderJson(array(
'result' => 'ok',
'objectId' => $this->admin->getNormalizedIdentifier($object)
));
}
// redirect to edit mode
return $this->redirectTo($object);
}
// show an error message if the form failed validation
if (!$isFormValid)
{
$this->get('session')->setFlash('sonata_flash_error', 'flash_edit_error');
}
elseif ($this->isPreviewRequested())
{
// enable the preview template if the form was valid and preview was requested
$templateKey = 'preview';
}
}
$view = $form->createView();
// set the theme for the current Admin Form
$this->get('twig')->getExtension('form')->renderer->setTheme($view, $this->admin->getFormTheme());
return $this->render($this->admin->getTemplate($templateKey), array(
'action' => 'edit',
'form' => $view,
'object' => $object,
));
}
}

Resources