SilverStripe 3.1 ListboxField save array to database - silverstripe

I want to know how to correctly save values selected with a ListboxField.
new ListboxField(
$name = "Categorie",
$title = "Catégorie(s)",
Categories::get()->map('ID','Title'),
$value = 1,
$size = 4,
$multiple = true
);
In my example, I can save more than one category to a $db field. I am using:
private static $db = array(
'Categorie' => 'Varchar'
);
It works, but when I'm trying to get back categories names into a template it dosen't work at all. The data stored for example is "1,4,9".
Is there a better way to store variables from ListboxField?

3dgoo is correct that you're probably best using a has_many or many_many relationship here. If for some reason you need to use a single text field, you would just need to manually add a method to your model to look up those related records:
public function getCategories() {
if (empty($this->Categorie)) return null;
return Categories::get()->filter('ID', explode(',', $this->Categorie));
}
You can then do something like the following in your template:
<% loop $Categories %><li>$Title</li><% end_loop %>

Use a $many_many relationship instead of a $db variable to manage your relationship between your object and Categories.
private static $many_many = array(
'Categories' => 'Categories'
);
public function getCMSFields() {
$fields = parent::getCMSFields();
$fields->addFieldToTab('Root.Main', ListboxField::create(
'Categories',
'Catégories',
Categories::get()->map('ID', 'Title')->toArray(),
1,
4,
true
));
return $fields;
}

Related

Silverstripe - Repeating / Reusable grouped fields

