Menu from database with unlimited sub-menus/items? - php

I'm trying to build this function that display a menu with sub menus from a database. Something that can show like sub-sub-sub-menus and so on... If that's ever something I need.
In the database i have 2 tables, menu and menuItem.
menu
menuItem
php file
function get_menu($id) {
require(dirname(__FILE__) . '/config.php');
// main base
$menu = " SELECT id, menu_name, menu_data, menu_order
FROM mos_menuItem
WHERE menu_parent_id='$id'
AND menu_sub='0'
ORDER BY menu_order";
$menuResult = mysqli_query($conn, $menu);
echo '<ul>';
while ($menuRow = mysqli_fetch_row($menuResult)) {
// main menu
echo '<li>'. $menuRow[1] .'</li>';
// sub base
$sub = "SELECT id, menu_name, menu_data, menu_order, menu_sub
FROM mos_menuItem
WHERE menu_parent_id='$menuRow[0]'
AND menu_sub>='1'
ORDER BY menu_order";
$subResult = mysqli_query($conn, $sub);
echo '<ul>';
while ($subRow = mysqli_fetch_row($subResult)) {
echo '<li>'. $subRow[1] .'</li>';
}
echo '</ul>';
}
echo '</ul>';
mysqli_close($conn);
}
so I need to loop something inside itself? I'm not sure where to go from here.

Recursion is your best friend here.
I am not sure what purpose is served by the menu_sub field. I assume it tells you at what level it is in the menu hierarchy. If that is the case, then you can safely ignore it (menu_parent_id would suffice) to create this multi-level menu hierarchy.
function getMenus($menu_id, $is_sub=false){
$sql = "SELECT id, menu_name, menu_data, menu_order
FROM mos_menuItem
WHERE menu_parent_id='$menu_id'
ORDER BY menu_order";
$mResult = mysqli_query($conn, $sub);
echo '<ul>';
while ($mRow = mysqli_fetch_row($mResult)) {
echo '<li>'. $mRow[1] .'';
getMenus($mRow[0], true);
echo '</li>';
}
echo '</ul>';
}
This does have one drawback where if you want the hierarchy to start from a parent other than 0 (I assume your root menu_id is 0), that particular menu would not show up in the hierarchy.

So after some trial and error i figure it out. It really helped to look at Sheikh Azad code.
The only difference is in my function i have AND menu_sub='$sub' to make it not going loop the main menu item if the id is the same.. If that make sense.
The function
function mos_get_menu_editor($menuId, $sub) {
require(dirname(__FILE__) . '/config.php');
$item = " SELECT id, menu_name, menu_data, menu_order, menu_sub
FROM mos_menuItem
WHERE menu_parent_id='$menuId'
AND menu_sub='$sub'
ORDER BY menu_order";
$itemResult = mysqli_query($conn, $item);
echo '<ul>';
while ($itemRow = mysqli_fetch_row($itemResult)) {
echo '<li>'. $itemRow[1] .'</li>';
mos_get_menu_editor($itemRow[0], 1);
}
echo '</ul>';
mysqli_close($conn);
}

Related

Building Multi Level Menu Dynamically with PDO in PHP

