I need to render a view with mixed data in it with a pagination limit of 6 items per page.
In this case I have a Controller that renders a view and passes an array of mixed data such as an array of Catalogs and Products.
$products = new Products;
$productsCriteria = new CDbCriteria;
$catalogs = new Catalogs;
$catalogsCriteria = new CDbCriteria;
$productsCriteria->condition = "status = 1 AND category_id = :category_param";
$productsCriteria->params = array(':category_param' => $id); //$id is passed as a parameter to the action
$productsCriteria->order = "created_on DESC";
$catalogsCriteria->condition = "status = 1 AND category_id = :category_param";
$catalogsCriteria->params = array(':category_param' => $id);
$catalogsCriteria->order = "created_on DESC";
$productsCount = $products->count($productsCriteria);
$catalogsCount = $catalogs->count($catalogsCriteria);
$pages = new CPagination($productsCount + $catalogsCount);
$pages->pageSize = 6;
$pages->applyLimit($productsCriteria);
$pages->applyLimit($catalogsCriteria);
$productResult = $products->findAll($productsCriteria);
$catalogResult = $catalogs->findAll($catalogsCriteria);
$result = array_merge($productResult, $catalogResult);
$this->render('list', array('data' => $result, 'pages' => $pages, 'productsCount' => $productsCount, 'catalogsCount' => $catalogsCount));
This works as it is, but it renders 6 items of each type. It renders 6 Catalogs and then it renders 6 Products per page. What I need is that it renders all of the Catalogs first, 6 per page, regardless of how many pages it needs and then it renders all of the Products, again, 6 per page and regardless of how many pages it needs.
On another note, the Products and Catalogs are in a many to many relationship. I've also tried extracting Products the following way:
$productsCriteria->join = ", catalog_product_relation cpr WHERE status = 1 AND category_id = :category_param AND cpr.product_id != t.id";
I used the
cpr.products_id != t.id
since I need to list Products that don't belong to any Catalog.
I used join since it only appends the string to the criteria without adding any MySQL tokens like condition does.
Merging the two criteria doesn't work since they're different data models.
Related
this is what I'm struggingling with: https://github.com/blueimp/jQuery-File-Upload
I came across this topic which is simarlar to my problem:
I have some products where you should be able to associate more than one image.
I have 3 tables
products ------------------ img_connecter ------------------- images
p_id p_name fk_p_id fk_i_id i_id i_name
1 Car 1 1 1 car_1.jpg
2 Bus 1 2 2 car_2.jpg
3 Truck 2 3 3 bus_1.jpg
.
This is the code from the UploaderHandler.php file:
protected function get_file_objects($iteration_method = 'get_file_object') {
$upload_dir = $this->get_upload_path();
if (!is_dir($upload_dir)) {
return array();
}
return array_values(array_filter(array_map(
array($this, $iteration_method),
scandir($upload_dir)
)));
}
The scandir function is showing all the images in the folder so i was trying to change it to:
protected function get_file_objects() {
$files = $this->query(" SELECT i_name FROM images i
LEFT JOIN img_connector c ON (c.fk_i_id = i.i_id)
LEFT JOIN products p ON (c.fk_p_id = p.p_id)
WHERE p_id = 1");
$files_array=array();
while($row = $files->fetch()){
array_push($files_array, $row['i_name']);
}
return array_values( array_filter( array_map(
array($this, 'get_file_object'),
$files_array
) ) );
}
The query where it says "WHERE p_id = 1"
1 is going to be a $_GET[] variable. For now I'm just giving it a static number.
I cant figure out why it dosent show the images that the query is pointing too ??
Am I missing something ?
You are only selecting columns id and name (which don't seem to be mentioned in your DB schema above) but then you're referring to a row called i_name in your result set. The query you're looking for is this one:
$files = $this->query("SELECT i_name FROM images i
LEFT JOIN img_connector c ON (c.fk_i_id = i.i_id)
LEFT JOIN products p ON (c.fk_p_id = p.p_id)
WHERE p_id = 1
");
So, next you're (very verbosely) sticking them into an array. Assuming you took my advice and went with PDO for database access, doesn't hurt to grab all the filenames at once:
$files_array = $files->fetchAll(PDO::FETCH_COLUMN);
Then you're passing every instance of the array through $this->get_file_object(), removing any empty ones, and returning the remaining values.
return array_values(
array_filter(
array_map([$this, 'get_file_object'], $files_array)
)
);
So the only thing left to check is what get_file_object is doing. The only instance under which it will return false is if the file doesn't exist in the upload directory. So, ensure the file is where it should be.
All this presupposes that you actually have a valid database connection; doing a var_dump($files_array) should tell you that pretty quickly.
I'm trying to filter some car parts depending on the categories they are related to.
A part can have many categories (in the code they are called tags), so I chose the HABTM relation with a join table.
Filtering works so far, but only with an OR condition with cake using the SQL command IN.
But I'm trying to filter only the parts that have all the selected categories, so I need to use an AND condition on the category array.
Here's the extracted code from the controller:
$this->Part->bindModel(array('hasOne' => array('PartsTagsJoin')));
$params['conditions'] = array('AND' => array('PartsTagsJoin.tag_id' => $selectedCats));
$params['group'] = array('Part.id');
$parts = $this->Part->find('all',$params);
$this->set('parts',$parts);
$selectedCats is an array like this: array(1,2,3,4,5);
The SQL output is:
'SELECT `Part`.`id`, `Part`.`name`, `Part`.`image`, `Part`.`image_dir`, `Part`.`time_created`, `Part`.`time_edited`, `Part`.`user_id`, `Part`.`editor_id`, `Part`.`notice`, `User`.`id`, `User`.`username`, `User`.`password`, `User`.`usergroup_id`, `User`.`realname`, `PartsTagsJoin`.`id`, `PartsTagsJoin`.`part_id`, `PartsTagsJoin`.`tag_id`
FROM `c33rdfloor`.`parts` AS `Part`
LEFT JOIN `c33rdfloor`.`users` AS `User` ON (`Part`.`user_id` = `User`.`id`)
LEFT JOIN `c33rdfloor`.`parts_tags_join` AS `PartsTagsJoin` ON (`PartsTagsJoin`.`part_id` = `Part`.`id`)
WHERE `PartsTagsJoin`.`tag_id` IN (1, 4, 8, 24)'
How can I filter the parts that have every id that is committed through the $selectedCats Array.
Thank you in advance for your help.
I've got it working thanks to this blog post:
http://nuts-and-bolts-of-cakephp.com/2008/08/06/habtm-and-join-trickery-with-cakephp/
It seems to be a little tricky to filter entries with all selected tags:
The key in achieving an AND relation is to get the count of the selected cats and match it with the ones of the query inside the group parameter.
This line did it:
$params['group'] = array('Part.id','Part.name HAVING COUNT(*) = '.$numCount);
In the End the code looked like this (for people interested in a solution):
// Unbinds the old hasAndBelongsToMany relation and adds a new relation for the output
$this->Part->unbindModel(array('hasAndBelongsToMany'=>array('PartsTag')));
$this->Part->bindModel(array('hasOne'=>array(
'PartsTagsJoin'=>array(
'foreignKey'=>false,
'type'=>'INNER',
'conditions'=>array('PartsTagsJoin.part_id = Part.id')
),
'PartsTag'=>array(
'foreignKey'=>false,
'type'=>'INNER',
'conditions'=>array(
'PartsTag.id = PartsTagsJoin.tag_id',
'PartsTag.id'=>$selectedCats
)))));
$numCount = count($selectedCats); // count of the selected tags
// groups the entries to the ones that got at least the count of the selected tags -> here the 'AND' magic happens
$params['group'] = array('Part.id','Part.name HAVING COUNT(*) = '.$numCount);
$parts = $this->Part->find('all', $params); // sends the query with the params
$this->set('tagCategories', $categories);
$this->set('selectedCats', $selectedCats);
$this->Part->recursive = 4;
$this->set('parts',$parts);
Currently I am creating a news section for a website.
I have created it so that the news type is separated by a string E.g. category = ,2,4,6,
I don't normally use strings a whole lot, and my initial thought was to do something like this:
$query_listelements = "SELECT * FROM newsitems WHERE released = 1 AND (category = 2 OR category = 4 OR category = 6) ORDER BY date_rel DESC";
Clearly this won't work as I need to isolate / expand the string. so it needs to be something like:
$query_listelements = "SELECT * FROM newsitems WHERE released = 1 AND (category = strval(",2,") OR category = strval(",4,") OR category = strval(",6,")) ORDER BY date_rel DESC";
I don't think the above is the right way to go out things either.
Any thoughts would be really valued!
You may use it like below, sounds better to me.
$category= array ( 2, 4, 6);
$category = implode(',', $category); //Now it is a string like '2,4,6'
$query_listelements = "SELECT * FROM newsitems WHERE released = 1 AND (category IN ($category)) ORDER BY date_rel DESC";
Please see the data tables and query below ..
Items
Id, Name
1, Item 1
2, Item 2
Categories
Id, Name, Parent ID
1, Furniture , 0
2, Tables, 1
3, Beds, 1
4, Dining Table, 2
5, Bar Table, 2
4, Electronics, 0
5, Home, 4
6, Outdoors, 4
7, Table lamp, 4
ItemCategory
ItemId, CategoryId
1, 2 .. Row1
2, 4 .. Row 2
2, 5 .. Row 3
ItemCategory table stores which items belongs to which category. An item can belong to top level and or sub category. there are about 3 level deep categories, that is, Tob level, sub level, and sub sub level.
Users select all of the categories they want to view and submit and I can query the database by using a sample query below..
SELECT * FROM items i INNER JOIN ItemCategory ic ON
ic.itemId = i.itemId AND ic.itemId IN ('comma separated category ids')
This works fine.
My question is that Is it possible to view all the items under a top level category even though it has not been directly assigned to the item. For example, if users select Furniture above, then it lists all the items belonging to its sub categories (even though the ItemCategory doesn't contain any record for it)??
I'm open to making necessary amendements to the data table or queries, please suggest a solution. Thank you.
Watcher has given a good answer, but I'd alter my approach somewhat to the following, so you have a structured recursive 2-dimensional array with categories as keys and items as values. This makes it very easy to print back to the user when responding to their search requirements.
Here is my approach, which I have tested:
$items = getItemsByCategory($topCategory);
//To print contents
print_r($items);
function getItemsByCategory($sid = 0) {
$list = array();
$sql = "SELECT Id, Name FROM Categories WHERE ParentId = $sid";
$rs = mysql_query($sql);
while ($obj = mysql_fetch_object($rs)) {
//echo $obj->id .", ".$parent." >> ".$obj->name."<br/>";
$list[$obj->name] = getItems($obj->id);
if (hasChildren($obj->id)) {
array_push($list[$obj->name],getItemsByCategory($obj->id));
}
}
return $list;
}
function getItems($cid) {
$list = array();
$sql = "SELECT i.Id, i.Name FROM Items p INNER JOIN ItemCategory ic ON i.id = ic.ItemId WHERE ic.CategoryId = $cid";
$rs = mysql_query($sql);
while ($obj = mysql_fetch_object($rs)) {
$list[] = array($obj->id, $obj->name);
}
return $list;
}
function hasChildren($pid) {
$sql = "SELECT * FROM Categories WHERE ParentId = $pid";
$rs = mysql_query($sql);
if (mysql_num_rows($rs) > 0) {
return true;
} else {
return false;
}
}
Hope this helps.
With recursion, anything is possible:
function fetchItemsByCat($cat, &$results) {
$itemsInCat = query("SELECT Items.Id FROM Items INNER JOIN ItemCategory ON ItemCategory.ItemId = Items.Id WHERE CategoryId = ?", array($cat));
while($row = *_fetch_array($itemsInCat))
array_push($results, $row['Id']);
$subCategories = query("SELECT Id FROM Categories WHERE Parent = ?", array( $cat ));
while($row = *_fetch_array($subCategories))
$results = fetchItemsByCat($row['Id'], $results);
return $results;
}
$startCat = 1; // Furniture
$itemsInCat = fetchItemsByCat($startCat, array());
The function is somewhat pseudo-code. Replace *_fetch_array with whatever Database extension you are using. The query function is however you are querying your database.
Also, this is untested, so you should test for unexpected results due to using an array reference, although I think it's good to go.
After calling the function, $itemsInCat will be an array of integer ids of all of the items/subitems that exist in the given start category. If you wanted to get fancy, you can instead return an array of arrays with each 2nd level array element having an item id as well as that item's assigned category id, item name, etc.
If you use MySQL, you're out of luck short of indexing your tree using typical techniques, which usually means pre-calculating and storing the paths, or using nested sets:
http://en.wikipedia.org/wiki/Nested_set_model
If you can switch to PostgreSQL, you can alternatively use a recursive query:
http://www.postgresql.org/docs/9.0/static/queries-with.html
Evidently, you can also recursively query from your app, but it's a lot less efficient.
I'm having some problems with creating pagination with a HABTM relationship. First, the tables and relationships:
requests (id, to_location_id, from_location_id)
locations (id, name)
items_locations (id, item_id, location_id)
items (id, name)
So, a Request has a Location the request is coming from and a Location the Request is going to. For this question, I'm only concerned about the "to" location.
Request --belongsTo--> Location* --hasAndBelongsToMany--> Item
(* as "ToLocation")
In my RequestController, I want to paginate all the Items in a Request's ToLocation.
// RequestsController
var $paginate = array(
'Item' => array(
'limit' => 5,
'contain' => array(
"Location"
)
)
);
// RequestController::add()
$locationId = 21;
$items = $this->paginate('Item', array(
"Location.id" => $locationId
));
And this is failing, because it is generating this SQL:
SELECT COUNT(*) AS count FROM items Item WHERE Location.id = 21
I can't figure out how to make it actually use the "contain" argument of $paginate...
Any ideas?
after 3 days searching, I found the way
var $paginate = array('Post'=>array('group'=>'Post.id'));
It's recomended to add group, because sometimes we will get duplicte posts in different categories
$this->Post->bindModel(array('hasOne'=>array('CategoriesPost')), false);
$out = $this->paginate('Post', array('CategoriesPost.category_id'=>array(1,4,7,6)));
Add false to use bind model to all queries, not only to the following
To paginate HABTM, you need to temporarily bind 'hasOne' join model to model which you paginate:
// prepare to paginate Item
$this->Item->bindModel(array('hasOne'=>array('ItemsLocation')));
$contain['ItemsLocation']=array();
$conditions[]=array('ItemsLocation.location_id'=>$locationId);
$order = array('Item.created' => 'desc'); // set order
...
$items = $this->paginate('Item', compact('conditions','contain','order'));
I've been able to get it working somewhat, but the solution doesn't feel very cakey at all.
$items = $this->paginate(
$this->Request->ToLocation->Item,
array(
"Item.id IN ("
. "SELECT item_id FROM items_locations "
. "WHERE location_id = " . $locationId
. ")"
)
);