Creating/Retrieving a Cart programmatically - prestashop

For a module that I'm writing I need to retrieve a cart for a certain user (not necessary registered) after that a link is called and some data are passed.
My idea was to receive back a previously id passed that can help me to identify a certain cart.
My big problem is that I've search a lot for code cart creation into prestashop. Finally I've found something into
/* Cart already exists */
if ((int)$this->context->cookie->id_cart)
{
$cart = new Cart($this->context->cookie->id_cart);
if ($cart->OrderExists())
{
unset($this->context->cookie->id_cart, $cart, $this->context->cookie->checkedTOS);
$this->context->cookie->check_cgv = false;
}
/* Delete product of cart, if user can't make an order from his country */
elseif (intval(Configuration::get('PS_GEOLOCATION_ENABLED')) &&
!in_array(strtoupper($this->context->cookie->iso_code_country), explode(';', Configuration::get('PS_ALLOWED_COUNTRIES'))) &&
$cart->nbProducts() && intval(Configuration::get('PS_GEOLOCATION_NA_BEHAVIOR')) != -1 &&
!FrontController::isInWhitelistForGeolocation() &&
!in_array($_SERVER['SERVER_NAME'], array('localhost', '127.0.0.1')))
unset($this->context->cookie->id_cart, $cart);
// update cart values
elseif ($this->context->cookie->id_customer != $cart->id_customer || $this->context->cookie->id_lang != $cart->id_lang || $currency->id != $cart->id_currency)
{
if ($this->context->cookie->id_customer)
$cart->id_customer = (int)($this->context->cookie->id_customer);
$cart->id_lang = (int)($this->context->cookie->id_lang);
$cart->id_currency = (int)$currency->id;
$cart->update();
}
/* Select an address if not set */
if (isset($cart) && (!isset($cart->id_address_delivery) || $cart->id_address_delivery == 0 ||
!isset($cart->id_address_invoice) || $cart->id_address_invoice == 0) && $this->context->cookie->id_customer)
{
$to_update = false;
if (!isset($cart->id_address_delivery) || $cart->id_address_delivery == 0)
{
$to_update = true;
$cart->id_address_delivery = (int)Address::getFirstCustomerAddressId($cart->id_customer);
}
if (!isset($cart->id_address_invoice) || $cart->id_address_invoice == 0)
{
$to_update = true;
$cart->id_address_invoice = (int)Address::getFirstCustomerAddressId($cart->id_customer);
}
if ($to_update)
$cart->update();
}
}
if (!isset($cart) || !$cart->id)
{
$cart = new Cart();
$cart->id_lang = (int)($this->context->cookie->id_lang);
$cart->id_currency = (int)($this->context->cookie->id_currency);
$cart->id_guest = (int)($this->context->cookie->id_guest);
$cart->id_shop_group = (int)$this->context->shop->id_shop_group;
$cart->id_shop = $this->context->shop->id;
if ($this->context->cookie->id_customer)
{
$cart->id_customer = (int)($this->context->cookie->id_customer);
$cart->id_address_delivery = (int)(Address::getFirstCustomerAddressId($cart->id_customer));
$cart->id_address_invoice = $cart->id_address_delivery;
}
else
{
$cart->id_address_delivery = 0;
$cart->id_address_invoice = 0;
}
// Needed if the merchant want to give a free product to every visitors
$this->context->cart = $cart;
CartRule::autoAddToCart($this->context);
}
that is contained into FrontController.php (that seems to be called in every page). So, to me, a cart should always be present during a "user session".
But - yes, there's a but - when I try to retrieve a cart (in that way, into controller of my module)
$context=Context::getContext();
$id_cart=$context->cart->id;
$id_cart isn't there, so cart seems to miss. So I'm a little bit confused.
What's goin' on here? Someone could give me some pointers?
PS.:
I've tried to replicate that function (only the else part) but it doesn't work

You can force cart generation when the user isn't logged in and there is no product in the cart:
$context = Context::getContext();
if (!$context->cart->id) {
$context->cart->add();
$context->cookie->id_cart = $context->cart->id;
}
$id_cart = $context->cart->id;
Take a look at the processChangeProductInCart method in controllers/front/CartController.php

