Magento Custom Collection Prevents Proper Massaction Checkbox Selection - php

Magento 1.7.0.2 - I have created a custom collection by extending the Catalog Product Collection. Essentially what the collection is: a collection of unique combination of products and if any product has custom options then each unique combination of product id/option id is another item in the collection. The collection and count are working just fine.
However, the "Select All" button for the mass action checkbox selection only selects rows with a product id and not those where the product_id is concatenated with the option_id as shown in the image below.
Here is my collection class.
<?php
class Lightsnholsters_Inventory_Model_Resource_Catalog_Product_Collection extends Mage_Catalog_Model_Resource_Product_Collection
{
public function getSelectCountSql()
{
$this->_renderFilters();
$countSelect = clone $this->getSelect();
$countSelect->reset(Zend_Db_Select::ORDER);
$countSelect->reset(Zend_Db_Select::LIMIT_COUNT);
$countSelect->reset(Zend_Db_Select::LIMIT_OFFSET);
$countSelect->reset(Zend_Db_Select::COLUMNS);
// Count doesn't work with group by columns keep the group by
if(count($this->getSelect()->getPart(Zend_Db_Select::GROUP)) > 0) {
$countSelect->reset(Zend_Db_Select::GROUP);
$countSelect->distinct(true);
$group = $this->getSelect()->getPart(Zend_Db_Select::GROUP);
$countSelect->columns("COUNT(DISTINCT ".implode(", ", $group).")");
} else {
$countSelect->columns('COUNT(*)');
}
return $countSelect;
}
protected function _initSelect()
{
parent::_initSelect();
$this->joinAttribute('name','catalog_product/name','entity_id');
$this->getSelect()->reset(Zend_Db_Select::COLUMNS);
$this->getSelect()->joinLeft(
array('opt' => $this->getTable('catalog/product_option')),
"e.entity_id = opt.product_id AND opt.type IN('radio','drop_down')",
array(''));
$this->getSelect()->joinLeft(
array('opt_val' => $this->getTable('catalog/product_option_type_value')),
"opt.option_id = opt_val.option_id AND opt_val.sku != '' AND opt_val.sku IS NOT NULL",
array('entity_id' => new Zend_Db_Expr("CONCAT_WS('_',e.entity_id,opt_val.option_type_id)"),
'option_type_id' => new Zend_Db_Expr("IF(opt_val.option_type_id > 0, opt_val.option_type_id,0)")));
$this->getSelect()->joinLeft(
array('opt_prc' => $this->getTable('catalog/product_option_type_price')),
"opt_val.option_type_id = opt_prc.option_type_id AND opt_prc.store_id = {$this->getStoreId()}",
array(''));
$this->getSelect()->joinLeft(
array('opt_ttl' => $this->getTable('catalog/product_option_type_title')),
"opt_val.option_type_id = opt_ttl.option_type_id AND opt_ttl.store_id = {$this->getStoreId()}",
array('name' => new Zend_Db_Expr("CONCAT_WS(' ',at_name.value,title)")));
return $this;
}
}
Here is the massaction code that I use in the grid class
protected function _prepareMassaction()
{
$this->setMassactionIdField('entity_id');
$this->setMassactionIdFilter('entity_id');
$this->getMassactionBlock()->setFormFieldName('entity_id');
$this->getMassactionBlock()->addItem('updateAttributes', array(
'label' => $this->__('Update Attributes'),
'url' => $this->getUrl('*/*/massUpdateAttributes'),
));
$statuses = array(
array('label' => '', 'value'=>''),
array('label' => $this->__('Disabled'), 'value'=>0 ),
array('label' => $this->__('Enabled') , 'value'=>1 )
);
$this->getMassactionBlock()->addItem('status', array(
'label'=> $this->__('Change status'),
'url' => $this->getUrl('*/*/massStatus', array('_current'=>true)),
'additional' => array(
'visibility' => array(
'name' => 'status',
'type' => 'select',
'class' => 'required-entry',
'label' => $this->__('Status'),
'values' => $statuses
)
)
));
Mage::dispatchEvent('lightsnholsters_inventory_autolist_grid_massaction_prepare', array('block' => $this));
return $this;
}
It seems as though whatever field I put in $this->setMassactionIdField('entity_id'); is fully ignored.
My goal is that when I click "Select All" that all checkboxes are selected, not just the ones that do not have the option_id concatenated on.
Thank you for your help.

