Drupal: Add onchange code to option_widgets in form_alter? - drupal

Do anyone know how I can add the following to form_alter?
Currently I've got two integer cck fields which are populated from sql using the php values command.
One field is for make, and the other is for model. Both makes and models are their own node types. Model has a node reference to make.
What I want to do on the Make drop down (CCK: INTEGER : SELECT), is on change, modify the contents of the model field. I've made my own bespoke form up, but it's too wieldy and want to use the basic drupal node edit forms.
Ideally I want to be able to pass this into the mix.
'#attributes' => array ('onchange'=> 'populatemodels(this,\'edit-field-model\')')
Does any one know of a way of doing this in the code?
In my form_alter adding the attribute doesn't produce any onchanges in the code:
#field_make (Array, 14 elements)
*
#type (String, 20 characters ) optionwidgets_select
*
#default_value (Array, 1 element)
o
0 (Array, 1 element)
+
value (String, 1 characters ) 8
*
#required (String, 1 characters ) 1
*
#columns (Array, 1 element)
o
0 (String, 5 characters ) value
*
#title (String, 4 characters ) Make
*
#description (String, 0 characters )
*
#delta (Integer) 0
*
#field_name (String, 10 characters ) field_make
*
#type_name (String, 3 characters ) car
*
#tree (Boolean) TRUE
*
#weight (String, 2 characters ) -1
*
#access (Boolean) TRUE
*
#count (Integer) 9
*
#attributes (Array, 1 element)
o
onchange (String, 39 characters ) populatemodels(this,'edit-field-model')
Thanks - Matt

