SilverStripe CMS, Dropdown Field search by title or another match? - silverstripe

Hi I would like to get a search either by title or by product code in this use case, but the map('ID', 'Title') method does not expect an array to the second parameter, how could I get it?
/* original snippet */
DropdownField::create(
'ProductID',
'Prodotto',
Product::get()->sort('Title', 'ASC')->map('ID', 'Title')
)->setEmptyString('-- Seleziona --');
/* dummy sample snippet */
DropdownField::create(
'ProductID',
'Prodotto',
Product::get()->sort('Title', 'ASC')->map('ID', ['Code','Title'])
)->setEmptyString('-- Seleziona --');
I took a look at the API, unfortunately I couldn't find a method that satisfies this for the DropdownField.
Thank you

Your question asks for a search but your code is generating a string for the DropDownField.
If you want more advanced labels for the Dropdown you'd need a helper method on the Product DataObject like
public function getCodeAndTitleFormatted()
{
return $this->Code . ': ' . $this->Title;
}
Then you use this method in your ->map() statement to create your DropDownField like:
DropdownField::create(
'ProductID',
'Prodotto',
Product::get()->sort('Title', 'ASC')->map('ID', 'getCodeAndTitleFormatted')
)->setEmptyString('-- Seleziona --');

Related

Does WordPress avoids arrays on wp_insert_post_data filter?

I'm using wp_insert_post_data filter hook to save some extra data which I get from a meta box in a custom post type. I want to save these extra data as the post_content not the post_meta_data.
There is a select box with Select2 which is possible to select more than one values (pillbox). Finally Select2 prepares an array of values. I've checked browser Network section and the value is an array but when I try to access the value in wp_insert_post_data, I just get the last item of the array as an string and not an array!
Here is my function :
function xxx_save_country_data(array $data): array
{
$countries = $_POST['xxx_countries_field']; //It should be an array but it is only string
return $data;
}
add_filter('wp_insert_post_data', 'xxx_save_country_data');
Posted data screenshot: https://i.stack.imgur.com/0kX97.png
In my plugin case, I'm using woocommerce_form_field to generate the select element and if you don't specify id parameter as the $args in this function, both id and name properties get the same string and so it becomes xxx_countries_field and not xxx_countries_field[] which was the problem!

Silverstripe has_one CMS dropdown filter before dropdown_field_threshold

I have a has_one in one of my models. But it exceeds the dropdown_field_threshold.
But in the getCMSFields method I have changed the query for that dropdown. But since it already exceeds the threshold before this method is called, the dropdown is converted to NumericDropdown or something like that.
Is there a way to alter the query before the threshold gets checked?
By the way, I know I can make a custom dropdown with my own query. But Silverstripe already handles a lot, so it would be better to just alter the query.
Edit: Example code
public function getCMSFields() {
$fields = parent::getCMSFields();
$questionnaire = $this->QuestionnaireSection()->Questionnaire();
$nextQuestionOptions = $questionnaire->Sections();
/** #var DropdownField $dropdownField */
$dropdownField = $fields->dataFieldByName("NextQuestionID");
$dropdownField->setSource($nextQuestionOptions->map()->toArray());
return $fields;
}
Unfortunately it is not possible to alter this query within the context of the class. An alternative may be to write your own scaffold function instead of calling parent::getCMSFields, but that is not really advised in this case.
If you have some way to obtain the current Questionnaire object globally, you could add an augmentDataQueryCreation function like below to QuestionnaireSection to add a where clause to the query that is being executed. Be aware however that this function is called in every case that QuestionnaireSection::get() is executed.
public function augmentDataQueryCreation(SQLSelect $query, DataQuery $dataQuery){
$baseTable = $this->baseTable();
$filter = 1; //Your global param here
$dataQuery->where("\"$baseTable\".\"QuestionnaireID\" = $filter");
}
An alternative to prevent the DropdownField from being replaced by a NumericField is to change the threshold of this switch by adding the following code to your mysite.yml. This does not solve your problem entirely, but is a good workaround.
SilverStripe\ORM\FieldType\DBForeignKey:
dropdown_field_threshold: 100000

Filter Entity Reference View with dynamic arguments from Reference Field