I have revisited this issue and figured out why my collection with rows that are purposefully and dynamically created with ->joinLeft and a dynamically created id column was not allowing the massaction blocks "Select All" to properly select checkboxes when clicked.
Its because when I extended Mage_Catalog_Model_Resource_Collection to use as a base for my own custom collection, that class had already been overriding the getAllIds() method of Varien_Data_Collection which the massaction block uses to determine which ids we actually want to select when we click "Select All".
In my module's collection class, where I had extended Magento's product collection, I simply had to create my own getAllIds() method which would completely override the parent version of the method.
Simply put, everything works perfect, see the code below.
<?php
class Lightsnholsters_Inventory_Model_Resource_Catalog_Product_Collection extends Mage_Catalog_Model_Resource_Product_Collection
{
/*
*
* we had to rewrite this function because the grid had the wrong count, which is actually why we created this class to begin with
*
*/
public function getSelectCountSql()
{
$this->_renderFilters();
$countSelect = clone $this->getSelect();
$countSelect->reset(Zend_Db_Select::ORDER);
$countSelect->reset(Zend_Db_Select::LIMIT_COUNT);
$countSelect->reset(Zend_Db_Select::LIMIT_OFFSET);
$countSelect->reset(Zend_Db_Select::COLUMNS);
// Count doesn't work with group by columns keep the group by
if(count($this->getSelect()->getPart(Zend_Db_Select::GROUP)) > 0) {
$countSelect->reset(Zend_Db_Select::GROUP);
$countSelect->distinct(true);
$group = $this->getSelect()->getPart(Zend_Db_Select::GROUP);
$countSelect->columns("COUNT(DISTINCT ".implode(", ", $group).")");
} else {
$countSelect->columns('COUNT(*)');
}
return $countSelect;
}
protected function _initSelect()
{
parent::_initSelect();
$this->joinAttribute('name','catalog_product/name','entity_id');
$this->getSelect()->reset(Zend_Db_Select::COLUMNS);
$this->getSelect()->joinLeft(
array('opt' => $this->getTable('catalog/product_option')),
"e.entity_id = opt.product_id AND opt.type IN('radio','drop_down')",
array(''));
$this->getSelect()->joinLeft(
array('opt_val' => $this->getTable('catalog/product_option_type_value')),
"opt.option_id = opt_val.option_id AND opt_val.sku != '' AND opt_val.sku IS NOT NULL",
array('entity_id' => new Zend_Db_Expr("CONCAT_WS('_',e.entity_id,opt_val.option_type_id)"),
'option_type_id' => new Zend_Db_Expr("IF(opt_val.option_type_id > 0, opt_val.option_type_id,0)")));
$this->getSelect()->joinLeft(
array('opt_prc' => $this->getTable('catalog/product_option_type_price')),
"opt_val.option_type_id = opt_prc.option_type_id AND opt_prc.store_id = {$this->getStoreId()}",
array(''));
$this->getSelect()->joinLeft(
array('opt_ttl' => $this->getTable('catalog/product_option_type_title')),
"opt_val.option_type_id = opt_ttl.option_type_id AND opt_ttl.store_id = {$this->getStoreId()}",
array('name' => new Zend_Db_Expr("CONCAT_WS(' ',at_name.value,title)")));
return $this;
}
/*
*
* we override the parent method for all ids because it does not fetch the proper ids for the massaction checkboxes
*
*/
public function getAllIds($limit = null, $offset = null)
{
$this->_renderFilters();
$connection = $this->getConnection();
$idsSelect = clone $this->getSelect();
$idsSelect->reset(Zend_Db_Select::LIMIT_COUNT);
$idsSelect->reset(Zend_Db_Select::LIMIT_OFFSET);
$statement = $connection->query($idsSelect);
$ids = $statement->fetchAll(PDO::FETCH_COLUMN);
return $ids;
}
}

Related

Magento Grid Filter all Entries with no Value

