I'm trying to implement a category attribute meant to link the current categories with other categories for a project specific usage.
For that I created a varchar attribute meant to store a coma separated list of category IDs, but I'd like to have the small picker icon next to the field which would display the category chooser, like the one in the condition section of the promotion admin screen.
I don't really see the kind of renderer I should implement to achieve that, I hope you guys will give me a hint.
Thanks for helping.
I made it - finally - so here it goes:
FYI: My category attribute is named "category_top_searches" and is a coma separated list of category IDs. The picker is meant to help contributing this list.
1 - Add a tab using an observer
A . Observer declaration
<adminhtml_catalog_category_tabs>
<observers>
<add_topsearches_tab>
<class>mymodule/observer</class>
<method>addTopSearchesTab</method>
</add_topsearches_tab>
</observers>
</adminhtml_catalog_category_tabs>
B . Observer implementation
public function addTopSearchesTab(Varien_Event_Observer $observer)
{
$tabs = $observer->getTabs();
$tabs->addTab(
'top_searches_tab', 'mymodule/catalog_category_edit_tab_topsearches'
);
}
2 - Define the block for the Tab
Need to implement the tab interface methods and the methods to fetch checked categories when initializing the tree
class MyNamespace_Adminhtml_Block_Catalog_Category_Edit_Tab_Topsearches
extends Mage_Adminhtml_Block_Catalog_Product_Edit_Tab_Categories
implements Mage_Adminhtml_Block_Widget_Tab_Interface
{
/**
* Specify template to use
*/
public function __construct()
{
parent::__construct();
$this->setTemplate('catalog/category/edit/categorypicker.phtml');
}
/**
* Checks when this block is readonly
*
* #return bool
*/
public function isReadonly()
{
return false;
}
/**
* getCategoryIds method
* #return array
*/
protected function getCategoryIds()
{
$category = Mage::registry('current_category');
$ids = explode(',', $category->getCategoryTopSearches());
return $ids;
}
/**
* getIdsString
*
* #return string
*/
public function getIdsString()
{
$category = Mage::registry('current_category');
return $category->getCategoryTopSearches();
}
/**
* Return Tab label
*
* #return string
*/
public function getTabLabel()
{
return $this->__('Top Searches');
}
/**
* Return Tab title
*
* #return string
*/
public function getTabTitle()
{
return $this->__('Top Searches');
}
/**
* Can show tab in tabs
*
* #return boolean
*/
public function canShowTab()
{
return true;
}
/**
* Tab is hidden
*
* #return boolean
*/
public function isHidden()
{
return false;
}
}
3 - Define the tree template
I copy pasted the core template of the category tree of product edit page app/design/adminhtml/default/default/template/catalog/product/edit/categories.phtml into app/design/adminhtml/default/mytheme/template/catalog/category/edit/categorypicker.phtml and made some slight changes :
Added a text field to display my IDs:
<div class="entry-edit">
<div class="entry-edit-head">
<h4 class="icon-head head-edit-form fieldset-legend"><?php echo Mage::helper('catalog')->__('Linked Categories') ?></h4>
</div>
<fieldset id="linked_cat">
<input type="text" name="linked_categories_list" id="linked_categories_list" value="<?php echo $this->getIdsString() ?>">
</fieldset>
</div>
And modified the tree header to be more adequate with my context
<div class="entry-edit">
<div class="entry-edit-head">
<h4 class="icon-head head-edit-form fieldset-legend"><?php echo Mage::helper('catalog')->__('Category picker') ?></h4>
</div>
<fieldset id="grop_fields">
<input type="hidden" name="linked_categories" id="linked_categories" value="<?php echo $this->getIdsString() ?>">
<div id="product-categories" class="tree"></div>
</fieldset>
</div>
I had to modify slightly the beforeLoad function to set the id param:
categoryLoader.on("beforeload", function(treeLoader, node) {
treeLoader.baseParams.category = node.attributes.id;
treeLoader.baseParams.id = node.attributes.id;
});
I added a function to implement a array_unique feature
Array.prototype.unique = function(){
'use strict';
var im = {}, uniq = [];
for (var i=0;i<this.length;i++){
var type = (this[i]).constructor.name,
// ^note: for IE use this[i].constructor!
val = type + (!/num|str|regex|bool/i.test(type)
? JSON.stringify(this[i])
: this[i]);
if (!(val in im)){uniq.push(this[i]);}
im[val] = 1;
}
return uniq;
}
And then modified the categoryAdd and categoryRemove functions to update my text field with the newly checked/unchecked ID
function categoryAdd(id) {
var ids = $('linked_categories').value.split(',');
ids.push(id);
var idList = ids.unique().join(',').replace(/^,/, '');
document.getElementById("linked_categories_list").value = idList;
$('linked_categories').value = ids.join(',');
}
function categoryRemove(id) {
var ids = $('linked_categories').value.split(',');
// bug #7654 fixed
while (-1 != ids.indexOf(id)) {
ids.splice(ids.indexOf(id), 1);
}
var idList = ids.unique().join(',').replace(/^,/, '');
document.getElementById("linked_categories_list").value = idList;
$('linked_categories').value = ids.join(',');
}
4 - Implement an observer to the category save event to inject the new value into the real attribute
A. Declaration
<catalog_category_save_before>
<observers>
<set_top_searches>
<class>mymodule/observer</class>
<method>setTopSearches</method>
</set_top_searches>
</observers>
</catalog_category_save_before>
B. Implementation
/**
* takes the fake attribute value and insert it into the real category_top_searches attribute
*
* #param Varien_Event_Observer $observer Observer
*
* #return void
*/
public function setTopSearches(Varien_Event_Observer $observer)
{
/** #var $category Mage_Catalog_Model_Category */
$category = $observer->getEvent()->getCategory();
$params = Mage::app()->getRequest()->getParams();
$ids = $params['linked_categories_list'];
$category->setData('category_top_searches', $ids);
}
5 - Finally I hid my original attribute by updating it in a setup script:
(I changed its group otherwise its dedicated tab would be empty but still displayed)
$installer->updateAttribute("catalog_category", "category_top_searches", 'group', "General");
$installer->updateAttribute("catalog_category", "category_top_searches", 'is_visible', false);
Dunno if anybody will use it since I'm pretty alone on this thread but I would had loved to find such a post when trying to achieve this feature... :D
If anyone is interested, I also implemented this category picker in configuration fields as a new frontend_type, it's pretty great !
Related
I created a module that displays custom fields in shipping at checkout and saves the data in the database in the both the quote and sales order table and I can confirm the data is there. I am trying to get the data to show in the admin when the orders are viewed and in the confirmation email that goes out. I have it so the template displays the echoed text in the admin order view but not the data from the database. Nothing shows currently in the email but I want to fix viewing the data in the order first.
To break it down, I have the data in the two tables in the DB and need to display it on the orders in the admin. I have the fields in extension_attributes.xml
The fields get saved to the DB here in Plugin > Quote > SaveToQuote.php:
class SaveToQuote
{
/**
* #var QuoteRepository
*/
protected $quoteRepository;
/**
* SaveToQuote constructor.
* #param QuoteRepository $quoteRepository
*/
public function __construct(
QuoteRepository $quoteRepository
) {
$this->quoteRepository = $quoteRepository;
}
/**
* #param \Magento\Checkout\Model\ShippingInformationManagement $subject
* #param $cartId
* #param \Magento\Checkout\Api\Data\ShippingInformationInterface $addressInformation
*/
public function beforeSaveAddressInformation(
\Magento\Checkout\Model\ShippingInformationManagement $subject,
$cartId,
\Magento\Checkout\Api\Data\ShippingInformationInterface $addressInformation
) {
if(!$extAttributes = $addressInformation->getExtensionAttributes())
return;
$quote = $this->quoteRepository->getActive($cartId);
$quote->setInputRoomShippingField($extAttributes->getInputRoomShippingField());
$quote->setInputFloorShippingField($extAttributes->getInputFloorShippingField());
$quote->setDateCustomShippingField($extAttributes->getDateCustomShippingField());
$quote->setSelectCustomShippingField($extAttributes->getSelectCustomShippingField());
}
}
But here is what I have come up with to view the data in the order in the admin so far with no luck.
First - view>adminhtml>layout>sales_order_view.xml
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" layout="admin-2columns-left" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
<body>
<referenceBlock name="order_info">
<block class="xxx\xxx\Block\Adminhtml\Order\View\OrderView" name="sales_order_view_custom" template="xxx_xxx::shipping_info.phtml" />
</referenceBlock>
</body>
Next in view>adminhtml>templates>shipping_info.phtml
<strong><?php echo __('Room Number') ?></strong>
<span class="room"><?=$block->getInputRoomShippingField(); ?></span>
<strong><?php echo __('Floor Number') ?></strong>
<span class="floor"><?=$block->getInputFloorShippingField(); ?></span>
<strong><?php echo __('Address') ?></strong>
<span class="address"><?=$block->getSelectCustomShippingField(); ?></span
I can see the echoed text above in the admin > order view but not the values called by $block.
Next - Block>Adminhtml>Order>View>OrderView.php
namespace xxx\xxx\Block\Adminhtml\Order\View;
class OrderView extends \Magento\Backend\Block\Template
{
}
I think I need the above so I can call $block
Next - Observer>ShippingDataToAdminObserver.php
namespace xxx\xxx\Observer;
use Magento\Framework\Event\Observer as EventObserver;
use Magento\Framework\Event\ObserverInterface;
class ShippingDataToAdminObserver implements ObserverInterface
{
/**
* #var \Magento\Framework\ObjectManagerInterface
*/
protected $_block;
/**
* #param \Magento\Framework\View\Element\Template $block
*/
public function __construct(
\Magento\Framework\View\Element\Template $block
)
{
$this->_block = $block;
}
/**
* #param EventObserver $observer
*/
public function execute(EventObserver $observer)
{
if($observer->getElementName() == 'order_shipping_view')
{
$orderShippingViewBlock = $observer->getLayout()->getBlock($observer->getElementName());
$order = $orderShippingViewBlock->getOrder();
$RoomDeliveryBlock = $this->_block;
$RoomDeliveryBlock->setTemplate('xxx_xxx::shipping_info.phtml');
$html = $observer->getTransport()->getOutput() . $RoomDeliveryBlock->toHtml();
$observer->getTransport()->setOutput($html);
}
}
}
Used the above to get data to display in admin > sales > order and uses OrderView.php above. I see the text but not the data.
Like I said, I am trying to get the data in the database (which is present) to show on the orders in the admin and eventually emails.
I don't know if this is too confusing to get help or not. Any thoughts are greatly appreciated.
I'm building the admin for a Magento2 store (currently on 2.1.7, they want to use the newest version until we go live and then want to stabilize a particular version). The module in question is supposed to display all existing orders, with an actionsColumn that contains links to cancel, edit, and open a detailed overview of the purchased items associated with that order. The order detail page contains a grid view that should display all order items associated with an order number passed in the URL.
In order to filter out Order Items that don't relate to the specific Order Number, I've extended the \Magento\Framework\View\Element\UiComponent\DataProvider\SearchResult class. This works except for one weird caveat. If, in the addFieldToFilter call, I replace $ordNum with, say, '10000', it grabs the correct data. When using $ordNum to call this dynamically, however, it returns no rows at all. This despite trying all sorts of casting and === checks to ensure that there's no difference between the hardcoded and dynamic values. Is this a Magento bug? I can't at all figure out why this would be the case.
<?php
class OrderItems extends \Magento\Framework\View\Element\UiComponent\DataProvider\SearchResult
{
protected function _initSelect()
{
$this->filterByOrderNum();
parent::_initSelect();
return $this;
}
private function filterByOrderNum()
{
$request = \Magento\Framework\App\ObjectManager::getInstance()
->get('Magento\Framework\App\Request\Http');
$ordNum = $request->getParam('order_num');
$this->addFieldToFilter('order_num', ['eq' => $ordNum]); //if I switch this to hardcoded 10000, this works. With the variable, no dice.
return $this;
}
}
I just fixed it by using mentioned below steps
Store param value in session in controller
public function execute() {
$this->_catalogSession->setTokenId($this->request->getParam('entity_id'));
$this->_view->loadLayout();
$this->_view->loadLayoutUpdates();
$this->_view->getPage()->getConfig()->getTitle()->set(__('Redeem Token History'));
$this->_view->renderLayout();
}
Use session value in dataprovider
$tokensCollection->addFieldToFilter('token_id', ['eq' => $this->_catalogSession->getTokenId()]);
Enjoy :)
Try this in place of the getParam statement:
$url = parse_url($request);
$path = explode('/',$url['path']);
$ordNum = $path[3];
Just to make sure we are on the same page, this is the full code:
<?php
class OrderItems extends \Magento\Framework\View\Element\UiComponent\DataProvider\SearchResult
{
protected function _initSelect()
{
$this->filterByOrderNum();
parent::_initSelect();
return $this;
}
private function filterByOrderNum()
{
$request = \Magento\Framework\App\ObjectManager::getInstance()
->get('Magento\Framework\App\Request\Http');
$url = parse_url($request);
$path = explode('/',$url['path']);
$ordNum = $path[3];
$this->addFieldToFilter('order_num', $ordNum); //if I switch this to hardcoded 10000, this works. With the variable, no dice.
return $this;
}
}
We have solved this issue by doing the following :
/**
* CcCustompriceProductListingDataProvider constructor.
* #param string $name
* #param string $primaryFieldName
* #param string $requestFieldName
* #param \Magento\Framework\Api\Search\ReportingInterface $reporting
* #param \Magento\Framework\Api\Search\SearchCriteriaBuilder $searchCriteriaBuilder
* #param \Magento\Framework\App\RequestInterface $request
* #param \Magento\Framework\Api\FilterBuilder $filterBuilder
* #param array $meta
* #param array $data
* #throws \Exception
*/
public function __construct(
$name,
$primaryFieldName,
$requestFieldName,
ReportingInterface $reporting,
SearchCriteriaBuilder $searchCriteriaBuilder,
RequestInterface $request,
FilterBuilder $filterBuilder,
array $meta = [],
array $data = []
) {
$data['config']['filter_url_params']['product_id'] = $request->getParam('cppc_product_id', 0);
parent::__construct($name, $primaryFieldName, $requestFieldName, $reporting, $searchCriteriaBuilder, $request, $filterBuilder, $meta, $data);
}
You do not need to use any other function. The reason why this is is because it is also updated with an update URL and that does not have that parameter. By using adding that to the data it also parses that into the update url.
You can see that here (Parent function)
/**
* #return void
*/
protected function prepareUpdateUrl()
{
if (!isset($this->data['config']['filter_url_params'])) {
return;
}
foreach ($this->data['config']['filter_url_params'] as $paramName => $paramValue) {
if ('*' == $paramValue) {
$paramValue = $this->request->getParam($paramName);
}
if ($paramValue) {
$this->data['config']['update_url'] = sprintf(
'%s%s/%s/',
$this->data['config']['update_url'],
$paramName,
$paramValue
);
$this->addFilter(
$this->filterBuilder->setField($paramName)->setValue($paramValue)->setConditionType('eq')->create()
);
}
}
}
By using Magento 2.1.3 after importing tier price cvs file, I can't edit products in backend anymore.The error in backend is
Warning: Illegal string offset 'price_qty' in vendor/magento/module-catalog/Model/Product/Attribute/Backend/Tierprice.php on line 74
In the report, I found 25 reports, the newest is
#1/vendor/magento/modulecatalog/Controller/Adminhtml/Product/Attribute/Validate.php(108): Magento\Catalog\Controller\Adminhtml\Product\Attribute\Validate->checkUniqueOption(Object(Magento\Framew
ork\DataObject), Array).
Can someone help me to resolve this problem pls. Thank you so much.
Tierprice.php
namespace Magento\Catalog\Model\Product\Attribute\Backend;
class Tierprice extends \Magento\Catalog\Model\Product\Attribute\Backend\GroupPrice\AbstractGroupPrice
{
protected $_productAttributeBackendTierprice;
/**
* #param \Magento\Directory\Model\CurrencyFactory $currencyFactory
* #param \Magento\Store\Model\StoreManagerInterface $storeManager
* #param \Magento\Catalog\Helper\Data $catalogData
* #param \Magento\Framework\App\Config\ScopeConfigInterface $config
* #param \Magento\Framework\Locale\FormatInterface $localeFormat
* #param \Magento\Catalog\Model\Product\Type $catalogProductType
* #param \Magento\Customer\Api\GroupManagementInterface $groupManagement
* #param \Magento\Catalog\Model\ResourceModel\Product\Attribute\Backend\Tierprice $productAttributeTierprice
*/
public function __construct(
\Magento\Directory\Model\CurrencyFactory $currencyFactory,
\Magento\Store\Model\StoreManagerInterface $storeManager,
\Magento\Catalog\Helper\Data $catalogData,
\Magento\Framework\App\Config\ScopeConfigInterface $config,
\Magento\Framework\Locale\FormatInterface $localeFormat,
\Magento\Catalog\Model\Product\Type $catalogProductType,
\Magento\Customer\Api\GroupManagementInterface $groupManagement,
\Magento\Catalog\Model\ResourceModel\Product\Attribute\Backend\Tierprice $productAttributeTierprice
) {
$this->_productAttributeBackendTierprice = $productAttributeTierprice;
parent::__construct(
$currencyFactory,
$storeManager,
$catalogData,
$config,
$localeFormat,
$catalogProductType,
$groupManagement
);
}
protected function _getResource()
{
return $this->_productAttributeBackendTierprice;
}
protected function _getAdditionalUniqueFields($objectArray)
{
$uniqueFields = parent::_getAdditionalUniqueFields($objectArray);
$uniqueFields['qty'] = $objectArray['price_qty'] * 1;
return $uniqueFields;
}
protected function _getDuplicateErrorMessage()
{
return __('We found a duplicate website, tier price, customer group and quantity.');
}
protected function _isPriceFixed($priceObject)
{
return $priceObject->isTierPriceFixed();
}
public function isScalar()
{
return false;
}
}
I was also having the same issue with tier pricing while product save. I just checked if $objectArray['price_qty'] isset before using it and that resolved my issue temporarily. However, its a temporary fix but works for now
Just replace below function
protected function _getAdditionalUniqueFields($objectArray)
{
$uniqueFields = parent::_getAdditionalUniqueFields($objectArray);
$uniqueFields['qty'] = $objectArray['price_qty'] * 1;
return $uniqueFields;
}
with
protected function _getAdditionalUniqueFields($objectArray)
{
$uniqueFields = parent::_getAdditionalUniqueFields($objectArray);
if(isset($objectArray['price_qty'])){
$uniqueFields['qty'] = $objectArray['price_qty'] * 1;
}
return $uniqueFields;
}
I had the same issue in 2.2.5 EE
after following these steps I managed to get this fixed.
https://github.com/magento/magento2/issues/8426
Backup your database (optional)
Locate your "tier_price" attribute ID (Stores -> Attributes -> Product -> Search for "tier_price") or from eav_attribute table
Search catalog_product_entity_decimal, catalog_product_entity_int, catalog_product_entity_text, and catalog_product_entity_varchar for any records with the attribute_id of your tier_price attribute.
Delete these rows
Try saving product again.
Is it possible to create custom HTML attributes on CHtml::checkboxList?
For example, I want to generate an input like this, adding the custom attribute "data-input-x":
<input class="customClass" id="Model_inputX_0" value="1" name="Model[relationX][]" type="checkbox" data-input-x="3">
I already tried using the code bellow, but it not worked:
echo $form->checkboxList($model, 'relationX', $dataList, array('class'=>'checkboxFase refeicaoFaseComum', 'data-input-x'=>3));
If you run your code and inspect element it you will see values the values created by Yii, the difference. Echos under a foreach loop will work nicely..
You can extend CHtml like that:
In folder "components" you create a new file named MyCHtml. In there create the class MyCHtml and copy the core code of framework for checkBoxList (https://github.com/yiisoft/yii/blob/1.1.16/framework/web/helpers/CHtml.php#L1123).
class MyCHtml extends CHtml {
//Final method is provided below
}
Then you add the parameter $extraAttributes=array() after $htmlOptions=array().
The trick is to add those attributes and their values at $htmlOptions array of each input.
If all your configurations are correct and you have access to your componenets as normal, you can call the new checkBoxList function like this:
<?php
//Values can be created dynamically or statically depending on situation
//Each value corresponds to each checkbox value that you want to contain the extra attribute
$extraAttributes = array(
'data-input-x'=>array(
6=>'k',
11=>'a',
7=>'b'),
'data-input-y'=>array(
6=>'c',
2=>'d'),
);
echo MyCHtml::checkboxList(($name, $select, $data, $htmlOptions, $extraAttributes);
?>
The whole class is the following:
<?php
class MyCHtml extends CHtml
{
/**
* Generates a list box.
* ...
* #param array $extraAttributes extra HTML attributes corresponding on each checkbox
* ...
*/
public static function checkBoxList($name,$select,$data,$htmlOptions=array(), $extraAttributes=array())
{
$template=isset($htmlOptions['template'])?$htmlOptions['template']:'{input} {label}';
$separator=isset($htmlOptions['separator'])?$htmlOptions['separator']:self::tag('br');
$container=isset($htmlOptions['container'])?$htmlOptions['container']:'span';
unset($htmlOptions['template'],$htmlOptions['separator'],$htmlOptions['container']);
if(substr($name,-2)!=='[]')
$name.='[]';
if(isset($htmlOptions['checkAll']))
{
$checkAllLabel=$htmlOptions['checkAll'];
$checkAllLast=isset($htmlOptions['checkAllLast']) && $htmlOptions['checkAllLast'];
}
unset($htmlOptions['checkAll'],$htmlOptions['checkAllLast']);
$labelOptions=isset($htmlOptions['labelOptions'])?$htmlOptions['labelOptions']:array();
unset($htmlOptions['labelOptions']);
$items=array();
$baseID=isset($htmlOptions['baseID']) ? $htmlOptions['baseID'] : self::getIdByName($name);
unset($htmlOptions['baseID']);
$id=0;
$checkAll=true;
foreach($data as $value=>$labelTitle)
{
$checked=!is_array($select) && !strcmp($value,$select) || is_array($select) && in_array($value,$select);
$checkAll=$checkAll && $checked;
$htmlOptions['value']=$value;
$htmlOptions['id']=$baseID.'_'.$id++;
//********This does the trick
foreach($extraAttributes as $attributesKey => $attributesValue) {
$found = false;
foreach($attributesValue as $subAttributesKey => $subAttributesValue) {
if ($value === $subAttributesKey) {
$htmlOptions[$attributesKey] = $subAttributesValue;
$found = true;
break;
}
}
if (!$found) {
$htmlOptions[$attributesKey] = '';
}
}
//********All the rest is the same with core method
$option=self::checkBox($name,$checked,$htmlOptions);
$beginLabel=self::openTag('label',$labelOptions);
$label=self::label($labelTitle,$htmlOptions['id'],$labelOptions);
$endLabel=self::closeTag('label');
$items[]=strtr($template,array(
'{input}'=>$option,
'{beginLabel}'=>$beginLabel,
'{label}'=>$label,
'{labelTitle}'=>$labelTitle,
'{endLabel}'=>$endLabel,
));
}
if(isset($checkAllLabel))
{
$htmlOptions['value']=1;
$htmlOptions['id']=$id=$baseID.'_all';
$option=self::checkBox($id,$checkAll,$htmlOptions);
$beginLabel=self::openTag('label',$labelOptions);
$label=self::label($checkAllLabel,$id,$labelOptions);
$endLabel=self::closeTag('label');
$item=strtr($template,array(
'{input}'=>$option,
'{beginLabel}'=>$beginLabel,
'{label}'=>$label,
'{labelTitle}'=>$checkAllLabel,
'{endLabel}'=>$endLabel,
));
if($checkAllLast)
$items[]=$item;
else
array_unshift($items,$item);
$name=strtr($name,array('['=>'\\[',']'=>'\\]'));
$js=<<<EOD
jQuery('#$id').click(function() {
jQuery("input[name='$name']").prop('checked', this.checked);
});
jQuery("input[name='$name']").click(function() {
jQuery('#$id').prop('checked', !jQuery("input[name='$name']:not(:checked)").length);
});
jQuery('#$id').prop('checked', !jQuery("input[name='$name']:not(:checked)").length);
EOD;
$cs=Yii::app()->getClientScript();
$cs->registerCoreScript('jquery');
$cs->registerScript($id,$js);
}
if(empty($container))
return implode($separator,$items);
else
return self::tag($container,array('id'=>$baseID),implode($separator,$items));
}
public static function activeCheckBoxList($model,$attribute,$data,$htmlOptions=array())
{
self::resolveNameID($model,$attribute,$htmlOptions);
$selection=self::resolveValue($model,$attribute);
if($model->hasErrors($attribute))
self::addErrorCss($htmlOptions);
$name=$htmlOptions['name'];
unset($htmlOptions['name']);
if(array_key_exists('uncheckValue',$htmlOptions))
{
$uncheck=$htmlOptions['uncheckValue'];
unset($htmlOptions['uncheckValue']);
}
else
$uncheck='';
$hiddenOptions=isset($htmlOptions['id']) ? array('id'=>self::ID_PREFIX.$htmlOptions['id']) : array('id'=>false);
$hidden=$uncheck!==null ? self::hiddenField($name,$uncheck,$hiddenOptions) : '';
return $hidden . self::checkBoxList($name,$selection,$data,$htmlOptions);
}
/**
* Generates a push Html button that can submit the current form in POST method.
* #param string $label the button label
* #param mixed $url the URL for the AJAX request. If empty, it is assumed to be the current URL. See {#link normalizeUrl} for more details.
* #param array $ajaxOptions AJAX options (see {#link ajax})
* #param array $htmlOptions additional HTML attributes. Besides normal HTML attributes, a few special
* attributes are also recognized (see {#link clientChange} and {#link tag} for more details.)
* #return string the generated button
*/
public static function ajaxSubmitHtmlButton($label,$url,$ajaxOptions=array(),$htmlOptions=array())
{
$ajaxOptions['type']='POST';
$htmlOptions['type']='submit';
return self::ajaxHtmlButton($label,$url,$ajaxOptions,$htmlOptions);
}
}
I've been trying to create a form in the article component backend, so that I can add few things attributes per article. I got this sample code from the joomla website documentation. http://docs.joomla.org/Adding_custom_fields_to_the_article_component
I understand the method onContentPrepareForm is an important function which adds this form in the joomla article backend. However, it doesn't appear for me. I somehow checked global other options and I got to see that the form comes up in the global article options as a seperate tab. However I'd like to add this form for each article.
The version of joomla I am using is 3.2 ...
Btw I have enabled the plugin in the backend ...
<?php
/**
* #package Joomla.Site
* #subpackage plg_content_rating
* #copyright Copyright (C) 2005 - 2012 Open Source Matters, Inc. All rights reserved.
* #license GNU General Public License version 2 or later; see LICENSE.txt
*/
defined('JPATH_BASE') or die;
jimport('joomla.utilities.date');
/**
* An example custom profile plugin.
*
* #package Joomla.Plugin
* #subpackage User.profile
* #version 1.6
*/
class plgContentRating extends JPlugin
{
/**
* Constructor
*
* #access protected
* #param object $subject The object to observe
* #param array $config An array that holds the plugin configuration
* #since 2.5
*/
public function __construct(& $subject, $config)
{
parent::__construct($subject, $config);
$this->loadLanguage();
}
/**
* #param string $context The context for the data
* #param int $data The user id
* #param object
*
* #return boolean
* #since 2.5
*/
function onContentPrepareData($context, $data)
{
if (is_object($data))
{
$articleId = isset($data->id) ? $data->id : 0;
if (!isset($data->rating) and $articleId > 0)
{
// Load the profile data from the database.
$db = JFactory::getDbo();
$query = $db->getQuery(true);
$query->select('profile_key, profile_value');
$query->from('#__user_profiles');
$query->where('user_id = ' . $db->Quote($articleId));
$query->where('profile_key LIKE ' . $db->Quote('rating.%'));
$query->order('ordering');
$db->setQuery($query);
$results = $db->loadRowList();
// Check for a database error.
if ($db->getErrorNum())
{
$this->_subject->setError($db->getErrorMsg());
return false;
}
// Merge the profile data.
$data->rating = array();
foreach ($results as $v)
{
$k = str_replace('rating.', '', $v[0]);
$data->rating[$k] = json_decode($v[1], true);
if ($data->rating[$k] === null)
{
$data->rating[$k] = $v[1];
}
}
}
}
return true;
}
/**
* #param JForm $form The form to be altered.
* #param array $data The associated data for the form.
*
* #return boolean
* #since 2.5
*/
function onContentPrepareForm($form, $data)
{
if (!($form instanceof JForm))
{
$this->_subject->setError('JERROR_NOT_A_FORM');
return false;
}
/* if (!in_array($form->getName(), array('com_content.article'))) {
return true;
}*/
// Add the extra fields to the form.
// need a seperate directory for the installer not to consider the XML a package when "discovering"
JForm::addFormPath(dirname(__FILE__) . '/rating');
$form->loadFile('rating',false);
return true;
}
/**
* Example after save content method
* Article is passed by reference, but after the save, so no changes will be saved.
* Method is called right after the content is saved
*
* #param string The context of the content passed to the plugin (added in 1.6)
* #param object A JTableContent object
* #param bool If the content is just about to be created
* #since 2.5
*/
public function onContentAfterSave($context, &$article, $isNew)
{
$articleId = $article->id;
if ($articleId && isset($article->rating) && (count($article->rating)))
{
try
{
$db = JFactory::getDbo();
$query = $db->getQuery(true);
$query->delete('#__user_profiles');
$query->where('user_id = ' . $db->Quote($articleId));
$query->where('profile_key LIKE ' . $db->Quote('rating.%'));
$db->setQuery($query);
if (!$db->query()) {
throw new Exception($db->getErrorMsg());
}
$query->clear();
$query->insert('#__user_profiles');
$order = 1;
foreach ($article->rating as $k => $v)
{
$query->values($articleId.', '.$db->quote('rating.'.$k).', '.$db->quote(json_encode($v)).', '.$order++);
}
$db->setQuery($query);
if (!$db->query()) {
throw new Exception($db->getErrorMsg());
}
}
catch (JException $e)
{
$this->_subject->setError($e->getMessage());
return false;
}
}
return true;
}
/**
* Finder after delete content method
* Article is passed by reference, but after the save, so no changes will be saved.
* Method is called right after the content is saved
*
* #param string The context of the content passed to the plugin (added in 1.6)
* #param object A JTableContent object
* #since 2.5
*/
public function onContentAfterDelete($context, $article)
{
$articleId = $article->id;
if ($articleId)
{
try
{
$db = JFactory::getDbo();
$query = $db->getQuery(true);
$query->delete();
$query->from('#__user_profiles');
$query->where('user_id = ' . $db->Quote($articleId));
$query->where('profile_key LIKE ' . $db->Quote('rating.%'));
$db->setQuery($query);
if (!$db->query())
{
throw new Exception($db->getErrorMsg());
}
}
catch (JException $e)
{
$this->_subject->setError($e->getMessage());
return false;
}
}
return true;
}
public function onContentPrepare($context, &$article, &$params, $page = 0)
{
if (!isset($article->rating) || !count($article->rating))
return;
// add extra css for table
$doc = JFactory::getDocument();
$doc->addStyleSheet(JURI::base(true).'/plugins/content/rating/rating/rating.css');
// construct a result table on the fly
jimport('joomla.html.grid');
$table = new JGrid();
// Create columns
$table->addColumn('attr')
->addColumn('value');
// populate
$rownr = 0;
foreach ($article->rating as $attr => $value) {
$table->addRow(array('class' => 'row'.($rownr % 2)));
$table->setRowCell('attr', $attr);
$table->setRowCell('value', $value);
$rownr++;
}
// wrap table in a classed <div>
$suffix = $this->params->get('ratingclass_sfx', 'rating');
$html = '<div class="'.$suffix.'">'.(string)$table.'</div>';
$article->text = $html.$article->text;
}
}
EDIT POST:
I added this code to /administrator/components/com_content/views/article/tmpl/edit.php
<?php
$i = 0;
$fieldSets = $this->form->getFieldsets();
foreach ($fieldSets as $name => $fieldSet) :
if($i <= 3){
$i++;
continue;
}
echo JHtml::_('bootstrap.addTab', 'myTab', $fieldSet->name, JText::_($fieldSet->label, true));
?>
<div class="tab-pane" id="<?php echo $name;?>">
<?php
if (isset($fieldSet->description) && !empty($fieldSet->description)) :
echo '<p class="tab-description">'.JText::_($fieldSet->description).'</p>';
endif;
foreach ($this->form->getFieldset($name) as $field):
?>
<div class="control-group">
<?php if (!$field->hidden && $name != "permissions") : ?>
<div class="control-label">
<?php echo $field->label; ?>
</div>
<?php endif; ?>
<div class="<?php if ($name != "permissions") : ?>controls<?php endif; ?>">
<?php echo $field->input; ?>
</div>
</div>
<?php
endforeach;
?>
</div>
<?php echo JHtml::_('bootstrap.endTab'); ?>
<?php endforeach; ?>
As you've mentioned the core tmpl file won't support your changes.
Hacking the core files (as you've done) is a very bad idea for several reason, including that they could be overwritten when the next 3.2.x security patch is released (like the upcoming 3.2.1) and most certainly will be when 3.5 is released. Given the rapidity Joomla releases security patches and their regular 1-click Major and Minor update cycle the last thing you want to be worrying about is your changes being blown away.
Luckily Joomla lets you create template overrides, the process for admin is pretty much the same as overrides for front-end templates.
Luckily for you the modification is in the right file, assuming you're using the default Admin template Isis simply copy
/administrator/components/com_content/views/article/tmpl/edit.php
to:
/administrator/templates/isis/html/com_content/article/edit.php
You may want to revert your original file but as mentioned it's almost certainly to get updated in either a security update or version update.
found a solution to your problem (and mine) in another article here solution to your problem hope it helps. It appears that the example plugin contains an error