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.
Related
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';
}
}
}
}
As part of my ongoing efforts to simplify the legacy codebase for a CodeIgniter3 application, I'm currently running into a problem. In short, I've dealt with an error statement earlier stating:
can't use method return value in write context
which might ring a bell for some readers. Nonetheless, I haven't seen this error since but I suspect that something is still going wrong. In short, I'm trying to push an associative array into another associative, multidimensional array which is the result of a method that returns a reference.
I've set up a system to easily alter the contents of a JSON, which is returned by reference through this method:
/**
* Function : items
* Target : Retrieves a reference to the items, decoded
*
* #author : Angev
* #since : 2.0
* #version : 1.0
*
* #return Referenced link to index 'items'.
*/
public function & items()
{
$list = (is_array($this->data)) ? $this->data : json_decode($this->data, true);
return $list['items'];
}
In short, the method $this->items() is part of a Model named 'CheckList'. The Checklist corresponds with a data-table in my database, which includes a 'data' column that represents the data belonging to a certain checklist: the JSON I'm trying to alter and which you can see being returned in this method. Another way of seeing the output of $this->items() is that it should return a reference to $this->data['items'].
This goes all and well, I've used this method many times during development - as a shorthand to accessing ['list'] - and it always returns exactly what I need it to return: a multidimensional array filled with unique indexes (strings) that contain the data belonging to each item of the checklist.
The problem however, arises in a method named update_checklist() in particular the following section:
$this->items()[$uid] = [
'parent_id' => $parent['id'],
... ,
];
I'd expect the method to add an index to the array returned by $this->items(), but it doesn't.
I'm not quite sure what goes wrong in this context, since I have earlier seen the error message written at the top of this question, but haven't seen it since.
However, no index is added to the array and whenever I do an immediate var_dump($this->items()) afterwards. It just shows the state of the array as it was before the execution of update_checklist().
In search of an answer, I've also tried wrapping the callback in parentheses, but to no avail:
( $this->items() )[$uid] = . . .
To temporary fix the problem, I've resorted to a more direct alteration of the ['items'] array by doing the following:
$this->data = json_decode($this->data, true);
$this->data['items'][$uid] = [
'parent_id' => $parent['id'],
... ,
];
Nonetheless, even though the code above works, I'm left wondering what the flaw is in my logic concerning the method reference return of $this->items() and why I cannot use this method when pushing into the referenced array.
How can I write the required changes to make $this->items()[] function as intended? Or I'd be interested in more clarity into the theory behind this structure and why it can't work.
As always, once you start formulating a question, you stumble upon the flaws in your logic. I've read over this question with a colleague and while discussing the solution just magically presented itself. I'll include the answer for future reference to anyone having the same problem.
/**
* Function : items
* Target : Retrieves a reference to the items, decoded
*
* #author : Angev
* #since : 2.0
* #version : 1.0
*
* #return Referenced link to index 'items'.
*/
public function & items()
{
$list = (is_array($this->data)) ? $this->data : json_decode($this->data, true);
return $list['items'];
}
The problem lies withing the items() method. This method surely returns a reference, but the reference is made to the preliminary variable $list, which in turn has no direct reference to $this->data. So instead of refering to $this->data['items'], the method returns a reference to $list, which is essentially a copy of $this->data, no no real reference.
To fix the problem, the following code was used:
public function & items()
{
if(!is_array($this->data) ) $this->data = json_decode($this->data, true);
return $this->data['items'];
}
As expected, the method now returns a reference to the actual data object.
So in short, what I've learned is that if you let a method return a reference, you need to make sure that whatever the method returns is actually a reference instead of a copy of the data you're trying to reference to.
I'll leave this question open for now to allow others to share any knowledge of insights in this matter.
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;
}
}
I made some classes having a lot of methods documented properly using PHP (something like a library).
Now, what the other developers will do is just require the PHP library I made in their code and use the predefined functions in it.
Is it possible to hide the PHP code (of the library I made) from the other PHP developers (requiring the file) and just show them the function name, parameters and its documentation without showing the code inside it? I'm not talking about obfuscation, which can be reversible, I'm talking about preventing users to actually see any code.
eg.
/**
*
* CREATE A NEW THREAD
* #param unknown_type $uid User ID of person who is creating the thread
* #param unknown_type $participant An array having collection of UID of people who are participating in this conversation
* #param unknown_type $msgtype Message Type Flags (1-normal, 2-chat, 3-sent as email, 4-profile post, 5-group post, 6-customer support)
* #param unknown_type $subject Subject of the thread
* #param unknown_type $tname Thread Name
* #param unknown_type $tpic Thread Cover Picture (Defaults to "")
* #param unknown_type $tflag Thread Flag (1-allowed,2-under review,3-blocked) (Defaults to 1)
* #return string|Ambigous <string, unknown> Thread ID on success, "" on failure
*/
public function createthread($uid,$participant,$msgtype,$subject,$tname,$tpic="",$tflag="1")
{
$randobj=new uifriend();
$tid=$randobj->randomstring(30,DB_MESSAGE,MSG_OUTLINE,msgoutline_tid);
$socialobj=new socialoperations();
$listid=$socialobj->createlist("threadlist_".$tid, "2",$msgtype,"1",$uid);
if($socialobj->addtolist($participant, $listid, $uid)!="SUCCESS")
{
return "";
}
if($listid=="")
{
$lasterror="An error occured in creating thread! Unable to Create Lists!";return "";
}
$dbobj=new dboperations();
$res=$dbobj->dbinsert("INSERT INTO ".MSG_OUTLINE." (".msgoutline_tid.",".msgoutline_subject.",".msgoutline_fid.",".msgoutline_participantid.",".msgoutline_msgtype.",".msgoutline_threadpic.",".msgoutline_threadflag.") VALUES
('$tid','$subject','$uid',$listid,'$msgtype','$tpic','$tflag')",DB_MESSAGE);
if($res=="SUCCESS")
{
return $tid;
}
else
{
$lasterror="Unable to create Thread!";return "";
}
}
The other developers must only be able to see the documentation I wrote above the function with the function name and parameters, but the code must not be accessible to them in any way.
Why I want this: I have a lot of secure code in my PHP file which I don't want to show to the other developers, but still allow them to call the functions and read the returned values.
You can't hide your code from other developers if you want to allow them call your functions directly. What you can do is to make a Web Service and give it's documentation to other developers.
Because I had a meta post so this was reopened and another meta post for formatting this question, I'll do my best to properly answer this question. Note that this is only a way of doing this, with its limitations stated at the end of the post.
The API
The remote server
You could create a web API in a different domain and access it from your main domain. I think the best way for explaining how it works is with a practical example. Imagine that your library includes the function 'joinstrings()', which takes 2 arguments. Then you have it in your separated web:
http://apiweb.com/functions.php
<?php
// Your API. I hope the real one is more complex than this (;
function joinstrings($s1, $s2)
{
return $s1 . $s2;
}
// More functions
The remote server access point
This is the public (but key-required) accessible page.
http://apiweb.com/joinstrings/index.php
<?php
// Check if the key is valid and if $v1 and $v2 aren't empty. Else, 'exit;'
include '../validate.php';
// Your API
include '../functions.php';
// The called function
echo joinstrings(urldecode($_GET['v1']), urldecode($_GET['v2']));
The wrapper
Now you can require all your programmers to learn how to use this API. Or, if you prefer to do it right, you'd make a wrapper that makes their life easier. You'd have a class with all the methods that you want to be accessible. You could do this wrapper with functions, but I think it's easier and better with an object and methods:
htpp://web.com/library.php
<?php
class DevelopersLibrary
{
private $Url = "http://apiweb.com/";
// Press your hand against the keyboard. A-Z0-9. Copy it in http://apiweb.com/validate.php
private $Key = "g139h0854g76dqfdbgng";
// Accesible method
public joinstrings($v1, $v2)
{
// Encode only the user input. You don't want to encode '?' nor '&'
if ($Return = file_get_contents($this->Url . 'joinstring'
'?key=' . $this->Key .
'&v1=' . urlencode($v1) .
'&v2=' . urlencode($v2)))
{
return $Return;
}
}
}
Developer's code
Finally, what your developers would do:
http://web.com/index.php
<?php
include './library.php';
$Lib = new DevelopersLibrary();
echo $Lib->joinstrings("Are you sure this is better", "than giving your developers access to the code?");
None of the code is tested, so you should expect some some typos.
Limitations
I can think of solutions for most limitations, but not to extend (more) this post I won't write them here. Ask for a solution to a limitation if you need it in the comments and I'll do my best. In normal case use, none of these limitations are THAT important.
Parameters passed. Using this method as described above, you can only pass numbers or strings as function parameters. Check out json_encoding() for passing other types.
Wrong returned values when there are bugs in the API or parameters passed. If there's a bug in the API, the developers cannot fix it and the returned value might be wrong. Now that might seem trivial, but what if they are trying to retrieve the join of 2 strings and retrieve another [wrong] string with the error text in it? Note: consider returning valid XML and then parsing it in your wrapper.
There's only a unique key which is there for preventing random users from using your API, not to be hidden from developers.
Slower speed. I don't think this even needs explanation.
Developer's extra work. This is solved this with the implementation of the wrapper.
Url length. There's a url length limitation for most browsers of 2000 characters, although I didn't find anything in the PHP manual for file_get_contents(). Read this SO question for more info about GET.
Sure there are more but these are the main ones I could think of.
I hope this long long answer is useful for you or someone.
I am trying to get the ID of the product that was most recently added to a user’s cart. A quick google search revealed this function
Mage::getSingleton('checkout/session')->getLastAddedProductId(true);
which is also used in Mage_Checkout_Block_Cart_Crosssell. When I try calling the function in my own controller however, it returns nothing.
I have tried to instantiate a core session via
Mage::getSingleton('core/session', array('name'=>'frontend'))
however this approach does not seem to work. I also tried to create the Crossell block and making the protected method that wraps around getLastAddedProductId function public however that returns null just like it does when I try calling it on its own.
Is there something I have to call or instantiate in order to use this function? Here’s my source listing for reference.
class Mymodule_Addcartaftermath_ItemaddedController extends Mage_Core_Controller_Front_Action {
public function generatemessageAction() {
$parameters = $this->getRequest()->getParams();
if (isset($parameters['ajax_call'])) {
$latest_item_id = Mage::getSingleton('checkout/session')->getLastAddedProductId(true);
$response = array('response' => $latest_item_id);
echo json_encode($response);
} else {
$this->_redirect('/');
}
}
}
I tried poking through the source code, particularly the checkout/model/session.php file in the core and I cannot seem to find the definition of the function. I also looked at it’s parent’s class definition but could not find it there either.
If this method is not available to me is there another way of retrieving the most recent item added? I know that the items are added sequentially and I could perhaps just get the last item of the list of items from the cart however this would not work in the case where the user adds the same item to the cart essentially increasing the quantity rather than actual item itself (e.g. the user adds a laptop the cart when there already is one)
The call to
Mage::getSingleton('checkout/session')->getLastAddedProductId(true);
Is actually clearing the session variable after it is read. Magento uses magic methods extensively. In this case you are using the __call magic method which in turn uses the getData() method. In Mage_Core_Model_Session_Abstract_Varien you will see that they override the default behaviour of getData() to expect the second parameter to be a boolean (The first parameter to getData is the key name for the value you are looking for). That boolean is a flag telling the session to clear the variable after reading.
You could always listen for the checkout_cart_product_add_after event and add the item to your own variable in the session. That event is actually fired on the line before setLastAddedProductId() is called.
try to grep the variable you are looking for. As they are coming from magic methods then its hard to find the exact function you are after so it's easier to see the places where data gets set than where it is used
grep '>setLastAddedProductId' app/code -rsn
to see where the product id gets set to that session variable
app/code/core/Mage/Checkout/Model/Cart.php:255: $this->getCheckoutSession()->setLastAddedProductId($product->getId());
and then you can ask this variable (if it is set else empty())
Mage::getSingleton('checkout/session')->getLastAddedProductId();
and you can see all the things that are in checkout/session and verify if the data is there.
var_dump(Mage::getSingleton('checkout/session'));
Haven't a clue why it works this way but it works for me in Magento 1.6...
<?php
require_once ( "app/Mage.php" );
umask(0);
Mage::app("default");
Mage::getSingleton('core/session', array('name'=>'frontend'));
$session = Mage::getSingleton('checkout/session');
$lastadded = $session->getData("last_added_product_id");
print_r($lastadded);
Apparently you have to instantiate the core/session and then the checkout/session. I've tried it every other way but this is the only way I've found it to work. Perhaps someone can explain why it works this way. Hope this helps you out!