I'm using Prestashop 1.6, and #yenshiraks answer did not work for me. I cannot use $context->cart->add();, because $context->cartis null.
This is what worked in my case:
$context = Context::getContext();
$cart_id = null
if($context->cookie->id_cart) {
$cart = new Cart($context->cookie->id_cart);
$cart_id = $cart->id; // just in case the cookie contains an invalid cart_id
}
if(empty($cart_id)) {
$cart = new Cart();
$cart->id_lang = (int)$context->cookie->id_lang;
$cart->id_currency = (int)$context->cookie->id_currency;
$cart->id_guest = (int)$context->cookie->id_guest;
$cart->id_shop_group = (int)$context->shop->id_shop_group;
$cart->id_shop = $context->shop->id;
$cart->add();
$cart_id = $cart->id;
}
$context->cookie->id_cart = $cart_id;
To answer the question at the end: Even though a cart is always generated in the FrontController, it is not saved to the database, therefore the id is null.
If you are in a context, where the FrontController is instantiated (any page of the frontend, $context->cart->add(); will suffice to save an empty cart to the database.
If on the other hand, you are in a script, that is called directly (like prestashop/modules/my_module/script.php), you have to use the above code.

Related

Yii2 Update the entry if it exists

Item $title defined earlier. I want what if found $find_title, update it with all fields from $title. Otherwise, create a new object
$find_title = Title::find()->where(["upc" => $title->upc])->one();
if ($find_title != null) {
$title->id = $find_title->id;
$title->save();
} else {
$title->save();
}
It worked in the laravel.
$find_title = Title::find()->where(["upc" => $title->upc])->one();
if ($find_title != null) {
$id = $title->id;
$find_title->attributes = $title->attributes;
$find_title->id = $id;
$find_title->save();
} else {
$title->save();
}
Here you assign all attributes of $title to $find_title, restore id, and then save;

Use Cecil to insert begin/end block around functions

this simple code works fine and allows to add a BeginSample/EndSample call around each Update/LateUpdate/FixedUpdate function. However it doesn't take in consideration early return instructions, for example as result of a condition. Do you know how to write a similar function that take in considerations early returns so that the EndSample call will be executed under every circumstance?
Note that I am not a Cecil expert, I am just learning now. It appears to me that Cecil automatically updates the operations that returns early after calling InsertBefore and similar functions. So if a BR opcode was previously jumping to a specific instruction address, the address will be updated after the insertions in order to jump to the original instruction. This is OK in most of the cases, but in my case it means that an if statement would skip the last inserted operation as the BR operation would still point directly to the final Ret instruction. Note that Update, LateUpdate and FixedUpdate are all void functions.
foreach (var method in type.Methods)
{
if ((method.Name == "Update" || method.Name == "LateUpdate" || method.Name == "FixedUpdate") &&
method.HasParameters == false)
{
var beginMethod =
module.ImportReference(typeof (Profiler).GetMethod("BeginSample",
new[] {typeof (string)}));
var endMethod =
module.ImportReference(typeof (Profiler).GetMethod("EndSample",
BindingFlags.Static |
BindingFlags.Public));
Debug.Log(method.Name + " method found in class: " + type.Name);
var ilProcessor = method.Body.GetILProcessor();
var first = method.Body.Instructions[0];
ilProcessor.InsertBefore(first,
Instruction.Create(OpCodes.Ldstr,
type.FullName + "." + method.Name));
ilProcessor.InsertBefore(first, Instruction.Create(OpCodes.Call, beginMethod));
var lastRet = method.Body.Instructions[method.Body.Instructions.Count - 1];
ilProcessor.InsertBefore(lastRet, Instruction.Create(OpCodes.Call, endMethod));
changed = true;
}
}
as a Bonus, if you can explain to me the difference between Emit and Append a newly created instruction with the same operand. does Append execute an Emit under the hood or does something more?
I may have found the solution, at least apparently it works. I followed the code used to solve a similar problem from here:
https://groups.google.com/forum/#!msg/mono-cecil/nE6JBjvEFCQ/MqV6tgDCB4AJ
I adapted it for my purposes and it seemed to work, although I may find out other issues. This is the complete code:
static bool ProcessAssembly(AssemblyDefinition assembly)
{
var changed = false;
var moduleG = assembly.MainModule;
var attributeConstructor =
moduleG.ImportReference(
typeof(RamjetProfilerPostProcessedAssemblyAttribute).GetConstructor(Type.EmptyTypes));
var attribute = new CustomAttribute(attributeConstructor);
var ramjet = moduleG.ImportReference(typeof(RamjetProfilerPostProcessedAssemblyAttribute));
if (assembly.HasCustomAttributes)
{
var attributes = assembly.CustomAttributes;
foreach (var attr in attributes)
{
if (attr.AttributeType.FullName == ramjet.FullName)
{
Debug.LogWarning("<color=yellow>Skipping already-patched assembly:</color> " + assembly.Name);
return false;
}
}
}
assembly.CustomAttributes.Add(attribute);
foreach (var module in assembly.Modules)
{
foreach (var type in module.Types)
{
// Skip any classes related to the RamjetProfiler
if (type.Name.Contains("AssemblyPostProcessor") || type.Name.Contains("RamjetProfiler"))
{
// Todo: use actual type equals, not string matching
Debug.Log("Skipping self class : " + type.Name);
continue;
}
if (type.BaseType != null && type.BaseType.FullName.Contains("UnityEngine.MonoBehaviour"))
{
foreach (var method in type.Methods)
{
if ((method.Name == "Update" || method.Name == "LateUpdate" || method.Name == "FixedUpdate") &&
method.HasParameters == false)
{
var beginMethod =
module.ImportReference(typeof(Profiler).GetMethod("BeginSample",
new[] { typeof(string) }));
var endMethod =
module.ImportReference(typeof(Profiler).GetMethod("EndSample",
BindingFlags.Static |
BindingFlags.Public));
Debug.Log(method.Name + " method found in class: " + type.Name);
var ilProcessor = method.Body.GetILProcessor();
var first = method.Body.Instructions[0];
ilProcessor.InsertBefore(first,
Instruction.Create(OpCodes.Ldstr,
type.FullName + "." + method.Name));
ilProcessor.InsertBefore(first, Instruction.Create(OpCodes.Call, beginMethod));
var lastcall = Instruction.Create(OpCodes.Call, endMethod);
FixReturns(method, lastcall);
changed = true;
}
}
}
}
}
return changed;
}
static void FixReturns(MethodDefinition med, Instruction lastcall)
{
MethodBody body = med.Body;
var instructions = body.Instructions;
Instruction formallyLastInstruction = instructions[instructions.Count - 1];
Instruction lastLeaveInstruction = null;
var lastRet = Instruction.Create(OpCodes.Ret);
instructions.Add(lastcall);
instructions.Add(lastRet);
for (var index = 0; index < instructions.Count - 1; index++)
{
var instruction = instructions[index];
if (instruction.OpCode == OpCodes.Ret)
{
Instruction leaveInstruction = Instruction.Create(OpCodes.Leave, lastcall);
if (instruction == formallyLastInstruction)
{
lastLeaveInstruction = leaveInstruction;
}
instructions[index] = leaveInstruction;
}
}
FixBranchTargets(lastLeaveInstruction, formallyLastInstruction, body);
}
private static void FixBranchTargets(
Instruction lastLeaveInstruction,
Instruction formallyLastRetInstruction,
MethodBody body)
{
for (var index = 0; index < body.Instructions.Count - 2; index++)
{
var instruction = body.Instructions[index];
if (instruction.Operand != null && instruction.Operand == formallyLastRetInstruction)
{
instruction.Operand = lastLeaveInstruction;
}
}
}
basically what it does is to add a Ret instuction, but then replace all the previous Ret (usually one, why should it be more than one?) with a Leave function (don't even know what it means :) ), so that all the previous jumps remain valid. Differently than the original code, I make the Leave instruction point to the EndSample call before the last Ret

Magento Rest API Product List (api/rest/products) how to know last page or product

I have 32 products in my magento store.
I am fetching these products using magento rest API, like below
http://localhost/magento/api/rest/products?page=1&limit=10
http://localhost/magento/api/rest/products?page=2&limit=10
http://localhost/magento/api/rest/products?page=3&limit=10
http://localhost/magento/api/rest/products?page=4&limit=10
but if I query below url too, still it is giving product list (please not page is equal to 5)
http://localhost/magento/api/rest/products?page=5&limit=10
So is there any way to know that this is the last page or last product??
ProductWork mojProduct = new ProductWork(_url, _adminUrlPart, _consumerKey,
_consumerSecret, _userName, _password);
MagentoApiResponse<IList<Product>> provera = new MagentoApiResponse<IList<Product>>();
provera.Result = new List<Product>();
for (int i = 0; i < 100; i++)
{
var filter = new Filter();
filter.PageSize = 100; // max number
filter.Page = i+1;
var primio = await mojProduct.GetAll(filter);
if (primio.Result != null & provera != null)
{
foreach (Product item in primio.Result)
{
if ( provera.Result.Count(a=>a.entity_id == item.entity_id) == 0) {
provera.Result.Add(item);
}
else
{
i = 101;
break;
}
}
}
else
{
break;
}
}
if (provera.Result != null)
{
foreach (Product item in provera.Result)
{
// your code here
}
}

Creating multiple mandatory fields in workflow

I have a requirement for multiple fields to be set whenever an issue is created.
I tried this
rule Mandatory
when <issue created or updated> {
Swimlane.required("Must have a swimlane");
UtgmsVehicleName.required("Must be attached to a vehicle ");
Subsystem.required("Subsystem must be set");
Assignee.required("Assignee must be set");
Fix versions.required("Fix versions must be set/");
}
What happens is that it continually asks for all the fields to be set. What is the best way to fulfil the requirement.
Based on Alex's suggestion i got this
rule MandatoryFields
when !isReported() {
var assigneeSet = Assignee != null;
var subSystemSet = Subsystem != null && Subsystem != {No subsystem};
var fixedVersionSet = Fix versions != null;
var assigneeValue = Assignee.oldValue;
var messageValue = "Mandatory fields:";
if (!assigneeSet) {
messageValue = messageValue + " Assignee";
}
if (!subSystemSet) {
messageValue = messageValue + " Subsystem";
}
if (!fixedVersionSet) {
messageValue = messageValue + " FixedVersion";
}
assert (assigneeSet && subSystemSet && fixedVersionSet): messageValue;
if (assigneeSet) {
Assignee = assigneeValue;
}
}
You can have something like
var noSwimlane = Swimlane == null;
var noSubsystem = Subsystem == null;
...
var message = "You need to fill the following fields: ";
if (noSwimlane) {
message += "Swimlane";
}
...
assert !(noSwimlane || noSubsystem || ...) : message;

(prestashop) How can I detect if there's a catalog price rule bound to a category or some of its parents?

I have catalog price rules for some categories. In frontend, in category.tpl, I have to notify if there are special prices bound to that specific category or some of its parents.
For now, I'm building a function on the controller that finds it with a query. I was wandering if there was some shortcut for doing this.
I wrote a function (a CategoryController method) to solve my problem and I share:
public function get_category_promo(){
//get the active country, since promo are also country-based
if($this->context->customer->isLogged()){
$current_country = Customer::getCurrentCountry($this->context->customer->id);
} else {
$current_country = $this->context->country->id;
}
$db = Db::getInstance();
$sql = '
SELECT
truncate(`reduction`,0) as reduction,
`reduction_type`,
`from`,
`to`,
`type` as category_type,
`value` as id_category,
`id_parent` as category_parent,
`level_depth` as depth
FROM
`'._DB_PREFIX_.'specific_price_rule_condition` rule_condition
INNER JOIN
`'._DB_PREFIX_.'specific_price_rule_condition_group` rule_group
on rule_condition.`id_specific_price_rule_condition_group` = rule_group.`id_specific_price_rule_condition_group`
INNER JOIN
`'._DB_PREFIX_.'specific_price_rule` price_rule
on price_rule.`id_specific_price_rule` = rule_group.`id_specific_price_rule`
INNER JOIN
`'._DB_PREFIX_.'category` category
on rule_condition.`value` = category.`id_category`
WHERE rule_condition.`type` = "category"';
$parents = $this->category->getParentsCategories();
array_shift($parents);//first is == this->category so I shift it out
$sql_aux = ' and (rule_condition.`value` = ' . $this->category->id_category;
foreach($parents as $parent){
$sql_aux .= ' or rule_condition.`value` = ' . $parent["id_category"];
}
$sql_aux .= ')';
$sql_aux .= ' and price_rule.`id_country` = ' . $current_country;
$sql_aux .= ' order by level_depth desc';
$sql .= $sql_aux;
$promo_data = $db->executeS($sql);
$promo_data = count($promo_data) > 0 ? $promo_data : null;
if(!$promo_data) return false;//stop
function validate_date($promo){
//if there are no dates
if((int)$promo['to'] == 0 && (int)$promo['from'] == 0) return true;
//if there is end promo date
if((int)$promo['to'] != 0){
$to = new DateTime($promo['to']);
//...and is still valid
if($to >= new DateTime('NOW')) return true;
}
}//end validate
//refine query results
$filtered = array_values(array_filter($promo_data,'validate_date'));
$promo_data = $filtered[0];//if there are more than one promo on the same category, the promo on the higher depth is used form the system, so I need only that promo
//promo without dates. Only refine to/from to better use in smarty
if((int)$promo_data['to'] == 0 && (int)$promo_data['from'] == 0){
$promo_data['to'] = 0;
$promo_data['from'] = 0;
$this->context->smarty->assign('promo_data', $promo_data);
return true;
}
if((int)$promo_data['to'] != 0){//promo ha send date
$to = new DateTime($promo_data['to']);
if($to >= new DateTime('NOW')){
$promo_data['to'] = $to->format('d-m-Y');
} else {
return false;//not a valid date
}
}
if((int)$promo_data['from'] != 0){
$from = new DateTime($promo_data['from']);
$promo_data['from'] = $from->format('d-m-Y');
} else {
$promo_data['from'] = 0;
}
$this->context->smarty->assign('promo_data', $promo_data);
}//end get_category_promo