I am working on a data driven menu system in PHP /MySQL. I can't figure out how to delete menu items without leaving some of them orphaned.
All top level menu items have a zero (0) parent id value indicating that they are top level. My gridview displays all menus, top level and sub menu items and it allows multiple selection for delete.
The problem is that if one of the items selected in the gridview for delete is a top level menu item, all sub menus under it will become orphaned.
What is the general logic I need to implement?
Simply delete the child items when you delete some item. If you only have a 2 levels of depth this shouldn't be too much of a problem. If you can have X levels, then you'll have to recursively delete every child element for every element you delete.
The below class will work with as many childs as you can create(infinit)... so considering your mysql tabel is structered as(id,parent,name), all you need is a function that gets all items from current level, loop through each item and call recursively the function again to get child items for current loop id, each time keeping the ids found in an array to delete later, below is the full code in which I accomplished it using a class, but it can be done with a global array and function also.
//full class below comprising of 2 methods(functions)
class menu_manager{
//this is the function called with id, to initiate the recursive function below
private function remove_menu_item($id){
$ids_to_delete ="";
//Zero global arrays for more than one call for this function get child menus id first in a an array
$this->child_items_ids = array(0 => $id);
$this->incrementby_one=0;
//call recursive function with $id provided
$this->get_array_of_child_ids($id);
//then createw ids for mysql IN Statment, foreach will create - 1,10,25,65,32,45,
foreach($this->child_items_ids as $k=>$v ) $ids_to_delete.=$v.",";
//Then we wrap it in around "(" and ")" and remove last coma to provide - (1,10,25,65,32,45)
$ids_to_delete="(".substr($ids_to_delete, 0, -1).")";
//then we Delete all id in one query only
$remove = $this->db->query("DELETE FROM menu WHERE id IN $ids_to_delete ");
if(!$remove) return false;
else return true;
}
/*this is the function that will be called as many times as a child is found,
this function is called inside of itself in the query loop*/
private function get_array_of_child_ids($id){
$query = $this->db->query("SELECT id,label,parent FROM menu WHERE parent='".$id."' ");
if($query){
if($query->num_rows > 0) { // if found any items
while($list = $query->fetch_assoc()){ // we loop through each item
//increments array index by 1
$this->incrementby_one += 1;
//place current id in the array
$this->child_items_ids[$this->incrementby_one] = intval($list["id"]);
//and we call this function again for the current id
$this->get_array_of_child_ids($list["id"]);
} // while closing
} // second if closing
} //first if closing
} // recursive function closing
} // class closing
//to call the class you need:
$delete_items = new menu_manager;
$delete_items->remove_menu_item($id); //$id is the id for the item to be removed
Related
I am having difficulty sorting my data results alphabetically when matching them with the User that has placed the item in their "Locker".
I have two queries; the first one searches the database for all of the items that the user placed in their 'locker', and the second query pulls the details of the item and sorts them into a list by which brand the items are.
I feel like there is a better way to do this rather than forcing the page to run the query once for each item, but am not sure the proper way to write out the mySQL in the most efficient way that works.
I think the solution would be to pull all IDs as an array, then somehow search and sort all of their associated brands in the second query.
I currently have:
//$lockerid is pulled earlier in the code based on which locker number is associated with this user
// Pull all of the items and their ids that are in this users locker
$userlockerquery= mysql_query("SELECT DISTINCT item_id FROM lockers WHERE user_id = '$profile_userid' AND locker_id ='$lockerid' ");
while($lockeritems=mysql_fetch_array($userlockerquery)){
$indi_item=$lockeritems[item_id];
$lockeritemdetails = mysql_query("SELECT DISTINCT brand FROM inventory WHERE id = '$indi_item' ");
$brands=mysql_fetch_array($lockeritemdetails );
$brandname=$brands[brand];
echo '<div>'.$brandname.'</div>';
}
Although the results do show up with all of the brands, My problem seems to be that since the query is ran once for each items id, it cannot have the list results talk to each other, and thus cannot have them ordered by ASC alphabetically, since the query is ran once per each item.
Also because of this, the DISTINCT flag does not have any effect, since it is not matching against any other results.
As an example, my results would return in divs in order of ID instead of brand, and repeating:
Nike
Puma
Puma
Converse
Rather than
Converse
Nike
Puma
Adding the ORDER BY flag to the second query did not help, so I figured I would try to ask here for some ideas. Please let me know if any other details are needed!
Maybe try something like this class. See if it will work for your needs. It's hard to check it without trying the sql queries, but provided I've written it properly, it should work.
class MyLocker
{
// Protected means that you can't use this variable outside of the functions/class
// so you can not use $myLocker->_array; It will throw an error
protected $_array;
// Construct is basically used as an auto-function. It will execute automatically
// when you create a new instance of the class so as soon as you do this:
// $myLocker = new MyLocker($_locker); you initiate the __construct
// When you label as public, you allow it to be used outside of itself
public function __construct($_array)
{
// When you set this variable, it is now open to use in all
// other functions in this class.
$this->_array = $_array;
}
// This is the method that will do everything
public function LockerContents()
{
// Loop through query. Since the $_array was set in the __construct
// it is available in this function as $this->_array
while($lockeritems = mysql_fetch_array($this->_array)){
// $brand is something we want to use in other functions but not
// outside the class so it is set here for use in the Fetch() function
$this->brand = $lockeritems['item_id'];
// We ant to use our Fetch() function to return our brand
$_brand = $this->Fetch();
// If brand available, set it to an array
if(!empty($_brand))
$array[] = $_brand;
}
if(isset($array)) {
// Sort the array
asort($array);
// Finally, we use the Display() function for the final output
$this->Display($array);
}
else { ?>
<div>Locker is empty.</div><?php
}
}
// Establish this as an in-class variable
protected $brand;
// Establish this as a public function incase we want to use it by itself
// To do so you would write $myLocker->Fetch(); outside of the class.
// Since you need $brand for this function to work, you would need to turn
// $brand from "protected" to "public" and write $myLocker->brand = 'whatever';
// before you run the $myLocker->Fetch();
public function Fetch()
{
$query = mysql_query("SELECT DISTINCT brand FROM inventory WHERE id = '".$this->brand."'");
$brands = mysql_fetch_array($query);
// Return brand
return (isset($brands['brand']))? $brands['brand']:"";
}
protected function Display($array)
{
if(is_array($array)) {
foreach($array as $object) { ?>
<div><?php echo $object; ?></div><?php
}
}
}
}
// You should be using mysqli_ or PDO for your db connections/functions.
$_locker = mysql_query("SELECT DISTINCT item_id FROM lockers WHERE user_id = '$profile_userid' AND locker_id ='$lockerid' ");
// If there are more than 0 rows, create locker.
if(mysql_num_rows($_locker) > 0) {
// Create new instance of the locker app
$myLocker = new MyLocker($_locker);
// Display the results
$myLocker->LockerContents();
}
I have a custom joomla MVC component.
The component has a table of items,
and a table of bids to deliver those items.
I have figured out how to display a list the relevant 'bids' on the view for an 'item' by adding this to the item model:
public function getBidsById($id) {
$db = JFactory::getDbo();
$query = $db->getQuery(true);
$query
->select('*')
->from('#__entrusters_bids ')
->where('item_id = ' . $id);
$db->setQuery($query);
$db->query();
return $db->loadObjectList();
}
This to the views/item/view.html.php:
$model = $this->getModel('Item', 'EntrustersModel');
$itemID = jRequest::getVar('id');
$bidsOnItem = $model->getBidsById($itemID);
$this->bidsOnItem = $bidsOnItem;
And e.g. this to the views/item/tmpl/default.php:
var_dump($this->items_by_id);
That all works fine.
Here's the actual question:
Now i need to show a COUNT of the bids on the LIST view (items - with an s) within each row of the foreach.
What is the BEST way of achieving this? I have tried adding the same to the items model and views/items/view.html.php, then I attempted something like this in the list:
<ul>
<?php foreach ($this->items as $item) : ?>
<li>Item stuff about the item. This item has <?php echo count($item->bidsOnItem); ?> bids</li>
<?php endforeach; ?>
</ul>
But that just returns 0 - not the count of bids on each item in the list. Grateful for help from the MVC experts out there.
You've gone and got yourself mixed up there. For a start to assign variables to a template, you need to do the following:
$bar = 'bar';
$this->assignRef('foo', $bar);
Also, your query returns a list of items rather than a multi-dimensional array of items. To get the count of bids by item, you need to run a separate query for each item
Eg.
Model
class SomethingModelTest extends JModelLegacy {
function getItemList() {
// query goes here
// return results
}
function getBidsByItemId($itemId) {
// query goes here
// return results
}
}
View
class SomethingViewTest extends LegacyView {
public function display() {
$items = $this->get('ItemList');
foreach ($items as &$item) {
$item->bids = $this->getModel()->getBidsByItemId($item->itemId);
}
$this->assignRef('items', $items);
parent::display();
}
}
And then use your for loop and instead of print count($blah); just use print $item->bids.
I have rephrased this question (hopefully in a more useful way) here: Add a count of related data to each item in a joomla MVC list view
Trying to get child of a specific category which is active. Please help. I am having trouble doing it. I'm currently able to show them all but not specifically. Would appreciate any help.
$category = Mage::getModel('catalog/category')->load(2);
$category->getChildCategories();
$tree = $category->getTreeModel();
$tree->load();
$ids = $tree->getCollection()->getAllIds();
here is code to load active category
/* Load category by id*/
$cat = Mage::getModel('catalog/category')->load($id);
/*Returns comma separated ids*/
$subcats = $cat->getChildren();
//Print out categories string
#print_r($subcats);
foreach(explode(',',$subcats) as $subCatid)
{
$_category = Mage::getModel('catalog/category')->load($subCatid);
if($_category->getIsActive())
{
$caturl = $_category->getURL();
$catname = $_category->getName();
if($_category->getImageUrl())
{
$catimg = $_category->getImageUrl();
}
echo '<h2><img src="'.$catimg.'" alt="" />'.$catname.'</h2>';
}
}
?>
hope this is sure help you.
As mentioned by mhaupt, it is faster to load a collection rather than each category in a loop. But, as far as I am concerned, there is no need to manually load the child categories. Basically this is what $category->getChildrenCategories() already does.
There is also a filter to get active categories only. Just call addIsActiveFilter() on the collection.
a.) Load active child categories via getChildren()
// 1. Get a list of all child category ids (e.g "12,23,11,42")
$subcategoryIds = $category->getChildren();
// 2. Create collection
$categoryCollection = Mage::getModel('catalog/category')->getCollection();
// 3. Add all attributes to select, otherwise you can not
// access things like $cat->getName() etc.
$categoryCollection->addAttributeToSelect('*');
// 4. Filter by ids
$categoryCollection->addIdFilter($subcategoryIds);
// 5. Add filter to collection to get active categories only
$categoryCollection->addIsActiveFilter();
b.) Load active child categories with getChildrenCategories()
// 1. Load collection
$categoryCollection= $category->getChildrenCategories();
// 2. Add filter to collection to get active categories only
$categoryCollection->addIsActiveFilter();
The collection will be loaded form the database as soon as it is accessed. If the collection is not loaded and $subcategories->count() is called only a "SELECT count(*)" will be fired against the database (in contrast to count($subcategories) which will force the collection to load itself).
Iterating the collection
foreach($categoryCollection as $category) {
echo $category->getName();
}
If you add more filters to the collection after accessing it, the collection will not load itself again automatically. To apply changes to the collection, just call $categoryCollection->load() to reload the collection from the database.
Those who are saying to use getAllChildren() instead of getChildren() are simply wrong.
Both methods return the exact same thing, with one difference, getAllChildren(true) will return an array instead of a comma delimited string. getAllChildren($bool asArray) defaults to false. My point being that either way you're going to have to use
Mage::getModel('catalog/category')->load($catId);
inside of a loop unless you use the function below.
private function fetchCatsById($onlyThese)
{
$cats = Mage::getModel('catalog/category')
->getCollection(true)
->addAttributeToSelect('*')
->addIdFilter($onlyThese)
->addAttributeToFilter('level','2')
->addIsActiveFilter();
return $cats;
}
$cats = $this->fetchCatsById($onlyThese);
The one answer liyakat wrote, should not be used in professional shops, because it raises a performance issue, because of the multiple n time loads of the category object, rather use the collection of categories for that, get all children
$cat->getAllChildren()
, then limit the category collection by the needed category ids like
$coll->addIdFilter($idFilter);
then you won't have to load n times against the database.
Please do keep in mind that loads within loops are one of the most often used bad code examples in any Magento projects and to avoid them!
Hello you will see below code
$category_model = Mage::getModel('catalog/category');
$_category = $category_model->load(13);
$all_child_categories = $category_model->getResource()->getAllChildren($_category);
print_r($all_child_categories);
If you want any number of subcategories of parent category than Click here http://magentoo.blogspot.com/2014/01/get-all-subcategories-of-parent-category-magento.html
What I am trying to do is write a "search" class that can search for a list of products and store them in an array.
I already have a "product" class that can be used to get the details of a specific product.
Here is my code:
class Product {
public $name;
public $price;
public $description;
public function getProductById ($id) {
$sql = 'SELECT name, price, description FROM product WHERE id = ' . $id;
$row = /* MySQL functions here to execute SQL statement and get a matching row */
$this->name = $row['name'];
$this->price = $row['price'];
$this->description = $row['description'];
return TRUE;
}
}
class Search {
public $results;
public $totalResults;
function __construct() {
$this->results = array ();
$this->totalResults = 0;
}
public function doSearch ($name) {
$sql = 'SELECT id FROM product WHERE name LIKE "%' . $name . '%"';
$rows = /* MySQL functions here to execute SQL statement and get a list of matching product ID's */
foreach ($rows as $row) {
$product = new Product;
$product->getProductById ($row['productid']);
$this->results[] = $product;
}
return TRUE;
}
}
$search = new Search;
$search->doSearch ('Fresh Flowers');
The problem with the above is that every matching record in the doSearch method will execute a query in the getProductById method. If there are 100 matching products, there will be 100 individual queries carried out in the Product class.
However, if I get the products directly in the doSearch method using a single query, this will then bypass the Product class altogether.
When a "product" is an object, what's the most appropriate way to write a search class that can return a list of "product" objects without the overhead of what I'm doing above?
Add a constructor to the Product class which takes name, price and description as parameters (or an assoziative array), to populate the object with the necessary values, decoupled of the database query.
Within doSearch, you can then create a SELECT which not only gets the ID but all relevant fields from the products table, and create the populated product objects immediately with new Product($name, $price, $description) or new Product($row), without calling getProductById for each product.
Create a class that populates instances of Product with data from the database.
The class can then create one or multiple instances of the Product class depending on how much data is being fetched.
Conclusion: Extract the getProductById from your Product class and put it somewhere else. It is a specialised method that only populates one instance.
Just grab what you want in the first place.
public function doSearch ($name) {
$sql = 'SELECT id, name, price, description FROM product
WHERE name LIKE "%' . $name . '%"';
// now just return the array
}
Or use PDO to return result sets as objects.
$result->setFetchMode(PDO::FETCH_INTO, new animals);
as discussed here: How can I simply return objects in PDO?
Your Product class shouldn't know anything about a database. It should contain the values representing a product, nothing more. Extract all stuff dealing with the database out of this class.
Searching for products is one way to access a list of products. Your next class should be a list of products then. Only when accessing one single product you'd not have to deal with a list, but this is probably less often than you think.
Ok, you have the product and the list of products, you now can go one step forward and add database access. You need a class that deals with giving you both one product (when searching by id) and lists of products (when searching by some text or other stuff). Only this class allows you to deal with the queries needed to access the database. The result sets of each query may directly be used inside the "list of products" class, probably by inheriting all the stuff that is defined in the general "list of products" class and adding dealing with database results.
So in the end you'll end up having:
Product -> ListOfProducts -> ProductDatabase -> DatabaseAccessLayer
I am having trouble with writing a simple(!) PHP function to return an array of values under the following scenario.
I have a database table that simply lists parent and child categories (e.g. menu, sub menu, sub sub menu) in two fields 'parent_id' and 'category_id'. I want to tunnel down through the table to pull out all the bottom category_ids - i.e. those that are not listed as 'parents' to any others but are children (or grand children or great grand children) of the category_id input to the function
I have a function a bit like this (it runs on osCommerce based code so the sql query stuff looks odd
function tep_get_bottom_categories( $categories_id) {
$bottom_categories_query = tep_db_query("select categories_id from categories where parent_id = '" . $categories_id . "'");
while ($bottom_categories = tep_db_fetch_array($bottom_categories_query)) {
if(XXXXXXXXXXXXX)//<---- tried lots of stuff here
{$results[]=$bottom_categories['categories_id'];}
else
{tep_get_bottom_categories( $bottom_categories['categories_id']);
}
}//end while loop
}// end function
If I input a value to the function that has children then it will produce those in an array of $bottom_categories and then do the same all the way down to the final bottom categories - however these are not singled out and placed in the $results array as I need something to go where XXXXXXXXXXXXXX is but that is evading me mightily.
I'm assuming that I need to label each loop of the function in some way but I fail to see how - thanks - Graeme
You can do this purely in SQL:
SELECT category_id, (SELECT count(*)
FROM categories AS ci
WHERE ci.parent_id=co.category_id) AS children
FROM categories AS co
WHERE parent_id=xxxxx
HAVING children=0
I would always run the else case and check if the query has any results. If it has no children you are ready and insert it into the list; otherwise same as before.
Example code:
function tep_get_bottom_categories( $categories_id) {
$bottom_categories_query = tep_db_query("select categories_id from categories where parent_id = '" . $categories_id . "'");
if( /* check num_rows == 0 */ {
{$results[]=$categories_id;}
} else {
while ($bottom_categories = tep_db_fetch_array($bottom_categories_query)) {
{tep_get_bottom_categories( $bottom_categories['categories_id']);}
}//end while loop
}
}// end function