I'm working on a Magento setup for Subaccounts.
Each customer has the field "parent_customer". If the field is set, the account is a subaccount.
What I'm trying to do is add a simple filter to the Grid overview, so that I can show only parent accounts of only subaccounts.
in the overriding Grid.php file I've placed the following snippet into the _prepareColumns() method:
$this->addColumn('parent_customer', array(
'header' => Mage::helper('customer')->__('Subaccount'),
'width' => '10',
'type' => 'options',
'options' => array('0'=>'Nein', '1' => 'Ja'),
'index' => 'parent_customer',
'filter_condition_callback' => array($this, '_callbackParentCustomer'),
));
And also added a new callback Method
protected function _callbackParentCustomer($collection, $column){
$value = $column->getFilter()->getValue();
if ($value === null) { //here check if filter is not null
return $this;
}
/**
* Here you can add filter to collection
* or do other manipulations with collection.
* As example you can check filter value and filter collection.
*/
if ($value != 0) {
Mage::log('show subaccounts', null, 'product.log', true);
$collection->addFieldToFilter('parent_customer', array('gt' => 0));
}else{
Mage::log('hide subaccounts', null, 'product.log', true);
$collection->addFieldToFilter('parent_customer', array('eq' => ''));
}
return $this;
}
The Logs show, that both filters trigger. If I want to only show subaccounts ('gt' => 0) all works fine.
Problem is, if I want to show all parent accounts I get an empty collection. Is there no way to filter for Items that don't have an attribute set?

Magento - Can't upload files in custom module

