Update Product Features via PHP in Prestashop - php

I would like to change the value from custom features (Product Features) (Artikeleigenschaften) in my own class.
To change product values is very simple:
$productObj = new Product($produktId);
$productObj->setFieldsToUpdate(array('ean13'));
$productObj->ean13 = "johndoo";
$productObj->save();
But is there a similar way to change the Product Features?

Here my solution:
To Update Product Features you have to update 2 tables in your Database:
ps_feature_value and ps_feature_value_lang
Do this with:
$id_feature_value = (int)FeatureValue::addFeatureValueImport
(
(int)$feature['id_feature'],
$newFeatureValue,
(int)$produktId,
$this->context->language->id,
1 // $custom
);
Product::addFeatureProductImport(
$produktId, (int)$feature['id_feature'], $id_feature_value
);
To Update Features with NOT custom Values use:
$arr = FeatureValue::getFeatureValueLang($id_dropdown);
foreach ( $arr AS $item)
if ($item['id_lang'] == $this->context->language->id)
{
$feature_val_name = $item['value'] ;
}
$id_feature_value = (int)FeatureValue::addFeatureValueImport
(
(int)$feature['id_feature'],
$feature_val_name,
$product->id,
$this->context->language->id,
false
);
$product->addFeatureProductImport( $product->id , (int)$feature['id_feature'], $id_feature_value );

Mh, Ive never tried to do this, but you could try this:
Product::addFeatureProductImport($productObjc->id, $id_feature, $id_feature_value);

I created this function based upon ImportControlloer. Add this function to Product.php override and use it
/**
* Adds a feature value to a Product. If You want to have feature selected from dropdown leave $custom to false.
* #param int|$id_product
* #param int|$id_feature_group
* #param string|$feature_value
* #param bool|true $custom
* #param int|$id_lang
* #return bool
*/
public static function setFeatureValueToProduct($id_product, $id_feature_group, $feature_value, $custom = false, $id_lang)
{
$group_feature_obj = Feature::getFeature($id_lang, $id_feature_group);
if ($group_feature_obj){
$feature_name = $group_feature_obj['name'];
$feature_value = $feature_value ? trim($feature_value) : '';
$position = false;
if (!empty($feature_name) && !empty($feature_value)) {
$id_feature = (int)FeatureCore::addFeatureImport($feature_name, $position);
$id_feature_value = (int)FeatureValue::addFeatureValueImport($id_feature, $feature_value, $id_product, $id_lang, $custom);
if (Product::addFeatureProductImport($id_product, $id_feature, $id_feature_value)){
Feature::cleanPositions();
return true;
} else {
return;
}
}
} else {
return;
}
}
Example of use:
Product::setFeatureValue(1597, 15, 'Dracarys', false, 1);
It will upload Dropdown value "Dracarys" into product with ID 1597 and group feature with ID 15. If You want to make it custom value (not-predefined) just set 4th parametr to true.


Product::addFeatureProductImport($productObjc->id, $id_feature, $id_feature_value);
This will assign the feature and its value to the product. But we need the id of the feature and values. The below code is working for me.
$feature = new Feature();
$feature->name = [
$lang_id => "string"
];
$feature->position = Feature::getHigherPosition() + 1;
$feature->add();
$featurevalue = new FeatureValue;
$featurevalue->id_feature = $feature->id;
$featurevalue->value = [
$lang_id => "string"
];
$featurevalue->add();
$product->addFeatureProductImport($product->id, $feature->id, $featurevalue->id);

