Magento - Hiding Categories with Disabled Items - php

I've recently included Prattski's Hide Empty Categories module to Magento, however; The extension removes categories that have no products available, which is great. What it doesn't do is hide categories for products disabled. Therefore, the categories are still visible when all items in a particular category are disabled.
Below is the Observer.php code from the extension:
<?php
/**
* Hide Empty Categories
*
* #category Prattski
* #package Prattski_HideEmptyCategories
* #copyright Copyright (c) 2011 Prattski (http://prattski.com/)
* #author Josh Pratt (Prattski)
*/
/**
* Event Observer
*
* #category Prattski
* #package Prattski_HideEmptyCategories
*/
class Prattski_HideEmptyCategories_Model_Observer extends Mage_Core_Model_Abstract
{
/**
* Remove hidden caegories from the collection
*
* #param Varien_Event_Observer $observer
*/
public function catalogCategoryCollectionLoadAfter($observer)
{
if ($this->_isApiRequest()) return;
$collection = $observer->getEvent()->getCategoryCollection();
$this->_removeHiddenCollectionItems($collection);
}
/**
* Remove hidden items from a product or category collection
*
* #param Mage_Eav_Model_Entity_Collection_Abstract|Mage_Core_Model_Mysql4_Collection_Abstract $collection
*/
public function _removeHiddenCollectionItems($collection)
{
// Loop through each category or product
foreach ($collection as $key => $item)
{
// If it is a category
if ($item->getEntityTypeId() == 3) {
$products = Mage::getModel('catalog/category')->load($item->getId())
->getProductCollection()
->addAttributeToSelect('entity_id')
->addAttributeToFilter('status',Mage_Catalog_Model_Product_Status::STATUS_ENABLED)
->addAttributeToFilter('visibility',Mage_Catalog_Model_Product_Visibility::VISIBILITY_BOTH);
$prodcount = $products->count();
if ($item->prodcount) {
$collection->removeItemByKey($key);
}
}
}
}
/**
* Return true if the reqest is made via the api
*
* #return boolean
*/
protected function _isApiRequest()
{
return Mage::app()->getRequest()->getModuleName() === 'api';
}
}
Any ideas on how to disable the categories with disabled products?

Related

PHPIDS on php 8.1 Deprecated Errors [duplicate]