I'm editing a custom plugin in magento in which I need add a field to upload an image and save it's path in the database together with other data. The form works fine with the additional data, as well as the database recording. However, it seems that the $_FILES variable always return empty. What is more curious is that an file called "cache_2ca019d1e2db75b611e5f3aa5c932970" is always generated in the media directory every time I try to upload an image in my module.
I've found people with similar problems here, but none of the presented solutions worked for me. I'm lost =/
This is my Form file:
<?php
class SmashingMagazine_BrandDirectory_Block_Adminhtml_Brand_Edit_Form
extends Mage_Adminhtml_Block_Widget_Form
{
protected function _prepareForm()
{
// instantiate a new form to display our brand for editing
$form = new Varien_Data_Form(array(
'id' => 'edit_form',
'action' => $this->getUrl(
'smashingmagazine_branddirectory_admin/brand/edit',
array(
'_current' => true,
'continue' => 0,
)
),
'method' => 'post',
'enctype' => 'multipart/form-data'
));
$form->setUseContainer(true);
$this->setForm($form);
// define a new fieldset, we only need one for our simple entity
$fieldset = $form->addFieldset(
'general',
array(
'legend' => $this->__('Brand Details')
)
);
$brandSingleton = Mage::getSingleton(
'smashingmagazine_branddirectory/brand'
);
// add the fields we want to be editable
$this->_addFieldsToFieldset($fieldset, array(
'name' => array(
'label' => $this->__('Name'),
'input' => 'text',
'required' => true,
),
'url_key' => array(
'label' => $this->__('URL Key'),
'input' => 'text',
'required' => true,
),
'image' => array(
'label' => $this->__('Image'),
'input' => 'image',
'required' => true,
'disabled' => false,
'readonly' => true,
),
'visibility' => array(
'label' => $this->__('Visibility'),
'input' => 'select',
'required' => true,
'options' => $brandSingleton->getAvailableVisibilies(),
),
/**
* Note: we have not included created_at or updated_at,
* we will handle those fields ourself in the Model before save.
*/
));
return $this;
}
/**
* This method makes life a little easier for us by pre-populating
* fields with $_POST data where applicable and wraps our post data in
* 'brandData' so we can easily separate all relevant information in
* the controller. You can of course omit this method entirely and call
* the $fieldset->addField() method directly.
*/
protected function _addFieldsToFieldset(
Varien_Data_Form_Element_Fieldset $fieldset, $fields)
{
$requestData = new Varien_Object($this->getRequest()
->getPost('brandData'));
foreach ($fields as $name => $_data) {
if ($requestValue = $requestData->getData($name)) {
$_data['value'] = $requestValue;
}
// wrap all fields with brandData group
$_data['name'] = "brandData[$name]";
// generally label and title always the same
$_data['title'] = $_data['label'];
// if no new value exists, use existing brand data
if (!array_key_exists('value', $_data)) {
$_data['value'] = $this->_getBrand()->getData($name);
}
// finally call vanilla functionality to add field
$fieldset->addField($name, $_data['input'], $_data);
}
return $this;
}
/**
* Retrieve the existing brand for pre-populating the form fields.
* For a new brand entry this will return an empty Brand object.
*/
protected function _getBrand()
{
if (!$this->hasData('brand')) {
// this will have been set in the controller
$brand = Mage::registry('current_brand');
// just in case the controller does not register the brand
if (!$brand instanceof
SmashingMagazine_BrandDirectory_Model_Brand) {
$brand = Mage::getModel(
'smashingmagazine_branddirectory/brand'
);
}
$this->setData('brand', $brand);
}
return $this->getData('brand');
}
}
And this is my Controller File:
<?php
class SmashingMagazine_BrandDirectory_Adminhtml_BrandController
extends Mage_Adminhtml_Controller_Action
{
/**
* Instantiate our grid container block and add to the page content.
* When accessing this admin index page we will see a grid of all
* brands currently available in our Magento instance, along with
* a button to add a new one if we wish.
*/
public function indexAction()
{
// instantiate the grid container
$brandBlock = $this->getLayout()
->createBlock('smashingmagazine_branddirectory_adminhtml/brand');
// add the grid container as the only item on this page
$this->loadLayout()
->_addContent($brandBlock)
->renderLayout();
}
/**
* This action handles both viewing and editing of existing brands.
*/
public function editAction()
{
/**
* retrieving existing brand data if an ID was specified,
* if not we will have an empty Brand entity ready to be populated.
*/
$brand = Mage::getModel('smashingmagazine_branddirectory/brand');
if ($brandId = $this->getRequest()->getParam('id', false)) {
$brand->load($brandId);
if ($brand->getId() < 1) {
$this->_getSession()->addError(
$this->__('This brand no longer exists.')
);
return $this->_redirect(
'smashingmagazine_branddirectory_admin/brand/index'
);
}
}
// process $_POST data if the form was submitted
if ($postData = $this->getRequest()->getPost('brandData')) {
try {
// image upload
if(isset($_FILES['image']['name']) and (file_exists($_FILES['image']['tmp_name'])))
{
try
{
$path = Mage::getBaseDir('media') . DS . 'banner' . DS;
$uploader = new Varien_File_Uploader('image');
$uploader
->setAllowedExtensions(array('jpg','png','gif','jpeg'));
$uploader->setAllowRenameFiles(false);
$uploader->setFilesDispersion(false);
$destFile = $path.$_FILES[$image]['name'];
$filename = $uploader->getNewFileName($destFile);
$uploader->save($path, $filename);
$data['img'] = $_FILES['image']['name'];
}
catch(Exception $e)
{
}
}
else
{
if(isset($data['image']['delete']) && $postData['image']['delete'] == 1)
$data['image'] = '';
else
unset($data['image']);
}
// continue
$brand->addData($postData);
$brand->save();
$this->_getSession()->addSuccess(
$this->__($_FILES['image']['name'])
);
// redirect to remove $_POST data from the request
return $this->_redirect(
'smashingmagazine_branddirectory_admin/brand/edit',
array('id' => $brand->getId())
);
} catch (Exception $e) {
Mage::logException($e);
$this->_getSession()->addError($e->getMessage());
}
/**
* if we get to here then something went wrong. Continue to
* render the page as before, the difference being this time
* the submitted $_POST data is available.
*/
}
// make the current brand object available to blocks
Mage::register('current_brand', $brand);
// instantiate the form container
$brandEditBlock = $this->getLayout()->createBlock(
'smashingmagazine_branddirectory_adminhtml/brand_edit'
);
// add the form container as the only item on this page
$this->loadLayout()
->_addContent($brandEditBlock)
->renderLayout();
}
public function deleteAction()
{
$brand = Mage::getModel('smashingmagazine_branddirectory/brand');
if ($brandId = $this->getRequest()->getParam('id', false)) {
$brand->load($brandId);
}
if ($brand->getId() < 1) {
$this->_getSession()->addError(
$this->__('This brand no longer exists.')
);
return $this->_redirect(
'smashingmagazine_branddirectory_admin/brand/index'
);
}
try {
$brand->delete();
$this->_getSession()->addSuccess(
$this->__('The brand has been deleted.')
);
} catch (Exception $e) {
Mage::logException($e);
$this->_getSession()->addError($e->getMessage());
}
return $this->_redirect(
'smashingmagazine_branddirectory_admin/brand/index'
);
}
/**
* Thanks to Ben for pointing out this method was missing. Without
* this method the ACL rules configured in adminhtml.xml are ignored.
*/
protected function _isAllowed()
{
/**
* we include this switch to demonstrate that you can add action
* level restrictions in your ACL rules. The isAllowed() method will
* use the ACL rule we have configured in our adminhtml.xml file:
* - acl
* - - resources
* - - - admin
* - - - - children
* - - - - - smashingmagazine_branddirectory
* - - - - - - children
* - - - - - - - brand
*
* eg. you could add more rules inside brand for edit and delete.
*/
$actionName = $this->getRequest()->getActionName();
switch ($actionName) {
case 'index':
case 'edit':
case 'delete':
// intentionally no break
default:
$adminSession = Mage::getSingleton('admin/session');
$isAllowed = $adminSession
->isAllowed('smashingmagazine_branddirectory/brand');
break;
}
return $isAllowed;
}
}
Thanks in advance.
// add the fields we want to be editable
$this->_addFieldsToFieldset($fieldset, array(
'name' => array(
'label' => $this->__('Name'),
'input' => 'text',
'required' => true,
),
'url_key' => array(
'label' => $this->__('URL Key'),
'input' => 'text',
'required' => true,
),
'image' => array(
'label' => $this->__('Image'),
'input' => 'image',
'name' => 'image',
'required' => true,
'disabled' => false,
),
'visibility' => array(
'label' => $this->__('Visibility'),
'input' => 'select',
'required' => true,
'options' => $brandSingleton->getAvailableVisibilies(),
),
/**
* Note: we have not included created_at or updated_at,
* we will handle those fields ourself in the Model before save.
*/
));
Try this by adding name attribute on image.