None of these suggestions worked for me. I had few requirements for this:
import should be able to run multiple times without duplicating a feature in product or feature values list
custom values should not be deleted even if they have the same "size" feature
no need to handle languages or multistore (thanks to datasource for only supporting one language and one shop)
This worked for me
/**
* Add or customize feature value to product feature and assign it to product
*
* Creates the feature and the value if they dont exists already
*/
private function _upsertFeatureValueToProduct($product, $featureName, $featureValue, $custom = false)
{
$id_lang = null;
// this creates the feature if required and always returns the id_feature
$id_feature = (int) \Feature::addFeatureImport($featureName);
if ($custom) {
// create or find a custom value
$id_feature_value = (int) \FeatureValue::addFeatureValueImport($id_feature, $featureValue, $product->id, $id_lang, $custom);
// change the existing value to new
$feature_value = new \FeatureValue($id_feature_value);
// in all languages (source only gives one language data)
$feature_value->value = array_fill_keys(\Language::getIDs(false), $featureValue);
$feature_value->update();
// assign the feature to the product (not strictly required if this was a update but doesn't hurt)
Product::addFeatureProductImport($product->id, $id_feature, $id_feature_value);
} else {
// non custom values. we first need to add the value and then assign it
// prestashop uses lang to find the existing feature. use the default language
$id_lang = \Configuration::get('PS_LANG_DEFAULT');
// add or get the feature id for this value
$id_feature_value = (int) \FeatureValue::addFeatureValueImport($id_feature, $featureValue, null, $id_lang, $custom);
// NOTE: product can have the same feature multiple times. But in this case we only get one value from source and want to update the feature
// so before adding the featurevalue delete existing feature assignments
$this->_deleteProductFeatureValueAssignments($product->id, $id_feature);
// assign the feature to the product
Product::addFeatureProductImport($product->id, $id_feature, $id_feature_value);
}
// required? doesn't hurt :)
\Feature::cleanPositions();
}
/**
* Delete product feature value assingments
*
* There is no api for this.. idea stolen from
*
https://github.com/PrestaShop/PrestaShop/blob/2c059b93062ce25bbde22355982e651215544e81/classes/Product.php#L2673
*
* TODO: open feature request, and remove this when prestashop has an api for this.
*/
private function _deleteProductFeatureValueAssignments($id_product, $id_feature)
{
$sql = '
DELETE fp
FROM `' . _DB_PREFIX_ . 'feature_product` fp
LEFT JOIN `' . _DB_PREFIX_ . 'feature_value` fv
ON fv.`id_feature_value` = fp.`id_feature_value`
WHERE
fp.`id_product` = ' . (int) $id_product .
' AND fp.`id_feature` = ' . (int) $id_feature .
' AND `custom`=0
';
return \Db::getInstance()->execute($sql);
}
now I can call this all day long without getting duplicate values in features or in the products
$this->_upsertFeatureValueToProduct($product, 'size', 'L');
$this->_upsertFeatureValueToProduct($product, 'size', 'XL');
$this->_upsertFeatureValueToProduct($product, 'size', 'custom', true); // one custom size just for fun
$this->_upsertFeatureValueToProduct($product, 'size', 'L');
Leaves me with two ready made feature values L and XL and a product with size=L and size=custom
screenshot of product features
There might still be bugs, just got this working :D

Related

Craft CMS ajax query for Entry associated with Category

I am sending a AJAx request to the server to query for all "properties" that are related to a certain category "destination".
The field in the entry is called Destination with the handle "destinationCategories"
My code currently looks like this;
public function actionGetLocation()
{
$locationId = craft()->request->getParam('id');
// Find the Actual Category Object from The DB, I did this just in case I need to query based on title, or something else
// The correct category is being returned
$criteria = craft()->elements->getCriteria(ElementType::Category);
$criteria->id = $locationId;
$criteria->limit = null;
$selectedCategory = $criteria->first();
unset($criteria);
$criteria = craft()->elements->getCriteria(ElementType::Entry);
// I am now attempting to query for
// destinationCategories = the category that I have queried from the DB
$criteria->destinationCategories = $selectedCategory->id;
$criteria->limit = null;
$properties = $criteria->find();
var_dump($properties);
foreach($properties as $property)
{
echo $property->title;
}
exit;
return $this->returnJson(array('status' => 'ok', 'options' => $subCategories));
}
The query is returning null even when results are available
Where am i going wrong I have tried
$criteria->destinationCategories = $selectedCategory->id;
$criteria->destinationCategories = $selectedCategory->title;
$criteria->destinationCategories = $selectedCategory->slug;
I guess you would have completed this project till now :) but to solve this could you try not to unset $criteria until you use any depending variable. Once your usage is done unset the variable.

Magento 1.9.X programmatically save custom attribute

