Pleaser help to create menu with nested urls.
I have a mulidimentual array, like this:
["Увлажнение"]=>
array(0) {
}
["Туалетная вода"]=>
array(0) {
}
["Духи и парфюмерная вода"]=>
array(0) {
}
["Мужские аксессуары"]=>
array(13) {
["Часы"]=>
array(1) {
["Часы"]=>
array(0) {
}
}
["Сумки и чехлы"]=>
array(8) {
["Спортивные сумки"]=>
array(0) {
}
["Сумки"]=>
array(0) {
}
["Рюкзаки"]=>
array(0) {
}
I have a function that create HTML Menu from this array:
function makeList($array) {
//Base case: an empty array produces no list
if (empty($array)) return '';
//Recursive Step: make a list with child lists
$output = '<ul>';
foreach ($array as $key => $subArray) {
$url = URLify::filter ($key);
$output .= '<li>' . $key .''. makeList($subArray) . '</li>';
}
$output .= '</ul>';
return $output;
};
That generame manu like:
УвлажнениеТуалетная водаДухи и парфюмерная водаМужские аксессуарыЧасыСумки и чехлыСпортивные сумкиСумки
Every menu urls like:
uvlazhnenie
tualetnaya-voda
duhi-i-parfyumernaya-voda
muzhskie-aksessuary
chasy
But i need urls like:
muzhskie-aksessuary/chasy
With nested (with delemiter) urls.
Please help me. Thanks.
You can use a second argument for your function for this:
function makeList($array, $url = '') {
//Base case: an empty array produces no list
if (empty($array)) return '';
//Recursive Step: make a list with child lists
$output = '<ul>';
foreach ($array as $key => $subArray) {
$url2 = $url . ($url == '' ? '' : '/') . URLify::filter ($key);
$output .= '<li>' . $key .''. makeList($subArray, $url2) . '</li>';
}
$output .= '</ul>';
return $output;
}
Related
I am decoding the array of json objects into html lists . i have tried with some demo it worked but when i deal with this array of json this gives error as : Invalid argument supplied for foreach(). what is i am missing ?
<?php
$json_string = '{"error":false,"data":[{"jb_product_category_id":"1","jb_product_category_name":"Mother","jb_product_category_prefix":"jbpm","jb_product_category_delete_status":"0","jb_product_category_created_on":"1501500876531","jb_product_category_updated_on":"1501500876531","subCategory1":[{"jb_product_subcategory1_1_id":"1","jb_product_subcategory1_2_category_id":"1","jb_product_subcategory1_3_name":"Cloths","jb_product_subcategory1_4_delete_status":"0","jb_product_subcategory1_5_created_on":"1501563015164","jb_product_subcategory1_6_updated_on":"1501563015164","subCategory2":[{"jb_product_subcategory2_1_id":"1","jb_product_subcategory2_2_category_id":"1","jb_product_subcategory2_3_subcategory1_id":"1","jb_product_subcategory2_4_name":"Pregnancy wear","jb_product_subcategory2_5_delete_status":"0","jb_product_subcategory2_6_created_on":"1501574226464","jb_product_subcategory2_7_updated_on":"1501574226464"}]}]},{"jb_product_category_id":"2","jb_product_category_name":"Child Wear","jb_product_category_prefix":"jbpc","jb_product_category_delete_status":"0","jb_product_category_created_on":"1502429483534","jb_product_category_updated_on":"1502429483534","subCategory1":[{"jb_product_subcategory1_1_id":"2","jb_product_subcategory1_2_category_id":"2","jb_product_subcategory1_3_name":"Girls","jb_product_subcategory1_4_delete_status":"0","jb_product_subcategory1_5_created_on":"1502429606169","jb_product_subcategory1_6_updated_on":"1502429606169","subCategory2":[{"jb_product_subcategory2_1_id":"2","jb_product_subcategory2_2_category_id":"2","jb_product_subcategory2_3_subcategory1_id":"2","jb_product_subcategory2_4_name":"Western","jb_product_subcategory2_5_delete_status":"0","jb_product_subcategory2_6_created_on":"1502429794573","jb_product_subcategory2_7_updated_on":"1502429794573"}]},{"jb_product_subcategory1_1_id":"3","jb_product_subcategory1_2_category_id":"2","jb_product_subcategory1_3_name":"Boys","jb_product_subcategory1_4_delete_status":"0","jb_product_subcategory1_5_created_on":"1505105190176","jb_product_subcategory1_6_updated_on":"1505105190176","subCategory2":[]}]}]}';
$array = json_decode($json_string, true);
function build_list($array) {
$list = '<ul>';
foreach($array as $key => $value) {
foreach($value as $key => $index) {
if(is_array($index)) {
$list .= build_list($index);
} else {
$list .= "<li>$index</li>";
}
}
}
$list .= '</ul>';
return $list;
}
echo build_list($array);
?>
Simply use only one foreach, the nested one seems useless :
foreach($array as $key => $index) {
if(is_array($index)) {
/* ... */
Just add a condition to check if $value is a valid array or not. This way it will not process $value if it's not an array and warnings will go away.
if (!is_array($value)) {
continue;
}
Use this condition inside foreach before the looping $array.
foreach($array as $key => $value) {
if (!is_array($value)) {
continue;
}
foreach($value as $k => $index) {
if(is_array($index)) {
$list .= build_list($index);
} else {
$list .= "<li>$index</li>";
}
}
}
Ideone link : Code
I am a stuck with Bigtree.
For my website I need to know a pageID, you can do that using a function.
$nav = $cms->getNavId($path, $previewing = false);
But this is what I get back
string(2) "15" array(0) { } string(0) ""
how can I catch the number (in this case 15)
If I do it this way
$nav = $cms->getNavId($path, $previewing = false);
foreach ($nav as $key) {
echo $key;
}
Then I get this output
15Array
So your loop looks like its doing what you want. If you replaced the echo in the loop with a var_dump it would be more clear.
So to grab the 15; do this:
$nav = $cms->getNavId($path, $previewing = false);
foreach ($nav as $key) {
if( is_numeric($key) ){
$navID = $key;
}
}
var_dump($navID);
Some kind of followup to my last question: for loop - move deeper on numeric key in multidimensional array
I have this array as input:
Array
(
[0] => apl_struct Object
(
[funcname] => say
[args] => Array
(
[0] => Array
(
[0] => apl_struct Object
(
[funcname] => text
[args] => Array
(
[value] => hello
)
)
)
)
)
)
I now have 2 functions working for me.
One is a func only for getting the next key/value in an associative array.
None of the next(), prev(), etc. were working for me like on indexed arrays:
function getnext($array, $key) {
$keys = array_keys($array);
if ((false !== ($p = array_search($key, $keys))) && ($p < count($keys) - 1)) {
return array('key' => $keys[++$p], 'value' => $array[$keys[$p]]);
} else {return false;}
}
The next function is my executer or constructer. he creates a semi-xmlstruct for me.
I tried to add recursion for skipping the numeric key. They're obviously nonsense and can be skipped.
I then want to check if all the values of the non-numeric keys are arrays or not.
If it is an array it indicates arguments to be followed and output should look like: INPUT.
If not, it's either the functionname (funcname) or indeed a real value for us like "hello".
function arr2xml($array, $level = 1, $pos = 1) {
$xml = '';
foreach ($array as $key => $value) {
if (is_object($value)) {$value = get_object_vars($value);}// convert object to array
if (is_numeric($key)) {
$xml .= arr2xml($value);
} else {
if (!is_array($value)) {
switch ($key) {
case 'funcname':
$nextkey = getnext($array, $key);
$xml .= str_repeat("\t", $level) . "<apl:$value>\n";
$xml .= arr2xml($nextkey['value'], $level++);
$xml .= str_repeat("\t", $level) . "</apl:$value>\n";
break;
case 'value':
$xml .= str_repeat("\t", $level) . "\t$value\n";
break;
}
} else {
$xml .= str_repeat("\t", $level) . "<$key pos='$pos'>\n\t";
$xml .= arr2xml($value, $level++, $pos++);
$xml .= str_repeat("\t", $level) . "</$key>\n";
}
}
}
return $xml;
}
but what I am getting out of this so far is this:
the function name was inserted right.
it is say and text.
also, in some wild circumstances, the -tag and the value are executed properly.
<apl:say>
<apl:text>
hello
</apl:text>
<args pos='1'>
hello
</args>
</apl:say>
<args pos='1'>
<apl:text>
hello
</apl:text>
<args pos='1'>
hello
</args>
</args>
</xml>
for me it looks like the recursion isn't really working. Am i missing something here?
I've tried to rebuild it from previous mentioned post.
Also I'm wondering about the multiple output I am getting here.
The tags seem to get filled right, but the actual arrangement is quite confusing for me.
I was expecting the output to look like this:
<apl:say>
<args pos='1'>
<apl:text>
<args pos='1'>
hello
</args>
</apl:text>
</args>
</apl:say>
Thanks in advance
TL;DR
It's a combination of the (completely superfluous function) getnext(), and how your arr2xml() recurses. I've provided here an arr2xml() replacement function which will do what you want, without any need for getnext().
A detailed description of what went wrong in your code, and how I suggest fixing it, follows.
function arr2xml($array, $level = 0, $pos = 1) {
$xml = '';
foreach ($array as $key => $value) {
if (is_object($value)) {
$value = get_object_vars($value);
}
if (is_numeric($key)) {
$xml .= arr2xml($value, $level+1);
continue;
} else {
if (!is_array($value)) {
switch ($key) {
case 'funcname':
array_shift($array);
$xml .= str_repeat(" ", $level) . "<apl:$value>\n";
$xml .= arr2xml($array, $level+1);
$xml .= str_repeat(" ", $level) . "</apl:$value>\n";
return $xml;
case 'value':
$xml .= str_repeat(" ", $level) . " $value\n";
return $xml;
}
} else {
$xml .= str_repeat(" ", $level) . "<$key pos='$pos'>\n ";
$xml .= arr2xml($value, $level+1, $pos+1);
$xml .= str_repeat(" ", $level) . "</$key>\n";
return $xml;
}
}
}
return $xml;
}
Here is an eval.in showing this new function in use on the same data structure you provided, giving you more or less the desired output (the whitespace may not be exactly what you wanted, I'll leave that as an exercise for you.)
What went wrong with your code
When funcname is 'say', the condition case 'funcname': calls getnext() with $key set to 'funcname' and $array set to:
array(2) {
["funcname"]=>
string(3) "say"
["args"]=>
array(1) {
[0]=>
array(1) {
[0]=>
object(apl_struct)#1 (2) {
["funcname"]=>
string(4) "text"
["args"]=>
array(1) {
["value"]=>
string(5) "hello"
}
}
}
}
}
You then find 'funcname' in that array ($p = array_search($key, $keys)) and create a new array containing only the value of the next item in the array:
return array('key' => $keys[++$p], 'value' => $array[$keys[$p]]);
The result is an array that no longer contains the 'args' key:
array(2) {
["key"]=>
string(4) "args"
["value"]=>
array(1) {
[0]=>
array(1) {
[0]=>
object(apl_struct)#1 (2) {
["funcname"]=>
string(4) "text"
["args"]=>
array(1) {
["value"]=>
string(5) "hello"
}
}
}
}
}
Thus, you will never get the tag you were hoping for, because the data structure has been corrupted by getnext() to remove the key you expected to find in order to construct it.
The duplicate values can be resolved by returning from the inner recursion earlier. Right now, you are recursing, processing the "inner" nodes, then returning back to the top and processing them again.
Instead, we can drop getnext entirely (since it doesn't even do what you wanted), and we can just use array_shift instead to throw away the left-most value of the array. We then continue processing $array like normal.
after some time spent i came up with this solution for my problem:
function apl2xml($array, $tlevel = 0) {
$ret = '';
$ret .= str_repeat("\t", $tlevel) . "<apl>\n";
foreach ($array as $key => $value) {
$ret .= $this->aplstruct2xml($value, $tlevel + 1);
}
$ret .= str_repeat("\t", $tlevel) . "</apl>\n";
return $ret;
}
function aplstruct2xml($apl_struct, $tlevel = 0) {
$ret = '';
if ($apl_struct->funcname == 'text') {
$ret .= str_repeat("\t", $tlevel) . "<text>\n";
$ret .= str_repeat("\t", $tlevel);
$ret .= $apl_struct->args[0] . "\n";
$ret .= str_repeat("\t", $tlevel) . "</text>\n";
} else {
$ret .= str_repeat("\t", $tlevel) . "<aplfunc:{$apl_struct->funcname}>\n";
foreach ($apl_struct->args as $key => $value) {
$ret .= str_repeat("\t", $tlevel + 1) . "<arg pos='$key'>\n";
$ret .= $this->apl2xml($value, $tlevel + 2);
$ret .= str_repeat("\t", $tlevel + 1) . "</arg>\n";
}
$ret .= str_repeat("\t", $tlevel) . "</aplfunc:{$apl_struct->funcname}>\n";
}
return $ret;
}
turns out that i did not need any recursion at all..
the so called apl just consisted of an apl_struct which could contain more apl's/apl_struct's
i created custom helper to build dynamic menu and i need to use this helper in all my site pages so i put the code to show menu in element and include it in default.ctp like this
<?php echo $this->element('menu'); ?>
,, but the default.ctp not defined the helper so how to define this helper in all views it give me this errors
Notice (8): Undefined variable: data [APP\views\elements\menu.ctp, line 5]
Warning (2): Invalid argument supplied for foreach() [APP\views\helpers\tree.php, line 28]
helpers/tree.php
<?php
class TreeHelper extends Helper
{
var $tab = " ";
var $helpers = array('Html');
// Main Function
function show($name, $data, $style='')
{
list($modelName, $fieldName) = explode('/', $name);
if ($style=='options') {
$output = $this->selecttag_options_array($data, $modelName, $fieldName, $style, 0);
} else {
//$style='';
$output = $this->list_element($data, $modelName, $fieldName, $style, 0);
}
return $this->output($output);
}
// This creates a list with optional links attached to it
function list_element($data, $modelName, $fieldName, $style, $level)
{
$tabs = "\n" . str_repeat($this->tab, $level * 2);
$li_tabs = $tabs . $this->tab;
$output = $tabs. "<ul>";
foreach ($data as $key=>$val)
{
$output .= $li_tabs . "<li>".$this->style_print_item($val[$modelName], $modelName, $style);
if(isset($val['children'][0]))
{
$output .= $this->list_element($val['children'], $modelName, $fieldName, $style, $level+1);
$output .= $li_tabs . "</li>";
}
else
{
$output .= "</li>";
}
}
$output .= $tabs . "</ul>";
return $output;
}
// this handles the formatting of the links if there necessary
function style_print_item($item, $modelName, $style='')
{
switch ($style)
{
case "link":
$output = $this->Html->link($item['name'], "view/".$item['id']);
break;
case "admin":
$output = $item['name'];
$output .= $this->Html->link(" edit", "edit/".$item['id']);
$output .= " ";
$output .= $this->Html->link(" del", "delete/".$item['id']);
break;
default:
$output = $item['name'];
}
return $output;
}
// recursively reduces deep arrays to single-dimensional arrays
// $preserve_keys: (0=>never, 1=>strings, 2=>always)
// Source: http://php.net/manual/en/function.array-values.php#77671
function array_flatten($array, $preserve_keys = 1, &$newArray = Array())
{
foreach ($array as $key => $child)
{
if (is_array($child))
{
$newArray =& $this->array_flatten($child, $preserve_keys, $newArray);
}
elseif ($preserve_keys + is_string($key) > 1)
{
$newArray[$key] = $child;
}
else
{
$newArray[] = $child;
}
}
return $newArray;
}
// for formatting selecttag options into an associative array (id, name)
function selecttag_options_array($data, $modelName, $fieldName, $style, $level)
{
// html code does not work here
// tried using " " and it didn't work
$tabs = "-";
foreach ($data as $key=>$val)
{
$output[] = array($val[$modelName]['id'] => str_repeat($tabs, $level*2) . ' ' . $val[$modelName]['name']);
if(isset($val['children'][0]))
{
$output[] = $this->selecttag_options_array($val['children'], $modelName, $fieldName, $style, $level+1);
}
}
$output = $this->array_flatten($output, 2);
return $output;
}
}
?>
elements/menu.ctp
<!-- This will turn the section name into a link -->
<h3>Basic hierarchical list with name as link</h3>
<?php echo $tree->show('Section/name', $data, 'link'); ?>
You have to define it in your AppController, located in the root of your app directory (and if it's not there, just create a file called app_controller.php You can use the file with the same name in the Cake core directory as a template for this file.
When you have your app_controller, add the following
var $helpers = array('Tree');
You might want to add some other standard helpers like Html, Form and Javascript in here as well. All the helpers that are in AppController will be available to all of your controllers.
I keep getting the following warning listed below on line 3.
Warning: Invalid argument supplied for foreach()
Here is the php code.
function dyn_menu($parent_array, $sub_array, $qs_val = "menu", $main_id = "nav", $sub_id = "subnav", $extra_style = "foldout") {
$menu = "<ul id=\"".$main_id."\">\n";
foreach ($parent_array as $pkey => $pval) {
if (!empty($pval['count'])) {
$menu .= " <li><a class=\"".$extra_style."\" href=\"".$pval['link']."?".$qs_val."=".$pkey."\">".$pval['label']."</a></li>\n";
} else {
$menu .= " <li>".$pval['label']."</li>\n";
}
if (!empty($_REQUEST[$qs_val])) {
$menu .= "<ul id=\"".$sub_id."\">\n";
foreach ($sub_array as $sval) {
if ($pkey == $_REQUEST[$qs_val] && $pkey == $sval['parent']) {
$menu .= "<li>".$sval['label']."</li>\n";
}
}
$menu .= "</ul>\n";
}
}
$menu .= "</ul>\n";
return $menu;
}
Here is the whole code I'm working on.
$mysqli = new mysqli("localhost", "root", "", "sitename");
$dbc = mysqli_query($mysqli,"SELECT id, label, link_url, parent_id FROM dyn_menu ORDER BY parent_id, id ASC");
if (!$dbc) {
// There was an error...do something about it here...
print mysqli_error();
}
while ($obj = mysqli_fetch_assoc($dbc)) {
if (empty($obj['parent_id'])) {
echo $parent_menu . $obj['id']['label'] = $obj['label'];
echo $parent_menu . $obj['id']['link'] = $obj['link_url'];
} else {
echo $sub_menu . $obj['id']['parent'] = $obj['parent_id'];
echo $sub_menu . $obj['id']['label'] = $obj['label'];
echo $sub_menu . $obj['id']['link'] = $obj['link_url'];
echo $parent_menu . $obj['parent_id']++;
}
}
mysqli_free_result($dbc);
function dyn_menu($parent_array, $sub_array, $qs_val = "menu", $main_id = "nav", $sub_id = "subnav", $extra_style = "foldout") {
$menu = "<ul id=\"".$main_id."\">\n";
foreach ($parent_array as $pkey => $pval) {
if (!empty($pval['count'])) {
$menu .= " <li><a class=\"".$extra_style."\" href=\"".$pval['link']."?".$qs_val."=".$pkey."\">".$pval['label']."</a></li>\n";
} else {
$menu .= " <li>".$pval['label']."</li>\n";
}
if (!empty($_REQUEST[$qs_val])) {
$menu .= "<ul id=\"".$sub_id."\">\n";
foreach ($sub_array as $sval) {
if ($pkey == $_REQUEST[$qs_val] && $pkey == $sval['parent']) {
$menu .= "<li>".$sval['label']."</li>\n";
}
}
$menu .= "</ul>\n";
}
}
$menu .= "</ul>\n";
return $menu;
}
function rebuild_link($link, $parent_var, $parent_val) {
$link_parts = explode("?", $link);
$base_var = "?".$parent_var."=".$parent_val;
if (!empty($link_parts[1])) {
$link_parts[1] = str_replace("&", "##", $link_parts[1]);
$parts = explode("##", $link_parts[1]);
$newParts = array();
foreach ($parts as $val) {
$val_parts = explode("=", $val);
if ($val_parts[0] != $parent_var) {
array_push($newParts, $val);
}
}
if (count($newParts) != 0) {
$qs = "&".implode("&", $newParts);
}
return $link_parts[0].$base_var.$qs;
} else {
return $link_parts[0].$base_var;
}
}
echo dyn_menu($parent_menu, $sub_menu, "menu", "nav", "subnav");
It's telling you that $parent_array isn't an array.
If you post the code that calls this function, we can tell you more.
Are you sure $parent_array is actually an array? Try checking it with is_array first (perhaps returning an empty string to represent the menu or whatever - adapt to your needs):
if (!is_array($parent_array)) {
return "";
}
This error happens when you supply not an array into forearch. Try print_r() first argument of every foreach
If you change your function signature to include type hinting (only works for arrays and objects), you'll be sure that your function gets what it needs:
function dyn_menu(array $parent_array, array $sub_array, //etc.)
And you should get an error message that pinpoints the caller of the function, which is where the problem really is.
It looks like you were expecting to build $parent_array in that while loop at the beginning. Instead it's just echoing stuff.
The lines like:
echo $parent_menu . $obj['id']['label'] = $obj['label'];
Should probably be like:
$menu['label'] = $obj['label'];
Then at the end (inside) of the loop add something like:
$parent_menu[$obj['id']] = $menu;
So you build the array you're using in dyn_menu.
In any case, the while loop looks like your problem. It's not building $parent_menu from the data.