Magento/PHP - Get Phones, Emails, Names for all customers in a selected customer group

I am working on a custom Magento(1.9.0.1) extension.
Let me show you what i have done already.
Here is the code for this Adminhtml form:
<?php
class VivasIndustries_SmsNotification_Block_Adminhtml_Sms_Sendmass_Edit_Form extends Mage_Adminhtml_Block_Widget_Form
{
public function _prepareLayout()
{
$ExtensionPath = Mage::getModuleDir('js', 'VivasIndustries_SmsNotification');
$head = $this->getLayout()->getBlock('head');
$head->addJs('jquery.js');
$head->addJs('vivas.js');
return parent::_prepareLayout();
}
protected function _prepareForm()
{
$form = new Varien_Data_Form(array(
'id' => 'edit_form',
'action' => $this->getUrl('*/*/save', array('id' => $this->getRequest()->getParam('id'))),
'method' => 'post',
));
$fieldset = $form->addFieldset('edit_form', array('legend'=>Mage::helper('smsnotification')->__('SMS Information')));
$CustomerGroups = Mage::getResourceModel('customer/group_collection')->toOptionArray();
$smsprice_value = Mage::getStoreConfig('vivas/smsprice/smsprice_value');
$smsprice_tag = Mage::getStoreConfig('vivas/smsprice/smsprice_tag');
$customerArray=array();
foreach($CustomerGroups as $each){
$count=Mage::getResourceModel('customer/customer_collection')
->addAttributeToFilter('group_id',$each['value'])->getSize();
$SMSPrice = $count * $smsprice_value;
$customerArray[]=array('value'=> $each['value'],'label'=> $each['label'].' - ('.$count.' Members) - ('.$SMSPrice.' '.$smsprice_tag.')');
}
$CustomerGroups = array_merge(array('' => ''), $customerArray);
$fieldset->addField('customergroups', 'select',
array(
'name' => 'customergroups',
'label' => Mage::helper('smsnotification')->__('Customer Group'),
'class' => 'required-entry',
'after_element_html' => '<br><small>If customer group is not selected the SMS will be sended<br> to all store members!</small>',
'values' => $CustomerGroups
)
);
$fieldset->addField('smstext', 'textarea', array(
'label' => Mage::helper('smsnotification')->__('SMS Text'),
'class' => 'required-entry',
'required' => true,
'name' => 'smstext',
'onclick' => "",
'onkeyup' => "CheckLetterSize(this)",
'after_element_html' => '<br><b style="color:brown;"><span id="charNum"></span><span id="charNum1"></span></b><br><small>SMS text must <b>NOT</b> be longer then 160 characters!</small>',
'tabindex' => 1
));
if ( Mage::getSingleton('adminhtml/session')->getsmsnotificationData() )
{
$form->setValues(Mage::getSingleton('adminhtml/session')->getsmsnotificationData());
Mage::getSingleton('adminhtml/session')->setsmsnotificationData(null);
} elseif ( Mage::registry('smsnotification_data') ) {
$form->setValues(Mage::registry('smsnotification_data')->getData());
}
// Add these two lines
$form->setUseContainer(true);
$this->setForm($form);
////
return parent::_prepareForm();
}
}
When this form is submitted i want to get all Names, phones and emails of the members in the selected group. The second thing is, when no customer group is selected it must give me names, phones and emails for all customers in the store.
I used to get information about the user from Order information like this:
class VivasIndustries_SmsNotification_Model_Observer
{
public function orderSaved(Varien_Event_Observer $observer)
{
/** **/
$CustomerPhone = $observer->getOrder()->getBillingAddress()->getTelephone();
$CustomerName = $observer->getOrder()->getBillingAddress()->getName();
$CustomerEmail = $observer->getOrder()->getBillingAddress()->getEmail();
}
}
I know that this is not even close to what i need but i don't know how to get this information.
Can you please help me out ?
Thanks in advance!
In your admin controller, where the form is posted:
$groupId = $this->getRequest()->getPost('customergroups', '');
if (!empty($groupId)) {
//Get customers from a group
$customers = Mage::getModel('customer/customer')
->getCollection()
->addAttributeToSelect('*')
->addFieldToFilter('group_id', $groupId);
} else {
//Get all customers
$customers = Mage::getModel('customer/customer')
->getCollection()
->addAttributeToSelect('*');
}

