CakePHP searchable tags - php

I have made an online shop for clothes and probably I need to make some tagging system.
The whole application is build on CakePHP and I need an idea for managing all the products, something similar to ebay.
For example to tag each product with it's price , type, producer, size , status
And for example some of them should be multi-searchable, to be able to search for an item with: price between $10 and $20, with size S or M

Have an attributes table that will basically act as a key/value storage and assign these attributes to each product.
Attributes itself could have an attribute_options table from where you can read the different available sizes for an attribute.
You'll then just have to search the attributes table and product table.

From what you describe, you shouldn't need any additional tables. Just add them as fields in the product table, and query based on that. It will be faster, more logically laid out...etc.
Your example would be searchable like below. (It seems a bit overkill, but will make any future finds really simple, and follows the fat controller, skinny model mantra:
//ProductsController
public function whatever() {
$opts = array(
'price_high' => 10,
'price_low' => 20,
'sizes' => array('S', 'M')
);
$this->Product->getProducts($opts);
}
//Product Model
public function getProduts($opts = null) {
//initialize variables
$params = array('conditions'=>array());
//size(s)
if(!empty($opts['sizes']) {
array_push($params['conditions'], array('Product.size'=>$opts['sizes']));
}
//price(s)
if(!empty($opts['price_high']) {
array_push($params['conditions'], array('Product.price <='=>$opts['price_high']));
}
if(!empty($opts['price_low']) {
array_push($params['conditions'], array('Product.price >='=>$opts['price_low']));
}
return $this->find('all', $params);
}

Related

Magento 2 - Visual Merchandiser Smart Categories with multi-select attributes

Why was support for multiselect attributes in Magento 2 explicitly removed from the collection of allowed attributes for the visual merchandisers "Smart Categories"? We had used this in Magento 1 but need a way to get this same functionality back in Magento 2.
This is where the attributes are explicitly filtered out from the collection.
vendor/magento/module-visual-merchandiser/Model/Config/Source/AllowedAttributes.php
I can remove this like so: (obviously I would do this via a plugin to actually implement this feature)
public function toOptionArray()
{
$entityTypeId = $this->type->loadByCode(\Magento\Catalog\Model\Product::ENTITY)->getId();
if ($entityTypeId) {
$collection = $this->attribute->getCollection()
->removeAllFieldsFromSelect()
->addFieldToSelect('attribute_code', 'value')
->addFieldToSelect('frontend_label', 'label')
->addFieldToFilter('entity_type_id', ['eq' = $entityTypeId]);
// removes multi: ->addFieldToFilter('frontend_input', ['neq' => 'multiselect']);
$attributes = $collection->toArray();
if (isset($attributes['items'])) {
$this->options = $attributes['items'];
}
}
return $this->options;
}
However I feel this is only the tip of the ice berg. What display logic is needed to properly parse these attributes and join the corresponding products to the collection?

How to match data from two different database tables and display it

I am looking for a solution which would withdraw essential data used to match other data from a different table. Then I would like to display this data inside a blade in a form of a table.
Inside the database, I have a "matching" entity which stores user's credentials which I would like to use for matching (for example desired price of the product). Entity contains "peopleID" as "matching" belongs to website users. When user is created, we assign matching options which are getting stored inside that "matching" entity. The number of rows inside Matching entity depends on the number of counties chosen during user creation stage.
I know that to withdraw matching data from the database I need to use a foreach loop.
The problem I have is when I output data inside the blade. For some reason it matches products only with the last item from an array. It should match prodtucts with all matching credentials.
Code:
$matchings = Match::where('PeopleID', '=', $id)->get();
$sales = DB::table('sales');
foreach ($matchings as $matching)
{
$county = $matching->county;
$sales->where('county', '=', $county);
}
$results = $sales->get();
So for one of the customers I have two matchings with different "counties". It only displays data for the last one added. How could I make it display data for other matching which contains a different county. I hope you know what I mean.
Thanks for any help.
Update - Major part of the code is done. Thank you for your help.
The second question is about adding the rest of matching options. As stated before the number of matches depends on the number of counties added. Each match has its own attributes. The idea is to show matched results for each county.
I know I will need some if statements to do this.
Here is an example which I would like to implement:
$maxprice = $match->maxprice;
$minprice = $match->minprice;
$brand_a = $match->brand_a;
$brand_b = $match->brand_b;
if($maxprice != '0')
{
$sales = DB::table('sales')->where('maxprice', '<=', $maxprice);
}
if($minprice != '0')
{
$sales = DB::table('sales')->where('minprice', '>=', $minprice);
}
if($brand_a == '1')
{
$sales = DB::table('sales')->where('brand_a', '1');
}
if($brand_b == '1')
{
$sales = DB::table('sales')->where('brand_b', '1');
}
To this code:
$user = User::find($id); // get our User with the Id (person id?)
$matches = $user->matches; // get all the matches
// or you could one line the above: $matches = User::find($id)->matches;
// get all the counties in the returned matches, could use pluck() method in higher version of laravel
$counties = [];
foreach($matches as $match) {
$counties[] = $match->county;
}
$results = DB::table('sales')->whereIn('county', $counties)->get();
Many Thanks for your help!
#update
Relationships:
Match:
public function people()
{
return $this->belongsTo('People', 'PeopleID', 'PeopleID');
}
People:
public function matches()
{
return $this->hasMany('Match', 'PeopleID', 'PeopleID');
}
I have a connection between those as Match holds people's "search" credentials. The first solution which you have provided works perfectly. Now, this solution filtered out sales by county which is a good move as now they need to be filtered by minimum and maximum price (minprice, maxprice) and other credentials such as brand_a and brand_b.
The idea of brand_a and brand_b:
Checkboxes are responsible for changing brand_a and brand_b value inside Matching. If these are checked the values inside Matching entity become '1'. If these are not checked they become '0' which means that sales don't have to be filtered out by those values.
Sales entity contains "Holdings" attribute. The value of "Holdings" can be brand_a or brand_b. Sales also contains "Price".
So, to make this clear:
Sale Entity contains: SaleID, Price, Holdings, County.
Holdings are values: brand_a or brand_b.
Price is just a number.
County is plain text.
Matching Entity contains: PeopleID, MinimumPrice, MaximumPrice, brand_a, brand_b, county.
PeopleID is a foreign key. We need to know which matching belongs to what user.
(there can be multiple matchings for one user depending on the number of counties chosen).
MinimumPrice and MaximumPrice are numbers.
brand_a and brand_b are the values (1 or 0) depending if the checkboxes were checked.
County is the name of a county.
Now, if person 1543 (peopleID = 1543) contains 3 matchings, each containing different search credentials.
1st:
PeopleID: 1543
MinimumPrice: 1000
MaximumPrice: 7000
brand_a: 0
brand_b: 1
county: county_a
2nd:
PeopleID: 1543
MinimumPrice: 2500
MaximumPrice: 10000
brand_a: 1
brand_b: 1
county: county_f
3rd:
PeopleID: 1543
MiniumPrice: 2000
MaximumPrice:9500
brand_a: 0
brand_b: 0
county: county_d
I need to match this data against the data that is inside the Sales. There can be over a 1,000 different sales with different prices etc. I just need to filter them and display Sales that are desired by the person based on person's matching.
I hope this better presents you the situation. Thanks.
In short, I belive you need to leverage Eloquent Relationships to easily retrieve the data you desire. Read up on relationships in the docs here: https://laravel.com/docs/4.2/eloquent#relationships.
I've made some assumptions so you may need to work the following into your actual setup. Also, I found it quite difficult to 100% understand your DB structure from your question, but from what I gather from your question your DB structure is like this:
User/Person *has many* Match
(Note: Name may be wrong, but you didn't mention what it's called in the question all I can see is the word "user" and "personId")
Match *belongs to* User/Person
Based on this I think you should set up your relationships like this:
User
class User extends Eloquent {
public function matches()
{
return $this->hasMany('Match');
}
//...
}
Match
class Match extends Eloquent {
public function user()
{
return $this->belongsTo('User');
}
//...
}
Then your code can look like this:
$user = User::find($id); // get our User with the Id (person id?)
$matches = $user->matches; // get all the matches
// or you could one line the above: $matches = User::find($id)->matches;
// get all the counties in the returned matches, could use pluck() method in higher version of laravel
$counties = [];
foreach($matches as $match) {
$counties[] = $match->county;
}
$results = DB::table('sales')->whereIn('county', $counties)->get();
A better approach to this issue (I think) would be to give County it's own entity then Match would have a county_id then you can use a has many through relationship if you can link up Match, County and Sales. You can read more about has many through in the docs here: https://laravel.com/docs/4.2/eloquent#has-many-through
Also, a side point... this part of your code:
$sales->where('county', '=', $county);
will just continuously add where statements to your query which I would imagine won't return anything if there's more than one.
Just to make this clearer, imagine you have 2 counties "county_1" and county "county_2", through your for loop your query would end up like this:
WHERE COUNTY = "county_1" // first loop
AND COUNTY = "county_2" // second loop
and as you can see a match cannot be two counties at one time! So you were probably looking for ->orWhere('county', 'county_value') after the first one was added, but a better approach is to use whereIn('county', $countiesArray) which you can pass an array you've built up, which is what I've done above.
Hope this helps! Let me know if it wasn't clear.
Edit
The best approach would be to establish relationships between the Sale and Match entities. As I still don't fully understand your database schema I can't advise so well on how you would approach that. If you gave some more details it may be possible.
Alternatively, you could approach the code by building up an array which you will use for applying conditions to your query. Consider your updated question with the four if statements, anytime you're repeating yourself like that, more often that not it can be simplified.
$filters = [
'maxprice' => $match->maxprice,
'minprice' => $match->minprice,
'brand_a' => $match->brand_a,
'brand_b' => $match->brand_b,
];
$salesQuery = DB::table('sales');
foreach($filters as $key => $value) {
if($value) {
$salesQuery->where($key, $value);
}
}
$results = $salesQuery->get();
As your conditionals are a bit stricter in your code from your question, you do to it like this instead:
foreach($filters as $key => $value) {
if ($value) {
if (in_array(['brand_a', 'brand_b'], $key)) {
$salesQuery->where($key, $value);
} else if($key === 'minprice') {
$salesQuery->where($key, '>=' $value);
} else if($key === 'maxprice') {
$salesQuery->where($key, '<=' $value);
}
}
}
the good thing about this approach is that you can easily add new conditionals via the filters array without having to write a new if statement and query/where code each time.
I'll stress this probably isn't the best approach, ideally you'd leverage Eloquent Relationships, but it may be a starting point if you can't figure that out right away.

Magento - Reassign catalog rules after modifying product attributes

I'm in the process of developing an extension for Magento 1.5.1.0, which allows me to add catalog price rules to products which quantity in stock is reduced to zero. I have added an attribute to my attribute-set called auto_discount_active. This attribute is my on/off switch which works as condition for my price rule.
I wrote an Observer that reacts on the events sales_order_place_after and catalog_product_save_before. It's task is to check wether to stock quantity of the current product has been changed and set my custom attribute to on or off.
The method which handles the catalog_product_save_before event works fine. After saving an article in the backend, the price rule becomes (in)active like it should. The code looks like following:
class Company_AutoDiscount_Model_Observer
{
public function updateAutoDiscount($observer)
{
/**
* #var Varien_Event
*/
$event = $observer->getEvent();
$product = $event->getProduct();
$data = $product->getStockData();
$discount = $data['qty'] < 1 ? true : false;
$attributes = $product->getAttributes();
$attribute = $attributes["auto_discount_active"];
if ($product->getAutoDiscountAllowed())
{
$product->setAutoDiscountActive($discount);
}
return $this;
}
}
Now I want to do the same thing, if someone places an order in my shop. That for I use the event sales_order_place_after which works so far. But after changing the custom attributes value, the price rules are not updated. My observer method looks like this:
public function updateAutoDiscountAfterOrder($observer)
{
/**
* #var Varien_Event
*/
$event = $observer->getEvent();
$order = $event->getOrder();
foreach ($order->getItemsCollection() as $item)
{
$productId = $item->getProductId();
$productIds[] = $productId;
$product = Mage::getModel('catalog/product')->setStoreId($order->getStoreId())->load($productId);
$data = $product->getStockData();
$discount = $data['qty'] < 1 ? true : false;
if ($product->getAutoDiscountAllowed())
{
$product->setAutoDiscountActive($discount);
$product->save();
}
Mage::getModel('catalogrule/rule')->applyAllRulesToProduct($productId);
}
return $this;
}
After placing an order and saving the bought article manually in the backend without changes, the price rule gets updated. But I have get the update working in my observer method.
What do I have to do to get the catalog price rule being assigned, after changing the custom attribute?
Thx in advance!
Okay, I want to advise you on some fairly major code optimisations.
You can reduce your collection size and remove the conditional logic inside your loop by using:
$order->getItemsCollection()->addFieldToFilter('is_in_stock', 0);
You could also update all the attributes with a much faster method than save(), by using:
Mage::getSingleton('catalog/product_action')
->updateAttributes($order->getItemsCollection()->addFieldToFilter('is_in_stock', 0)->getAllIds(), array('auto_discount_active' => 1), 0);
Also, bear in mind, you'll also need to apply your observer to any product stock level modification, ie. product save, import, credit memo (refund) - so its a fairly expansive area. You would probably be better served rewriting the stock class, as there isn't too many events dispatched that will give you enough scope to cover this.
Finally, to perform the assignation of rules, I would suggest extending the resource model for the rule (Mage/CatalogRule/Model/Mysql4/Rule.php) so that you can pass in your array of product ids (to save it iterating through the entire catalogue).
You could simply extend getRuleProductIds() to take a Mage::registry variable (if set) with your product ids from the collection above. Then after running the code above, you could just execute
Mage::getModel('catalogrule/rule')->load(myruleid)->save();
Which will re-index and apply rules to new products as necessary - for only the products that have changed.
I would imagine this method cutting overheads by an extremely significant amount.

CakePHP having trouble saving data (HABTM)

I am trying to create wish lists.
There are user and product models. A user has a wish list. A wish list has many products.
The reason I am making it user has wishlist and wishlist has products is so I can have a url like wish_lists/add/:product_id
I created a table called wish_lists with id, user_id, and name.
I also created a table called products_wish_lists with wish_list_id and product_id.
I made here is the wishlists controller:
class WishListsController extends AppController
{
var $hasOne = 'User';
var $hasMany = 'Product';
function beforeFilter()
{
parent::beforeFilter();
$this->Auth->deny('add');
}
function add($id)
{
$user = $this->Session->read("Auth.User");
$this->WishList->set(array(
'User.id' => $user['id'],
'Product.id'=>$id,
'WishList.name'=>'default'
));
if($this->WishList->save())
{
$this->Session->setFlash('This product has been added to your wishlist.', 'flash_good');
}
else
{
$this->Session->setFlash('Error: This product was not added to your wishlist.', 'flash_bad');
}
$this->redirect(array("controller"=>"products","action"=>"view",$id));
}
}
When I go to localhost/wish_lists/add/1 It tells me everytime that it saved. but no data is being added to the database.
Not sure what I am doing wrong?
I never do it that way, I always build a $data array to pass as parameter one to the save function.
I'm not sure, therefore, whether that syntax will allow you to specify the model as you have done, i.e. 'Model.field'. In any case, $this->WishList->save() will only save the Wishlist part.
Better, in my opinion, would be:
$saveData = array(
'User'=>array('id'=>$user['id']),
'Product'=>array('id'=>$id),
'WishList'=>array('name'=>'default'));
$this->WishList->saveAll($saveData);
(or something like that, I've been programming ColdFusion for the last three months and my PHP can be a bit addled)
you are setting the array wrong. it should be $data['User']['id'] = 123; $data['Product']['id'] = 321;
$this->Wishlist->saveAll($data);
There is no point saving the name as that can be found from the product table.
you can have a look at the code here for more ideas https://github.com/Infinitas-Plugins/shop
there is a generic component method in the following link that saves products to the cart or wishlist (different db's) as its pretty much the same thing.
https://github.com/Infinitas-Plugins/shop/blob/master/controllers/components/shop.php#L62

Best way to store this data in a variable?

Some quick background info: I'm coding up a site which matches books to the classes they're required for.
I have two pieces of data that I need to represent in my code-- which books go with which classes, and the data (titles, authors, pricing, etc.) on these books.
Currently I represent this all with two arrays: $classArray, and $Books_data.
The advantage of this approach over a one-variable approach is that I don't repeat myself-- if a Book is required multiple times for different classes, only the ISBN needs to be stored in the $classArray and I can store the data in the $Books_array. This advantage is especially poignant because I have to query the pricing data from API's on the fly. If I only had a $classBooksArray, I'd have to loop the query responses into a big array, repeating myself (seemingly) unnecessarily.
The disadvantage of this approach is that these variables follow each other almost everywhere like Siamese twins. Nearly every function that needs one, needs the other. And my coding spidey sense tells me it might be unnecessary.
So, what would be the best way to store this data? Two arrays, one array, or some other approach I haven't mentioned (e.g. passing by reference).
Why not an associative which has two keys - one pointing to an array of classes, one to store Books
data?
$allData = array("classes" => &$classArray, "books" => &$Books_data);
That way you're only passing around 1 variable (less clutter) but retain all the benefits of separate data stores for books and classes.
Though, to be honest, if it's just TWO sets of data, so IMHO your spidey sense is wrong - passing both as separate parameters is perfectly fine. Once you get into a set of siamise sextuplet variables, then the above approach starts to actually bring benefits.
A multidimensional array.
$collection = array(
'classes' => array( /* Contents of $classArray */),
'books' => array( /* Contents of $Books_data */)
);
function some_function($collection) {
// looping over books
foreach ($collection['books'] as $book) {
// yadda yadda
}
}
Or better yet a class:
/* Define */
class Collection {
private $books;
private $classes;
public function __construct($classes = array(), $books = array()) {
$this->books = $books;
$this->classes = $classes;
}
public function addBook($book) {
$this->books[] = $book;
}
public function addClass($class) {
$this->classes[] = $class;
}
public function get_classes() {
return $this->classes;
}
public function get_books() {
return $this->books;
}
}
function some_function(Collection $col) {
// looping over books
foreach ($col->get_books as $book) {
// yadda yadda
}
}
/* Usage */
$collection = new Collection(); // you also could pass classes and books in the
// constructor.
$collection->addBook($book);
somefunction($collection);
If your datas were a database, your current proposal being a normal form would be canonical. The two variables would just become tables and ISBN a foreign key to books table (with a third table as a class has several books). I would probably stick with the current implementation as it will be very easy to transform to database when that will be necessary (and it usually happens faster than expected).
EDIT: a comment, say it is already in a database... what I do not understand is why you would want to store a full database in memory instead of just keeping what is necessary for the current task.
Let's be OO and put the arrays into an object. Define a class with those properties, load it up, and call its methods. Or, if you must have other functions operating with the object's data, pass the instance around. Disallow direct access to the data, but provide methods for extracting the salient info.
class book_class_association {
protected $books_to_classes = array();
protected $classes_to_books = array();
function __construct() {
$this->books_to_classes = array(
'mathbook1' => array('math'),
'mathbook2' => array('math'),
);
$this->classes_to_books = array(
'math' => array('mathbook1', 'mathbook2'),
);
}
function classes_for_book( $class_name ) {
return $this->books_to_classes[$class_name];
}
function books_for_class( $class_name ) {
return $this->classes_to_books[$class_name];
}
}
Is there a reason you are not storing this data in a database and then querying the database? It is a many to many relationship, and you would need 3 tables - class , book, and class_book_intersection.
So for example, you ui could have "select class" from a list, where the list is derived from the rows in class.
Then if the class id selected is 123. The query would then be something like:
Select book.title, book.cost
from book
inner join class_book_intersection
on
class_book_intersection.classid = 123 and
class_book_intersection.bookid = book.bookid

Categories