I have this data in my database:
id nameOfPerson parent
3 John NULL
4 Michel 3
5 Husam 4
6 Khalaf 5
7 Mark 5
----------------------------
and i want to display it in list like this
John
Michel
Husam
Khalaf
Mark
but in my code he just displayed
John
Michel
How i want to Displeyed all data according to parent like above list ?
this is my function whats the wrongs ?
public function familyTree(){
$query = "SELECT id, nameOfPerson, parent FROM person WHERE parent is null";
$statment = $this->db->prepare($query);
$statment->execute();
echo '<ul id ="family">';
while($family = $statment->fetch(PDO::FETCH_OBJ)){
echo '<li>'. $family->nameOfPerson;
$query1 = "SELECT id, nameOfPerson, parent FROM person WHERE parent = :id";
$statment1 = $this->db->prepare($query1);
$statment1->bindValue('id', $family->id);
$statment1->execute();
if($statment1->rowCount() > 0){
echo '<ul>';
while($family2 = $statment1->fetch(PDO::FETCH_OBJ)){
echo '<li>' . $family2->nameOfPerson . '</li>';
}
echo '</ul>';
}
echo '</li>';
}
echo '</ul>';
}
ASSUMPTIONS
Every child has only 1 parent.
CODE
First, I retrieve all data from DB table as it is not efficient to retrieve data individually for each parent increasing DB calls and affecting response time.
Build an associative array of parent and children where every parent has 2 details of each child - nameOfPerson and id.
Iterate over this array in a recursive way and keep building the list for each child and ultimately return them.
Refer SQL Fiddle for table rows.
<?php
$pdo = new PDO("mysql:dbname=newdummy;host=localhost","root","");
$query = $pdo->query("Select * from hierarchy");
$family_tree = [];
$root_parent = -1;
$root_parent_name = "";
function makeTree($query,&$family_tree,&$root_parent,&$root_parent_name){
while($row = $query->fetch(PDO::FETCH_ASSOC)){
if(is_null($row['parent'])){
$root_parent = $row['id'];
$root_parent_name = $row['nameOfPerson'];
}else{
if(!isset($family_tree[$row['parent']])){
$family_tree[$row['parent']] = [];
}
$family_tree[$row['parent']][] = array($row['nameOfPerson'],$row['id']);
}
}
}
function buildList($family_tree,$parent){
$list = "<ul>";
foreach($family_tree[$parent] as $each_child){
$list .= "<li>" . $each_child[0];
if(isset($family_tree[$each_child[1]])){
$list .= buildList($family_tree,$each_child[1]);
}
$list .= "</li>";
}
$list .= "</ul>";
return $list;
}
makeTree($query,$family_tree,$root_parent,$root_parent_name);
echo "<ul>";
echo "<li>$root_parent_name";
echo buildList($family_tree,$root_parent);
echo "</li>";
echo "</ul>";
OUTPUT
<ul>
<li>John
<ul>
<li>Michel
<ul>
<li>
Husam
<ul>
<li>khalaf</li>
<li>Mark</li>
</ul>
</li>
</ul>
</li>
<li>Tross</li>
<li>David</li>
</ul>
</li>
</ul>

Iterating values fetched from mysqli