Magento Admin Edit Form Fields - Custom Model Field(s)

Wanting to add a custom Model to be rendered in a custom Magento admin form. Just cant seem to get the source model to render any of the options. Couldn't really find anything on google as it was mostly to do with system/config source model examples. See code below
Model File (My/Module/Model/MyModel.php)
<?php
class My_Module_Model_MyModel extends Mage_Core_Model_Abstract
{
static public function getOptionArray()
{
$allow = array(
array('value' => '1', 'label' => 'Enable'),
array('value' => '0', 'label' => 'Disable'),
);
return $allow;
}
}
and my form tab file - Tab shows up with multiselect field, but its blank (My/Module/Block/Adminhtml/Module/Edit/Tab/Data.php)
<?php
class My_Module_Block_Adminhtml_Module_Edit_Tab_Data extends Mage_Adminhtml_Block_Widget_Form
{
protected function _prepareForm(){
$form = new Varien_Data_Form();
$this->setForm($form);
$fieldset = $form->addFieldset('module_form', array('legend'=>Mage::helper('module')->__('Module Information')));
$object = Mage::getModel('module/module')->load( $this->getRequest()->getParam('module_id') );
echo $object;
$fieldset->addField('module_enabled', 'multiselect', array(
'label' => Mage::helper('module')->__('Allowed Module'),
'class' => 'required-entry',
'required' => true,
'name' => 'module_enabled',
'source_model' => 'My_Module_Model_MyModel',
'after_element_html' => '<small>Select Enable to Allow</small>',
'tabindex' => 1
));
if ( Mage::getSingleton('adminhtml/session')->getModuleData() )
{
$form->setValues(Mage::getSingleton('adminhtml/session')->getModuleData());
Mage::getSingleton('adminhtml/session')->setModuleData(null);
} elseif ( Mage::registry('module_data') ) {
$form->setValues(Mage::registry('module_data')->getData());
}
return parent::_prepareForm();
}
}
So I have other fields, tabs that all save the data etc but just cant get the values to render using a custom model inside the multiselect field.
Looks like method name in the source model is incorrect. Also, you probably don't need to extend Mage_Core_Model_Abstract in source models.
Try this:
<?php
class My_Module_Model_MyModel
{
public function toOptionArray()
{
return array(
array('value' => '1', 'label' => Mage::helper('module')->__('Enable')),
array('value' => '0', 'label' => Mage::helper('module')->__('Disable')),
);
}
}
OP's solution migrated from the question to an answer:
updated the MyModel.php to get a foreach in a collection (CMS Pages
for example)
<?php
class My_Module_Model_MyModel
{
public function toOptionArray($withEmpty = false)
{
$options = array();
$cms_pages = Mage::getModel('cms/page')->getCollection();
foreach ($cms_pages as $value) {
$data = $value->getData();
$options[] = array(
'label' => ''.$data['title'].'('.$data['identifier'].')',
'value' => ''.$data['identifier'].''
);
}
if ($withEmpty) {
array_unshift($options, array('value'=>'', 'label'=>Mage::helper('module')->__('-- Please Select --')));
}
return $options;
}
and within My/Module/Block/Adminhtml/Module/Edit/Tab/Data.php I just
removed "source_model" and replaced it with
'values' => Mage::getModel('module/mymodel')->toOptionArray(),
Just to add, also had the issue of multiselect values not
saving/updating the multiselect field on refresh/save on the edit
page. To get this working, I edited the admin controller under the
saveAction (or the action name to save the form data). See below my
saveAction in the controller for the admin/backend located in
My/Module/controllers/Adminhtml/ModuleController.php
public function saveAction() {
$model = Mage::getModel('module/module');
if ($data = $this->getRequest()->getPost()) {
$model = Mage::getModel('module/module');
$model->setData($data)
->setModuleId($this->getRequest()->getParam('module_id'));
try {
if ($model->getCreatedTime() == NULL || $model->getUpdateTime() == NULL) {
$model->setCreatedTime(now())->setUpdateTime(now());
} else {
$model->setUpdateTime(now());
}
$ModuleEnabled = $this->getRequest()->getParam('module_enabled');
if (is_array($ModuleEnabled))
{
$ModuleEnabledSave = implode(',',$this->getRequest()->getParam('module_enabled'));
}
$model->setModuleEnabled($ModuleEnabledSave);
//save form data/values per field
$model->save();
Mage::getSingleton('adminhtml/session')->addSuccess(Mage::helper('module')->__('Item
was successfully saved'));
Mage::getSingleton('adminhtml/session')->setFormData(false);
if ($this->getRequest()->getParam('back')) {
$this->_redirect('*/*/edit', array('module_id' => $model->getModuleId()));
return;
}
$this->_redirect('*/*/');
return;
} catch (Exception $e) {
Mage::getSingleton('adminhtml/session')->addError($e->getMessage());
Mage::getSingleton('adminhtml/session')->setFormData($data);
$this->_redirect('*/*/edit', array('module_id' => $this->getRequest()->getParam('module_id')));
return;
}
}
Mage::getSingleton('adminhtml/session')->addError(Mage::helper('module')->__('Unable
to find item to save'));
$this->_redirect('*/*/');
}
This saves an imploded array (ie 2, 3 ,6, 23, 28,) into the database
value and renders the selected multiselect fields on the corresponding
tab on refresh/update/save

CakePHP input is the wrong input type. Should be text field but is dropdown :S

I have a added the cakePHP tags functionality to my applications. I have a table called projects that can have many tags associated. So I have a tags table with an id and a tag varchar field. A projects table with a tags varchar field, and a link table projects_tags that has an id, tag_id, and project_id field. When I add the tags input to my view the tags that exist get populated in a dropdown box for some reason. Can anyone figure out what it is I've done wrong here?
Here is my tags model:
<?php
class ProjectTag extends AppModel {
var $name = 'ProjectTag';
var $hasAndBelongsToMany = array('Tag' =>
array('className' => 'Tag',
'joinTable' => 'tags',
'foreignKey' => 'tag_id',
'conditions' => '',
'order' => '',
'limit' => '',
'unique' => true,
'finderQuery' => '',
'deleteQuery' => '',
),
array('className' => 'Project',
'joinTable' => 'projects',
'foreignKey' => 'project_id',
'conditions' => '',
'order' => '',
'limit' => '',
'unique' => true,
'finderQuery' => '',
'deleteQuery' => '',
)
);
}
?>
Here is my add project view add.ctp:
<?php
echo $form->create('Project');
echo $form->input('title', array('label' => 'Title'));
echo $form->input('website', array('label' => 'Website'));
echo $form->input('description', array('label' => 'Description'));
echo $form->input('language_id', array('label' => 'Language'));
echo $form->input('tags');
echo $form->end('Post project');
?>
Here is the tag behavior model:
<?php
class TagBehavior extends ModelBehavior {
/**
* Initiate behaviour for the model using specified settings.
*
* #param object $model Model using the behaviour
* #param array $settings Settings to override for model.
*
* #access public
*/
function setup(&$model, $settings = array()) {
$default = array( 'table_label' => 'tags', 'tag_label' => 'tag', 'separator' => ',');
if (!isset($this->settings[$model->name])) {
$this->settings[$model->name] = $default;
}
$this->settings[$model->name] = array_merge($this->settings[$model->name], ife(is_array($settings), $settings, array()));
}
/**
* Run before a model is saved, used to set up tag for model.
*
* #param object $model Model about to be saved.
*
* #access public
* #since 1.0
*/
function beforeSave(&$model) {
// Define the new tag model
$Tag =& new Tag;
if ($model->hasField($this->settings[$model->name]['table_label'])
&& $Tag->hasField($this->settings[$model->name]['tag_label'])) {
// Parse out all of the
$tag_list = $this->_parseTag($model->data[$model->name][$this->settings[$model->name]['table_label']], $this->settings[$model->name]);
$tag_info = array(); // New tag array to store tag id and names from db
foreach($tag_list as $t) {
if ($res = $Tag->find($this->settings[$model->name]['tag_label'] . " LIKE '" . $t . "'")) {
$tag_info[] = $res['Tag']['id'];
} else {
$Tag->save(array('id'=>'',$this->settings[$model->name]['tag_label']=>$t));
$tag_info[] = sprintf($Tag->getLastInsertID());
}
unset($res);
}
// This prepares the linking table data...
$model->data['Tag']['Tag'] = $tag_info;
// This formats the tags field before save...
$model->data[$model->name][$this->settings[$model->name]['table_label']] = implode(', ', $tag_list);
}
return true;
}
/**
* Parse the tag string and return a properly formatted array
*
* #param string $string String.
* #param array $settings Settings to use (looks for 'separator' and 'length')
*
* #return string Tag for given string.
*
* #access private
*/
function _parseTag($string, $settings) {
$string = strtolower($string);
$string = preg_replace('/[^a-z0-9' . $settings['separator'] . ' ]/i', '', $string);
$string = preg_replace('/' . $settings['separator'] . '[' . $settings['separator'] . ']*/', $settings['separator'], $string);
$string_array = preg_split('/' . $settings['separator'] . '/', $string);
$return_array = array();
foreach($string_array as $t) {
$t = ucwords(trim($t));
if (strlen($t)>0) {
$return_array[] = $t;
}
}
return $return_array;
}
}
?>
Here is my project_controller add:
function add() {
$this->set("Languages", $this->Project->Language->find("list"));
if (!empty($this->data)) {
if ($this->Project->save($this->data)) {
$this->Session->setFlash('Project added.');
$this->redirect(array('action' => 'index'));
}
}
}
cdburgess is right about the reason the dropdown is appearing, but you can override that reasonable default to meet your own needs by tweaking the Cake "magic" field:
echo $form->input('tags', array( 'type' => 'text' ) );
I won't swear by the syntax, but this will create the tags input as a textbox.
IMO, this is a much better option than dropping an association that has so much power behind it in other places.
It's because of the relationship in the model. CakePHP will automatically associate the tags and create a drop down for you. That is the purpose of having an associated field. It is basically saying, "This tag needs to be something that exists in this 'tag' table." Therefore it pre-populates it with the data from tags.
Take out the HABTM relationship and it will go back to a test box.

Categories