I am rendering navigation from a 2 dimensional array, using lists, as shown:
<ul> <li>parent
<ul> <li>level 1</li> <li>level 1
<ul> <li>level 3</li> </ul>
</li> </ul>
<li> </ul>
</li> </ul>
Anyway to close the <li> and </ul> correctly I find I need some data from the next array in the foreach sequence. How can I retrieve this?
Although I've had choppy results with them in the past you can use the below functions to move the current array pointer.
prev() - http://www.php.net/manual/en/function.prev.php
next() - http://www.php.net/manual/en/function.next.php
end() - http://www.php.net/manual/en/function.end.php
reset() - http://www.php.net/manual/en/function.reset.php
Hopefully that'll help.
Here's a solution:
$arr = array(1,2,3,4,5);
foreach ($arr as $foo) {
if (empty($isrewind)) {
reset($arr);
$isrewind = true;
}
echo "node: $foo\n";
$copy = $arr; // make a copy of the array
$next = next($copy);
echo "next: $next\n";
$copy = $arr; // make a copy of the array
$prev = prev($copy);
echo "prev: $prev\n";
next($arr); // don't forget to advance the pointer on the original array
}
I've demonstrated the prev bit just for the example. You can easily do that without prev() by saving the element at the end of each iteration.
The if empty bit resets the array pointer to the beginning, because foreach will advance the pointer once when it makes a copy of the array.
The above example yields:
node: 1
next: 2
prev:
node: 2
next: 3
prev: 1
node: 3
next: 4
prev: 2
node: 4
next: 5
prev: 3
node: 5
next:
prev: 4
If you have to do something like this though, there might be a better way just by rearranging your data structure (hard to tell without code).
IHMO it's not possible with a simple foreach loop on an array that has non-numeric indexes. But you can extend the SPL class CachingIterator to wrap your array in this. In your extended class you can implement methods to return the previous and next index/value without advancing the internal element pointer.
If you have an array with numeric indexes use a for loop instead of a foreach. You can then use $i+1 and $i-1 to look at different array indexes than the current index.
Not sure if this is what he asking for, but here's my shot
$list = array (
'Home page' => null,
'Articles' => array (
'Diving' => null,
'Skydiving' => null,
'Stackoverflowing' => null,
),
'Nobody cares' => array (
'Hey' => null,
'It is just a test' => null,
),
);
function render_menu ($list)
{
echo "<ul>\n";
foreach ($y = new RecursiveArrayIterator ($list, RecursiveIteratorIterator::SELF_FIRST) as $key => $item)
{
if ($y->hasChildren ())
{
echo "<li>$key\n";
render_menu ($item);
echo "</li>\n";
}
else
{
echo "<li>$key</li>\n";
}
}
echo "</ul>\n";
}
render_menu ($list);
Found the answer staring me in the face. Retrieved the arrays I need like this:
foreach ($tree as $key => $link) {
$level = $link['level'];
$next_key = $key+1;
$prev_key = $key-1;
$next_array = $tree[$next_key];
$prev_array = $tree[$prev_key];
And then I could test the level property (indicating deph inside the navigation tree) with a buch of if statements to correctly insert the closing markup.
Messy code and I'm not proud of it, but it works!
Quite angry with myself for not seeing it before.
I assume you are using php. I dont think it's possible, you must know the index of element and index the array. Sth like this:
$i=0;
foreach ($array as $element){
echo $element;
//do sth with $array[$i+1];
//do sth with $array[$i-1];
$i++;
}
Have no other clue
Related
Bottom line, I have a huge multidimensional array returned by ldap_get_entries that I am trying to parse into different groups based on the location attribute.
In PowerShell I could do something like:
$location1 = Get-ADUser -Filter * | ? {?_.l -eq "test_location"}
What I am currently doing within PHP (as far as my brain will let me go) is something like:
Foreach ($records as $record) {
if (isset($record['l'])) {
switch ($record['l'][0]) {
case "test_location1":
$location1[] = $record;
continue 2;
case "test_location2":
$location2[] = $record;
continue 2;
}
}
}
I then use a foreach loop (alternative syntax) against an array of location variables ($location1, $location2), that I used the above method, to sort records into their appropriate location variable. In this way, I build an HTML table grouping each locations records together. I build a new table for each location with some HTML code in between each group, so I don't believe sorting the ldap results by location will work, as it would output one large table.
Is there any way to specify a WHERE clause in PHP? So that I can assign all records in an array with a matching key value to variable1 and all records in an array with a different matching key value to variable2.
I am assuming I am tackling this in an amateur scripting way.. If there is an easier way to accomplish this task (maybe skipping the assign records to variables part), or any sort of "best practice" I am missing here, I am up for learning it.
Thanks in advance!!
As far as I understood your question you want something like this:
$recordsSorted = array();
foreach ($records as $record) {
if (! isset($record['l'])) {
continue;
}
$recordsSorted[$records['l'][0]][] = $record;
}
ksort($recordsSorted);
foreach($recordsSorted as $location => $records) {
usort($records, function($a, $b){
return strnatcasecmp($a['uid'][0], $b['uid'][0]);
});
echo '<h1>$location</h1><ul>';
foreach ($records as $record) {
echo '<li>' . $record['dn'] . '</li>';
}
echo '</ul>';
}
This will first put the first entry to the location-attribute of an entry as key of an array. That key can then be used to sort the array.
To output the content then it iterates over the new array and sorts the content - in this case using the first value of the uid-attribute (change the uid to whatever attribute you need). This sorted content is then output to HTML.
the first array $recordsSorted might look somewhat like this:
array(
'London' => array(
array(<entry from the LDAP>),
array(<another entry from LDAP>),
),
'Paris' => array(
array(<a further entry from the LDAP>),
),
'Berlin' => array(
array(<and even another entry from the LDAP>),
),
);
The result would then look somewhat like this:
<h1>Berlin</h1>
<ul>
<li>[UID of and even another entry from the LDAP]</li>
</ul>
<h1>London</h1>
<ul>
<li>[UID of another entry from LDAP]</li>
<li>[UID of entry from the LDAP]</li>
</ul>
<h1>Paris</h1>
<ul>
<li>[UID of a further entry from the LDAP]</li>
</ul>
Does that look like it could help you?
This is what i am currently using
<?php $sidebar = $this->data['sidebar'];
$lastKey = array_pop(array_keys($sidebar));
$sidebar = $this->data['sidebar'][$lastKey]; ?>
<?php foreach($sidebar as $key => $item) { ?>
<li id="<?php echo Sanitizer::escapeId( "pt-$key" ) ?>"<?php
if ($item['active']) { ?> class="active"<?php } ?>><?php echo htmlspecialchars($item['text']) ?></li>
<?php } ?>
This is what i get (http://pastebin.com/t6Y2ZtMF) when i print_r($sidebar);
I want to get the last Array which is Categories and turn it into links.
I am new to php, so my method could be wrong even though it works. I is there a right way to pull the Categories Array or the above code is good as it is?
$lastValue = end($array);
$lastKey = key($array); // current key, which is the last since you called end()
After update:
You don't seem to be needing the key, only the array:
<?php $lastSidebarValue = end($this->data['sidebar']); ?>
<?php foreach ($lastSidebarValue as $key => $item) : ?>
business as usual...
<?php endforeach; ?>
Since you know you want the key 'Categories' though (not the last key), this seems the most logical thing to do:
<?php foreach ($this->data['sidebar']['Categories'] as $key => $item) : ?>
business as usual...
<?php endforeach; ?>
I think the end() function would be a great solution: http://php.net/manual/en/function.end.php
It basically returns the value of the last element in the array being passed to it.
$sidebar = end($sidebar);
If you want to get a key/value pair without popping & pushing the array, set the internal cursor to the end of the array and then use list and each to get the key & value.
// set up your array however you had it
$array = ...;
// move the cursor to the end of the array
end($array);
// use list() and each() to extract your key/value pair
list($key,$val) = each($array);
// $key will now have the last key
// $val will have the last value
perhaps, end:
$fruits = array('apple', 'banana', 'cranberry');
echo end($fruits); // cranberry
You can use `end()` instead of `array_pop()`. But both works for the **last element** of the array. The only difference is `end()` **points out** the **last element** of the array without effecting it and `array_pop()` **pops** the element off the **end** of array.
Please go through the following links for detail information
end() | array_pop()
I have a table in a database that contains a variety of paths to pages of my website. Each path is listed only one time. I currently have a very long and convoluted series of queries and PHP to pull all these and rewrite the data into an unordered list (to create a menu for my website). It seems that there is probably a relatively simple looping approach that would work MUCH more efficiently, but I cannot seem to get anything working. I have found TONS of PHP scripts that create UL lists from file trees, but all of them either don't work or can't handle the inherently not recursive nature of my query results (some require multi-dimensional arrays of my paths, which would be fine except for my trouble with creating those). I did find a script that works pretty close, but it formats the <ul> portion incorrectly by placing sub-lists outside of the <li> section (I will explain below)
Here is a sample:
DB returns the following in results array:
about/contact/
about/contact/form/
about/history/
about/staff/
about/staff/bobjones/
about/staff/sallymae/
products/
products/gifts/
products/widgets/
and I want to create the following output:
<ul>
<li>about/
<ul>
<li>about/contact/
<ul>
<li>about/contact/form/</li>
</ul>
</li>
<li>about/history/</li>
<li>about/staff/
<ul>
<li>about/staff/bobjones/</li>
<li>about/staff/sallymae/</li>
</ul>
</li>
</ul>
</li>
<li>products/
<ul>
<li>products/gifts/</li>
<li>products/widgets/</li>
</ul>
</li>
</ul>
So I got very close with a script found here: http://www.daniweb.com/forums/thread285916.html but I have run into a problem. It turns out that the script that I found creates improperly formatted UL lists. In a CORRECT situation, a sub-list is contained within the <li> of the parent element. In this scripting, the parent <li> is closed and then a <ul> block is inserted. The overall script is actually fairly elegant in the way that it keeps up with the levels and such, but I cannot wrap my head around it enough to figure out how to fix it. I have the whole thing in a function here:
function generateMainMenu()
{
global $db;
$MenuListOutput = '';
$PathsArray = array();
$sql = "SELECT PageUrlName FROM `table`";
$result = mysql_query($sql, $db) or die('MySQL error: ' . mysql_error());
while ($PageDataArray = mysql_fetch_array($result))
{
$PathsArray[] = rtrim($PageDataArray['PageUrlName'],"/"); //this function does not like paths to end in a slash, so remove trailing slash before saving to array
}
sort($PathsArray);// These need to be sorted.
$MenuListOutput .= '<ul id="nav">'."\n";//get things started off right
$directories=array ();
$topmark=0;
$submenu=0;
foreach ($PathsArray as $value) {
// break up each path into it's constituent directories
$limb=explode("/",$value);
for($i=0;$i<count($limb);$i++) {
if ($i+1==count($limb)){
// It's the 'Leaf' of the tree, so it needs a link
if ($topmark>$i){
// the previous path had more directories, therefore more Unordered Lists.
$MenuListOutput .= str_repeat("</ul>",$topmark-$i); // Close off the Unordered Lists
$MenuListOutput .= "\n";// For neatness
}
$MenuListOutput .= '<li>'.$limb[$i]."</li>\n";// Print the Leaf link
$topmark=$i;// Establish the number of directories in this path
}else{
// It's a directory
if($directories[$i]!=$limb[$i]){
// If the directory is the same as the previous path we are not interested.
if ($topmark>$i){// the previous path had more directories, therefore more Unordered Lists.
$MenuListOutput .= str_repeat("</ul>",$topmark-$i);// Close off the Unordered Lists
$MenuListOutput .= "\n";// For neatness
}
// (next line replaced to avoid duplicate listing of each parent)
//$MenuListOutput .= "<li>".$limb[$i]."</li>\n<ul>\n";
$MenuListOutput .= "<ul>\n";
$submenu++;// Increment the dropdown.
$directories[$i]=$limb[$i];// Mark it so that if the next path's directory in a similar position is the same, it won't be processed.
}
}
}
}
$MenuListOutput .= str_repeat("</ul>",$topmark+1);// Close off the Unordered Lists
return $MenuListOutput."\n\n\n";
}
and it returns something like this:
<ul id="nav">
<li>about</li>
<ul>
<li>history</li>
<li>job-opportunities</li>
<li>mission</li>
<li>privacy-policy</li>
</ul>
<li>giftcards</li>
<li>locations</li>
<ul>
<li>main-office</li>
<li>branch-office</li>
</ul>
<li>packages</li>
</ul>
Anyone have an idea of where I need to add in some additional logic and how I can accomplish this? Other ideas on a better way to do this? It seems like this is such a common issue that there would be a simple/standard method of handling something like this. Maybe if I could figure out how to create a multi-dimensional array from my paths then those could be iterated to make this work?
EDIT: More Complex :-(
I tried casablanca's response and it worked perfectly...except I then realized that now I have a follow-up to make things more difficult. In order to display the "name" of the page, I need to also have that info in the array, thus the path probably works better as the array key and the name in the value. Any thoughts on changing like this:
$paths = array(
"about/contact/ " => "Contact Us",
"about/contact/form/ " => "Contact Form",
"about/history/ " => "Our History",
"about/staff/ " => "Our Staff",
"about/staff/bobjones/ " => "Bob",
"about/staff/sallymae/ " => "Sally",
"products/ " => "All Products",
"products/gifts/ " => "Gift Ideas!",
"products/widgets/ " => "Widgets"
);
and then using something like this line within the buildUL function:
echo ''.$paths[$prefix.$key].'';
Edit:
Changed to cater for updated question.
I'm using an array index of __title to hold the page title. As long as you never have a directory in your tree called __title this should be fine. You're free to change this sentinel value to anything you wish however.
I have also changed it so the list building function returns a string, so that you can store the value for use later in your page. (You can of course just do echo build_list(build_tree($paths)) to output the list directly.
<?php
$paths = array(
'about/contact/' => 'Contact Us',
'about/contact/form/' => 'Contact Form',
'about/history/' => 'Our History',
'about/staff/' => 'Our Staff',
'about/staff/bobjones/' => 'Bob',
'about/staff/sallymae/' => 'Sally',
'products/' => 'All Products',
'products/gifts/' => 'Gift Ideas!',
'products/widgets/' => 'Widgets'
);
function build_tree($path_list) {
$path_tree = array();
foreach ($path_list as $path => $title) {
$list = explode('/', trim($path, '/'));
$last_dir = &$path_tree;
foreach ($list as $dir) {
$last_dir =& $last_dir[$dir];
}
$last_dir['__title'] = $title;
}
return $path_tree;
}
function build_list($tree, $prefix = '') {
$ul = '';
foreach ($tree as $key => $value) {
$li = '';
if (is_array($value)) {
if (array_key_exists('__title', $value)) {
$li .= "$prefix$key/ ${value['__title']}";
} else {
$li .= "$prefix$key/";
}
$li .= build_list($value, "$prefix$key/");
$ul .= strlen($li) ? "<li>$li</li>" : '';
}
}
return strlen($ul) ? "<ul>$ul</ul>" : '';
}
$tree = build_tree($paths);
$list = build_list($tree);
echo $list;
?>
Indeed a multi-dimensional would help here. You can build one by splitting each path into components and using those to index into the array. Assuming $paths is your initial array, the code below will build a multi-dimensional array $array with keys corresponding to the path components:
$array = array();
foreach ($paths as $path) {
$path = trim($path, '/');
$list = explode('/', $path);
$n = count($list);
$arrayRef = &$array; // start from the root
for ($i = 0; $i < $n; $i++) {
$key = $list[$i];
$arrayRef = &$arrayRef[$key]; // index into the next level
}
}
You can then iterate over this array using a recursive function, which you can use to naturally build a recursive UL list as in your example. In each recursive call, $array is a sub-array of the entire array this is being currently processed and $prefix is the path from the root to the current sub-array:
function buildUL($array, $prefix) {
echo "\n<ul>\n";
foreach ($array as $key => $value) {
echo "<li>";
echo "$prefix$key/";
// if the value is another array, recursively build the list
if (is_array($value))
buildUL($value, "$prefix$key/");
echo "</li>\n";
}
echo "</ul>\n";
}
The initial call would simply be buildUL($array, '').
I wrote a recursive function, which returns an array with the paths to all files/folders in a given path. An array is already sorted and returns the exact information i want, but i struggle to display it properly in html lists.
Array_of_paths = (
[0] => /path/to/folderA/
[1] => /path/to/folderA/subfolderAA/
[2] => /path/to/folderB/
[3] => /path/to/folderB/subfolderBB/
[4] => /path/to/folderB/subfolderBB/fileBB.txt
[5] => /path/to/folderB/fileB.txt
[6] => /path/to/folderC/
...
)
I want to put these paths in <ul>,<li> tags to see something like this:
<ul>
<li>/path/to/folderA/
<ul>
<li>/path/to/folderA/folderAA/</li>
</ul>
</li>
<li>/path/to/folderB
<ul>
<li>/path/to/folderB/subfolderBB/
<ul>
<li>/path/to/folderB/subfolderBB/fileBB.txt</li>
</ul>
</li>
<li>/path/to/folderB/fileB.txt</li>
</ul>
</li>
<li>/path/to/folderC/</li>
</ul>
=>
<ul>
<li>/path/to/folderA/
<ul>
<li>/path/to/folderA/folderAA/</li>
</ul>
</li>
<li>/path/to/folderB
<ul>
<li>/path/to/folderB/subfolderBB/
<ul>
<li>/path/to/folderB/subfolderBB/fileBB.txt</li>
</ul>
</li>
<li>/path/to/folderB/fileB.txt</li>
</ul>
</li>
<li>/path/to/folderC/</li>
</ul>
I managed to find a couple of similars questions, but the answers were in Ruby language. So, what's the problem solving idea behind this?
$lastD = 0;
foreach ($p as $e)
{
$depth = substr_count($e, '/');
//if this is a file, then add one to the depth count
if (substr($e,-1) != '/')
$depth++;
if ($depth > $lastD)
{
echo "<ul>";
$lastD = $depth;
}
if ($depth < $lastD)
{
echo "</ul>";
$lastD = $depth;
}
echo "<li>$e";
}
Returns:
/path/to/folderA//path/to/folderA/subfolderAA//path/to/folderB//path/to/folderB/subfolderBB//path/to/folderB/subfolderBB/fileBB.txt/path/to/folderB/fileB.txt/path/to/folderC/
If your are in PHP5, use RecursiveDirectoryIterator and RecursiveIteratorIterator to do the job.
$dir = new RecursiveDirectoryIterator("/path");
$it = new RecursiveIteratorIterator($dir);
foreach ($it as $key => $value) {
// Use $it->getDepth() and $value->getRealpath()
// with Byron's code to generate your list
}
I'm using this bit of code you published.
The structure of nested UL's is not quite right, so I just added a quick fix in order to have the closing ul tags so it can be used with more levels.
....
if ($depth < $lastD)
{
$closingULs=$lastD-$depth;
for($i=0;$i<$closingULs;$i++)
{
$uls.="</ul>";
}
echo $uls;
$lastD = $depth;
}
IMHO it's better to store the data in a more efficient and more similar format, something hierarchical. You can explode() your array by / and create the tree via arrays, then it'll be easy to foreach the array and build the HTML list.
foreach ( $paths as $path )
{
$pieces = explode('/', $path);
foreach ( $pieces as $piece )
{
$pathtree[$piece] = '';
}
}
This new $pathtree array is much smaller, probably 1/4th as small as your $paths array. From this point you just need to foreach it to build your HTML list tree.
$mainMenu['Home'][1] = '/mult/index.php';
$mainMenu['Map'][1] = '/mult/kar.php';
$mainMenu['MapA'][2] = '/mult/kara.php';
$mainMenu['MapB'][2] = '/mult/karb.php';
$mainMenu['Contact'][1] = '/mult/sni.php';
$mainMenu['Bla'][1] = '/mult/vid.php';
This is a menu, 1 indicates the main part, 2 indicates the sub-menu. Like:
Home
Map
-MapA
-MapB
Contat
Bla
I know how to use foreach but as far as I see it is used in 1 dimensional arrays. What I have to do in the example above?
You would need to nest two foreach BUT, there is nothing about your data structure that easily indicates what is a sub-item. Map vs. MapA? I guess a human could figure that out, but you'll have to write a lot of boilerlate for your script to sort that.. Consider restructuring your data so that it more closely matches what you are trying to achieve.
Here's an example. You can probably come up with a better system, though:
$mainMenu = array(
'Home' => '/mult/index.php',
'Map' => array(
'/mult/kar.php',
array(
'MapA' => '/mult/kara.php',
'MapB' => '/mult/karb.php'
)
),
'Contact' => '/mult/sni.php',
...
);
You nest foreach statements; Something like this should do the work.
foreach($mainMenu as $key=>$val){
foreach($val as $k=>$v){
if($k == 2){
echo '-' . $key;
}else{
echo $key;
}
}
}
Foreach can just as easily be used in multi-dimensional arrays, the same way you would use a for loop.
Regardless, your approach is a little off, here's a better (but still not great) solution:
$mainMenu['Home'][1] = '/mult/index.php';
$mainMenu['Map'][1] = '/mult/kar.php';
$mainMenu['Map']['children']['MapA'] = '/mult/kara.php';
$mainMenu['Map']['children']['MapB'] = '/mult/karb.php';
$mainMenu['Contact'][1] = '/mult/sni.php';
$mainMenu['Bla'][1] = '/mult/vid.php';
foreach($mainMenu as $k => $v){
// echo menu item
if(isset($v['children'])){
foreach($v['children'] as $kk => $vv){
// echo submenu
}
}
}
That said, this only does 1-level of submenus. Either way, it should help you get the idea!