Decode Product Customization on frontend and on backend (and emails) - php

I've got a prestashop setup that has a small 'customization form' that currently saves the information to the products default customization text input. I did this to save time on having to write a complete custom module to add additional customization form fields and such.
Currently all the inputs are serialized (json) and entered as a long string into the text input like this:
Client Customization: %5B%5B%7B%22name%22%3A%22trophy%5B1%5D%5Bline1%5D%22%2C%22engraving%22%3A%22Test%20Trophy%22%7D%2C%7B%22name%22%3A%22trophy%5B1%5D%5Bline2%5D%22%2C%22engraving%22%3A%22test%20trophy%22%7D%2C%7B%22name%22%3A%22trophy%5B1%5D%5Bline3%5D%22%2C%22engraving%22%3A%221111111%22%7D%5D%5D
On the front end - when the customized data is displayed I can use PHP to decode & display it appropriately.
Is there a way where I can change that globally somewhere so I don't have to try and find every place where it might display and add that PHP code?
I'm running into the issue that I can't seem to find where to add the PHP code to 'decode' that string for the emails that are being sent out - so the long ugly string is being seen instead of the nice few lines of customization the user entered.
Any thoughts on how to handle this? Is there a spot where I can globally assign the decoded string to the products customization?

You could either try the PaymentModule class to decode the string just before the emails are sent, or Product's method called "getAllCustomizedDatas" for a more "global" approach.
And then test a lot, of course :)
Here's a quick draft of the second approach:
<?php
class Product extends ProductCore
{
public static function getAllCustomizedDatas($id_cart, $id_lang = null, $only_in_cart = true, $id_shop = null)
{
$datas = parent::getAllCustomizedDatas($id_cart, $id_lang, $only_in_cart, $id_shop);
/*
* Iterate over $datas, you're looking for
* [id_product][id_product_attribute][id_address_delivery][id_customization][datas]
* Datas will contain an array of fields broken by their type. You can then decode
* the ones that need to be decoded and return the result:
*/
return $datas;
}
}

Related

Updating data after backend action TYPO3 11.5