This question already has answers here:
Reference: Return type of ... should either be compatible with ..., or the #[\ReturnTypeWillChange] attribute should be used
(2 answers)
Closed last year.
i got a problem with PHPIDS on a PHP 8.1 Server.
Here the Errors:
PHP Deprecated: Return type of IDS\Report::count() should either be compatible with Countable::count(): int, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in phpids\ib\IDS\Report.php on line 205
PHP Deprecated: Return type of IDS\Report::getIterator() should either be compatible with IteratorAggregate::getIterator(): Traversable, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in phpids\lib\IDS\Report.php on line 218
I know Deprecated Errors are not that bad, but i want that everything works Perfect.
I Searched in Google several things but no solution found.
Hopefully you find the problem.
i use the actual PHPIDS version from Github (https://github.com/PHPIDS/PHPIDS)
i know this version is not the newest but i didn't found a newer one.
thx for the help
and here the Script Report.php
<?php
/**
* PHPIDS
*
* Requirements: PHP5, SimpleXML
*
* Copyright (c) 2008 PHPIDS group (https://phpids.org)
*
* PHPIDS is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3 of the License, or
* (at your option) any later version.
*
* PHPIDS is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with PHPIDS. If not, see <http://www.gnu.org/licenses/>.
*
* PHP version 5.1.6+
*
* #category Security
* #package PHPIDS
* #author Mario Heiderich <mario.heiderich#gmail.com>
* #author Christian Matthies <ch0012#gmail.com>
* #author Lars Strojny <lars#strojny.net>
* #license http://www.gnu.org/licenses/lgpl.html LGPL
* #link http://php-ids.org/
*/
namespace IDS;
/**
* PHPIDS report object
*
* The report objects collects a number of events and thereby presents the
* detected results. It provides a convenient API to work with the results.
*
* Note that this class implements Countable, IteratorAggregate and
* a __toString() method
*
* #category Security
* #package PHPIDS
* #author Christian Matthies <ch0012#gmail.com>
* #author Mario Heiderich <mario.heiderich#gmail.com>
* #author Lars Strojny <lars#strojny.net>
* #copyright 2007-2009 The PHPIDS Group
* #license http://www.gnu.org/licenses/lgpl.html LGPL
* #link http://php-ids.org/
*/
class Report implements \Countable, \IteratorAggregate
{
/**
* Event container
*
* #var Event[]|array
*/
protected $events = array();
/**
* List of affected tags
*
* This list of tags is collected from the collected event objects on
* demand when IDS_Report->getTags() is called
*
* #var string[]|array
*/
protected $tags = array();
/**
* Impact level
*
* The impact level is calculated on demand by adding the results of the
* event objects on IDS\Report->getImpact()
*
* #var integer
*/
protected $impact = 0;
/**
* Centrifuge data
*
* This variable - initiated as an empty array - carries all information
* about the centrifuge data if available
*
* #var array
*/
protected $centrifuge = array();
/**
* Constructor
*
* #param array $events the events the report should include
*
* #return Report
*/
public function __construct(array $events = null)
{
foreach ((array) $events as $event) {
$this->addEvent($event);
}
}
/**
* Adds an IDS_Event object to the report
*
* #param Event $event IDS_Event
*
* #return self $this
*/
public function addEvent(Event $event)
{
$this->clear();
$this->events[$event->getName()] = $event;
return $this;
}
/**
* Get event by name
*
* In most cases an event is identified by the key of the variable that
* contained maliciously appearing content
*
* #param string|integer $name the event name
*
* #throws \InvalidArgumentException if argument is invalid
* #return Event|null IDS_Event object or false if the event does not exist
*/
public function getEvent($name)
{
if (!is_scalar($name)) {
throw new \InvalidArgumentException('Invalid argument type given');
}
return $this->hasEvent($name) ? $this->events[$name] : null;
}
/**
* Returns list of affected tags
*
* #return string[]|array
*/
public function getTags()
{
if (!$this->tags) {
$this->tags = array();
foreach ($this->events as $event) {
$this->tags = array_merge($this->tags, $event->getTags());
}
$this->tags = array_values(array_unique($this->tags));
}
return $this->tags;
}
/**
* Returns total impact
*
* Each stored IDS_Event object and its IDS_Filter sub-object are called
* to calculate the overall impact level of this request
*
* #return integer
*/
public function getImpact()
{
if (!$this->impact) {
$this->impact = 0;
foreach ($this->events as $event) {
$this->impact += $event->getImpact();
}
}
return $this->impact;
}
/**
* Checks if a specific event with given name exists
*
* #param string|integer $name the event name
*
* #throws \InvalidArgumentException if argument is illegal
* #return boolean
*/
public function hasEvent($name)
{
if (!is_scalar($name)) {
throw new \InvalidArgumentException('Invalid argument given');
}
return isset($this->events[$name]);
}
/**
* Returns total amount of events
*
* #return integer
*/
public function count()
{
return #count($this->events);
}
/**
* Return iterator object
*
* In order to provide the possibility to directly iterate over the
* IDS_Event object the IteratorAggregate is implemented. One can easily
* use foreach() to iterate through all stored IDS_Event objects.
*
* #return \Iterator the event collection
*/
public function getIterator()
{
return new \ArrayIterator($this->events);
}
/**
* Checks if any events are registered
*
* #return boolean
*/
public function isEmpty()
{
return empty($this->events);
}
/**
* Clears calculated/collected values
*
* #return void
*/
protected function clear()
{
$this->impact = 0;
$this->tags = array();
}
/**
* This method returns the centrifuge property or null if not
* filled with data
*
* #return array
*/
public function getCentrifuge()
{
return $this->centrifuge;
}
/**
* This method sets the centrifuge property
*
* #param array $centrifuge the centrifuge data
*
* #throws \InvalidArgumentException if argument is illegal
* #return void
*/
public function setCentrifuge(array $centrifuge = array())
{
if (!$centrifuge) {
throw new \InvalidArgumentException('Empty centrifuge given');
}
$this->centrifuge = $centrifuge;
}
/**
* Directly outputs all available information
*
* #return string
*/
public function __toString()
{
$output = '';
if (!$this->isEmpty()) {
$output .= vsprintf(
"Total impact: %d<br/>\nAffected tags: %s<br/>\n",
array(
$this->getImpact(),
implode(', ', $this->getTags())
)
);
foreach ($this->events as $event) {
$output .= vsprintf(
"<br/>\nVariable: %s | Value: %s<br/>\nImpact: %d | Tags: %s<br/>\n",
array(
htmlspecialchars($event->getName()),
htmlspecialchars($event->getValue()),
$event->getImpact(),
implode(', ', $event->getTags())
)
);
foreach ($event as $filter) {
$output .= vsprintf(
"Description: %s | Tags: %s | ID %s<br/>\n",
array(
$filter->getDescription(),
implode(', ', $filter->getTags()),
$filter->getId()
)
);
}
}
$output .= '<br/>';
if ($centrifuge = $this->getCentrifuge()) {
$output .= vsprintf(
"Centrifuge detection data<br/> Threshold: %s<br/> Ratio: %s",
array(
isset($centrifuge['threshold']) && $centrifuge['threshold'] ? $centrifuge['threshold'] : '---',
isset($centrifuge['ratio']) && $centrifuge['ratio'] ? $centrifuge['ratio'] : '---'
)
);
if (isset($centrifuge['converted'])) {
$output .= '<br/> Converted: ' . $centrifuge['converted'];
}
$output .= "<br/><br/>\n";
}
}
return $output;
}
}
As of PHP 8.1, you have to fix the return type of the functions count() and getIterator() to match with interfaces.
public count(): int (see Countable)
public getIterator(): Traversable (see IteratorAggregate)
class Report implements \Countable, \IteratorAggregate
{
protected array $events;
public function count(): int
{
return count($this->events);
}
public function getIterator(): \Traversable
{
return new \ArrayIterator($this->events);
}
}

Symfony 3 Exception: Type error: Argument 1 passed to Doctrine\Common\Collections\ArrayCollection

Please help me with my project for a shopping cart. I am trying to add products to the basket and get this error while adding a new product: Type error: Argument 1 passed to Doctrine\Common\Collections\ArrayCollection::__construct() must be of the type array, object given, called in C:\Users\Angel's\Desktop\untitled2\vendor\doctrine\orm\lib\Doctrine\ORM\UnitOfWork.php on line 605.
Here is my CartController:
<?php
namespace AppBundle\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use AppBundle\Entity\Product;
use AppBundle\Entity\Cart;
use AppBundle\Entity\Shipping;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\DateType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
class CartController extends Controller
{
/**
* #Route("/", name="homepage")
*/
/*public function indexAction(Request $request)
{
// replace this example code with whatever you need
return $this->render('default/index.html.twig', array(
'base_dir' => realpath($this->container->getParameter('kernel.root_dir').'/..').DIRECTORY_SEPARATOR,
));
}*/
/**
* #Route("/cart", name="view_cart")
*/
public function showAction()
{
# Get object from doctrine manager
$em = $this->getDoctrine()->getManager();
# Get logged user then get his ['id']
$user = $this->container->get('security.token_storage')->getToken()->getUser();
/** Check IF user have exist cart **/
# select cart from database where user id equal to cureent logged user using [ findByUser() ]
$user_cart = $this->getDoctrine()
->getRepository('AppBundle:Cart')
->findBy(['user' => $user]);
if ( $user_cart )
{
# Then select all user cart products to display it to user
$user_products = $this->getDoctrine()
->getRepository('AppBundle:Shipping')
->findBy( array('cart' => $user_cart[0]->getId()) );
# pass selected products to the twig page to show them
return $this->render('cart/show.html.twig', array(
'products' => $user_products,
'cart_data' => $user_cart[0],
));
}
//return new Response(''. $user_products[0]->getProduct()->getPrice() );
# pass selected products to the twig page to show them
return $this->render('cart/show.html.twig');
}
/**
* #Route("/cart/addTo/{productId}", name="add_to_cart")
* #param $productId
* #return \Symfony\Component\HttpFoundation\RedirectResponse
*/
public function addAction($productId)
{
# First of all check if user logged in or not by using FOSUSERBUNDLE
# authorization_checker
# if user logged in so add the selected product to his cart and redirect user to products page
# else redirect user to login page to login first or create a new account
$securityContext = $this->container->get('security.authorization_checker');
# If user logged in
if ( $securityContext->isGranted('IS_AUTHENTICATED_REMEMBERED') )
{
# Get object from doctrine manager
$em = $this->getDoctrine()->getManager();
# Get logged user then get his ['id']
$user = $this->container->get('security.token_storage')->getToken()->getUser();
# for any case wewill need to select product so select it first
# select specific product which have passed id using ['find(passedID)']
$product = $this->getDoctrine()
->getRepository('AppBundle:Product')
->find($productId);
/** Check IF user have exist cart **/
# select cart from database where user id equal to cureent logged user using [ findByUser() ]
$exsit_cart = $this->getDoctrine()
->getRepository('AppBundle:Cart')
->findBy(['user' => $user]);
# if there's no cart to this user create a new one
if ( !$exsit_cart )
{
# defince cart object
$cart = new Cart();
# set user whose own this cart
$cart->setUser($user);
# set initail total price for cart which equal to product price
$cart->setTotalPrice($product->getPrice());
$cart->setQuantity(1);
# persist all cart data to can use it in create shipping object
$em->persist($cart);
# flush it
$em->flush();
# create shipping object
$ship = new Shipping();
# set all its data quantity initail equal to 1 and passed product and cart created
$ship->setQuantity(1);
$ship->setProduct($product);
$ship->setCart($cart);
# persist it and flush doctrine to save it
$em->persist($ship);
$em->flush();
}
# if user have one so just add new item price to cart price and add it to shipping
else
{
# Get cart from retrived object
$cart = $exsit_cart[0];
# set initail total price for cart which equal to product price
$cart->setTotalPrice($cart->getTotalPrice() + $product->getPrice());
# persist all cart data to can use it in create shipping object
$em->persist($cart);
# flush it
$em->flush();
# create shipping object
$ship = new Shipping();
# set all its data quantity initail equal to 1 and passed product and cart created
$ship->setQuantity(1);
$ship->setProduct($product);
$ship->setCart($cart);
# persist it and flush doctrine to save it
$em->persist($ship);
$em->flush();
}
//return new Response('user id '.$product->getId());
return $this->redirect($this->generateUrl('products_index'));
}
# if user not logged in yet
else
{
# go to adding product form
return $this->redirect($this->generateUrl('login'));
}
}
/**
* #Route("/cart/remove/{itemProduct}/{itemCart}", name="remove_item")
*/
public function removeActione($itemProduct, $itemCart)
{
# get an object from doctrine db and get Shipping Entity to work on it
$em = $this->getDoctrine()->getManager();
$repository = $em->getRepository('AppBundle:Shipping');
# select wanted item from shipping table to delete it
$ship = $repository->findOneBy(array('product' => $itemProduct, 'cart' => $itemCart));
# Calculate the new total price for cart by subtract deleted item price from total one
$final_price = $ship->getCart()->getTotalPrice() - ($ship->getProduct()->getPrice() * $ship->getQuantity());
# update the total price of cart
$ship->getCart()->setTotalPrice($final_price);
# Remove item from db
$em->remove($ship);
$em->flush();
return $this->redirect($this->generateUrl('view_cart'));
}
/**
* #Route("/cart/edit/{itemProduct}/{itemCart}", name="edit item")
*/
public function editActione(Request $request, $itemProduct, $itemCart)
{
# in the start check if user edit field and click on button
if ( $request->getMethod() === 'POST' )
{
# read data from quantity field
$new_quantity =$request->request->get('quantity');
# get oject from doctrine manager to mange operation
$em = $this->getDoctrine()->getManager();
$repository = $em->getRepository('AppBundle:Shipping');
# select wanted item from shipping table to edit it
$ship = $repository->findOneBy(array('product' => $itemProduct, 'cart' => $itemCart));
# check if new quantity less than old one so subtract total price
# otherwise, add to it
if( $ship->getQuantity() < $new_quantity )
{
# edit selected item quantity
$ship->setQuantity($new_quantity);
# Calculate the new total price for cart by sum added item price to total one
$final_price = $ship->getCart()->getTotalPrice() + $ship->getProduct()->getPrice();
# update the total price of cart
$ship->getCart()->setTotalPrice($final_price);
}
elseif( $ship->getQuantity() > $new_quantity )
{
# edit selected item quantity
$ship->setQuantity($new_quantity);
# Calculate the new total price for cart by sum added item price to total one
$final_price = $ship->getCart()->getTotalPrice() - $ship->getProduct()->getPrice();
# update the total price of cart
$ship->getCart()->setTotalPrice($final_price);
}
# flush operations to update database
$em->flush();
}
//return new Response(''. $new_quantity );
return $this->redirect($this->generateUrl('view_cart'));
}
/**
* #Route("/cart/clear/{cart}", name="clear_cart")
*/
public function clearActione($cart)
{
# get an object from doctrine db and get Shipping Entity to work on it
$em = $this->getDoctrine()->getManager();
$repository = $em->getRepository('AppBundle:Shipping');
# select wanted item from shipping table to delete it
$ship = $repository->findBy(array('cart' => $cart));
# Fetch all them using foeach loop and delete them
foreach ($ship as $one_prod)
{
# Remove item from db
$em->remove($one_prod);
$em->flush();
}
$cart_repository = $em->getRepository('AppBundle:Cart');
$one_cart = $cart_repository->findOne(['id' => $cart]);
$em->remove($one_cart);
$em->flush();
return $this->redirect($this->generateUrl('view_cart'));
}
}
This is my Cart Entity code:
<?php
namespace AppBundle\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use AppBundle\Entity\Shipping;
#use AppBundle\Entity\User;
#use AppBundle\Entity\Product;
/**
* #ORM\Entity
* #ORM\Table(name="cart")
*/
class Cart
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\Column(type="decimal", scale=2)
*/
private $total_price;
/**
* #ORM\Column(type="integer")
*/
private $quantity;
/**
* #ORM\OneToOne(targetEntity="User", inversedBy="cart")
*/
private $user;
/** #ORM\OneToMany(targetEntity="Shipping", mappedBy="cart") */
protected $cartProducts;
/**
* Constructor
*/
public function __construct()
{
$this->cartProducts = new ArrayCollection();
}
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set total_price
*
* #param string $totalPrice
* #return Cart
*/
public function setTotalPrice($totalPrice)
{
$this->total_price = $totalPrice;
return $this;
}
/**
* Get total_price
*
* #return string
*/
public function getTotalPrice()
{
return $this->total_price;
}
/**
* Set quantity
*
* #param integer $quantity
* #return Cart
*/
public function setQuantity($quantity)
{
$this->quantity = $quantity;
return $this;
}
/**
* Get quantity
*
* #return integer
*/
public function getQuantity()
{
return $this->quantity;
}
/**
* Set user
*
* #param \AppBundle\Entity\User $user
* #return Cart
*/
public function setUser(\AppBundle\Entity\User $user = null)
{
$this->user = $user;
return $this;
}
/**
* Get user
*
* #return \AppBundle\Entity\User
*/
public function getUser()
{
return $this->user;
}
/**
* Add cartProducts
*
* #param Shipping $cartProducts
* #return Collection
*/
public function addCartProduct(array $cartProducts)
{
$this->cartProducts[] = $cartProducts;
return $this;
}
/**
* Remove cartProducts
*
* #param Shipping $cartProducts
*/
public function removeCartProduct(array $cartProducts)
{
$this->cartProducts->removeElement($cartProducts);
}
/**
* Get cartProducts
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getCartProducts()
{
return $this->cartProducts;
}
}
And my Shipping Entity:
<?php
// src/AppBundle/Entity/User.php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
#use AppBundle\Entity\User;
#use AppBundle\Entity\Product;
/**
* #ORM\Entity
* #ORM\Table(name="shipping")
*/
class Shipping
{
/**
* #ORM\Column(type="integer")
*/
private $quantity;
/**
* #ORM\Id()
* #ORM\ManyToOne(targetEntity="Product", inversedBy="cartProducts")
*/
protected $product;
/**
* #ORM\Id()
* #ORM\ManyToOne(targetEntity="Cart", inversedBy="cartProducts")
*/
protected $cart;
/**
* Set product
*
* #param \AppBundle\Entity\Product $product
* #return Shipping
*/
public function setProduct(\AppBundle\Entity\Product $product)
{
$this->product = $product;
return $this;
}
/**
* Get product
*
* #return \AppBundle\Entity\Product
*/
public function getProduct()
{
return $this->product;
}
/**
* Set cart
*
* #param \AppBundle\Entity\Cart $cart
* #return Shipping
*/
public function setCart(\AppBundle\Entity\Cart $cart)
{
$this->cart = $cart;
return $this;
}
/**
* Get cart
*
* #return \AppBundle\Entity\Cart
*/
public function getCart()
{
return $this->cart;
}
/**
* Set quantity
*
* #param integer $quantity
* #return Shipping
*/
public function setQuantity($quantity)
{
$this->quantity = $quantity;
return $this;
}
/**
* Get quantity
*
* #return integer
*/
public function getQuantity()
{
return $this->quantity;
}
}
Try using doctrine query builder with ->select('your join entity', 'alias') by using ->select() method you retrieve array not object from query

Magento2 Enterprise update smart Categories

My task is related to the smart categories. Smart categories are those which assign the products based on the rules.
Rules of categories can be
Category A: Those products which quantity is more than 1000 should be assign to this category.
Category B: Assign the products based on the brand of the product.
Magento 2 Enterprise provide us rules functionality after adding which we can achieve the above functionality. But we want to update category whenever any product full fill the rule then it should assign to rule related category.
Magento 2 Enterprise does not assign newly products which follow the rules automatically until we re-save the category. So i try to re-save the smart categories using cron. It's code is
class Category
{
/**
* #var \Magento\Catalog\Model\ResourceModel\Category\CollectionFactory
*/
protected $_categoryCollectionFactory;
/**
* #var \Magento\Catalog\Api\CategoryRepositoryInterface
*/
protected $_repository;
/**
* #var \Psr\Log\LoggerInterface
*/
protected $_logger;
/**
* #var \Magento\VisualMerchandiser\Model\Rules
*/
protected $_modelRulesFactory;
/**
* Index constructor. Here we are gettingthe logger for log. categorycollection to get all the categories and category repository to save the smart categories
* #param \Magento\Framework\App\Action\Context $context
* #param LoggerInterface $logger
* #param \Magento\Catalog\Model\ResourceModel\Category\CollectionFactory $categoryCollectionFactory
* #param \Magento\Catalog\Api\CategoryRepositoryInterface $repository
* #param RulesFactory $modelRulesFactory
*/
public function __construct(\Psr\Log\LoggerInterface $logger,
\Magento\Catalog\Model\ResourceModel\Category\CollectionFactory $categoryCollectionFactory,
\Magento\Catalog\Api\CategoryRepositoryInterface $repository,
RulesFactory $modelRulesFactory) {
$this->_logger = $logger;
$this->_categoryCollectionFactory = $categoryCollectionFactory;
$this->_repository = $repository;
$this->_modelRulesFactory = $modelRulesFactory;
}
/**
* In this function we load all the categories and only save the smart categories so rules for products can apply and assign the new best seller products according to rules
*
* #throws \Magento\Framework\Exception\LocalizedException
*/
public function execute() {
try{
$this->_logger->addDebug('Updating categories');
$categories = $this->_categoryCollectionFactory->create();
$categories->addAttributeToSelect('*');
$rulesModel = $this->_modelRulesFactory->create();
foreach ($categories as $category) {
$rule = $rulesModel->loadByCategory($category);
if ($rule->getId() && $rule->getIsActive()) {
$category->setStoreId(0);
$rule->setIsActive(1);
$this->_repository->save($category);
}
}
}catch (Exception $e){
$this->_logger->addDebug($e->getMessage());
}
}
}
The above code works very good with those rules which are using the rules based on the default attributes. For example if rule is quantity > 1000 here quantity is default attributes so those categories update very well. But if it is like Brand = 'Nike', then after execute the above code it's assigned products become 0.
Can you please help me why it is happening? Thank you very much.

symfony2/doctrine entity property not filled

After doctrine createQueryBuilder execute, I get results with a property of objects that is not filled.
here my code
In UserSecurityManager (service)
... $repository = $this->em->getRepository('VenusUserBundle:Role');
$query = $repository->createQueryBuilder('r')
->where('r.lft >= :role_lft AND r.rgt <= :role_rgt')
->orderBy('r.rgt', 'ASC')
->setParameter('role_lft', $result['lft'])
->setParameter('role_rgt', $result['rgt'])
->getQuery();
$availableRoles = $query->getResult(); ...
//debug
foreach ($availableRoles as $key => $value) { echo '<br>CODE='.$value->getCode().' LFT='.$value->getLft().' NAME=('.$value->getName().') * '; }
...
Role Class
namespace MyApp\UserBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Security\Core\Role\RoleInterface;
/**
* #ORM\Entity
* #ORM\Table(name="Role")
*/
class Role implements RoleInterface, \Serializable
{
/**
* #ORM\Id
* #ORM\Column(type="string", length=100)
*
* #var string $code
*/
protected $code;
/**
* #ORM\Column(name="name", type="string", length=100)
*
* #var string $name
*/
private $name;
/**
* #ORM\Column(type="integer")
*
* #var integer $lft
*/
protected $lft; //nested tree
/**
* #ORM\Column(type="integer")
*
* #var integer $rgt
*/
protected $rgt; //nested tree
/**
* #ORM\OneToMany(targetEntity="Role", mappedBy="parent")
*/
private $children;
/**
* #ORM\ManyToOne(targetEntity="Role", inversedBy="children")
* #ORM\JoinColumn(name="parent_code", referencedColumnName="code")
*/
private $parent;
/**
* #ORM\ManyToMany(targetEntity="User", mappedBy="roles")
*
* #var \Doctrine\Common\Collections\ArrayCollection
*/
protected $users;
public function __construct()
{
$this->name = '';
$this->users = new \Doctrine\Common\Collections\ArrayCollection();
$this->children = new \Doctrine\Common\Collections\ArrayCollection();
}
// #see \Serializable::serialize()
public function serialize()
{
// ! Don't serialize $users field !
return \serialize(array(
$this->code,
$this->name,
$this->parent,
$this->children,
));
}
// #see \Serializable::unserialize()
public function unserialize($serialized)
{
list(
$this->code,
$this->name,
$this->parent,
$this->children,
) = \unserialize($serialized);
}
//public function __toString() {
// return $this->name;
//}
/**
* Sets the role code.
*
* #param string $value The code.
*/
public function setCode($value)
{
$this->code = $value;
}
/**
* Gets the code.
*
* #return integer The code.
*/
public function getCode()
{
return $this->code;
}
/**
* Gets the role name.
*
* #return string The name.
*/
public function getName()
{
return $this->name;
}
/**
* Sets the role name.
*
* #param string $name The name.
*/
public function setName($name)
{
$this->name = $name;
}
/**
* Get parent
*
* #return MyApp\UserBundle\Entity\Role
*/
public function getParent()
{
return $this->parent;
}
/**
* Set parent
*
* #param MyApp\UserBundle\Entity\Role $role
*/
public function setParent(\MyApp\UserBundle\Entity\Role $role)
{
$this->parent = $role;
}
/**
* Gets the role left.
*
* #return string The left.
*/
public function getLft()
{
return $this->lft;
}
/**
* Sets the role left.
*
* #param string $left Left.
*/
public function setLft($lft)
{
$this->lft = $lft;
}
public function getRole()
{
return $this->code;
}
/**
* Remove a user
*
* #param \MyApp\UserBundle\Entity\User $user
*/
public function removeUser(\MyApp\UserBundle\Entity\User $user)
{
$this->users->removeElement($user);
}
/**
* Add a user
*
* #param \MyApp\UserBundle\Entity\User $user
*/
public function addUser(\MyApp\UserBundle\Entity\User $user)
{
$this->users->add($user);
}
/**
* Remove all users
*
*/
public function removeUsers()
{
$this->users->clear();
}
/**
* Set the collection of related users
*
* #param \Doctrine\Common\Collections\ArrayCollection $users
*/
public function setUsers(\Doctrine\Common\Collections\ArrayCollection $users)
{
$this->users = $users;
}
/**
* Get users
*
* #return Doctrine\Common\Collections\Collection
*/
public function getUsers()
{
return $this->users;
}
}
The line
foreach ($availableRoles as $key => $value) {
echo '<br>CODE='.$value->getCode().' LFT='.$value->getLft().' NAME=('.$value->getName().') * '; }
Display
CODE=client LFT=4 NAME=(client) *
CODE=studio LFT=6 NAME=(studio) *
CODE=commercial LFT=8 NAME=(commercial) *
CODE=user_manager LFT=11 NAME=(user manager) *
CODE=company_manager LFT=13 NAME=(company manager) *
CODE=admin LFT=3 NAME=(administrator) *
CODE=prod LFT=10 NAME=(prod) *
CODE=superadmin LFT= NAME=() * //<-- THE BUG : name is empty !!!
CODE=root LFT=1 NAME=(megaroot) *
And the data in database :
code parent_code name lft rgt
admin superadmin administrator 3 15
client admin client 4 5
commercial admin commercial 8 9
company_manager admin company manager 13 14
prod admin prod 10 15
root NULL megaroot 1 18
studio admin studio 6 7
superadmin root superadmin 2 15
user_manager admin user manager 11 12
for superadmin, The property "name" is not filled, I don't understand.
Do you have an idea ?
I made some other tests :
If the parent of role "administrator" is the role "client"
code parent_code name lft rgt
admin client administrator 3 15
client admin client 4 5
commercial admin commercial 8 9
company_manager admin company manager 13 14
prod admin prod 10 15
root NULL megaroot 1 18
studio admin studio 6 7
superadmin root superadmin 2 15
user_manager admin user manager 11 12
CODE=client LFT= NAME=() * <-- BUG HERE !!!
CODE=studio LFT=6 NAME=(studio) *
CODE=commercial LFT=8 NAME=(commercial) *
CODE=user_manager LFT=11 NAME=(user manager) *
CODE=company_manager LFT=13 NAME=(company manager) *
CODE=admin LFT=3 NAME=(administrator) *
CODE=prod LFT=10 NAME=(prod) *
CODE=superadmin LFT=2 NAME=(superadmin) *
CODE=root LFT=1 NAME=(megaroot) *
If the parent of role "administrator" is the role "client" and the parent of role "client" is the role "root"
code parent_code name lft rgt
admin client administrator 3 15
client admin client 4 5
commercial admin commercial 8 9
company_manager admin company manager 13 14
prod admin prod 10 15
root NULL megaroot 1 18
studio admin studio 6 7
superadmin root superadmin 2 15
user_manager admin user manager 11 12
CODE=client LFT= NAME=() * <-- BUG HERE !!!
CODE=studio LFT=6 NAME=(studio) *
CODE=commercial LFT=8 NAME=(commercial) *
CODE=user_manager LFT=11 NAME=(user manager) *
CODE=company_manager LFT=13 NAME=(company manager) *
CODE=admin LFT=3 NAME=(administrator) *
CODE=prod LFT=10 NAME=(prod) *
CODE=superadmin LFT=2 NAME=(superadmin) *
CODE=root LFT= NAME=() * <-- BUG HERE !!!
Thanks and sorry for my english.
Phil
If I execute
$query->setHint(\Doctrine\ORM\Query::HINT_REFRESH, true);
before
$availableRoles = $query->getResult();
ok, the result is good but I don't know the cause yet :s.
The problem come from my code ?! or it's a bug in doctrine 2.4 ?
it would be a problem with doctrine cache.
When I call this "entitymanager->clear()" before execute query, all is good.
$this->em->clear('MyApp\UserBundle\Entity\Role');
$repository = $this->em->getRepository('MyAppUserBundle:Role');
What's the explain of this problem of cache ? Because I don't configure caching in my symfony project.
is "em->clear()" dangerous for the rest of application ?

How to display configurable product in each color in Magento product listing?

I have a configurable product which is available in many different colors and sizes. I want the configurable product to appear once for every color. My idea is to assign one simple product of the configurable product in every color to the category of the configurable product. Then I want to change the listing, so that the (colored) simple product links to it's master product (the configurable one).
The other way would be, to just assign the configurable product to a category and then list it multiple times with different colors. But I think this would be to complicated.
Solution
Sincerely I have lost my code. But here is how I've managed it:
Set visibility for all slave products to catalog so that they
appear in the product listing
Override the Product Model and it's getProductUrl function:
public function getProductUrl($useSid = null)
{
$product = $this;
$product->loadParentProductIds();
$parentIds = $product->getParentProductIds();
if(count($parentIds) > 0 && $product->getTypeId() == Mage_Catalog_Model_Product_Type::TYPE_SIMPLE)
{
$parent = Mage::getModel("catalog/product")->setId($parentIds[0])->load();
return $this->getUrlModel()->getProductUrl($parent, $useSid);
}
return $this->getUrlModel()->getProductUrl($product, $useSid);
}
This way each slave product links to it's master product. The tricky part is to attach the attributes to the url. You can add #attributecode1=value1&attributecode2=value2 to the url to preselect the attribute select boxes. I only had this part quick & dirty and am pretty sure someone can do this much better.
Example for preselection:
http://demo.magentocommerce.com/anashria-womens-premier-leather-sandal-7.html
http://demo.magentocommerce.com/anashria-womens-premier-leather-sandal-7.html#502=43
I don't understand why you just don't make a configurable product based on size for every color? That way you don't need to hack the way Magento works.
If you make a simple product that is part of a configurable product visible on the frontend, it will not link to a configurable product, if it is part of one (as you have found out). It wouldn't really make sense for you either because if your configurable products are based on size AND color, the simple products are going to have a set size and set color.
You would be done, fully functional, and hack-free if you just made a configurable product for each shirt color. Then, you can also use related products to show other shirt colors.
The less hacking, the better. That's my opinion.
Necroing this thread in case others need to do this in Magento 2.
Below is my solution. Please keep in mind that it is hacky and will break many things, so only use if you're a Magento developer who knows what he/she is doing and can either fix or live with the negative side-effects of this code.
registration.php
<?php
use \Magento\Framework\Component\ComponentRegistrar;
ComponentRegistrar::register(ComponentRegistrar::MODULE, 'Antti_ConfigurableProductSplitter', __DIR__);
etc/module.xml
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
<module name="Antti_ConfigurableProductSplitter" >
<sequence>
<module name="Magento_Catalog" />
</sequence>
</module>
</config>
etc/frontend/events.xml
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Event/etc/events.xsd">
<event name="catalog_block_product_list_collection">
<observer name="cps_catalog_block_product_list_collection" instance="Antti\ConfigurableProductSplitter\Observer\CatalogBlockProductCollectionBeforeToHtmlObserver" shared="false" />
</event>
<event name="cps_product_data_merge_after">
<observer name="cps_product_data_merge_after" instance="Antti\ConfigurableProductSplitter\Observer\SetColorPreselectedAfterProductDataMerge" shared="false" />
</event>
</config>
Observer/CatalogBlockProductCollectionBeforeToHtmlObserver.php
<?php
namespace Antti\ConfigurableProductSplitter\Observer;
use Magento\Framework\Event\ObserverInterface;
use Antti\ConfigurableProductSplitter\Model\ProductCollectionSplitter;
class CatalogBlockProductCollectionBeforeToHtmlObserver implements ObserverInterface
{
/**
* #var ProductCollectionSplitter
*/
private $productSplitter;
/**
* CatalogBlockProductCollectionBeforeToHtmlObserver constructor.
*
* #param ProductCollectionSplitter $productSplitter
*/
public function __construct(
ProductCollectionSplitter $productSplitter
) {
$this->productSplitter = $productSplitter;
}
/**
* #param \Magento\Framework\Event\Observer $observer
*
* #return $this
* #throws \Magento\Framework\Exception\LocalizedException
*/
public function execute(\Magento\Framework\Event\Observer $observer)
{
$productCollection = $observer->getEvent()->getCollection();
if ($productCollection instanceof \Magento\Framework\Data\Collection) {
if (!$productCollection->isLoaded()) {
$productCollection->load();
}
$this->productSplitter->splitConfigurables($productCollection);
}
return $this;
}
}
Observer/SetColorPreselectedAfterProductDataMerge.php
<?php
namespace Antti\ConfigurableProductSplitter\Observer;
use Magento\Framework\Event\ObserverInterface;
use Magento\Catalog\Api\Data\ProductInterface;
use Magento\Eav\Model\Config as EavConfig;
class SetColorPreselectedAfterProductDataMerge implements ObserverInterface
{
/**
* #var EavConfig
*/
private $eavConfig;
/**
* ProductDataMerger constructor.
*
* #param EavConfig $eavConfig
*/
public function __construct(
EavConfig $eavConfig
) {
$this->eavConfig = $eavConfig;
}
/**
* #param \Magento\Framework\Event\Observer $observer
*
* #return $this
* #throws \Magento\Framework\Exception\LocalizedException
*/
public function execute(\Magento\Framework\Event\Observer $observer)
{
$product = $observer->getEvent()->getSimple();
$merged = $observer->getEvent()->getMerged();
$this->setColorPreselected($merged, $product->getColor());
return $this;
}
/**
* #param ProductInterface $product
* #param int $color
*
* #throws \Magento\Framework\Exception\LocalizedException
*/
private function setColorPreselected(ProductInterface &$product, int $color)
{
$attribute = $this->eavConfig->getAttribute(\Magento\Catalog\Model\Product::ENTITY, 'color');
$preconfiguredValues = new \Magento\Framework\DataObject();
$preconfiguredValues->setData('super_attribute', [$attribute->getId() => $color]);
$product->setPreconfiguredValues($preconfiguredValues);
// TODO: should test whether this works if there is no url rewrite
$product->setRequestPath(sprintf('%s#%d=%d', $product->getRequestPath(), $attribute->getId(), $color));
}
}
Model/ProductDataMerger.php
<?php
namespace Antti\ConfigurableProductSplitter\Model;
use Magento\Catalog\Api\Data\ProductInterface;
use Magento\Framework\EntityManager\EventManager;
class ProductDataMerger
{
/**
* #var EventManager
*/
private $eventManager;
/**
* #param EventManager $eventManager
*/
public function __construct(
EventManager $eventManager
) {
$this->eventManager = $eventManager;
}
/**
* #param ProductInterface $product
* #param ProductInterface $parentProduct
*
* #return ProductInterface
*/
public function merge(ProductInterface $product, ProductInterface $parentProduct)
{
$merged = clone $parentProduct;
$merged->setParentId($merged->getId());
$merged->setId($product->getId());
$this->setImageFromChildProduct($merged, $product);
$this->eventManager->dispatch(
'cps_product_data_merge_after',
['merged' => $merged, 'simple' => $product, 'configurable' => $parentProduct]
);
return $merged;
}
/**
* #param ProductInterface $product
* #param ProductInterface $childProduct
*/
public function setImageFromChildProduct(ProductInterface &$product, ProductInterface $childProduct)
{
foreach (['image', 'small_image', 'thumbnail'] as $imageType) {
if ($childProduct->getData($imageType) && $childProduct->getData($imageType) !== 'no_selection') {
$product->setData($imageType, $childProduct->getData($imageType));
} else {
$product->setData($imageType, $childProduct->getData('image'));
}
}
}
}
Model/ProductCollectionSplitter.php
<?php
namespace Antti\ConfigurableProductSplitter\Model;
use Magento\ConfigurableProduct\Model\Product\Type\Configurable;
use Antti\ConfigurableProductSplitter\Model\ProductDataMerger;
use Magento\Catalog\Model\Layer\Resolver;
class ProductCollectionSplitter
{
/**
* #var \Magento\Catalog\Model\Layer
*/
private $catalogLayer;
/**
* #var ProductDataMerger
*/
private $productDataMerger;
/**
* ProductCollectionSplitter constructor.
*
* #param Resolver $layerResolver
* #param ProductDataMerger $productDataMerger
*/
public function __construct(
Resolver $layerResolver,
ProductDataMerger $productDataMerger
) {
$this->catalogLayer = $layerResolver->get();
$this->productDataMerger = $productDataMerger;
}
/**
* #param \Magento\Framework\Data\Collection $collection
*
* #return $this
* #throws \Magento\Framework\Exception\LocalizedException
*/
public function splitConfigurables(\Magento\Framework\Data\Collection $collection)
{
$items = $collection->getItems();
if (sizeof($items) == 0) {
return $this;
}
$configurables = $otherProducts = [];
$colorFilterValue = $this->getCurrentColorFilterValue();
foreach ($items as $index => $product) {
if ($product->getTypeId() === Configurable::TYPE_CODE) {
/** #var \Magento\ConfigurableProduct\Model\ResourceModel\Product\Type\Configurable\Product\Collection $childProducts */
$childProducts = $product->getTypeInstance()->getUsedProductCollection($product);
if ($colorFilterValue !== null) {
$childProducts->addAttributeToFilter('color', ['eq' => $colorFilterValue]);
}
$childProducts->groupByAttribute('color');
foreach ($childProducts as $childProduct) {
$childProduct->setParentId($product->getId());
$otherProducts[] = $childProduct;
}
$configurables[$product->getId()] = $product;
} else {
$otherProducts[] = $product;
}
$collection->removeItemByKey($index);
}
foreach ($otherProducts as $product) {
if ($product->getParentId() && isset($configurables[$product->getParentId()])) {
$product = $this->productDataMerger->merge($product, $configurables[$product->getParentId()]);
}
$collection->addItem($product);
}
return $this;
}
/**
* #return string|null
* #throws \Magento\Framework\Exception\LocalizedException
*/
private function getCurrentColorFilterValue()
{
/** #var \Magento\Catalog\Model\Layer\Filter\Item $filter */
foreach ($this->catalogLayer->getState()->getFilters() as $filter) {
if($filter->getFilter()->getAttributeModel()->getName() == 'color') {
return $filter->getValueString();
}
}
return null;
}
}
Known issues:
Because of modifying collection items after it has loaded, the collection count will be
invalid, and this may cause issues elsewhere.
Also product ids in the collection will be invalid since configurable items' ids gets replaced by simple products' ids.
If the collection will be loaded again elsewhere, configurable products do not get splitted.
Products per page limiter does not work anymore.
Products count on product list is invalid.
3rd party search modules such as Klevu might not work
Other 3rd party modules may have issues with the implementation.
Product stock data, reviews etc. are broken on product list (although is probably easy to fix in frontend).
Not sure if implementation will work without url rewrites (should be easy to fix though).
Other issues I might not be aware of.
One way would be to make the size and color part of the catalog number (or whatever unique identifying number you are using for the product)
So lets say you have a widget that comes in 2 colors and 3 sizes, and it's catalog number is "qwe123". You would enter the following 2 items into the system, along with appropriate images. I'm assuming you already have a way to deal with the sizes.
qwe123-red
qwe123-blue
There is no extra programing involved to do it this way, but if you want to link to the other colors that are available from the product page then you will have to parse out the first part of the catalog number and search for the ones that match.
In order to redirect simple products to configurable parent product, you can create a Plugin (Interceptor) for Magento\Catalog\Model\Product::getProductUrl(), where to change URL for simple products:
if ($product->getTypeId() === 'simple') {
/*Get the configurable parent product URL and assign it to a simple product.*/
}
To preselect a simple product in a configurable product, the address of a simple product should look like this for example:
/mona-pullover-hoodlie.html#143=167&93=53
where
/mona-pullover-hoodlie.html - configurable product URL,
143, 93 - attributes IDs,
167, 53 - option IDs.
Attributes IDs and option IDs can be obtained using Magento\ConfigurableProduct\Model\Product\Type\Configurable::getConfigurableAttributesAsArray($product) function.
I made a VCT Simple Product URL module on Magento Marketplace that solves this problem.

Categories