I made a simple import script and I'm trying to programatically save 3 custom attributes (att1, att2, att3) together with all other info (name, description, price, category..).
So basically I have:
public function insert_product($data) {
$product = Mage::getModel('catalog/product');
try {
$sku = $data['code'];
if ($this->prodottiImportati[$sku]) {
$sku = $data['code'] . '-1';
}
$this->prodottiImportati[$sku] = true;
$product->setSku($sku);
$product->setName($data['name']);
$product->setDescription($data['desc']);
$product->setShortDescription($data['short_desc']);
$product->setManufacturer('');
$product->setPrice($data['price']);
$product->setTypeId('simple');
$product->setAttributeSetId($this->attributeSet);
$categorie = $this->get_categories($data);
$product->setCategoryIds($categorie);
$product->setWeight($data['peso']);
$product->setTaxClassId(2); // taxable goods
$product->setVisibility(4); // catalog, search
$product->setStatus(1); // enabled
$product->setWebsiteIds($data['store_id']);
$stockData = $product->getStockData();
$stockData['qty'] = $data['qty'];
if ($data['quantita'] > 0) {
$stockData['is_in_stock'] = 1;
} else {
$stockData['is_in_stock'] = 0;
}
$stockData['manage_stock'] = 1;
$stockData['use_config_manage_stock'] = 0;
$product->setStockData($stockData);
$product->setIsMassupdate(true)->setExcludeUrlRewrite(true);
$product->save();
$productID = $product->getId();
} catch(Exception $e) {
echo ($e->getMessage());
}
return $productID;
}
First thing I tryed was adding a
$productID = $this->insert_product($data);
Mage::getSingleton('catalog/product_action')->updateAttributes(
array($productID), array(
'att1' => $data['att1'],
), $data['store_id']);
So basically updating things after the insert function was called, using the ID got after the insert. store_id is the ID of the store in that given language. Didn't save anything.
Second attempt, I follwed this: Magento add custom options with programmatically importing products
I tryed that within the insert_product function and also outside after $productID = $this->insert_product($data); Neither worked.
Last I tryed a magical $product->setAtt1('value'); witin the insert_product function, not sure how Magento would understand how to set att1 that way, but...you know, I read it somewhere and I gave it a try ;)
att1, att2 and att3 are spelled lowercase, althoug they have an uppercase label (think that dosen't matter here), they are part of an attribute group (I'm passing it with $product->setAttributeSetId($this->setAttributi)) and they are all multiple selection attributes, so I could in teory pass multiple values to them.
I'm sure I'm missing something on the way. Can anyone help?
After 10 more minutes since I wrote here, I was able to find the way. I took me forever to solve it.
The clue of this is that you have to add attributes ID, not values. That happens at least for me with multiple selection attributes, not sure if it's true all the time.
Here is how I did:
In the function insert_product I added:
$optionId = $this->get_option_id_by_code('att1', 'Value of the attribute you need to add');
$product->setAtt1($optionId);
So if yor attribute is named, let's say "brand" it will be:
$optionId = $this->get_option_id_by_code('brand', 'Nike');
$product->setBrand($optionId);
If your attribute can have multiple values, you need to change the code above to:
$optionId = array();
foreach ($myAttributesArray as $someValues) {
$optionId[] = $this->get_option_id_by_code('att1', $someValues);
}
$product->setAtt1($optionId);
The foreach is just an example, you need to loop through your mutiple values and get the respective ID and add them all toghether with setAtt1 passing them as an array. I'm working on an improved version where the get_option_id_by_code function does all at once in a more efficient way. This is kust a "basic" version that works, feel free to make it fancy and smart.
Then I created an other function called get_option_id_by_code like this:
protected function get_option_id_by_code($attrCode, $optionLabel) {
$attrModel = Mage::getModel('eav/entity_attribute');
$attrID = $attrModel->getIdByCode('catalog_product', $attrCode);
$attribute = $attrModel->load($attrID);
$options = Mage::getModel('eav/entity_attribute_source_table')
->setAttribute($attribute)
->getAllOptions(false);
foreach ($options as $option) {
if ($option['label'] == $optionLabel) {
return $option['value'];
}
}
return false;
}
To be honest I found this with a collage of other sources / authors, so I need to be thankful to a bunch of smarter programmers here and there, since it took a while for me to strouggle with this simple task I wrote the solution here hoping to help you guys. Thanks!

