SilverStripe translate fieldlabels - silverstripe

I simply use _t() to translate CMS Fields in a DataObject: TextField::create('Title', _t('cms.TitleField', 'Title'));. I thought translating $summary_fields was just as simple, but it's not.
Instead of trying to translate Fields and their accompanying summary_fields seperately, I believe I noticed a better way how these fields are translated using the function FieldLabels as used in SiteTree.
Is there way I can translate these both fields in one place (DRY principle) and apply to both easily by calling the var?

Yes I would certainly say the use of FieldLabels is for localisation / translation because of the comment "Localize fields (if possible)" here in the DataObject code...
public function summaryFields() {
$fields = $this->stat('summary_fields');
// if fields were passed in numeric array,
// convert to an associative array
if($fields && array_key_exists(0, $fields)) {
$fields = array_combine(array_values($fields), array_values($fields));
}
if (!$fields) {
$fields = array();
// try to scaffold a couple of usual suspects
if ($this->hasField('Name')) $fields['Name'] = 'Name';
if ($this->hasDatabaseField('Title')) $fields['Title'] = 'Title';
if ($this->hasField('Description')) $fields['Description'] = 'Description';
if ($this->hasField('FirstName')) $fields['FirstName'] = 'First Name';
}
$this->extend("updateSummaryFields", $fields);
// Final fail-over, just list ID field
if(!$fields) $fields['ID'] = 'ID';
// Localize fields (if possible)
foreach($this->fieldLabels(false) as $name => $label) {
// only attempt to localize if the label definition is the same as the field name.
// this will preserve any custom labels set in the summary_fields configuration
if(isset($fields[$name]) && $name === $fields[$name]) {
$fields[$name] = $label;
}
}
return $fields;
}

Related

How can I get the format the results of a CheckboxsetField as a comma separated string in SilverStripe?

