Replacing "product name" with "Item + productId" when product is added in Magento via admin panel - magento

I want to auto generate product names in Magento.
When I'm going to add a product, for the product name I will type some string.
When I save the product, I want the product name to be automatically generated such that the product name becomes Item."productId".

Answering assuming that OP wants to incorporate the auto-increment value from the entity table into business data. This is generally not a great idea.
This is an interesting task which can be easily accomplished with Magento's EAV implementation - particularly when working in the catalog module. First, some background.
When an EAV entity is saved, it has a nice, neat array of key => value pairs which represent the attributes and attribute values for that entity:
Mage_Catalog_Model_Product->_data['attribute_code'] = 'attribute value';
During the save process, the EAV resource model takes this array and iterates over it. For each attribute, identified by its code (attribute_code in the above example) and its entity (catalog_product in the case of products), the configuration for the attribute itself is loaded. Of particular importance is the "backend model" for an attribute, as it is invoked to do pre- and post-processing of/relating to the value.
In the current case, there is a piece of information which will not be present when we are saving the attribute, at least, not in a way in which we can use it: the new product id. This can be used to adjust the original value as part of the save process.
It's always nice to have an example from the core, so, refer to the price attribute and its backend model, Mage_Catalog_Model_Product_Attribute_Backend_Price which can be seen in the eav_attribute table:
SELECT `attribute_code`, `backend_model`
FROM `eav_attribute`
LEFT JOIN `eav_entity_type` USING (`entity_type_id`)
WHERE `attribute_code` = 'price';
#+----------------+-----------------------------------------+
#| attribute_code | backend_model |
#+----------------+-----------------------------------------+
#| price | catalog/product_attribute_backend_price |
#+----------------+-----------------------------------------+
#1 row in set (0.00 sec)
When a product is saved, the price attribute's backend_model is instantiated and (in this case) the afterSave() method is called. Incidentally, this method is what updates pricing by conversion rate for website-scoped pricing. This same approach can be used to modify the name attribute.
The following setup script will add the backend model:
<?php
$installer = Mage::getResourceModel('catalog/setup','default_setup');
$installer->startSetup();
$installer->updateAttribute(
'catalog_product',
'name',
'backend_model',
'custom/product_attribute_backend_name'
);
$installer->endSetup();
The corresponding afterSave() method should do the trick:
public function afterSave($object)
{
$value = $object->getData($this->getAttribute()->getAttributeCode());
$origData = $object->getOrigData();
$origValueExist = $origData && array_key_exists($this->getAttribute()->getAttributeCode(), $origData);
//don't do this in case of update
if ($object->getStoreId() != 0 || !$value || $origValueExist) {
return $this;
}
//append autoinc id
$newValue = $value .'.'. $object->getId(); // or whatever
//assume global store, otherwise the stated need is getting weird!
$object->addAttributeUpdate($this->getAttribute()->getAttributeCode(), $newValue, 0);
return $this;
}

If you're doing this from the admin panel product edit screen, you're going to have to remove the "Required" class from the "Name" field so you can save it without the name. This means overriding the Edit form to replace that field specifically. Then you'll have to overload the save-related methods on the product model (or you can do it from the controller), but the child will have to generate the name on save before it goes to the database.
For example:
class Module_Catalog_Model_Product extends Mage_Catalog_Model_Product
{
protected function _beforeSave()
{
parent::_beforeSave();
$productName = 'Item' . $this->getId();
if (!$this->getId() && !$this->getName())
{
$this->setName('Item Unnamed');
} elseif ($this->getId()) && strcasecmp($this->getName(), $productName) <> 0)
{
$this->setName($productName);
}
}
}
The only problem with this is that it requires two saves. If you want to have it work on the fly, you'll have to do a second save using the _afterSave() method. Or, once again, you can do it from the controller.