I think I've had a break through.
cck/options_widgets.module
optionwidgets_select_process($element, $edit, &$form_state, $form) {
Additionally in my form_alter code, I added
drupal_add_js('sites/all/modules/adaptive/adaptive.js');
Which holds the populatemodels script
I've modded this code so that it passes attributes onto the next step
$element[$field_key] = array(
'#type' => 'select',
'#title' => $element['#title'],
'#description' => $element['#description'],
'#required' => isset($element['#required']) ? $element['#required'] : $field['required'],
'#multiple' => isset($element['#multiple']) ? $element['#multiple'] : $field['multiple'],
'#options' => $options,
'#default_value' => isset($element['#value'][$field_key]) ? $element['#value'][$field_key] : NULL,
>>> '#attributes' => isset($element['#attributes']) ? $element['#attributes'] : NULL, <<
);
By adding '#attributes' => isset($element['#attributes']) ? $element['#attributes'] : NULL, the onchange now appears in the rendered code.

Related

Drupal 9 - Changing Dynamic entity reference field cardinality with existing content

I need to change the cardinality of the dynamic entity reference field from 3 to -1 on Drupal 9 site with existing content. Is there a way to change that field configuration without breaking a site? When I do it from UI the site crashes with an error:
Drupal\Core\Database\DatabaseExceptionWrapper: Exception in Enhanced Landing Page Content[enhanced_landing_page_content]: SQLSTATE[42S22]: Column not found: 1054 Unknown column 'node__field_feature_medium_reference.field_feature_medium_reference_target_id_int'
in 'on clause': SELECT "node_field_data"."nid" AS "nid", "node_field_data_node__field_feature_medium_reference"."nid" AS "node_field_data_node__field_feature_medium_reference_nid" FROM "node_field_data" "node_field_data" LEFT JOIN "node__field_feature_medium_reference"
"node__field_feature_medium_reference" ON node_field_data.nid = node__field_feature_medium_reference.entity_id AND node__field_feature_medium_reference.deleted = :views_join_condition_0 LEFT JOIN "node_field_data" "node_field_data_node__field_feature_medium_reference"
ON node__field_feature_medium_reference.field_feature_medium_reference_target_id_int = node_field_data_node__field_feature_medium_reference.nid AND node__field_feature_medium_reference.field_feature_medium_reference_target_type = :views_join_condition_1
WHERE ((node_field_data.nid = :node_field_data_nid1)) AND (("node_field_data_node__field_feature_medium_reference"."status" = :db_condition_placeholder_2) AND ("node_field_data"."langcode" IN (:db_condition_placeholder_3)) AND ("node_field_data_node__field_feature_medium_reference"."langcode"
= :db_condition_placeholder_4)) LIMIT 5 OFFSET 0; Array ( [:node_field_data_nid1] => 225032 [:db_condition_placeholder_2] => 1 [:db_condition_placeholder_3] => en [:db_condition_placeholder_4] => en [:views_join_condition_0] => 0 [:views_join_condition_1]
=> node ) in main() (line 19 of index.php).

Does the symfony translation system support an arbitrary-length list of elements to be concatenated?

I need to translate variable-length sentences in Symfony 3.4.
Sample sentence: "Included: 4 breakfasts, 1 brunch and 3 dinners."
For the exemple let's say the following constraints:
I have to express the number of breakfasts, brunches, lunches and dinners that a travel may have in a full itinerary
The list of "things" to enumerate is known and finite. There is singular and plural translation for each of the key-words.
Not all of them have to appear. If there are 0 units, the full block will be omitted.
The list is comma-separated.
Except the last block which is added with an "and" connector.
If there's only 1 block there's no comma and no "and" connector.
Each can be singular/plural.
There are multiple languages, for this example I'll use English and Spanish, but in the reality there is also catalan.
The keywords are the following in english-singular / english-plural / spanish-singular / spanish-plural:
breakfast / breakfasts / desayuno / desayunos
brunch / brunches / alumerzo-desayuno / almuerzo-desayunos
lunch / lunches / almuerzo / almuerzos
dinner / dinners / cena / cenas
So... I can imagine the following tests cases:
[
'language' => 'en-US',
'parameters' => [
'breakfast' => 4,
'dinner' => 1,
],
'expectedOutput' => 'Included: 4 breakfasts and 1 dinner.'
],
[
'language' => 'es-ES',
'parameters' => [
'breakfast' => 2,
'brunch' => 1,
'lunch' => 5,
'dinner' => 2,
],
'expectedOutput' => 'Incluido: 2 desayunos, 1 desayuno-almuerzo, 5 almuerzos y 2 cenas.'
],
[
'language' => 'en-US',
'parameters' => [
'breakfast' => 1,
],
'expectedOutput' => 'Included: 1 breakfast.'
],
[
'language' => 'es-ES',
'parameters' => [
'dinner' => 4,
'lunch' => 3,
],
'expectedOutput' => 'Incluido: 3 almuerzos y 4 cenas.'
],
Questions
Is this supported by the native translation engine in Symfony 3.4?
If not, is this supported by the native translation engines of Symfony 4 or 5?
Otherwise, is there any known pattern or strategy that fits this case?
What the translation engine can do to some extent is use a number to switch between different cases (for singular/plural). Have a look at transChoice for symfony 3.4 (til 4.1.x apparently). Since symfony 4.2, the ICU message format (follow links too) may be used to handle pluralization.
However, (conditional) string building is not really a task for the translation engine at all (even though for simple cases transChoice or the ICU message format might work out fine). So you'll probably end up building the strings from bits and pieces.
Building the string in php
// fetch translations for the itemss
$items = [];
foreach($parameters as $key => $count) {
if($count <= 0) {
continue;
}
$items[] = $translator->transChoice($key, $count);
}
// handle 2+ items differently from 1 item
if(count($items) > 1) {
// get last item
$last = array_pop($items);
// comma-separate all other items, add last item with "and"
$itemstring = implode(', ', $items)
. $translator->trans('included_and')
. $last;
} else {
$itemstring = $items[0] ?? ''; // if no items or something like
// $translator->trans('included_none')
}
return $translator->trans('included', ['%items%' => $itemstring]);
and in the translation file (english):
'included' => 'Included: %items%.',
'included_and' => ' and ',
'breakfast' => '%count% breakfast|%count% breakfasts', // etc. for all other stuff
Please note, that transChoice should automatically set the %count% placeholder to the count provided as the second parameter to transChoice.
alternate approaches
You could easily split the translatable strings into breakfast_singular and breakfast_plural if you wanted to avoid the use of transChoice, you would have to replace
transChoice($key, $count)
with
trans($key.($count > 1 ? '_plural' : '_singular'), ['%count%' => $count])
You could also expand the included string into included_single and included_multiple where the latter is being translated as 'Included: %items% and %last%', with appropriate parameters and conditionals.
Ultimately though, string generation or more specifically language generation isn't really a job for a simple translation engine.

Whats the correct key/value Syntax to convert this Array with Symfony/XmlEncoder?

im building my request data as an array structure and want to use the symfony XmlEncoder to encode my Array to XML.
so i guess i got the fundamental part right, it looks like that for example:
$request_object = [
"acc-id" => $all_credentials,
"req-id" => $request_guid,
"tran-type" => "spec-url"
];
the syntax im looking for encodes in the following format, with attribute and value:
<amount currency="EUR">1.99</amount>
i have the possiblity to use the # sign on an array key, but how to also fit in the value?
$request_object = [
"acc-id" => $all_credentials,
"req-id" => $request_guid,
"tran-type" => "spec-url"
"am" => ["#attr"=>"attrval"]
];
this should be
<am attr="attrval"/>
but how to write it that i can also set the value? like:
<am attr="attrval">VALUE</am>
help is much appreciated
Use '#' as the index for the scalar value.
I found it by looking through the tests for the encoder.
#src:https://github.com/symfony/serializer/blob/master/Tests/Encoder/XmlEncoderTest.php
#line: 196
public function testEncodeScalarRootAttributes()
{
$array = [
'#' => 'Paul',
'#eye-color' => 'brown',
];
$expected = '<?xml version="1.0"?>'."\n".
'<response eye-color="brown">Paul</response>'."\n";
$this->assertEquals($expected, $this->encoder->encode($array, 'xml'));
}
...
#line: 234
public function testEncodeScalarWithAttribute()
{
$array = [
'person' => ['#eye-color' => 'brown', '#' => 'Peter'],
];
$expected = '<?xml version="1.0"?>'."\n".
'<response><person eye-color="brown">Peter</person></response>'."\n";
$this->assertEquals($expected, $this->encoder->encode($array, 'xml'));
}

Get array with key and value as my select attributes in DQL

I'm doing a query using DQL
$query = $this->createQueryBuilder('i')
->select('(i.sortiesNoSortie)','COUNT(i.participantsNoParticipant)')
->groupBy('i.sortiesNoSortie');
$result = $query->getQuery()->getArrayResult();
but i'm getting this
array:3 [
0 => array:2 [
1 => "76"
2 => "1"
]
1 => array:2 [
1 => "82"
2 => "1"
]
2 => array:2 [
1 => "83"
2 => "1"
]
]
And I want to get that in a simple array with key and value from my select
76 => 1
82 => 1
83 => 1
Is that possible ?
For obvious reasons, the result is a 2D array as it is representing the rows and columns that you would receive when executing the query. From the top of my head, there isn't a doctrine provided function to transform it into your desired format.
You can however use PHP to reformat the array for you.
$result = $this->createQueryBuilder('i')
->select('(i.sortiesNoSortie)','COUNT(i.participantsNoParticipant)')
->groupBy('i.sortiesNoSortie')
->getQuery()
->getArrayResult();
$output = array();
foreach($result as $row) {
$output[$row[0]] = $row[1];
}
While it seems at first that this is not ideal, realistically if doctrine were to implement something to format it the way you want it, it would most likely do something similar and just wrap it in a nice function for you.
Having said that, there may be a nicer way to process the data using PHP other than a foreach loop. Either way, don't shy away completely from reformatting your data in PHP after getting it from doctrine. Obviously this would be a case by case thing but it seems that you are processing small amounts of data, so the extra processing won't be an issue.
Edit
Thanks #Matteo for reminding me. array_column is the function that you can use to replace the foreach loop.
$output = array_column($result, 1, 0);

Save geofield programmatically in Drupal 8

I have read in a lot of sources that I should use the geofield_compute_values() function when trying to programmatically save coordinates in Drupal.
However it does not work for me, that function is undefined in the Drupal 8.5.2 that I am using.
I've installed geofield using composer and I can use it as usual in the admin area and there are no problems with saving there.
Here are some examples I've tried with, the first example gives me undefined function geofield_compute_values() :
$geofield_data = geofield_compute_values([
'lat' => $lat,
'lon' => $lon,
], GEOFIELD_INPUT_LAT_LON);
$cbisProduct->set('field_koordinater', $geofield_data);
I have also tried this out with no successful result and no errors :
$geofield = [
'geom' => "POINT (" . $lon . " " . $lat . ")",
'geo_type' => 'point',
'lat' => $lat,
'lon' => $lon,
'left' => $lon,
'top' => $lat,
'right' => $lon,
'bottom' => $lat,
];
$cbisProduct->set('field_koordinater', $geofield);
Seems like you're trying to use the geofield_compute_values() function which was available in 7.x version, but not in 8.x
You should look into the wkt_generator service. i.e.
<?php $wktGenerator = \Drupal::service('geofield.wkt_generator'); ?>
I haven't tried this, but something like this should work:
<?php
$point = [
'lat' => $request->get('lat'),
'lon' => $request->get('lon'),
];
$value = \Drupal::service('geofield.wkt_generator')->WktBuildPoint($point);
$node->field_koordinater->setValue($value);
Also, WktGeneratorTest.php and GeofieldItemTest.php files could be a good start to see how to use the service in your implementation.
This function is not available in Drupal 8. You have to rely on the basic GeofieldItem class that extends FieldItemBase. Also, as mentioned by oman, you can use WktGenerator to easily build points, polygons, etc.
Here a working example. Let's say your have an entity $cbisProduct with a multivalued geofield field_koordinater, and you want to set the first item with arbitrary lat/lon coordinates :
// Get geofield item
$geofield = $cbisProduct->get('field_koordinater')->get(0);
// Generate a point [lat, lon]
$coord = ['45.909621', '6.127147'];
$point = \Drupal::service('geofield.wkt_generator')->WktBuildPoint($coord);
// Calling this function will compute values AND assign geodata to the field instance
$geofield->setValue($point);
// You can read the computed geodata from the field
$geodata = $geofield->getValue();
//dpm($geodata);
// Explicitly set field data (needed if $geofield is not a reference)
$cbisProduct->set('field_koordinater', [$geodata]);
// Save entity
$cbisProduct->save();
Under the hood, GeofieldItem::setValue calls another method responsible to directly assign the computed values to the field instance :
# \Drupal\geofield\Plugin\Field\FieldType\GeofieldItem
protected function populateComputedValues() {
/* #var \Geometry $geom */
$geom = \Drupal::service('geofield.geophp')->load($this->value);
if (!empty($geom)) {
/* #var \Point $centroid */
$centroid = $geom->getCentroid();
$bounding = $geom->getBBox();
$this->geo_type = $geom->geometryType();
$this->lon = $centroid->getX();
$this->lat = $centroid->getY();
$this->left = $bounding['minx'];
$this->top = $bounding['maxy'];
$this->right = $bounding['maxx'];
$this->bottom = $bounding['miny'];
$this->geohash = substr($geom->out('geohash'), 0, GEOFIELD_GEOHASH_LENGTH);
$this->latlon = $centroid->getY() . ',' . $centroid->getX();
}
}
Note : You don't necessarily need WktGenerator for building points, as long as you know the geofield type and how geophp should handle it. For example, the following 2 statements are equivalent :
$point = \Drupal::service('geofield.wkt_generator')->WktBuildPoint($coord);
// is equivalent to
$point = GEOFIELD_TYPE_POINT . '(' . implode(' ', $coord) . ')');
But it is safer to rely on the WktGenerator especially with more complex data types.

Resources