I would like to implement a layout in which a column is repeated several times and can be specified as dynamic content in the CMS. However, I cannot find an object type that would fit for this.
Do I have to specify the input fields individually for each column?
private static $db = [
'Intro_Headline' => 'Varchar',
'Intro_Subheadline' => 'Varchar',
'Intro_Text' => 'HTMLText',
'Intro_Headline2' => 'Varchar',
'Intro_Subheadline2' => 'Varchar',
'Intro_Text2' => 'HTMLText',
...
];
--
$fields = parent::getCMSFields();
//Intro Field 1
$fields->addFieldToTab('Root.Intro', TextField::create('Intro_Headline', 'Headline'), 'Content');
$fields->addFieldtoTab('Root.Intro', TextField::create('Intro_Subheadline', 'Subheadline'), 'Content');
$fields->addFieldToTab('Root.Intro', HTMLEditorField::create('Intro_Text', 'Text'), 'Content');
//Intro Field 2
$fields->addFieldToTab('Root.Intro', TextField::create('Intro_Headline2', 'Headline'), 'Content');
$fields->addFieldtoTab('Root.Intro', TextField::create('Intro_Subheadline2', 'Subheadline'), 'Content');
$fields->addFieldToTab('Root.Intro', HTMLEditorField::create('Intro_Text2', 'Text'), 'Content');
...
Or can someone tell me which field type I can't find?
Update:
Now I have a $has_many variable in addition to my $db variable in my page-model:
private static $has_many = [
'Intro_Columns' => IntroColumn::class,
];
In the getCMSFields() function I add them like this:
$fields->addFieldToTab('Root.Columns', GridField::create('Intro_Columns', 'Columns', IntroColumn::get()), 'Content');
And my data object looks like this:
class IntroColumn extends DataObject
{
private static $db = [
'img_url' => 'Text',
'headline' => 'Varchar',
'subheadline' => 'Varchar',
'text' => 'Text',
'link' => 'Text'
];
}
But the fields are not yet displayed in the CMS. How do I output the data fields from a data object?
For things to be repeatable, you will have to put them into a different object, and then link multiple of those objects to your current object/page.
GridFIeld
The default way of doing this in SilverStripe 4 is using the built in Database Relation ($has_many or $many_many instead of $db) and GridField` as the form field.
I'd recommend you go through this tutorial: https://docs.silverstripe.org/en/4/developer_guides/model/relations/
In paticular the section about $has_many will apply to your usecase. (Example where 1 Team has multiple Players or 1 Company multiple People)
$has_many/$many_many is a very generic option and can be used for any number of possible database relation (linking Categories, Images, Pages, ...)
elemental module
Another option is an officially supported module called elemental. This is very specifically built for repeatable content.
https://github.com/silverstripe/silverstripe-elemental
serialized-dataobject module
Probably not ideal for your usecase, but I maintain a module that provides an alternative to GridField, but it's more suitable for small form fields. The HTMLEditor is to large to be useful in this module.
https://github.com/Zauberfisch/silverstripe-serialized-dataobject
PS: regardless of what way you go, I highly recommend you go through the tutorial from (1.). It's a pretty important fundamental functionality of SilverStripe.
EDIT: response to your updated question:
If you are using GridField, I'd recommend the following:
class Page extends SiteTree {
private static $has_many = [
'Intro_Columns' => IntroColumn::class,
];
public function getCMSFields() {
$fields = parent::getCMSFields();
$fields->addFieldToTab('Root.Columns', new GridField(
'Intro_Columns',
'My Columns',
$this->Intro_Columns(),
new GridFieldConfig_RecordEditor()
), 'Content');
return $fields;
}
}
class IntroColumn extends DataObject {
private static $db = [
'headline' => 'Varchar',
'subheadline' => 'Varchar',
'text' => 'Text',
'link' => 'Text'
];
private static $has_one = [
'Image' => 'Image',
]
public function getCMSFields() {
$fields = new FieldList();
$fields->push(new TextField('headline', 'My Headline'));
$fields->push(new TextField('subheadline', 'My Subheadline'));
// ... and so on
$fields->push(new UploadField('Image', 'Upload an image'));
return $fields;
}
}
Note that I am using $this->Intro_Columns() as the Value for the GridField and not IntroColumn::get(). Because $this->Intro_Columns() is a automatically generated Method that returns all IntroColumn objects linked to the current page. But $this->Intro_Columns() would return all IntroColumns from all Pages
In the template, you cal also access this automatically generated method:
<!-- Page.ss -->
<h1>Page Title: $Title</h1>
<div class="intro-columns">
<% loop $Intro_Columns %>
<div class="intro-column">
<!-- here we are scoped into a single IntroColumn, so you can use all DB fields and methods of that object -->
<h2>$headline <br> $subheadline</h2>
$Image.URL <br>
...
</div>
<% end_loop %>
</div>

Display user's data from database on their profile pages

In Drupal 7, is it possible to show a result of a DB query on each users' respective profile page, in some table? I need to do this programmatically within my existing module. So the input to the query would be the ID of a user whose profile is currently being viewed.
Only to show the queried data - no administration, no edits, nothing else.
something along the lines of.. (image)
Also the block or field or whatever would make this possible needs to be configurable through the _permission() hook as to who can or cannot view it.
I thought since this is basically just a query with no extra custom stuff there would be an easy way via the Drupal API.
you can create custom block for that and view it in current user profile
/**
* Implements hook_block_info().
*/
function custom_block_block_info() {
$blocks = array();
$blocks['my_block'] = array(
'info' => t('My Custom Block'),
'status' => TRUE,
'region' => 'Content',
'visibility' => BLOCK_VISIBILITY_LISTED,
'pages' => 'user/*',
);
return $blocks;
}
/**
* Implements hook_block_view().
*/
function custom_block_view($delta = '')
{
// The $delta parameter tells us which block is being requested.
switch ($delta)
{
case 'my_block':
// Create your block content here
$block['subject'] = t('This is just a test block created programatically');
$block['content'] = _user_detail_list();
break;
}
return $block;
}
/**
* Implements costome code we want to print().
*/
function _user_detail_list(){
//enter your query and output in some variable
$value = "<p>User Detail</p>"
return $value;
}
Note :- Here profile is extended with new block
There will be some coding to get what you want, but if you just want style/show the data that's already available with the "user" object then #1 below will do it.
Easy way(#1):
1. Create a view and choose the "user" info that you need shown and give it a path. Then in your sub-theme use the correct template -see the code snippets.
https://www.drupal.org/forum/support/post-installation/2011-04-04/modify-the-default-profile-pagelayout
other ways:
use the user-profile.tpl.php see
https://api.drupal.org/api/drupal/modules%21user%21user-profile.tpl.php/7.x
in your module, you need to call and reach out to the hook_user_view.
https://api.drupal.org/api/drupal/modules%21user%21user.api.php/function/hook_user_view/7.x
Here you fetch user profile data from database then follow it
function modulename_menu() {
$items['user-data'] = array(
'title' => 'User data',
'page callback' => 'user_data',
'access callback' => ('user_is_logged_in'),
'#type' => MENU_NORMAL_ITEM,
);
return $items;
}
function user_data(){
global $user;
$user_fields = user_load($user->uid);
$output = "" //return those $user_fields values into table using theme('table',header,rows)
return $output;
}
https://www.drupal.org/node/156863 (for create table view)
i.e
global $user;
$user_fields = user_load($user->uid);
$firstname = $user_fields->field_firstname['und']['0']['value'];
$lastname = $user_fields->field_lastname['und']['0']['value'];

Sonata admin export fields with collection fields

I'm trying to make custom columns for export, but I can't access children. Is there any possibility to do that ?
My code at this moment looks like this:
public function getExportFields()
{
return [
'ID' => 'id',
'Transaction number' => 'transactionNumber',
'Loan account' => 'loan',
'Loan name' => 'loan.name',
'Amount' => 'amount',
//'Amount ($)' => '',
'Transaction type' => 'transactionCategory',
'Reference' => 'transactionAssociation.cashTransaction.transactionNumber',
'Date' => 'date'
];
}
I can't find out a solution. I was thinking to use PropertyAccess, but I don't know how to integrate it here.
I'm using Symfony 3.X with Sonata.
To get the collection records in export you cannot directly do this by specifying the property with association, A workaround for to achieve this you can define a new unmapped property in your entity with a getter function which will get all the collection details like in your main entity define new property as
protected $cashTransactionNumber;
public function getCashTransactionNumber()
{
$cashTransactionNumber = array();
$i = 1;
foreach ($this->getTransactionAssociation() as $key => $transactionAssociation) {
$cashTransactionNumber [] = $i .
') No.:' . $transactionAssociation->somemethod()->__toString()() .
/** Other properties */;
$i++;
}
return $this->cashTransactionNumber = join(' , ', $cashTransactionNumber );
}
then in your getExportFields() method call this property
public function getExportFields(){
return array(
'Reference'=>'cashTransactionNumber ',
....// Other properties
);
}
Reference: Exporting one to many relationship on sonata admin

How to change output of a certain Pods field?

I have two pods: course and teacher.
Each course has a teacher.
I use shortcodes in order to build a form to define new course:
[pods name='course' form='1' fields='name, teacher' ]
When defining a new course, the user can choose the teacher for this course.
By default, the name of the teacher is displayed in a drop down list. I wonder if I can change the output of the teachers in the drop down list.
For example, in addition to the name I want to display a certain field, such as location of the teacher in the drop down list.
Is this possible using the built-in shortcodes of Pods 2?
Update:
Following the instructions of Scott, I solved the problem. I wrote the solution into the comment section but formating was lost. Below, I put the code again:
function pods_teacher_pick_data($data, $name, $value, $options, $pod, $id){
if ($name == "pods_field_teachers") {
foreach ($data as $id => &$value) {
$p = pods('teacher', $id);
$name = $p->display('name');
$city = $p->display('profile.city.name');
$value = $name . ' - ' . $city;
}
}
return $data;
}
add_filter('pods_field_pick_data', 'pods_teacher_pick_data', 1, 6);
Not built in yet, but you can take over the data output using the filter: pods_field_pick_data
$data = apply_filters( 'pods_field_pick_data', $data, $name, $value, $options, $pod, $id );
Adding a filter to that filter should give you the ability to change what appears in the drop-down, or other relationship input types.
Edit: I just added a similar filter for filtering the autocomplete data too.
$pick_data = apply_filters( 'pods_field_pick_data_ajax', array(), $field[ 'name' ], null, $field, $pod, 0, $data );
$data in this array is actually the fully setup PodsData object
EDIT (02/07/2013):
In Pods 2.3, I've added a quick function that should streamline adding custom relationship objects. This is preferrer over overriding an existing relationship or using the custom simple definition dynamically. Pretty easy to use, check it out at https://github.com/pods-framework/pods/issues/1033
$options = array(
'group' => 'Special Relationships', // Whatever you want the selection group to be, defaults to label
'simple' => false, // Whether this field is related by strings or integer IDs, integer IDs get stored in wp_podsrel, strings are stored in the field itself either serialized (meta-based) or json encoded (table-based)
'data' => array( // Custom define the items to select from manually
1 => 'Gravity Form 1',
2 => 'Gravity Form 2'
),
'data_callback' => 'get_custom_gravity_forms_list', // Provide a callback function to call to define the data (instead of setting 'data' above)
'value_to_label_callback' => 'get_custom_gravity_forms_list', // Provide a callback function to define the data when called through PodsField_Pick::value_to_label
'simple_value_callback' => 'get_custom_gravity_forms_list' // Provide a callback function to define the data when called through PodsField_Pick::simple_value
);
pods_register_related_object( 'gravity-form', 'Gravity Forms', $options );
For ajax, it seems need to use a bit different filter pods_field_pick_data_ajax_items like this:
function pods_teacher_pick_data_ajax_items($items, $name, $null, $field, $pod, $id){
if ($name == "pods_meta_categorie" || $name == 'categorie') {
foreach ($items as &$item) {
$id = $item['id'];
$value = $item['text'];
$p = pods('dvd_categories', $id);
$code = $p->display('code');
$item['text'] .= ' - ' . $code;
}
}
return $items;
}
add_filter('pods_field_pick_data_ajax_items', 'pods_teacher_pick_data_ajax_items', 1, 6);
Note different structure of arrays $data and $items.
Filter pods_teacher_pick_data_ajax looks very strange and useless to me as it doesnt accept $data array.

Map array to entity in Symfony2/Doctrine2

I'm using the DoctrineFixtures bundle to create example entities during development. In my ORM fixtures load() method, I define the data as associative arrays and create the entity object in a loop.
<?php
// ...
public function load($manager) {
$roleDefs = array(
'role-1' => array(
'role' => 'administrator'
),
'role-2' => array(
'role' => 'user'
),
);
foreach($roleDefs as $key => $roleDef) {
$role = new Role();
$role->setRole($roleDef['role']);
$manager->persist($role);
$this->addReference($key, $role);
}
$manager->flush();
}
I always use the same array schema. Every array element uses the property name (in underscore notation) of the entity as index. If the entity structure becomes more complex, there are a lot of $entity->setMyProperty($def['my_property']); lines.
I think the problem of mapping propertynames to setter methods is a very common problem in Symfony and Doctrine as this type of mapping is found in many situations (e.g. mapping forms to entities).
Now I'm wondering if there is a built-in method that can be used for mapping. It would be nice to have a solution like
foreach($defs as $key => $def) {
$entity = $magicMapper->getEntity('MyBundle:MyEntity', $def);
// ...
}
Has someone an idea how this can be achieved?
Thanks a lot,
Hacksteak
I sometimes use loops when creating fixtures. I'm not sure if this solution fits your requirements, but I find that the most flexible way to build fixtures and quickly add new properties over time if you need is to do the following... Assuming the creation of a bunch of blog posts:
// an array of blog post fixture values
$posts = array(
array(
'title' => 'Foo',
'text' => 'lorem'
'date' => new \DateTime('2011-12-01'),
),
array(
'title' => 'Bar',
'text' => 'lorem'
'date' => new \DateTime('2011-12-02'),
),
// more data...
);
// loop over the posts
foreach ($posts as $post) {
// new entity
$post = new Post();
// now loop over the properties of each post array...
foreach ($post as $property => $value) {
// create a setter
$method = sprintf('set%s', ucwords($property)); // or you can cheat and omit ucwords() because PHP method calls are case insensitive
// use the method as a variable variable to set your value
$post->$method($value);
}
// persist the entity
$em->persist($post);
}
This way you can add more properties by just adding the new values to your array.

Resources