Working on Typo3 11.5.13
I'm trying to update some data on my pages table after a be_user changed something.
I read something about setting hooks for that purpose but I can't seem to find a good explanation as to how hooks actually function within Typo3 and how to configure one, especially for my purpose.
As far as I can see, this problem I have should be quickly solved but the complexity of the typo3 doc is hindering my progress again. Maybe you can explain how I can accomplish my goal.
Simply put: A backend user is supposed to choose a date in a datepicker and some dateinterval in the settings of a page. After saving(Or even after picking both values) I would like to update the "Next time happening" field the user can see but not change to be updated to the given date plus the dateinterval chosen.
If you have some sort of idea please share it with me.
Generally hooks are not that good documented. Modern Events are easier to find and better commented. However, if I get your use case right, using DataHandler Hooks are they way to go. That mean, every place which are using the DataHandler to save data are then covered. The backend form engine are using DataHandler.
Basic information about hooks in the core documentation:
https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/ApiOverview/Events/Hooks/Index.html
How to identify or find hooks, events, signalslots (depending on TYPO3 version):
https://usetypo3.com/signals-and-hooks-in-typo3.html
https://daniel-siepmann.de/posts/migrated/how-to-find-hooks-in-typo3.html
Introduction or "DataHandler" explained:
https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/ApiOverview/Typo3CoreEngine/Database/Index.html
Basicly, DataHandler has two main kind of processings:
Data manipulations -> process_datamap()
Actions (move,delete, copy, translate) -> process_cmdmap()
For DataHandler, you register a class only for datamap and/or processmap, not for a concrete hook itself.
// <your-ext>/Classes/Hooks/MyDataHandlerHooks.php
namespace <Vendor>\<YourExt>\Hooks;
class MyDataHandlerHooks {}
// <your-ext>/ext_localconf.php
// -> for cmdmap hooks
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['processCmdmapClass']['yourextname']
= \Vendor\YourExt\Hooks\MyDataHandlerHooks::class;
// -> for datamap hooks
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['processDatamapClass']['yourextname']
= \Vendor\YourExt\Hooks\MyDataHandlerHooks::class;
You need to register your class only for these kind of hooks you want to consume. And you do not have to implement all hooks.
Hooks can be looked up in \TYPO3\CMS\Core\DataHandling\DataHandler (as hooks are normally searched.
Next step would be to find the proper hook for your use case, and simply add that hook method to your class. Naming the hooks are not chooseable for DataHandler hooks.
TYPO3 Core tests contains a test fixture class for DataHandler hooks - which is not complete, but contains at least the most common ones (along with the needed method signatures) since 8.x:
https://github.com/TYPO3/typo3/blob/main/typo3/sysext/core/Tests/Functional/DataHandling/DataHandler/Fixtures/HookFixture.php
So you may have to look into the version for your core version to get a feeling how the signature should look for that core version.
Generally I would guess one of these two:
processDatamap_postProcessFieldArray(): Hook with prepared field array, and you can simple add your new stuff to write or update it and it will be saved. Good if you need to change the record directly.
processDatamap_afterDatabaseOperations(): Hook after record has been changed. This is a good startpoint if you need to do other things after saving a record.
Given your usecase, I would tip on the first one, so here a example implementation (in the class and registering as datamap hook as explained above):
// <your-ext>/Classes/Hooks/MyDataHandlerHooks.php
namespace <Vendor>\<YourExt>\Hooks;
class MyDataHandlerHooks {
/**
* #param string|int $id
*/
public function processDatamap_postProcessFieldArray(
string $status, // Status of the current operation, 'new' or 'update'
string $table, // The table currently processing data for
$id, // The record uid currently processing data for,
// [integer] or [string] (like 'NEW...')
array &$fieldArray, // The field array of a record, cleaned to only
// 'to-be-changed' values. Needs to be &$fieldArray to be considered reference.
DataHandler $dataHandler
): void
{
// $fieldArray may be stripped down to only the real fields which
// needs to be updated, mainly for $status === 'update'. So if you
// need to be sure to have correct data you may have to retrieve
// the record to get the current value, if not provided as with new
// value.
if ($table === 'be_users'
&& $status === 'update'
&& array_key_exists('target_field_name', $fieldArray)
) {
$valueToReactTo = $fieldArray['target_field_name'];
if ($valueToReactTo === 'some-check-value') {
// needs not to be there
$fieldArray['update-field'] = 'my-custom-value';
}
}
}
}

Unexpected behavior by CodeIgniter while loading views

I have a viewer helper function that loads the main content alongside the footer/header. The bug/unexpected behavior occurred when I loaded the array's key for the header that shares the same name for a variable in the main content view - the same array is loaded for both the header and main content.
I thought it's normal, since the same $data array was sent to the header and main content as-well(as mentioned before). So the variable will naturally be present in both views. But, well, it wasn't exactly like that. I unset the $data variable after sending the data to the header then re-created it when I wanted to send some data to the main view - but still the problem is not fixed.
I made a simple example for this bug/unexpected behavior:
Consider this view, named test:
<?php
echo $some_data;
And this controller:
class Test extends CI_Controller {
function index() {
$data['some_data'] = 'Some data.';
$this->load->view('test', $data);
/*
* Output:
* Some data.
*/
unset($data);
unset($data['some_data']);//Just to make sure it's not PHP's fault.
$this->load->view('test');
/*
* Output:
* Some data.
*
* Even though the $data variable is unsetted AND not passed!
*/
$different_data = array();
$this->load->view('test', $different_data);
/*
* Output:
* Some Data.
*
* Still outputs the same thing, even though
* I'm sending different array(and the $data array is unstted).
*
*/
}
}
Note: The whole code will output Some data. three times.
The only way to solve this issue is sending a different array and setting the array key(which is some_data) to something else which will override the old one.
So, is this a bug or something made by CodeIgniter's dudes?
we had the same problem like you and our Alien coworker found THE solution:
if file: codeigniter\system\core\Loader.php
find the code: (i think the line number is 806):
$this->_ci_cached_vars = array_merge($this->_ci_cached_vars, $_ci_vars);
and correct it to:
$this->_ci_cached_vars = $_ci_vars;
best regards
This is expected behavior.
Once variables are set they become available within the controller class and its view files. Sending an array in $this->load->view() is the same as sending an array directly to $this->load->vars() before calling the view file. This simplifies things for most people using multiple views in a controller. If you are using multiple view files in a single controller and want them to each have their own set of variables exclusively, you'll need to manually clear out the $this->load->_ci_cached_vars array between view calls.
A code comment in the Loader class describes another situation showing why this is the desired default behavior:
//You can either set variables using the dedicated $this->load_vars()
//function or via the second parameter of this function. We'll merge
//the two types and cache them so that views that are embedded within
//other views can have access to these variables.
This is a CodeIgniter issue. The variables you are sending in seems to be cached until you override them. I have encountered this myself and can verify it.
$this->load->view('test', array('some_data' => NULL));

How to get data from Magento System Configuration

I just wandering on how I can get the configuration data for my custom module. The configuration can be set from the admin system->configuration and how to pull it in frontend?
$configValue = Mage::getStoreConfig('sectionName/groupName/fieldName');
sectionName, groupName and fieldName are present in etc/system.xml file of your module.
The above code will automatically fetch config value of currently viewed store.
If you want to fetch config value of any other store than the currently viewed store then you can specify store ID as the second parameter to the getStoreConfig function as below:
$store = Mage::app()->getStore(); // store info
$configValue = Mage::getStoreConfig('sectionName/groupName/fieldName', $store);
you should you use following code
$configValue = Mage::getStoreConfig(
'sectionName/groupName/fieldName',
Mage::app()->getStore()
);
Mage::app()->getStore() this will add store code in fetch values so that you can get correct configuration values for current store this will avoid incorrect store's values because magento is also use for multiple store/views so must add store code to fetch anything in magento.
if we have more then one store or multiple views configured then this will insure that we are getting values for current store
Magento 1.x
(magento 2 example provided below)
sectionName, groupName and fieldName are present in etc/system.xml file of the module.
PHP Syntax:
Mage::getStoreConfig('sectionName/groupName/fieldName');
From within an editor in the admin, such as the content of a CMS Page or Static Block; the description/short description of a Catalog Category, Catalog Product, etc.
{{config path="sectionName/groupName/fieldName"}}
For the "Within an editor" approach to work, the field value must be passed through a filter for the {{ ... }} contents to be parsed out. Out of the box, Magento will do this for Category and Product descriptions, as well as CMS Pages and Static Blocks. However, if you are outputting the content within your own custom view script and want these variables to be parsed out, you can do so like this:
<?php
$example = Mage::getModel('identifier/name')->load(1);
$filter = Mage::getModel('cms/template_filter');
echo $filter->filter($example->getData('field'));
?>
Replacing identifier/name with the a appropriate values for the model you are loading, and field with the name of the attribute you want to output, which may contain {{ ... }} occurrences that need to be parsed out.
Magento 2.x
From any Block class that extends \Magento\Framework\View\Element\AbstractBlock
$this->_scopeConfig->getValue('sectionName/groupName/fieldName');
Any other PHP class:
If the class (and none of it's parent's) does not inject \Magento\Framework\App\Config\ScopeConfigInterface via the constructor, you'll have to add it to your class.
// ... Remaining class definition above...
/**
* #var \Magento\Framework\App\Config\ScopeConfigInterface
*/
protected $_scopeConfig;
/**
* Constructor
*/
public function __construct(
\Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig
// ...any other injected classes the class depends on...
) {
$this->_scopeConfig = $scopeConfig;
// Remaining constructor logic...
}
// ...remaining class definition below...
Once you have injected it into your class, you can now fetch store configuration values with the same syntax example given above for block classes.
Note that after modifying any class's __construct() parameter list, you may have to clear your generated classes as well as dependency injection directory: var/generation & var/di
for example if you want to get EMAIL ADDRESS from config->store email addresses.
You can specify from wich store you will want the address:
$store=Mage::app()->getStore()->getStoreId();
/* Sender Name */
Mage::getStoreConfig('trans_email/ident_general/name',$store);
/* Sender Email */
Mage::getStoreConfig('trans_email/ident_general/email',$store);

Object property saving via $object->save(); problems

I'm a magento programmer and I've been loosing several long minutes to figure out why a property on an object was not saved in the database.
Let's explain, here are 3 pieces of code that I would expect to do the same thing :
First code
$order = Mage::getModel('sales/order')->load(1873);
$myInfo = 'important piece of information';
$order->getPayment()->setAdditionalInformation('my_info',$myInfo);
$order->getPayment()->save(); //No information in the database is saved
No value saved in database.
Second code
$order = Mage::getModel('sales/order')->load(1873);
$myInfo = 'important piece of information';
$payment = $order->getPayment();
$payment->setAdditionalInformation('my_info',$myInfo);
$payment->save(); //No information in the database is saved
No value saved in database.
Third code
$order = Mage::getModel('sales/order')->load(1873);
$myInfo = 'important piece of information';
$order->getPayment()->setAdditionalInformation('my_info',$myInfo)->save(); //YEAHHH ! It works ! I now have that in my database.
Finally, I got it !
The code from setAdditionalInformation
/**
* Additional information setter
* Updates data inside the 'additional_information' array
* or all 'additional_information' if key is data array
*
* #param string|array $key
* #param mixed $value
* #return Mage_Payment_Model_Info
* #throws Mage_Core_Exception
*/
public function setAdditionalInformation($key, $value = null)
{
if (is_object($value)) {
Mage::throwException(Mage::helper('sales')->__('Payment disallow storing objects.'));
}
$this->_initAdditionalInformation();
if (is_array($key) && is_null($value)) {
$this->_additionalInformation = $key;
} else {
$this->_additionalInformation[$key] = $value;
}
return $this->setData('additional_information', $this->_additionalInformation);
}
note: The final setData() always returns $this
Question, Why ?
I think I've forgot some specificities about the way PHP works, especially for the first code. I would understand that it doesn't work because of some memory stuff with PHP.
But the two other pieces of code, why doesn't it work ?
Thanks,
Hugues.
These pieces of code are identical from Magento view - you didn't forget anything about how PHP works. With default Magento installation all 3 snippets must produce same results.
If the results of those code blocks are different, then you should:
a) turn off all custom extensions you use and try your code blocks without them - maybe some of extensions modify the default behavior of Order or Payment models.
b) check that your code snippets are really same as presented in this question - maybe there were other code lines that you thought of as non-important and didn't include in this question
c) check that you update view in your MySQL client after executing each code snippet - maybe you see just some old information in payment table
c2) check that you don't use replicated MySQL severs - maybe you update information on master DB, but sees payment table from slave DB, where these changes haven't yet been synced to
d) check that no other code executes after yours - maybe some other model or controller modifies additional_information and so deletes all your changes. Try to insert 'exit' just after your code so you'll be sure about it.
Not a Magento user, but it looks as if each method is returning an object which is required by the next method in the chain.
If you call each method individually, the object they create or modify won't contain any changes made by the previous method calls. By chaining the method calls, each one picks up the changes made by the previous call.