So I am creating a dynamic menu. Where I have stored the main categories of the menu in a separate table and subcategories in another table.
They are stored in this way
Categories Table
id cat_name
1 HOME,
2 PRODUCTS,
3 SOLUTIONS,
4 NEWS & GALLERY,
5 DOWNLOADS,
6 CONTACT
Right Now I am running a query
$sql="SELECT * FROM categories";
$query = mysqli_query($con, $sql);
while($row = mysqli_fetch_assoc($query)) {
$row['cat_name'];
$cat=explode(",",$row['cat_name']);
}
Then wherever needed I am printing the values like this
<?php echo $cat[0]; .. echo $cat[1]; //and so on ?>
It looks like this right now. And it is supposed to look likethis
But the problem with this solution is that I have to define the index of the array to print it.
I am developing the admin panel for this one and I want that if the admin adds a new menu item then it should automatically fetch it and display it.
But with this solution it is not possible.
I am thinking of iterating the values with a for loop but cannot get it right.
Use foreach ($query as $rows)
or
Use for ($i=0; $i < count($query); $i++)
Probably You can try this also
$sql="SELECT * FROM categories";
$query = mysqli_query($con, $sql);
while($row = mysqli_fetch_assoc($query))
{
$row['cat_name'];
$cat=explode(",",$row['cat_name']);
foreach($cat as $catss)
{
$cat = trim($catss);
$categories .= "<category>" . $cat . "</category>\n";
}
}
You can try this way may be you wish like this for dynamic menu:
$sql="SELECT * FROM categories";
$query = mysqli_query($con, $sql);
$menu = '';
$menu .= '<ul>';
while($row = mysqli_fetch_assoc($query))
{
$menu .= '<li>' . $row['cat_name'] . '</li>';
}
$menu .= '</ul>';
echo $menu;
When you are fetching data from table then you can directly print that value using field name. if you want to further use that so, you have to add in one array like:
$cat[] = $row['cat_name'];
then you can use this value in that manner using for or while:
foreach ($cat as $value) {
echo $value;
}
echo '<ul>';
foreach ($cat as $val) {
echo "<li>{$val}</li>";
}
echo '</ul>';
Instead of the index. Also this should be faster and readable than for loop.
You can save it in array then later access the values.
$sql="SELECT * FROM categories";
$query = mysqli_query($con, $sql);
$cat = array();
while($row = mysqli_fetch_assoc($query)) {
$cat[]=$row['cat_name']
}
Now:
foreach ($cat as $category) {
$cate_gory = explode("," , $category);
foreach ($cate_gory as $categories) {
echo $categories . "<br>";
}
}
Foreach Loop
foreach($cats as $cat)
{
echo $cat;
}
Basic For Loop
for ($i=0; $i<count($cats); $i++)
{
echo $cats[$i];
}
Simple Use Case
<?php
$cats = 'Stack overflow PHP questions';
$cats = explode(' ',$cats);
$html = '<ul>';
foreach($cats as $cat)
{
$html .= '<li>' . $cat . '</li>';
}
$html .= '</ul>';
echo $html;
?>
Your Code
In this case the while loop you are using will iterate over all the values returned from the sql statement, so there is no need to add more complexity. I would remove the commas from the field names and echo out the html this way.
$sql="SELECT * FROM categories";
$query = mysqli_query($con, $sql);
$html = '<ul>';
while($row = mysqli_fetch_assoc($query)) {
$html .= '<li>' . $row['cat_name'] . '</li>';
}
$html .= '</ul>';
echo $html;
?>

Counting the size of an array, inside an array element - PHP