I would use a Magento Event to do this:
Since Models in Magento have an event prefixes (just take a look at Mage_Catalog_Model_Product and look for $_eventPrefix, for our current model the eventPrefix is set to catalog_product.
If you now take a look at Mage_Core_Model_Abstract and search for _eventPrefix. You see that eventPrefix are found in _beforeLoad, _afterLoad, _beforeSave, _afterSave and a few others. In these methods you can see an event is dispatched using something as below:
Mage::dispatchEvent($this->_eventPrefix.'_save_before', $this->_getEventData());
This means you have an event available called catalog_product_save_before. With this event you can hook into Magento at that time and do your thing, change the field in this case, and Magento handles the rest.
Take a look at http://www.magentocommerce.com/wiki/5_-_modules_and_development/0_-_module_development_in_magento/customizing_magento_using_event-observer_method for more information how to use these events and turn them into a module. If you don't know how to build modules for Magento and want to learn, there are some awesome on-demand video's for free: http://www.magentocommerce.com/training/on-demand

First I want to thanks to all users which write in the topic. Thanks a lot of guys!
I did it, but I make it easier. (because I have very basic knowledge in Magento and it would toke more time)
So... With my colegues decided to make it with php/jquery/ajax.
First we create one single php file, which return the last id:
<?php
header('Access-Control-Allow-Origin: *');
require_once 'app/Mage.php';
umask(o);
Mage::app('default');
Mage::getSingleton('core/session', array('name'=>'frontend'));
$model = Mage::getModel('catalog/product'); //getting product model
$collection = $model->getCollection(); //products collection
foreach ($collection as $product) //loop for getting products
{
$id=$product->getId();
}
if($id)echo $id+1; //id of product
else{
echo 0;
}
?>
After step one I set the value of input (i.e. I auto generate the name):
if($j('#name').val()=='' && window.location.href.indexOf("admin/catalog_product/new/") > -1) {
$j.post('http://www.website.com/file.php', function(data) {
$j('#name').val('Item №'+data);
});
}
Thanks again for help.
Best Regards,
Jordan!

Related

Laravel Backpack : Storing Belongs To Many relationships using custom view

I have a flight class and this flight has a custom view field like so:
This represents a belongs to many relationship which stores website_id / flight_id and pricing as pivot data in a pivot table.
The custom view uses JS to send this data back to the controller in this format:
{"1":{"price_adult":"434","price_child":"545"},"2":{"price_adult":"323","price_child":"324"},"3":{"price_adult":"434","price_child":"43"}}
Trying to send this data with the request doesn't create the relations fields, and because I do not have a flight ID at the point of creating this within the controller I can not loop this JSON to make the relations manually.
Can anyone point out what the best course of action is or if there is support for this? I took a look at the docs but they are woefully short and patchy in terms of being much help.
EDIT:
I should have said I can probably make this work using a custom name attribute on the model for the relation, then add a set mutator to loop this data and update the prices relation but I don't want to go down this route if there is support for this I am missing out of the box in backpack.
EDIT2:
Someone asked about the relation:
$this->belongsToMany(Website::class, 'website_pricing')->withPivot('price_adult', 'price_child');
This is working fine its not a problem with the relation working its how can I get backpack to store the data as a relation when the flight has no ID yet, or how can I pass the data I posted above in such a way that the backpack crud controller can handle it?
You may need to create a flight first, if no flight id is being provided. Can you explain the database relational structure more?
Basically thought I should post what I did because no one could provide an answer to this.
So basically you have to copy the store / update functions from the parent, changing a few lines.
$this->crud->hasAccessOrFail('create');
// fallback to global request instance
if (is_null($request)) {
$request = \Request::instance();
}
// replace empty values with NULL, so that it will work with MySQL strict mode on
foreach ($request->input() as $key => $value) {
if (empty($value) && $value !== '0') {
$request->request->set($key, null);
}
}
// insert item in the db
$item = $this->crud->create($request->except(['save_action', '_token', '_method']));
$this->data['entry'] = $this->crud->entry = $item;
// show a success message
\Alert::success(trans('backpack::crud.insert_success'))->flash();
// save the redirect choice for next time
parent::setSaveAction();
return parent::performSaveAction($item->getKey());
Basically any line which references a function in the parent class using $this->method needs to be changed to parent::
This line is what I used to submit the relations JSON string passed to the controller as relations $item->prices()->sync(json_decode($request->input('prices'), true));
This is done after the line containing $item = $this->crud->create as the item id that just got stored will be available at that point.

Add Child SKUs to Magento Fulltext Search Index

I'm not sure why this isn't part of the stock functionality of Magento, but I want customers to be able to search for a configurable product by a child SKU. For some reason, Magento doesn't index the child SKUs.
I found in app/code/core/Mage/CatalogSearch/Model/Resource/Fulltext.php
$dynamicFields = array(
'int' => array_keys($this->_getSearchableAttributes('int')),
'varchar' => array_keys($this->_getSearchableAttributes('varchar')),
'text' => array_keys($this->_getSearchableAttributes('text')),
'decimal' => array_keys($this->_getSearchableAttributes('decimal')),
'datetime' => array_keys($this->_getSearchableAttributes('datetime')),
);
I've tried several variations, without success. SKU is a "static" attribute, accessible through $this->_getSearchableAttributes('static'). I'm fine with getting all static attributes, but it's not working. Depending on what a try I either get no change in results or an error that the static attribute table doesn't exist (which make sense, because static attributes are in the product entity table).
Does anybody have a suggestion to solve this problem?
Online research has found suggestions to add a hidden attribute with these values, but that shouldn't be needed. I would rather solve the problem properly.
So many ways to do this..
However, I will lead with something I believe would be much cleaner rather than screwing around with the actual Fulltext in app/code/core/Mage/CatalogSearch/Model/Resource/Fulltext.php.
The problem is the lines AFTER that snippet you included:
// status and visibility filter
$visibility = $this->_getSearchableAttribute('visibility');
$status = $this->_getSearchableAttribute('status');
$statusVals = Mage::getSingleton('catalog/product_status')->getVisibleStatusIds();
$allowedVisibilityValues = $this->_engine->getAllowedVisibility();
which ultimately leads to:
core/Mage/CatalogSearch/Model/Resource/Fulltext/Engine.php
/**
* Retrieve allowed visibility values for current engine
*
* #return array
*/
public function getAllowedVisibility()
{
return Mage::getSingleton('catalog/product_visibility')->getVisibleInSearchIds();
}
then:
core/Mage/Catalog/Model/Product/Visibility.php
public function getVisibleInSearchIds()
{
return array(self::VISIBILITY_IN_SEARCH, self::VISIBILITY_BOTH);
}
So what you need to do is go to your simple product associated with your configurable and change the visibility to 'Search'.
However, here it is in action now:
However, yes, that doesn't look pretty at all. The next order of business is now modifying the search results so that when it lists a simple product item that's a.) associated to a configurable product b.) visibility ID is explicitly set to Mage_Catalog_Model_Product_Visibility::VISIBILITY_IN_SEARCH, you need to render the item in search results to the actual configurable product parent (and the rest of its details, such as images).
(I'll continue this later. my computer decided to crap out on me, and I lost over 50% of what I was writing. Even the part where we modify the fulltext class! Darn. see you later.)
I've used the method of setting simple product visibility to search. Then instead of displaying the simple product load/display its parent product.
Beside the frontend query in loop performance issues I never found this solution very satisfying. So after another whack at it I found another option.
I'm adding simple product SKUs to the fulltext index for grouped products but the same approach should work for configurable products.
Here is the idea:
Mage_CatalogSearch_Model_Resource_Fulltext->_prepareProductIndex
contains
$typeInstance = $this->_getProductTypeInstance($productData['type_id']);
if ($data = $typeInstance->getSearchableData($product)) {
$index['options'] = $data;
}
So by overriding the getSearchableData method in the product type class you can add data to the fulltext index. For grouped product my updated looked like:
... extends Mage_Catalog_Model_Product_Type_Grouped
{
/**
* all child SKUs to search data
*/
public function getSearchableData($product=null)
{
$searchData = parent::getSearchableData();
$adapter = Mage::getSingleton('core/resource')->getConnection('core_read');
$select = $adapter->select()
->from(
array('p' => Mage::getSingleton('core/resource')->getTableName('catalog/product')),
array('sku')
)
->joinLeft(
array('r' => Mage::getSingleton('core/resource')->getTableName('catalog/product_relation')),
'r.child_id = p.entity_id',
array()
)
->where('r.parent_id = ?', $this->getProduct($product)->getId());
$query = $adapter->query($select);
while ($row = $query->fetch()) {
$searchData[] = $row['sku'];
}
return $searchData;
}
Reindexed search and catalogsearch_fulltext.dataindex should contain the additional data.
There is a solution for this but it is a paid extension (we license it), it is not build in to Magento standard so you cannot search by child attributes and just return the parent configurable/grouped/bundle product.

Attribute Options/Labels sorting define on Site View Level?

I using Magento 1.6.2.
Is there any way to set the position for Attribute Option Labels on a Site View Level and not on a global level?
Reason: Here the Values for Color in English and German
Black / Schwarz
Clear / Transparent
Copper / Kupfer
Yellow / Gelb
It is obvious that the sorting is different for different languages.
Overriding the position value and sorting the values in the frontpage code is not possible because there are options where alphanumeric sorting doesn't make sense:
i.e. Small Medium Large
Please help
Yes, this is very possible. But it's a fairly deep change, depending on what you would like to accomplish with this. This will get you started:
You want to first add a new column to eav/attribute_option_value table. Here is the setup script for that:
$installer = $this;
$installer->startSetup();
$installer->run("
ALTER TABLE `{$this->getTable('eav/attribute_option_value')}` ADD COLUMN `sort_order` INT UNSIGNED NULL DEFAULT 0;
");
$installer->endSetup();
Next, you need to rewrite Mage_Eav_Model_Mysql4_Entity_Attribute_Option_Collection. When performing the join for the store filter, you need to add your sort_order there:
public function setStoreFilter($storeId=null, $useDefaultValue=true)
{
if (is_null($storeId)) {
$storeId = Mage::app()->getStore()->getId();
}
$sortBy = 'store_default_value';
if ($useDefaultValue) {
$this->getSelect()
->join(array('store_default_value'=>$this->_optionValueTable),
'store_default_value.option_id=main_table.option_id',
array('default_value'=>'value'))
->joinLeft(array('store_value'=>$this->_optionValueTable),
'store_value.option_id=main_table.option_id AND '.$this->getConnection()->quoteInto('store_value.store_id=?', $storeId),
array('store_value'=>'value',
'value' => new Zend_Db_Expr('IF(store_value.value_id>0, store_value.value,store_default_value.value)',
'sort_order'))) // ADDED
->where($this->getConnection()->quoteInto('store_default_value.store_id=?', 0));
}
else {
$sortBy = 'store_value';
$this->getSelect()
->joinLeft(array('store_value'=>$this->_optionValueTable),
'store_value.option_id=main_table.option_id AND '.$this->getConnection()->quoteInto('store_value.store_id=?', $storeId),
'value',
'sort_order') // ADDED
->where($this->getConnection()->quoteInto('store_value.store_id=?', $storeId));
}
$this->setOrder("store_value.sort_order", 'ASC'); // CHANGED
return $this;
}
To show what is going on: each attribute has a source model. The source model is responsible for providing the values in a frontend dropdown-type list (select, multiselect). If the source model is Mage_Eav_Model_Entity_Attribute_Source_Table, which it will be by default if the attribute type is select or multiselect, then this code retrieves the values:
$collection = Mage::getResourceModel('eav/entity_attribute_option_collection')
->setPositionOrder('asc')
->setAttributeFilter($this->getAttribute()->getId())
->setStoreFilter($this->getAttribute()->getStoreId())
->load();
As you can see, we are rewriting the setStoreFilter() function. This is the last one called. You might want to add an unshiftOrder('main_table.sort_order', 'ASC') at the beginning of the rewritten function, just for good measure that the `main_table.sort_order does not get in the way.
I'll leave it to you to make the necessary adjustments to the admin panel to provide the option for setting the sort order matrix.

Storing product attributes with orders in Magento

I am looking for a solution to storing product attributes with each order. Essentially I have a need for storing a unique item lot number for each product that can then be searched on the front end to find out which orders contained products from a specific lot. My initial thought was to do this with product attributes and store the attribute with each product in an order.
Does anybody have a better solution or can point me in the right direction for implementing this solution?
Edit: Still looking for a solution to this
I agree the lot number should be a product attribute but you don't need to store it twice. Just join it with order table when you later need it - That way the information is kept up to date (unless you want to know what it was at the time of ordering, in which case this is all wrong).
Unfortunately the order tables are flattened, not EAV, so don't handle attributes so well. The collection's joinAttribute() method is a stub. You can get around that with this query patterns library (self promotion disclaimer; I'm using it here because I wrote the attribute functions and don't want to redo the work) and then extending it with a class specific to you.
class Knectar_Select_Product_Lot extends Knectar_Select_Product
{
public function __construct()
{
parent::__construct();
$this->joinAttribute('lots', 'catalog_product', 'lot_number',
'products.entity_id', 0, 'lot_number'
);
}
public static function enhance(Varien_Db_Select $select, $tableName, $condition, $columns = null, $type = self::LEFT_JOIN)
{
$select->_join(
$type,
array($tableName => new self()),
$condition,
$columns ? $columns : 'lot_number'
);
}
}
The enhance() function does the hard bit, you only need to call it and filter by it's column.
$orderItems = Mage::getResourceModel('sales/order_item_collection');
Knectar_Select_Product_Lot::enhance($orderItems->getSelect, 'lots', 'lots.product_id = main_table.product_id');
$orderItems->addFieldToFilter('lot_number', 'ABC123');
If you are using the collection in an admin grid then it's column filters will do addFieldToFilter() for you.

Adding a custom field to Magento's subscription module

The newsletter subscription module in Magento has only one field (email) by default. After I add an extra field to the form (say country), how can I get the form data to show up in the Magento back-end and be sent as an email to a preset recipient? Thanks.
If you want to add some custom fields for Magento newsletter subscriber (for example subscriber_name), you should do the following:
Add new column for newsletter_subscriber table
Add text input to newsletter template
Create observer for newsletter_subscriber_save_before event
In the observer you can get your custom field's value from request and assign it to subscriber's object:
public function newsletterSubscriberSave(Varien_Event_Observer $observer)
{
$subscriber = $observer->getEvent()->getSubscriber();
$name = Mage::app()->getRequest()->getParam('subscriber_name');
$subscriber->setSubscriberName($name);
return $this;
}
UPDATE:
Here is the detailed article explaining how to add Country field
Also, I have created a free module, it is available on the GitHub
There are a few things that you need to take care of to make this work:
Add a new column for your data to the appropriate database table
Make sure that Magento saves your new field to the database
Present the data in the admin backend
Record the data when you get a new newsletter subscription
Here's how you can do all those things:
Ad. 1)
Using phpMyAdmin, MySQL command line, or whatever is your preferred DB manipulation method, add a new column "country" as, say, varchar(100) to the newsletter_subscriber table.
Ad. 2)
Magento will automatically give you access to the new field through the getCountry() and setCountry() methods on the Mage_Newsletter_Model_Subscriber object. The only thing it won't do is save your field back to the DB after it has been changed with code somewhere in the system. To get it saved you need to modify _prepareSave(Mage_Newsletter_Model_Subscriber $subscriber) function found in Mage_Newsletter_Model_Mysql4_Subscriber (app/code/core/Mage/Newsletter/Model/Mysql4/Subscriber.php). Be sure to make a local copy of the file first and not modify the core file. Here's what you need to add:
protected function _prepareSave(Mage_Newsletter_Model_Subscriber $subscriber)
{
$data = array();
$data['customer_id'] = $subscriber->getCustomerId();
$data['store_id'] = $subscriber->getStoreId()?$subscriber->getStoreId():0;
$data['subscriber_status'] = $subscriber->getStatus();
$data['subscriber_email'] = $subscriber->getEmail();
$data['subscriber_confirm_code'] = $subscriber->getCode();
//ADD A NEW FIELD START
//note that the string index for the $data array
//must match the name of the column created in step 1
$data['country'] = $subscriber->getCountry();
//ADD A NEW FIELD END
(...)
}
Ad. 3)
You will need to modify (a local copy of) the file app/code/core/Mage/Adminhtml/Block/Newsletter/Subscriber/Grid.php. The method you are looking for is called _prepareColumns(). In there you will see a series of calls to $this->addColumn(). You need to add a corresponding call for your "Country" field with the following code:
$this->addColumn('country', array(
'header' => Mage::helper('newsletter')->__('Country'),
//the index must match the name of the column created in step 1
'index' => 'country',
'default' => '----'
));
If you want the field to appear at the end of the grid (as the last column) add it as the last call, otherwise, squeeze it between the existing calls exactly where you want it to end up in the admin.
Ad. 4)
This is a part I did not have to do in my customization of the Magento newsletter, so it will be mostly theoretical. The subscription occurs in the controller located at app/code/core/Mage/Newsletter/controllers/SubscriberController.php. Here's the code of the newAction method with my proposed changes:
public function newAction()
{
if ($this->getRequest()->isPost() && $this->getRequest()->getPost('email')) {
$session = Mage::getSingleton('core/session');
$email = (string) $this->getRequest()->getPost('email');
try {
if (!Zend_Validate::is($email, 'EmailAddress')) {
Mage::throwException($this->__('Please enter a valid email address'));
}
$status = Mage::getModel('newsletter/subscriber')->subscribe($email);
if ($status == Mage_Newsletter_Model_Subscriber::STATUS_NOT_ACTIVE) {
$session->addSuccess($this->__('Confirmation request has been sent'));
}
else {
$session->addSuccess($this->__('Thank you for your subscription'));
}
//ADD COUNTRY INFO START
//at this point we may safly assume that subscription record was created
//let's retrieve this record and add the additional data to it
$subscriber = Mage::getModel('newsletter/subscriber')->loadByEmail($email);
//assuming that the input's id is "country"
$subscriber->setCountry((string) $this->getRequest()->getPost('country'));
//don't forget to save the subscriber!
$subscriber->save();
//ADD COUNTRY INFO END
}
catch (Mage_Core_Exception $e) {
$session->addException($e, $this->__('There was a problem with the subscription: %s', $e->getMessage()));
}
catch (Exception $e) {
$session->addException($e, $this->__('There was a problem with the subscription'));
}
}
$this->_redirectReferer();
}
Going through the above steps should take care of the most part of your problem. Let me know how that last part worked out, as I did not have a chance to test it.
Once you have your additional field in the Subscriber object you can do whatever you want with it. I did not really get what you mean by
be sent as an email to a preset recipient
If you can explain that I will try to help you out with this part too.
Edit - how to send a mail when someone subscribes
Just add the following code to the controller after the part which adds country to a subscriber object.
$mail = new Zend_Mail();
$mail->setBodyHtml("New subscriber: $email <br /><br />Country: ".$this->getRequest()->getPost('country'));
$mail->setFrom("youremail#email.com")
->addTo("admin#mysite.com")
->setSubject("Your Subject here");
$mail->send();
Adding to the accepted answer, you can also get away with this a little easier if you're adding a date, datetime, or timestamp-type column.
In my case, I wanted to add a "Subscribed at Date" to my grid. To do this, I wrote my upgrade script, column type being TIMESTAMP and the default value being CURRENT_TIMESTAMP. This way, when the row is added, the current date/time is recorded.
Then, all you have to do is add your block customizations. I'd suggest doing it by extending Magento's grid block rather than doing the local codepool override though. This way, you only need to override _prepareColumns();
Old thread but if someone has the same question, there is a free extension, that adds fields for gender, firstname and lastname and makes it available in the backend grid for export via xml/csv: http://www.magentocommerce.com/magento-connect/extended-newsletter-subscription-for-guests.html
Perhaps you can extend the code to fit your needs.
This is a warning for anyone who's installed the Ebizmarts_MailChimp extension.
It's a great extension. But it adds subscriber_firstname and subscriber_lastname to the newsletter_subscriber table.
If you intend to create these fields, you should either "require" the Ebizmarts_MailChimp extension or check the fields don't exist before your extension creates them.
In the opposite, where you've created them and want to install the the Ebizmarts_MailChimp extension after you've created these fields, you will have to comment out the addColumn code for these two fields during installation.

Resources