Search filter in cakephp with dynamic options

I want to apply search filters in my project. I have options tables where options are being saved with the option values with parent id of option id. For example brand is saving as option with parent id set to 0 and all brands have brand id as their parent id set and while saving product I am saving product options in product_options table. Now i want to apply filters in product listing page. I am using following code for filtration:
$conditions = array();
$product_options = $this->ProductOption->find('list',array('fields'=>array('product_id'),'conditions'=>array('ProductOption.option_value_id'=>$data['data']['options'])));
$conditions = array_merge($conditions,array('Product.id'=>array_unique($product_options)));
$prod_info = $this->paginate('Product',$conditions);
$this->set(compact('prod_info'));
When I search any product with their brand name it works fine but if I try to search with the price (also an option) then it gives other brand products also which have price equal to filter price option. Please check following link to understand problem correctly.
http://primemart.in/Food-Processors-Ii4zRGAKYAo=
Please anyone help me to come out my problem.
Thanks.
Please have a look on my code which I used to pass conditions in and to get results
$product_options = $this->ProductOption->find('list',array(
'fields'=>array('product_id'),
'conditions'=>array('ProductOption.option_value_id'=>$data['data']['options'])
));
//$this->Option->unBindModel(array('belongsTo'=>'Product'));
$product_options = $this->Option->find('all', array(
'conditions'=>array('Option.id'=>$data['data']['options'])
));
//pr($product_options);
$opt_arr = array();
foreach ($product_options as $op) {
$opt_arr[$op['Option']['parent_id']][] = $op['Option']['id'];
}
$conditions_arr = array();
foreach($opt_arr as $opt) {
$key_arr = array();
foreach($opt as $op) {
$key_arr['OR']['ProductOption.option_value_id'][] = $op;
}
$conditions_arr['AND'][] = $key_arr;
}
$pr_options = $this->ProductOption->find('list', array(
'conditions'=>$conditions_arr,
'fields'=>array('product_id')
));
$conditions = array_merge($conditions, array('Product.id'=>array_unique($pr_options)));
I would try code bellow. I assume that $conditions constist of the other conditions you mention in your question.
$conditions = ... // other conditions you mentioned
$conditions = array('AND'=>array($conditions, array('Product.id'=>array_unique($product_options))));
$prod_info = $this->paginate('Product',$conditions);

Magento Custom Variable issue

I am in the process of writing a custom module whereby the user can enter a shipping cost per product.
I have added a custom variable to all of my products called 'initial_shipping_charge'. In my Get Shipping Rate function I am trying to loop through the products in my basket and get the variable (these will be added to a overall total).
The var_dump($shipping_price_initial); line returns NULL and not the variable that it contains - any idea why this is not working?
Thanks in advance.
protected function _getStandardShippingRate()
{
$rate = Mage::getModel('shipping/rate_result_method');
/* #var $rate Mage_Shipping_Model_Rate_Result_Method */
$rate->setCarrier($this->_code);
/**
* getConfigData(config_key) returns the configuration value for the
* carriers/[carrier_code]/[config_key]
*/
$shipping_price_value = rand(10 , 50);
//Create a basket session object
$session = Mage::getSingleton('checkout/session');
foreach ($session->getQuote()->getAllItems() as $item) {
$item_id = $item->getId();
$_basketProduct = Mage::getModel('catalog/product')->load($item_id);
$shipping_price_initial = $_basketProduct->getAttribute('initial_shipping_charge');
var_dump($shipping_price_initial);
}
$rate->setCarrierTitle($this->getConfigData('title'));
$rate->setMethod('standand');
$rate->setMethodTitle('Standard');
$rate->setPrice($shipping_price_value);
$rate->setCost(0);
return $rate;
}
$_basketProduct->getAttribute('initial_shipping_charge');
Should be
$_basketProduct->getData('initial_shipping_charge');
or
$_basketProduct->getInitialShippingCharge();
But i guess you don't even have the product cause you fetch the quote item id and try to load a product with that id....you need to:
$item->getProduct()
P.S.:
Product->Quote->Order
There is a conversion process, look for "quote to order conversion" or "product to quote item conversion"