I need to filter an "Entity Reference View" autocomplete widget by passing arguments from a "Reference Field" in my content type.
I researched on this and found that PHP Code type Contextual Filter is the most suggested way to achieving this but as PHP Code has now been removed from Drupal 8 Core, what are the alternatives to this common use case? (an earlier suggestion to use PHP Code: Drupal 7: how to filter view content (with entity reference field) based on current page content)
While editing the reference field, it seems that only hard-coded values be mentioned in "View arguments" field and there is no dynamical way of achieving this (e.g. from URL/query string etc).
It's hacky, but you can use a hook_form_alter. Here's an example passing the current NID as an argument:
/**
* Implements hook_form_alter().
*/
function MYMODULE_form_alter(&$form, FormStateInterface $form_state, $form_id)
{
if ($form_id == 'node_NODETYPE_edit_form') {
$node = $form_state->getFormObject()->getEntity();
$form['field_MY_REF_FIELD']['widget'][0]['target_id']['#selection_settings']['view']['arguments'][0] = $node->id();
}
}
For example.com/path?id=55
[current-page:query:id]
For example.com/path/55
[current-page:url:args:last]
For example.com/path/alias
[current-page:url:unaliased:args:last]

Drupal 6 getting the node title from a submitted form

I am using form_alter to edit the submit function when editing content. In my custom function I wish to edit a custom message to the screen with the title name. I thought a way I could do this is something as follows
function mymodule_myfunction(&$form) {
drupal_set_message(t('Some text ' . $form['#node']->title));
}
The title is not being joined to the 'Some text'
I am calling my function by using the following line in my form_alter:
$form['#submit'][] = 'mymodule_myfunction';
All submit functions get two parameters passed to them: $form, which is the final form array after all of the adjustments for hook_form_alter and the like, and $form_state which among other values contains the submitted values, which have been cleaned and checked for ranges. (For instance, if you have three items in a select box, the data in $form_state['values'] already has made sure that the value for that input is one of the three legal values.)
Generally, you shouldn't use $form['#post'] - it's not part of the published way to get at values, and an update to the core to handle some problem with FAPI could conceivably break your code.
Try this:
function mymodule_myfunction($form, &$form_state) {
drupal_set_message(t('Some Message #title'),
array('#title' => $form_state['values']['title'])));
}
Note the corrected use of the t() function - the intent of that function is to allow other users to translate text, and so by using 'Some Message #title' the translator knows more about what is going on. Additionally you get the advantage that text fed through the t function in this way also is fed through check_plain(), which prevents someone from doing something malicious with the input.
DKinzer recommended using dsm($form)to see the variables. The Node title is not populated. It can be found in the Post array. The following line allowed me to do what I wanted.
drupal_set_message(t('Some Text '.$form['#post']['title']));
Try changing the signature of your
function mymodule_myfunction(&$form) {
drupal_set_message(t('Some text ' . $form['#node']->title));
}
To:
function mymodule_myfunction($form, &$form_state) {
drupal_set_message(t('Some text ' . $form['#node']->title));
}
Also try installing the devel module so you can do things like
dsm($form);
dsm($form_state);
And see exactly what you are dealing with.
Also, if all you want to do is give a message when a new node of type 'X' is created a better way is to use hook_nodeapi;
It could look something like this;
function modulename_nodeapi(&$node, $op, $a3 = NULL, $a4 = NULL) {
if ($op == 'insert' && $node->type == 'my node type') {
drupal_set_message($node-title . ' is cool.');
}
}

Removing [nid:n] in nodereference autocomplete

