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.
Related
I'm trying to build a view that shows the most recent (now four but eventually five) years of data in a chart. I'm able to pull the data successfully in a variable the shows the most recent 4 years(I don't have the fifth year in the database yet). In order to break it down for the view and add some calculations on associated arrays, I have created a collection. I can get to the first and last array entity but can not figure out how to break out the middle two arrays.
public function viewAsuFiveYear()
{
$asuFiveYearEnrollments = $this->Enrollments->find('all', [
'contain' => ['Azinstitutions', 'FallFtes', 'FallHeadcounts', 'ProjectedEnrollments',
'FallHeadcounts.Locations', 'FallHeadcounts.StudentTypes', 'FallHeadcounts.ResidentStatuses', 'FallHeadcounts.Campuses',
'FallFtes.Locations', 'FallFtes.StudentTypes', 'FallFtes.ResidentStatuses', 'FallFtes.Campuses']
], ['limit' => 5])->where(['azinstitution_id' => 1])->order(['enrollment_year' => 'DESC']);
$collection = new Collection($asuFiveYearEnrollments);
$yearone = $collection->first();
$yearTwo = $collection->take(1, 1);
$yearFour = $collection->last();
$collection1HC = new Collection($yearone->fall_headcounts);
$onCampusesHc1 = $collection1HC->match(['location_id' => 1 ])->match(['campus_id' => NULL]);
$collection4HC = new Collection($yearFour->fall_headcounts);
$onCampusesHc4 = $collection4HC->match(['location_id' => 1 ])->match(['campus_id' => NULL]);
$this->set(compact('asuFiveYearEnrollments', 'azinstitutions', 'yearone', 'yearTwo', 'yearFour', 'onCampusesHc1', 'onCampusesHc4'));
}
I tried ->take (one array, second position) but it times out with that. Not sure what filter I can use to get to the 2nd or 3rd entity array.
I believe I've figured it out. May not be the best answer but it works. for the second year I skipped the 1st array and grabbed the first array in what was left.
and the same with the third year.
$yearTwo = $collection->skip(1)->first();
$yearThree = $collection->skip(2)->first();
I have the following code to get a Magento 2 product collection:
<?php namespace Qxs\Related\Block;
class Related extends \Magento\Framework\View\Element\Template
{
public function __construct(
\Magento\Backend\Block\Template\Context $context,
\Magento\Catalog\Model\ResourceModel\Product\CollectionFactory $productCollectionFactory,
\Magento\Catalog\Model\Product\Attribute\Source\Status $productStatus,
\Magento\Catalog\Model\Product\Visibility $productVisibility,
\Magento\Framework\Registry $registry,
array $data = []
)
{
$this->_productCollectionFactory = $productCollectionFactory;
$this->productStatus = $productStatus;
$this->productVisibility = $productVisibility;
$this->_registry = $registry;
parent::__construct($context, $data);
}
public function getProductCollection()
{
try {
$product = $this->_registry->registry('current_product');
$range_percentage = 35;
$price_temp = round($product->getFinalPrice() / 100 * $range_percentage);
$price_from = $product->getFinalPrice() - $price_temp;
$price_to = $product->getFinalPrice() + $price_temp;
$categories = $product->getCategoryIds();
$collection = $this->_productCollectionFactory->create();
$collection->addAttributeToSelect('*')
->addCategoriesFilter(['in' => $categories])
->addPriceDataFieldFilter('%s >= %s', ['min_price', $price_from])
->addPriceDataFieldFilter('%s <= %s', ['min_price', $price_to])
->addMinimalPrice()
->addAttributeToFilter('entity_id', ['neq' => $product->getId()])
->addAttributeToFilter('status', ['in' => $this->productStatus->getVisibleStatusIds()])
->setVisibility($this->productVisibility->getVisibleInSiteIds())
->setPageSize(5);
return $collection;
} catch (\Exception $e) {
var_dump($e->getMessage());
}
}
}
Code above is updated with a working example
It will return a result with addtofieldfilter 'price' but it does not work with final_price attribute. I need to sort based on final_price because configurable products don't have a price. The code returns: invalid attribute name.
How can I filter on price range in final_price attribute?
Thanks,
The final_price is part of the price index tables, so you can't work with it the same way as you would do with fields and attributes. You need to join in the price index to be able to filter and sort based on final_price. Luckily, Magento has added a few nifty functions for us to use on the product collection; addPriceDataFieldFilter() and addFinalPrice().
Solution
To be able to achieve the logic you describe above, you would want to change your code to something like this:
$collection = $this->_productCollectionFactory->create();
$collection->addAttributeToSelect('*')
->addCategoriesFilter(['in' => $categories])
->addPriceDataFieldFilter('%s >= %s', ['final_price', $price_from])
->addPriceDataFieldFilter('%s <= %s', ['final_price', $price_to])
->addFinalPrice()
->addAttributeToFilter('entity_id', ['neq' => $product->getId()])
->addAttributeToFilter('status', ['in' => $this->productStatus->getVisibleStatusIds()])
->setVisibility($this->productVisibility->getVisibleInSiteIds())
->setPageSize(5);
Note the order of the functions. You must always call addFinalPrice() after all of the addPriceDataFieldFilter() or else the filter won't be applied.
Bonus
If you want to sort by final_price, you can add following code after addFinalPrice():
$collection->getSelect()->order('price_index.final_price ASC');
References
https://github.com/magento/magento2/blob/2.2.9/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php#L1465
https://github.com/magento/magento2/blob/2.2.9/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php#L2265
For each post, there is a custom field name "Function", the key/value pair is like this:
Key : Functions
Value : <!--en-->Nourishing Yin and invigorating the vital essence of kidneys.<!--:--><!--tw-->滋陰補腎。<!--:-->
The problem is if I simply use get_post_meta , it return string of both language, how can I get the value based on the language?
I am using qTranslate right now, Thanks.
Updated (the code):
$custom_fields = get_post_custom(get_the_ID());
$function = get_post_custom_values('Functions', get_the_ID());
You can simply fetch the strings considering comments as prefix and suffix -
After you get the custom field value,
e.g.
$function = "<!--en-->Nourishing Yin and invigorating the vital essence of kidneys.<!--:--><!--tw-->滋陰補腎。<!--:-->";
$arr = explode("<!--:-->", $function);
$new_arr = array();
foreach($arr as $a ){
if(!empty($a)){
$lang = str_replace( "-->", "", substr($a, 4, 5) );
$str = substr($a, 9);
$new_arr[$lang] = $str;
}
}
Now $new_arr will have key/value pairs like array(language_code => sentence).
If you do print_r($new_arr);
It will give output as follows:
Array ( [en] => Nourishing Yin and invigorating the vital essence of kidneys. [tw] => 滋陰補腎。 )
Now you can identify the strings using their respective language codes.
In Symfony 2, I am using the translation:update command to generate translations YML files from my templates where I already have defined teh translation strings.
I get .yml files where everything is mixed up.
I am searching for a tool, a script that could refactor this :
menu.home: __en.menu.home
menu.projects: __en.menu.projects
information.address: __en.information.address
information.agent.languages.english: __en.information.agent.languages.english
information.agent.languages.russian: __en.information.agent.languages.russian
information.agent.name: __en.information.agent.name
to :
information:
address: __en.information.address
agent:
languages:
english: __en.information.agent.languages.english
russian: __en.information.agent.languages.russian
name: __en.information.agent.name
menu:
home: __en.menu.home
projects: __en.menu.projects
Here is a code snippet to do what you ask:
use Symfony\Component\Yaml\Yaml;
use Symfony\Component\Yaml\Dumper;
// ...
$dottedYaml = Yaml::parse(file_get_contents('dotted-file.yml'));
$nestedYaml = array();
foreach ($dottedYaml as $dottedKey => $value) {
$levels = explode('.', $dottedKey);
$levelYaml =& $nestedYaml;
do {
$level = array_shift($levels);
if (!isset($levelYaml[$level])) {
$levelYaml[$level] = array();
}
$levelYaml =& $levelYaml[$level];
} while (count($levels));
$levelYaml = $value;
}
$dumper = new Dumper();
file_put_contents('nested-file.yml', $dumper->dump($nestedYaml, 5));
Please be aware that even though my code works for your example if you have something like this:
menu: "here is the problem"
menu.home: __en.menu.home
menu.projects: __en.menu.projects
You cannot actually convert to a nested notation unless you decide some kind of convention if a certain level contains a scalar value AND an array of nested values:
menu:
home: __en.menu.home
projects: __en.menu.projects
value: "here is the problem" # we made up a "value" attribute to store the top level value
I am using smarty templating engine and am encountering a math issue. I am trying to create a total amount (sum) based on amounts in the array. (Normally I would do this at the server level, but do to the way that I create the array, dont think that is possible.) I am merging two arrays into one, but each array shares an 'Amount' which I am trying to determine the 'Total Amount'
Here are the steps I am taking two arrays pushing into one array:
foreach ($data_main1 as $transaction_main1) {
$json_decoded = json_decode($transaction_main1['NewObject']);
$amount = $transaction_main1['Amount'];
$mycart1[] = array('ParentType' => $ParentType, 'Amount' => $amount);
}
$mycart2=array();
foreach ($data_main2 as $transaction_main2) {
$json_decoded = json_decode($transaction_main2['NewObject']);
$amount = $transaction_main2['Amount'];
$mycart2[] = array('ParentType' => $ParentType, 'Amount' => $amount);
}
$mycart = array_merge((array)$mycart1, (array)$mycart2);
$smarty->assign('cart', $mycart);
Here is my Smarty along with the math equation that does not show the value:
{assign var=last value=$cart[cart].Amount+1}
(I am certainly open to the idea of creating a total amount on the array_merge, just unsure how to do that, or even if it is possible)
Do you know about the {math} Smarty bult-in function?
{math equation="x + 1" x=$cart[cart].Amount}
Let me know if it works.