Drupal 6 Views 2: Setting Date Arguments

Passing uid as an argument works fine with this code:
$bouts = views_get_view_result('Results', 'page_1', array($user->uid));
The key line in views_get_view_result that sets arguments is:
$view->set_arguments($args);
But what about passing date ranges?
Also, if something is specified as a filter on a view, is there a way to prorammatically alter it?
views_get_view_result:
/**
* Investigate the result of a view.
* from Drupal.org.
*
* #param string $viewname
* The name of the view to retrieve the data from.
* #param string $display_id
* The display id. On the edit page for the view in question, you'll find
* a list of displays at the left side of the control area. "Defaults"
* will be at the top of that list. Hover your cursor over the name of the
* display you want to use. A URL will appear in the status bar of your
* browser. This is usually at the bottom of the window, in the chrome.
* Everything after #views-tab- is the display ID, e.g. page_1.
* #param array $args
* Array of arguments. (no keys, just args)
* #return
* array
* An array containing an object for each view item.
* string
* If the view is not found a message is returned.
*/
function views_get_view_result($viewname, $display_id = NULL, $args = NULL) {
$view = views_get_view($viewname);
if (is_object($view)) {
if (is_array($args)) {
$view->set_arguments($args);
}
if (is_string($display_id)) {
$view->set_display($display_id);
}
else {
$view->init_display();
}
$view->pre_execute();
$view->execute();
/* print "<pre> $viewname: $display_id";
print_r(get_class_methods($view)); */
return $view->result;
}
else {
return t('View %viewname not found.', array('%viewname' => $viewname));
}
}
As for passing data ranges and given the posted function definition, you could pass date ranges to that only if the view would accept them as arguments. I'm not 100% sure, but afaik date ranges can only be defined as filters, not as arguments, which leads to your second Question:
Programmatically altering the views filter settings is possible, but a bit messy, given the rather complicated view object/array mashup structure. In your posted function above, the first line is
$view = views_get_view($viewname);
After that, $view contains the whole view object. The filter settings are defined per display, so assuming you have a view with only a default display, you will find the filter settings under
$view->display['default']->display_options['filters']
(Note the object/array notation mix - the display is a contained object of type views_display)
The 'filters' array contains one entry per filter, with varying elements depending on the filter type. For your purpose, I would suggest to create a dummy view with just the filter you are interested in, with preconfigured/hardcoded values. Using a debugger (or var_dump/print_r) you can then take a look at the filter array after view creation. From what you find there, you should be able to deduce how to inject your custom date range.
Disclaimer: Poking around in the view like this is a bit annoying and not to effective, but it works. As of yet, I have not found a concise documentation of Views2 that would explain the innards in a straight forward way, as I find the official API documentation a bit lacking concerning the usage from code. (Of course this could well be just me being to stupid ;)
If you're using views 2, you can use the GUI to add a date argument. Then in the url, you can put :
www.yousite.com/yourview/startDate--finishDate
For the startDate/finishDate, the format is YYYY-MM-DD-HH.
GL!

Categories