Using the autocomplete field for a cck nodereference always displays the node id as a cryptic bracketed extension:
Page Title [nid:23]
I understand that this ensures that selections are unique in case nodes have the same title, but obviously this is a nasty thing to expose to the user.
Has anyone had any success in removing these brackets, or adding a different unique identifier?
Ultimately, you need to change the output of nodereference_autocomplete() in nodereference.module.
To do this properly, you want a custom module to cleanly override the function.
This function is defined as a menu callback, thus,
/**
* Implementation of hook_menu_alter().
*/
function custom_module_menu_alter(&$items) {
$items['nodereference/autocomplete']['page callback'] = 'custom_module_new_nodereference_autocomplete';
}
Then, copy the nodereference_autocomplete function into your custom module, changing it's name to match your callback. Then change this one line:
$matches[$row['title'] ." [nid:$id]"] = '<div class="reference-autocomplete">'. $row['rendered'] . '</div>';
Dropping the nid reference.
$matches[$row['title']] = '<div class="reference-autocomplete">'. $row['rendered'] . '</div>';
I believe the identifier is purely cosmetic at this point, which means you could also change the text however you like. If it is not purely cosmetic, well, I haven't tested to see what will happen in the wrong conditions.
I always meant to identify how to do this. Thank you for motivating me with your question.
What Grayside has posted will work... as long as you don't have two nodes with the same title. In other words, if you want to do as Grayside has proposed, you need to be aware that the nid is not entirely unimportant. The nodereference_autocomplete_validate() function does two things. It checks to see if there is a node that matches, and if so, it passes the nid on, setting it to the $form_state array. If it can't find a node, it will set an error. If the nid is present, it will be used to get the node, which also is faster, the code is here:
preg_match('/^(?:\s*|(.*) )?\[\s*nid\s*:\s*(\d+)\s*\]$/', $value, $matches);
if (!empty($matches)) {
// Explicit [nid:n].
list(, $title, $nid) = $matches;
if (!empty($title) && ($n = node_load($nid)) && $title != $n->title) {
form_error($element[$field_key], t('%name: title mismatch. Please check your selection.', array('%name' => t($field['widget']['label']))));
}
}
This just checks to see if there is a nid and checks if that node matches with the title, if so the nid is passed on.
The 2nd option is a bit slower, but it is here errors can happen. If you follow the execution, you will see, that if will try to find a node based on title alone, and will take the first node that matches. The result of this, is that if you have two nodes with the same title, one of them will always be used. This might not be a problem for you, but the thing is, that you will never find out if this happens. Everything will work just fine and the user will think that he selected the node he wanted to. This might be the case, but he might as well have chosen the wrong node.
So in short, you can get rid of the nid in the autocomplete callback, but it has 2 drawbacks:
performance (little)
uncertainty in selecting the correct node.
So you have to think about it, before going this route. Especially, since you most likely wont be able to find the problem of the selection of the wrong nodes, should it happen. Another thing to be aware of, is that the nid showing up, also brings some valuable info to the users, a quick way to lookup the node, should they be in doubt if it is the one they want, if several nodes have similar titles.
I got Grayside's answer to work, but I had to use MENU alter, instead of the FORM alter he posted. No biggy!
function custommodule_menu_alter(&$items) {
$items['nodereference/autocomplete']['page callback'] = 'fp_tweaks_nodereference_autocomplete';
}
I've found an alternative solution is to change your widget type to select list and then use the chosen module to convert your list to an autocomplete field.
This handles nodes with the same title, and actually I think the UI is better than the one provided by the autocomplete widget.
To anyone coming across this (rather old) topic by way of a google search - for Drupal 7 please consider using entityreference module and "Entity Reference" field type if possible.
You can acheive a lot more in configuration with an "Entity Reference" field. It doesn't have this problem with the nid in square brackets.
Here is the full Drupal 7 version (References 7.x-2.1) of Grayside's answer. This goes in your custom module:
/**
* Implementation of hook_menu_alter().
*/
function custom_menu_alter(&$items) {
$items['node_reference/autocomplete/%/%/%']['page callback'] = 'custom_new_node_reference_autocomplete';
}
/**
* Implementation of Menu callback for the autocomplete results.
*/
function custom_new_node_reference_autocomplete($entity_type, $bundle, $field_name, $string = '') {
$field = field_info_field($field_name);
$instance = field_info_instance($entity_type, $field_name, $bundle);
$options = array(
'string' => $string,
'match' => $instance['widget']['settings']['autocomplete_match'],
'limit' => 10,
);
$references = node_reference_potential_references($field, $options);
$matches = array();
foreach ($references as $id => $row) {
// Markup is fine in autocompletion results (might happen when rendered
// through Views) but we want to remove hyperlinks.
$suggestion = preg_replace('/<a href="([^<]*)">([^<]*)<\/a>/', '$2', $row['rendered']);
// Add a class wrapper for a few required CSS overrides.
$matches[$row['title']] = '<div class="reference-autocomplete">' . $suggestion . '</div>'; // this is the line that was modified to remove the "[nid:XX]" disambiguator
}
drupal_json_output($matches);
}

Resources