I'm trying to create some filters for a datalist. I'd like the user to be able to select one or multiple filters from a list of tags and then spit out a list of objects based on those filters. All is good using this code to grab data based on the URL params being sent...
public function index(SS_HTTPRequest $request)
{
// ...
if($tagsParam = $request->getVar('tags')) {
$articles = new ArrayList();
$tagids = explode(",", $tagsParam);
foreach($tagids AS $tagid) {
$tag = Category::get()->byID($tagid);
$articleitems = $tag->getManyManyComponents('Articles')->sort('Date DESC');
foreach($articleitems AS $articleitem) {
$articles->push($articleitem);
}
}
}
$data = array (
'Articles' => $articles
);
if($request->isAjax()) {
return $this->customise($data)->renderWith('ListItems');
}
return $data;
}
That code works fine with a URL like mysite.com/?tags=1,2,3
My issue comes with trying to generate that URL based on the filters built with a CheckboxSetField. Here is my code for that...
public function ArticlesSearchForm()
{
$tagsmap = $this->getTags()->map('ID', 'Title')->toArray();
$form = Form::create(
$this,
'ArticlesSearchForm',
FieldList::create(
CheckboxSetField::create('tags')
->setSource($tagsmap)
),
FieldList::create(
FormAction::create('doArticlesSearch','Search')
)
);
$form->setFormMethod('GET')
->setFormAction($this->Link())
->disableSecurityToken()
->loadDataFrom($this->request->getVars());
return $form;
}
When the user submits that form, the URL generated is something along the lines of mysite.com?tags%5B1%5D=1&tags%5B2%5D=2&action_doArticlesSearch=Search Obviously, it's passing the values as an array. How can I pass a simple comma separated list?
Rather than trying to change the return of CheckboxSetField, I'd recommend changing your code. Given you are converting the comma-separated list list into an array already here:
$tagids = explode(",", $tagsParam);
Something like this, will skip this step:
public function index(SS_HTTPRequest $request)
{
// ...
if($tagsParam = $request->getVar('tags')) {
$articles = new ArrayList();
//This has a minor risk of going bad if $tagsParam is neither an
//array of a comma-separated list
$tagids = is_array($tags) ? $tagsParam : explode(",", $tagsParam);

Symfony 2.8 dynamic ChoiceType options

in my project I have some forms with choice types with a lot of options.
So I decided to build an autocomplete choice type based on jquery autocomplete, which adds new <option> HTML elements to the original <select> on runtime. When selected they are submitted correctly, but can't be handled within the default ChoicesToValuesTransformer, since the don't exist in my form when I create it.
How can I make symfony accept my dynamically added values?
I found this answer Validating dynamically loaded choices in Symfony 2 , where the submitted values are used to modify the form on the PRE_SUBMIT form event, but couldn't get it running for my situation. I need to change choices known to the current type instead of adding a new widget to the form
To deal with dynamically added values use 'choice_loader' option of choice type. It's new in symfony 2.7 and sadly doesn't have any documentaion at all.
Basically it's a service implementing ChoiceLoaderInterface which defines three functions:
loadValuesForChoices(array $choices, $value = null)
is called on build form and receives the preset values of object bound into the form
loadChoiceList($value = null)
is called on build view and should return the full list of choices in general
loadChoicesForValues(array $values, $value = null)
is called on form submit and receives the submitted data
Now the idea is to keep a ArrayChoiceList as private property within the choice loader. On build form loadValuesForChoices(...) is called, here we add all preset choices into our choice list so they can be displayed to the user. On build view loadChoiceList(...) is called, but we don't load anything, we just return our private choice list created before.
Now the user interacts with the form, some additional choices are loaded via an autocomplete and put into th HTML. On submit of the form the selected values are submitted and in our controller action first the form is created and afterwards on $form->handleRequest(..) loadChoicesForValues(...) is called, but the submitted values might be completly different from those which where included in the beginning. So we replace our internal choice list with a new one containing only the submitted values.
Our form now perfectly holds the data added by autocompletion.
The tricky part is, that we need a new instance of our choice loader whenever we use the form type, otherwise the internal choice list would hold a mixture of all choices.
Since the goal is to write a new autocomplete choice type, you usually would use dependency injection to pass your choice loader into the type service.
But for types this is not possible if you always need a new instance, instead we have to include it via options. Setting the choice loader in the default options does not work, since they are cached too. To solve that problem you have to write a anonymous function which needs to take the options as parameters:
$resolver->setDefaults(array(
'choice_loader' => function (Options $options) {
return AutocompleteFactory::createChoiceLoader();
},
));
Edit:
Here is a reduced version of the choice loader class:
use Symfony\Component\Form\ChoiceList\ArrayChoiceList;
use Symfony\Component\Form\ChoiceList\ChoiceListInterface;
use Symfony\Component\Form\ChoiceList\Loader\ChoiceLoaderInterface;
class AutocompleteChoiceLoader implements ChoiceLoaderInterface
{
/** #var ChoiceListInterface */
private $choiceList;
public function loadValuesForChoices(array $choices, $value = null)
{
// is called on form creat with $choices containing the preset of the bound entity
$values = array();
foreach ($choices as $key => $choice) {
// we use a DataTransformer, thus only plain values arrive as choices which can be used directly as value
if (is_callable($value)) {
$values[$key] = (string)call_user_func($value, $choice, $key);
}
else {
$values[$key] = $choice;
}
}
// this has to be done by yourself: array( label => value )
$labeledValues = MyLabelService::getLabels($values);
// create internal choice list from loaded values
$this->choiceList = new ArrayChoiceList($labeledValues, $value);
return $values;
}
public function loadChoiceList($value = null)
{
// is called on form view create after loadValuesForChoices of form create
if ($this->choiceList instanceof ChoiceListInterface) {
return $this->choiceList;
}
// if no values preset yet return empty list
$this->choiceList = new ArrayChoiceList(array(), $value);
return $this->choiceList;
}
public function loadChoicesForValues(array $values, $value = null)
{
// is called on form submit after loadValuesForChoices of form create and loadChoiceList of form view create
$choices = array();
foreach ($values as $key => $val) {
// we use a DataTransformer, thus only plain values arrive as choices which can be used directly as value
if (is_callable($value)) {
$choices[$key] = (string)call_user_func($value, $val, $key);
}
else {
$choices[$key] = $val;
}
}
// this has to be done by yourself: array( label => value )
$labeledValues = MyLabelService::getLabels($values);
// reset internal choice list
$this->choiceList = new ArrayChoiceList($labeledValues, $value);
return $choices;
}
}
A basic (and probably not the best) option would be to unmap the field in your form like :
->add('field', choiceType::class, array(
...
'mapped' => false
))
In the controller, after validation, get the data and send them to the entity like this :
$data = request->request->get('field');
// OR
$data = $form->get('field')->getData();
// and finish with :
$entity = setField($data);

Filter ModelAdmin by many_many relation

I'm managing the DataObject class 'trainer' with ModelAdmin. A trainer has a many_many relation to my other class 'language'.
On my 'trainer' class I'm manipulating the 'searchableFields' function to display a ListboxField in the filters area.
public function searchableFields() {
$languagesField = ListboxField::create(
'Languages',
'Sprachen',
Language::get()->map()->toArray()
)->setMultiple(true);
return array (
'Languages' => array (
'filter' => 'ExactMatchFilter',
'title' => 'Sprachen',
'field' => $languagesField
)
);
}
That works like expected and shows me the wanted ListboxField. The Problem is, after selecting 1 or 2 or whatever languages and submitting the form, I'm receiving
[Warning] trim() expects parameter 1 to be string, array given
Is it possible here to filter with an many_many relation? And if so, how? Would be great if someone could point me in the right direction.
Update:
Full Error Message: http://www.sspaste.com/paste/show/56589337eea35
Trainer Class: http://www.sspaste.com/paste/show/56589441428d0
You need to define that logic within a $searchable_fields parameter instead of the searchableFields() which actually constructs the searchable fields and logic.
PHP would be likely to throw an error if you go doing fancy form stuff within the array itself, so farm that form field off to a separate method in the same DataObject and simply call upon it.
See my example, I hope it helps.
/* Define this DataObjects searchable Fields */
private static $searchable_fields = array(
'Languages' => array (
'filter' => 'ExactMatchFilter',
'title' => 'Sprachen',
'field' => self::languagesField()
)
);
/* Return the searchable field for Languages */
public function languagesField() {
return ListboxField::create(
'Languages',
'Sprachen',
Language::get()->map()->toArray()
)->setMultiple(true);
}
Yes, it's possible. You just need to override two methods - one in Trainer data object and one in TrainerModelAdmin. First one will make a field, second one will do filtering.
Trainer Data Object:
public function scaffoldSearchFields($_params = null)
{
$fields = parent::scaffoldSearchFields($_params);
// get values from query, if set
$query = Controller::curr()->request->getVar('q');
$value = !empty($query['Languages']) && !empty($query['Languages']) ? $query['Languages'] : array();
// create a field with options and values
$lang = ListboxField::create("Languages", "Sprachen", Language::get()->map()->toArray(), $value, null, true);
// push it to field list
$fields->push($lang);
return $fields;
}
Trainer Model Admin
public function getList()
{
$list = parent::getList();
// check if managed model is right and is query set
$query = $this->request->getVar('q');
if ($this->modelClass === "Trainer" && !empty($query['Languages']) && !empty($query['Languages']))
{
// cast all values to integer, just to be sure
$ids = array();
foreach ($query['Languages'] as $lang)
{
$ids[] = (int)$lang;
}
// make a condition for query
$langs = join(",", $ids);
// run the query and take only trainer IDs
$trainers = DB::query("SELECT * FROM Trainer_Languages WHERE LanguageID IN ({$langs})")->column("TrainerID");
// filter query on those IDs and return it
return $list->filter("ID", $trainers);
}
return $list;
}

Get gravity forms fields

I am using the gravity form on my site. I am working on create the custom report for this I have need gravity form fields name and id based on specific form id.Please let me know how I can do it.
I am using the following function but it is showing all forms info based on it. it is looking very hard to parse it. Please let me know any function so I can get fields name easily.
$forms = RGFormsModel::get_forms_by_id(13);
try this
function get_all_form_fields($form_id){
$form = RGFormsModel::get_form_meta($form_id);
$fields = array();
if(is_array($form["fields"])){
foreach($form["fields"] as $field){
if(isset($field["inputs"]) && is_array($field["inputs"])){
foreach($field["inputs"] as $input)
$fields[] = array($input["id"], GFCommon::get_label($field, $input["id"]));
}
else if(!rgar($field, 'displayOnly')){
$fields[] = array($field["id"], GFCommon::get_label($field));
}
}
}
//echo "<pre>";
//print_r($fields);
//echo "</pre>";
return $fields;
}
It's not that hard to parse:
$fields=array();
foreach ( $forms['fields'] as $field ) {
$fields[]=array($field['id'] => $field['inputName']);
}
P.S. I'm assuming you use Gravity Forms < 1.7 as RGFormsModel::get_forms_by_id is a deprecated function since 1.7
// Get the Form fields
$form = RGFormsModel::get_form_meta($form_id);
// Run through the fields to grab an object of the desired field
$field = RGFormsModel::get_field( $form, $field_id );
I use the above to get a specific field I want to filter the value of. The $field contains an object with all the properties you want.
echo $field->label // Gets the label
echo $field->inputName // Gets the name
echo $field->type // Gets the type
echo $field->cssClass // Gets the CSS Classes as a string
You are able to get the entered value/content of a field by using rgpost() and by referencing the id ($field->id).
// Check the entered value of every field
foreach( $form['fields'] as &$field ) {
// Get the content for the specific field
$fieldContent = rgpost( "input_".$field->id );
// Check the content
if ( $fieldContent == ... ){}
}

Generate url aliasing based on taxonomy term

I have a vocab category and four terms within it. what i want to do is if content is tagged with a termin in particular say "term1" to have the url generated as word1/[node:title] and for all the other tags just the standard url formatting.
If i wanted the term in the url obviously id use pattern replacement but i want another word to be used if a particular tag is used
I can't think of an easy plug-and-play way of achieving this. You may have to create your own token for the "Default path pattern" in Pathauto's URL alias settings:
/**
* Implementation of hook_token_info().
*/
function MODULE_token_info() {
$info['tokens']['node']['node-term-path'] = array(
'name' => t('Node path by term'),
'description' => t('The path to a node based on its taxonomy terms.'),
);
return $info;
}
/**
* Implementation of hook_tokens().
*/
function MODULE_tokens($type, $tokens, array $data = array(), array $options = array()) {
$replacements = array();
if ($type == 'node' && !empty($data['node'])) {
$node = $data['node'];
foreach ($tokens as $name => $original) {
switch ($name) {
case 'node-term-path':
$items = field_get_items('node', $node, 'TAXONOMY_FIELD_NAME');
foreach ($items as $item) {
$tids[] = $item['tid'];
}
if (in_array(TID_OF_TERM1, $tids)) {
// Path for nodes with term1
$replacements[$original] = 'word1/'. pathauto_cleanstring($node->title);
}
else {
// Path for other nodes
$replacements[$original] = 'content/'. pathauto_cleanstring($node->title);
}
break;
}
}
}
return $replacements;
}
Found a simple way actually to anyone who need a similar solution use the module Entity Reference.
http://drupal.org/project/entityreference
I just created a new field for the user account select entity reference then you can choose any entity within drupal to reference.
(ie so you can select a term/content/anything)

Resources