So I'm trying to create a dynamic navigation bar that is built through menu items stored in a database.
Each menu item is assigned a parent_id, if that id is 0, that item is a parent; if that id is a value above 0, then that menu item is the child of another menu item. (Imagine hovering over a navigation menu item and getting a drop down of children items)
The following function is what builds the multi-dimensional array from the records in the database.
function build_menu($menu_id){
$mysqli = new mysqli("localhost", "user", "password", "db");
if($mysqli->connect_errno){
echo "Failed to connect to MySQL: " . $mysqli->connect_errno;
}
$stmt = $mysqli->prepare("SELECT `id`, `name`, `order`, `parent_id`, `path` FROM menu_items WHERE `group_id` = ? ORDER BY `order` ASC");
$menu_id = intval($mysqli->real_escape_string($menu_id));
$stmt->bind_param('i', $menu_id);
if($stmt->execute()){
$results = resultToArray($stmt->get_result());
$parents = array();
$children = array();
$menu = array();
for($i=0; $i<count($results); $i++){
if($results[$i]['parent_id'] === 0){
$parents[] = $results[$i];
} else {
$children[] = $results[$i];
}
}
for($i=0; $i<count($parents); $i++){
$menu[$i] = $parents[$i];
for($j=0; $j<count($children); $j++){
if($children[$j]['parent_id'] === $parents[$i]['id']){
$menu[$i][$j] = $children[$j];
}
}
}
return($menu);
} else {
echo "Failed to execute statement: " . $stmt->errno . ": " . $stmt->error;
}
$mysqli->close();
return(0);
}
This is the function that takes that array and turns it into HTML code to be used as the navigation bar.
function get_menu($menu_id) {
if(build_menu($menu_id)){
$menu = build_menu($menu_id);
$menu_html = '<ul>';
for($i=0; $i<count($menu); $i++){
$menu_item_path = ($menu[$i]['path'] != NULL ? $menu[$i]['path'] : '#');
$menu_html .= '<li>' . $menu[$i]['name'] . '';
if(count($menu[$i])>0){
$menu_html .= '<ul>';
for($j=0; $j<count($menu[$i]); $j++){
if(isset($menu[$i][$j])){
$menu_html .= '<li>' . $menu[$i][$j]['name'] . '</li>';
}
}
$menu_html .= '</ul>';
}
$menu_html .= '</li>';
}
$menu_html .= '</ul>';
return($menu_html);
}
}
I presumed that if(count($menu[$i])>0) would count the array inside the selected element, but for some reason, it counts the keys and therefore always true.
I know this is a pretty long question, I do appreciate those of you that have read this far. If you can help me figure out a way to see if a menu item has children items, that would be great.
use array_filter to count only the items in array not empty or null
Process of generating HTML menu can be a lot easier than your example.
function generateMenu(array $menu, $parentId = 0) {
if (!isset($menu[$parentId])) {
return;
}
$output = '<ul>';
foreach ($menu[$parentId] as $record) {
$output .= '<li>';
$output .= '' . $record['title'] . '';
$output .= generateMenu($menu, $record['id']);
$output .= '</li>';
}
$output .= '</ul>';
return $output;
}
echo generateMenu($menu);
Above code would output HTML like:
x First level #1
o Second level #8
x First level #2
o Second level #4
- Third level #6
- Third level #7
o Second level #5
x First level #3
demo
But for that to work, you must fix your build_menu() function, to return array like this, which is very easy, just create one array $menu and fill it like $menu[$row['parent_id']][] = $row;, where $row is record row fetched from database.

PHP - Query and function and while clause?

Question: How can I tie together my query with my function? I would like to print my list, but I am not sure how to connect the two. Also, am I using "entries" correctly below?
/* data handling */
$result = mysql_query("SELECT * FROM Items LEFT JOIN Categories on Categories.CategoryID = Items.FK_CategoryID WHERE Items.FK_UserID = $_SESSION[user_id] ORDER BY CategorySort, CategoryName ASC, ItemSort, ItemTitle");
/* output logic */
function render_list($ItemTitle, array $entries)
{
echo '<ul><li>' . $ItemTitle . '<ul>';
foreach($entries as $entry)
{
echo '<li>' . $entry['ItemID'] . '</li>';
}
echo '</ul></li></ul>';
}
render_list();
Do I need to use a while clause?
// loop through topics
while($row = mysql_fetch_array($result)) {
render_list;
}
Yeah, replace your foreach loop with that while loop. Take that "type" (array) out of the function definition though, you don't need to do that in loosely typed php language. Here's a rework of the code you have:
/* output logic */
function render_list($ItemTitle, $entries)
{
echo '<ul><li>' . $ItemTitle . '<ul>';
while($entry = mysql_fetch_array($entries))
{
echo '<li>' . $entry['ItemID'] . '</li>';
}
echo '</ul></li></ul>';
}
$result = mysql_query("SELECT * FROM Items LEFT JOIN Categories on Categories.CategoryID = Items.FK_CategoryID WHERE Items.FK_UserID = $_SESSION[user_id] ORDER BY CategorySort, CategoryName ASC, ItemSort, ItemTitle");
render_list('My Title', $result);

Magento - get a parent category and all sub-sub-categories