Return all page_names in WordPress, without excess?

I need to retrieve an array of every page_name registered in WordPress. I've got two issues, I can do a get_pages() and such, but it literally pulls every freakin thing about each page including their content. Totally unnecessary overhead when all I need is page_name for each.
The other is that I'd like to do it with a built in method if possible, as this is for an in-house plugin and we'd like to keep it compatible with the mainline. (worst case will just access the DB directly and get them) I know you can include/exclude in the get_pages() call, but I haven't figured out if it is possible to exclude retrieving everything but one, instead of the opposite.
It needs to be dynamic in that it cannot have any hard-coded strings i.e. know anything about the pages themselves or what they're called. Also no extra junk like it being in an unordered list or something. Straight up array, no levels needed. (sub-pages are listed same as primary)
Any ideas guys? I've searched and searched..but the documentation is retarded for these types of things as you guys probably know.
Thanks.
Example of what I'd like in the end or similar:
Array
(
[0] => stdClass Object
(
[page_name] => 'page1'
)
[1] => stdClass Object
(
[page_name] => 'page2'
)
[2] => stdClass Object
(
[page_name] => 'page3'
)
[3] => stdClass Object
(
[page_name] => 'page4'
)
)
To limit the fields returned, you can set up a filter. In your themes functions.php file, or a plugin, try
add_filter( 'get_pages', 'get_pages_filter' );
function get_pages_filter( $res ){
$res = array_map( 'get_pages_title', $res );
return $res;
}
function get_pages_title( $item ){
return (object) array( 'page_name' => $item->post_name );
}
$pages = get_pages();
var_dump( $pages );
I checked get_pages() source code and there's no way you can limit what's being requested from a database. Pages are fetched at lines 3177-3184, and as you can see, there's a hardcoded SELECT * FROM query.
If anyone has a clean solution, please chime in. For now I'm just going to use WordPress's built in DB connection and grab them manually.
For the curious...I just threw this together quick...probably not the best..
/**
* get_wpdb_values()
*
* DESC:
*
* Allows you to make a WP Database connection
* and return an Array => Object of what ever
* values you need from a table.
*
* Was made for the purpose of returning a page
* list, but since you pass it the field you
* want returned, along with with optional filters
* it can easily be used for other purposes.
*
* USAGE:
*
* array get_wpdb_values ( string $table [, string $field [, array $filters ]] )
*
* PARAMETERS:
*
* ----------|-----------------------------------------------------------------
* $table | Required table you want to return values from.
* | DO NOT INCLUDE THE WP PREFIX! (e.g. use 'posts' not 'wp_posts'
* ----------|-----------------------------------------------------------------
* $field | Optional field name you want returned. (Default returns * all)
* ----------|-----------------------------------------------------------------
* $filters | Optional filtering passed as field => value array.
* ----------|-----------------------------------------------------------------
*/
function get_wpdb_values( $table = null,
$field = "*",
$filters = null )
{
// Get access to the
// WordPress Database
// class in this scope
global $wpdb;
// If we weren't passed any
// arguments, get out quick
if(is_null($table) || empty($table))
return false;
// Add optional filters if
// they were passed in
if(!is_null($filters))
{
// Counter is so we can tell
// if there is more then one
// filter so we can add the
// AND separator in the
// SQL query
$WHERE = "WHERE ";
$counter = 0;
foreach ($filters as $key => &$value)
{
$counter++;
// If we're on the second or more
// pair, we add the AND to chain
// conditional WHERE's
$AND = ($counter >= 2) ? " AND" : null;
// Append to existing WHERE
// statement
$WHERE .= "$AND $key = '$value' ";
}
}
else
{
// No filters passed
$WHERE = null;
}
// Get WordPress formatted
// table name
$wp_table = $wpdb->$table;
// Putting it all together
$query = "SELECT $field FROM $wp_table $WHERE ";
// Make actual DB call
// through the built in
// MySQL interface for
// WordPress to at least
// attempt to remain
// compatible with mainline
return $wpdb->get_results($query);
}

Categories