I have a single category, that has 2 subcategories. Within each of these categories, are 5 subcategories.
Is there a way to get a list of all of these 10 sub-sub-categories?
Thanks
EDIT:
Something like this:
Main Category
Sub_Cat_1
Cat_1
Cat_2
Cat_3
Sub_Cat_2
Cat_4
Cat_5
Cat_6
Wanting output like:
Cat_1
Cat_2
Cat_3
Cat_4
Cat_5
Cat_6
Thanks
All answers so far load children categories in a loop which is generally bad practice and causes execution of many SQL queries where a single one would suffice.
Performant Single Query Solution:
Let $parentCategory be your Main Category, then this collection will load all subcategories, two levels below:
$subcategoryCollection = Mage::getModel('catalog/category')
->getCollection()
->addFieldToFilter('level', $parentCategory->getLevel() + 2)
->addFieldToFilter('path', ['like' => $parentCategory->getData('path') . '/%']);
The path field contains the category id prefixed with all ancestor ids in the form 1/2/3. Database wise it is a column in catalog_category_entity that has an index, so comparison like this has no performance issues.
Figured it out:
$cat = Mage::getModel('catalog/category')->load(24);
$subcats = $cat->getChildren();
foreach(explode(',',$subcats) as $subCatid)
{
$_category = Mage::getModel('catalog/category')->load($subCatid);
if($_category->getIsActive()) {
$sub_cat = Mage::getModel('catalog/category')->load($_category->getId());
$sub_subcats = $sub_cat->getChildren();
foreach(explode(',',$sub_subcats) as $sub_subCatid)
{
$_sub_category = Mage::getModel('catalog/category')->load($sub_subCatid);
if($_sub_category->getIsActive()) {
echo '<li class="sub_cat">'.$_sub_category->getName().'</li>';
}
}
}
}
Thanks for looking!
I have developed a recursive function to get all children of a category
$categoryObject = Mage::getModel('catalog/category')->load(CATID);
$children = MODEL_OBJECT->getChildCategories($categoryObject);
// IN MODLE FILE
public $_catIds = array();
public function getChildCategories($categoryObject){
$categories = $categoryObject->getChildrenCategories();
foreach ($categories as $catgory){
if($catgory->hasChildren()){
$this->getChildCategories($catgory);
}
$this->_catIds[] = $catgory->getId();
}
return $this->_catIds;
}
Hope this will help you.
$this->getCurrentCategory()->getParentCategory()->getData();
try this
What I do is recursively find the id of the ancestor category.
Once found it, I can print the current category sub categories.
If the current category has no children, than I will just print the father's subcategory.
In this way you can have infinite subcategories and still being able to display a sub category list.
$_helper = Mage::helper("catalog/category");
$rootCat = Mage::app()->getStore()->getRootCategoryId();
$current = Mage::registry('current_category');
show_cat($current,$_helper,$rootCat);
function show_cat($child,$_helper,$rootCat){
if ($child){
//look for anchestor
$parentid = $child->getParentId();
$parent = Mage::getModel("catalog/category")->load($parentid);
if($parentid != $rootCat)
{
//find the anchestor
show_cat($parent,$_helper,$rootCat);
}else{
//is root
$_subcategories = $parent->getChildrenCategories();
if(count($_subcategories)>0){
echo '<ul>';
foreach($_subcategories as $_category){
echo '<li>';
echo ''.$_category->getName().'';
if($child->getId() == $_category->getId()){
$current = Mage::registry('current_category');
if ($current){
//handle current
$_current_subcategories = $current->getChildrenCategories();
if(count($_current_subcategories)>0){
//the current cat has childrens
echo '<ul>';
foreach($_current_subcategories as $_sub_category){
echo '<li>';
echo ''.$_sub_category->getName().'';
echo '</li>';
}
echo '</ul>';
}else{
//the current cat has no childrens
$current_parent = $current->getParentId();
$current_parent = Mage::getModel("catalog/category")->load($current_parent );
$_current_subcategories = $current_parent ->getChildrenCategories();
echo '<ul>';
foreach($_current_subcategories as $_sub_category){
echo '<li>';
echo ''.$_sub_category->getName().'';
echo '</li>';
}
echo '</ul>';
}
}
}
echo '</li>';
}
echo '</ul>';
}
}
}
}

Categories