find biggest numbered folder in subdirectories and serve it with json - php

I have folder structure like below:
-Manga
-Berserk
-345
-346
-One Piece
-840
-841
And want JSON output like below:
{"0" => ["name":"Berserk", "last":"346"],
"1" => ["name":"One Piece, "last":"841"]}
Like the title says I want JSON output that gives me every series name in manga folder and biggest numbered folder that belongs to that series.
I have something like this at the moment.
<?php
$path = dirname(__FILE__) . "/../../manga";
$dir = new DirectoryIterator($path);
$data = array("mangas"=> array());
foreach ($dir as $fileinfo) {
if (!$fileinfo->isDot() && $fileinfo->getFilename() !== ".DS_Store") {
array_push($data["mangas"], $fileinfo->getFilename());
}
}
header('Content-Type: application/json; charset=UTF-8');
echo json_encode($data);
?>
Edit: Slim Framework API
$app->get('/ana', function ($request, $response, $args) {
$path = "../../manga";
function getAll($path)
{
$dirs = [];
foreach (new DirectoryIterator($path) as $item) {
if (!$item->isDir() || $item->isDot()) {
continue;
}
if ($max = getMaxInDir($item->getRealPath())) {
$dirs[] = [
'name' => $item->getFilename(),
'last' => $max,
];
}
}
return $dirs;
}
function getMaxInDir($path)
{
$max = 0;
foreach (new DirectoryIterator($path) as $item) {
$name = $item->getFilename();
if (!$item->isDir() || $item->isDot() || !is_numeric($name)) {
continue;
}
if ($current = (int)$name > $max) {
$max = $current;
};
}
return $max;
}
return $this->response->withJson(getAll($path));
});
Output of http://127.0.0.1/api/public/ana
[{"name":"Berserk","last":true},{"name":"One Piece","last":true}]

First of all your desired output is not a valid JSON. It should rather be
[
{"name":"Berserk","last":346},
{"name":"One Piece","last":841}
]
Now one way to do it
echo json_encode(getAll($path));
function getAll($path)
{
$dirs = [];
foreach (new DirectoryIterator($path) as $item) {
if (!$item->isDir() || $item->isDot()) {
continue;
}
if ($max = getMaxInDir($item->getRealPath())) {
$dirs[] = [
'name' => $item->getFilename(),
'last' => $max,
];
}
}
return $dirs;
}
function getMaxInDir($path)
{
$max = 0;
foreach (new DirectoryIterator($path) as $item) {
$name = $item->getFilename();
if (!$item->isDir() || $item->isDot() || !is_numeric($name)) {
continue;
}
if (($current = (int)$name) > $max) { // updated line
$max = $current;
};
}
return $max;
}

Related

Combine 2 foreach loops into one with different logic

I'm looking for the way to combine 2 foraech loops into one (merge 2 functions):
The result function should return both: ($is - boolean & $d - array).
Traversable data in both functions - the same.
Is it possible? What will be a good solution for that?
public function func1($p, $c) {
$is = 0;
if (!empty($p)) {
foreach($p as $k=>$v) {
if ((!empty($c['prod']) && $c['prod'] == $v['ref'] && $c['status'])) {
$is = 1;
break;
}
}
}
return $is;
}
public function func2($p) {
$d = [];
if (!empty($p)) {
foreach($p as $k=>$v) {
$prod = [
'name' => $v['name'],
'price' => $v['price'],
'reference' => $v['ref']
];
$d[] = $prod;
}
}
return $d;
}
Thanks.
My take, but not tested.
public function func3($p, $c) {
$is = 0;
$d = [];
if (!empty($p)) {
foreach($p as $k=>$v) {
$d[] = [
'name' => $v['name'],
'price' => $v['price'],
'reference' => $v['ref']
];
if ($is === 0 && (!empty($c['prod']) && $c['prod'] == $v['ref'] && $c['status'])) {
$is = 1;
}
}
}
return [$is, $d];
}

Multidimensional array directory map

I'm trying to get a directory structure in a multidimensional array.
I got this far:
function dirtree($dir, $regex = '', $ignoreEmpty = false)
{
if (!$dir instanceof DirectoryIterator) {
$dir = new DirectoryIterator((string) $dir);
}
$dirs = array();
$files = array();
foreach ($dir as $node) {
if ($node->isDir() && !$node->isDot()) {
$tree = dirtree($node->getPathname(), $regex, $ignoreEmpty);
if (!$ignoreEmpty || count($tree)) {
$dirs[$node->getFilename()] = $tree;
}
} elseif ($node->isFile()) {
$name = $node->getFilename();
if ('' == $regex || preg_match($regex, $name)) {
$files[] = $name;
}
}
}
asort($dirs);
sort($files);
return array_merge($dirs, $files);
}
But I am having issues getting the folder name instead of the index 0,1 .etc. This seems to be due to the fact that my directories have numeric names?
Array
(
[0] => Array // 0 should be the folder name
(
[0] => m_109225488_1.jpg
[1] => t_109225488_1.jpg
)
[1] => Array
(
[0] => m_252543961_1.jpg
[1] => t_252543961_1.jpg
)
The solution was rather simple thanks to: Merge array without loss key index
Instead of array_merge simply do $dirs + $files
Potential solution (potential issue point out by Roger Gee):
function dirtree($dir, $regex = '', $ignoreEmpty = false)
{
if (!$dir instanceof DirectoryIterator) {
$dir = new DirectoryIterator((string) $dir);
}
$dirs = array();
$files = array();
foreach ($dir as $node) {
if ($node->isDir() && !$node->isDot()) {
$tree = dirtree($node->getPathname(), $regex, $ignoreEmpty);
if (!$ignoreEmpty || count($tree)) {
$dirs[$node->getFilename()] = $tree;
}
} elseif ($node->isFile()) {
$name = $node->getFilename();
if ('' == $regex || preg_match($regex, $name)) {
$files[] = $name;
}
}
}
return $dirs + $files;
}
Better solution?
function dirtree($dir, $regex = '', $ignoreEmpty = false)
{
if (!$dir instanceof DirectoryIterator) {
$dir = new DirectoryIterator((string) $dir);
}
$filedata = array();
foreach ($dir as $node) {
if ($node->isDir() && !$node->isDot()) {
$tree = dirtree($node->getPathname(), $regex, $ignoreEmpty);
if (!$ignoreEmpty || count($tree)) {
$filedata[$node->getFilename()] = $tree;
}
} elseif ($node->isFile()) {
$name = $node->getFilename();
if ('' == $regex || preg_match($regex, $name)) {
$filedata[] = $name;
}
}
}
return $filedata;
}
Using the array union operation is dangerous since you can potentially overwrite existing files. Consider the following directory structure:
a <-- directory
├── 0 <-- directory (empty)
├── b <-- regular file
└── c <-- directory
└── d <-- regular file
Now consider running the operation using the array union. I get the following result:
array(2) {
[0]=>
array(0) {
}
["c"]=>
array(1) {
[0]=>
string(1) "d"
}
}
Notice how regular file b is not present? This is because the array union operation prefers the existing 0 index over the 0 index from the right operand (which contains the regular files).
I would stick with the original implementation present in the question or use a special bucket for files that doesn't contain a valid filesystem name (e.g. :files:). Note that this may be platform-specific as to what you choose.
In the case of the original implementation, you can decide whether the index is a directory vs regular file by calling is_array or is_scalar on the value. Note that since the directories array is the first parameter to array_merge, you are guaranteed that no directory indexes get incremented and will always refer to the correct directory names.
Here's how you could determine just the directory names:
function getDirectoryNames($result) {
$ds = [];
foreach ($result as $key => $value) {
if (is_array($value)) {
$ds[] = $key;
}
}
return $ds;
}
What you are looking for is ksort instead of asort.
<html>
<body>
<?php
function dirtree($dir, $regex = '', $ignoreEmpty = false)
{
if (!$dir instanceof DirectoryIterator) {
$dir = new DirectoryIterator((string) $dir);
}
$dirs = array();
$files = array();
foreach ($dir as $node) {
if ($node->isDir() && !$node->isDot()) {
$tree = dirtree($node->getPathname(), $regex, $ignoreEmpty);
if (!$ignoreEmpty || count($tree)) {
$dirs[$node->getFilename()] = $tree;
}
} elseif ($node->isFile()) {
$name = $node->getFilename();
if ('' == $regex || preg_match($regex, $name)) {
$files[] = $name;
}
}
}
ksort($dirs);
sort($files);
return array_merge($dirs, $files);
}
?>
<body>
<pre>
<?=var_dump(dirtree(getcwd());?>
</pre>
</body>
</html>
This will do the work for you.
But as mentioned, a better solution would be to seperate directories and files like this:
<html>
<body>
<?php
class DirNode {
public $name;
public $dirs=[];
public $files=[];
public function DirNode($dirName) {
$this->name = $dirName;
}
public function printDir($prefix="") {
echo($prefix.$this->name."\n");
foreach($this->dirs as $dir=>$subDir) {
echo($prefix.$dir."\n");
$subDir->printDir($prefix." ");
echo("\n");
}
foreach($this->files as $file) {
echo($prefix.$file."\n");
}
}
}
function dirtree($dir, $regex = '', $ignoreEmpty = false)
{
if (!$dir instanceof DirectoryIterator) {
$dir = new DirectoryIterator((string) $dir);
}
$directory = new DirNode($dir);
foreach ($dir as $node) {
if ($node->isDir() && !$node->isDot()) {
$tree = dirtree($node->getPathname(), $regex, $ignoreEmpty);
if (!$ignoreEmpty || count($tree)) {
$directory->dirs[$node->getFilename()] = $tree;
}
} elseif ($node->isFile()) {
$name = $node->getFilename();
if ('' == $regex || preg_match($regex, $name)) {
$directory->files[] = $name;
}
}
}
ksort($directory->dirs);
sort($directory->files);
return $directory;
}
$dirfiles = dirtree(getcwd().'/..');
echo("<pre>");
echo($dirfiles->printDir());
echo("</pre>");
?>
</body>
</html>

PHP FTP recursive directory listing

I'm trying to make a recursive function to get all the directories and sub directories from my ftp server in an array.
I tried a lot of functions I've found on the web. The one that works best for me is this one:
public function getAllSubDirFiles() {
$dir = array(".");
$a = count($dir);
$i = 0;
$depth = 20;
$b = 0;
while (($a != $b) && ($i < $depth)) {
$i++;
$a = count($dir);
foreach ($dir as $d) {
$ftp_dir = $d . "/";
$newdir = ftp_nlist($this->connectionId, $ftp_dir);
foreach ($newdir as $key => $x) {
if ((strpos($x, ".")) || (strpos($x, ".") === 0)) {
unset($newdir[$key]);
} elseif (!in_array($x, $dir)) {
$dir[] = $x;
}
}
}
$b = count($dir);
}
return $dir ;
}
The problem with this function is it wont allow the directory to have a "." in it's name and every file that is located in the root directory will be considered a directory as well. So I adjusted the function and got this:
public function getAllSubDirFiles($ip, $id, $pw) {
$dir = array(".");
$a = count($dir);
$i = 0;
$depth = 20;
$b =0;
while (($a != $b) && ($i < $depth)) {
$i++;
$a = count($dir);
foreach ($dir as $d) {
$ftp_dir = $d . "/";
$newdir = ftp_nlist($this->connectionId, $ftp_dir);
foreach ($newdir as $key => $x) {
if (!is_dir('ftp://'.$id.':'.$pw.'#'.$ip.'/'.$x)) {
unset($newdir[$key]);
} elseif (!in_array($x, $dir)) {
$dir[] = $x;
}
}
}
$b = count($dir);
}
return $dir ;
}
This works pretty good but and gives the result I want. but it's so slow it's unusable.
I also tried working with ftp_rawlist but it has the same drawback of being horribly slow.
public function getAllSubDirFiles() {
$dir = array(".");
$a = count($dir);
$i = 0;
$depth = 20;
$b = 0;
while (($a != $b) && ($i < $depth)) {
$i++;
$a = count($dir);
foreach ($dir as $d) {
$ftp_dir = $d . "/";
$newdir = $this->getFtp_rawlist('/' . $ftp_dir);
foreach ($newdir as $key => $x) {
$firstChar = substr($newdir[$key][0], 0, 1);
$a = 8;
while ($a < count($newdir[$key])) {
if ($a == 8) {
$fileName = $ftp_dir . '/' . $newdir[$key][$a];
} else {
$fileName = $fileName . ' ' . $newdir[$key][$a];
}
$a++;
}
if ($firstChar != 'd') {
unset($newdir[$key]);
} elseif (!in_array($fileName, $dir)) {
$dir[] = $fileName;
}
}
}
$b = count($dir);
}
return $dir;
}
public function getFtp_rawlist($dir) {
$newArr = array();
$arr = ftp_rawlist($this->connectionId, $dir);
foreach ($arr as $value) {
$stringArr = explode(" ", $value);
$newArr[] = array_values(array_filter($stringArr));
}
return $newArr;
}
I've been stuck on this problem for the last couple of days and I'am getting desperate. If any one has any suggestion please let me know
If your server supports MLSD command and you have PHP 7.2 or newer, you can use ftp_mlsd function:
function ftp_mlsd_recursive($ftp_stream, $directory)
{
$result = [];
$files = ftp_mlsd($ftp_stream, $directory);
if ($files === false)
{
die("Cannot list $directory");
}
foreach ($files as $file)
{
$name = $file["name"];
$filepath = $directory . "/" . $name;
if (($file["type"] == "cdir") || ($file["type"] == "pdir"))
{
// noop
}
else if ($file["type"] == "dir")
{
$result = array_merge($result, ftp_mlsd_recursive($ftp_stream, $filepath));
}
else
{
$result[] = $filepath;
}
}
return $result;
}
If you do not have PHP 7.2, you can try to implement the MLSD command on your own. For a start, see user comment of the ftp_rawlist command:
https://www.php.net/manual/en/function.ftp-rawlist.php#101071
If you cannot use MLSD, you will particularly have problems telling if an entry is a file or folder. While you can use the ftp_size trick, calling ftp_size for each entry can take ages.
But if you need to work against one specific FTP server only, you can use ftp_rawlist to retrieve a file listing in a platform-specific format and parse that.
The following code assumes a common *nix format.
function ftp_nlst_recursive($ftp_stream, $directory)
{
$result = [];
$lines = ftp_rawlist($ftp_stream, $directory);
if ($lines === false)
{
die("Cannot list $directory");
}
foreach ($lines as $line)
{
$tokens = preg_split("/\s+/", $line, 9);
$name = $tokens[8];
$type = $tokens[0][0];
$filepath = $directory . "/" . $name;
if ($type == 'd')
{
$result = array_merge($result, ftp_nlst_recursive($ftp_stream, $filepath));
}
else
{
$result[] = $filepath;
}
}
return $result;
}
For DOS format, see: Get directory structure from FTP using PHP.
I've build an OOP FTP Client library that's can help you on this a lot, using just this code you can retrieve a list of only the directories with addition useful information like (chmod, last modified time, size ...).
The code :
// Connection
$connection = new FtpConnection("localhost", "foo", "12345");
$connection->open();
// FtpConfig
$config = new FtpConfig($connection);
$config->setPassive(true);
$client = new FtpClient($connection);
$allFolders =
// directory, recursive, filter
$client->listDirectoryDetails('/', true, FtpClient::DIR_TYPE);
// Do whatever you want with the folders
This code a variation of Martin Prikryl one. It is slower but do not have any failures with whitespaces. Use this code only if you have any problems with the code above.
function ftp_list_files_recursive($ftp_stream, $path){
$lines = ftp_nlist($ftp_stream, $path);
$result = array();
foreach ($lines as $line) {
if (ftp_size($ftp_stream, $line) == -1) {
$result = array_merge($result, ftp_list_files_recursive($ftp_stream, $line));
}
else{
$result[] = $line;
}
}
return $result;
}

Only variables should be passed by reference in

I inherited maintenance of a Drupal 7 website and I have zero PHP experience and I need some help. I have just updated Drupal to 7.31 and am getting the error "Only variables should be passed by reference in..." on lines 43 and 86 of an .inc file.
Here is line 43:
$item = array_shift(array_values($data));
Here is line 86:
$item = array_shift(array_values($data));
Can anyone help me out?
I don't know if this is allowed but here the code for the .inc file
<?php
$plugin = _cdd_core_content_type_plugin('nauticus_core', 'menu_full_leaf', 'Page: menu leaf', array(
'description' => 'Add menu children',
), array(
'override_title' => 1,
'override_title_text' => ''
), 'Sail Nauticus');
function nauticus_core_menu_full_leaf_content_type_plugin_render($subtype, &$conf, $panel_args) {
$trail = menu_get_active_trail();
$menu = '';
if (is_array($trail)) {
$leaf = array_pop($trail);
if (isset($leaf['menu_name'])) {
$menu_name = $leaf['menu_name'];
if ($menu_name !== 'navigation') {
$depth = $leaf['depth'];
$p1 = $leaf['p1'];
$data = menu_tree_page_data($menu_name, $depth + 1);
foreach ($data as $i => $item) {
if ($item['link']['mlid'] !== $leaf['plid']
&& !($leaf['plid'] == 0 && ($item['link']['mlid'] == $leaf['mlid']))
) {
unset($data[$i]);
}
}
if (sizeof($data) > 1) {
$menu = menu_tree_output($data);
foreach ($menu as $i => $item) {
if (isset($item['#original_link'])) {
if ($item['#original_link']['mlid'] !== $p1) {
if (isset($menu[$i]['#below'])) {
$menu[$i]['#below'] = array();
}
}
}
}
}
elseif (sizeof($data) == 1) {
$item = array_shift(array_values($data));
if (isset($item['below']) && !empty($item['below'])) {
$menu = menu_tree_output($item['below']);
foreach ($menu as $i => $item) {
if (isset($item['#original_link'])) {
if ($item['#original_link']['mlid'] !== $p1) {
if (isset($menu[$i]['#below'])) {
$menu[$i]['#below'] = array();
}
}
}
}
}
}
}
}
}
return $menu;
}
function nauticus_core_menu_full_leaf_content_type_plugin_title($subtype, &$conf, $panel_args) {
$title = 'Navigation';
$trail = menu_get_active_trail();
if (is_array($trail)) {
$leaf = array_pop($trail);
if (isset($leaf['menu_name'])) {
$menu_name = $leaf['menu_name'];
if ($menu_name !== 'navigation') {
$depth = $leaf['depth'];
$p1 = $leaf['p1'];
$data = menu_tree_page_data($menu_name, $depth + 1);
foreach ($data as $i => $item) {
if ($item['link']['mlid'] !== $leaf['plid']
&& !($leaf['plid'] == 0 && ($item['link']['mlid'] == $leaf['mlid']))
) {
unset($data[$i]);
}
}
if (sizeof($data) >= 1) {
$item = array_shift(array_values($data));
if (isset($item['link']) && !empty($item['link'])) {
if (isset($item['link']['link_title'])) {
$title = $item['link']['link_title'];
}
}
}
}
}
}
return $title;
}
array_shift requires that the first argument be a pass by reference and array_values returns an array.
change those lines that you have $item = array_shift(array_values($data)); to
$item = array_values($data);
$item = array_shift(&$item);
the & means the variable is pass by reference. This should fix your problem.
if that does not work just remove the &.
Change
$item = array_shift(array_values($data));
to
$tmp = array_values($data);
$item = array_shift($tmp);

Recursive directory listing in ownCloud

I'm writing code that I'd like to recursively list directories. So far I've written code that lists level 1 directories. Does anyone have any ideas about how I can recurse infinitely?
$res = OC_Files::getDirectoryContent('');
$list = array();
foreach($res as $file) {
if ($file['type'] == 'dir') {
$res1 = OC_Files::getDirectoryContent('/'.$file['name']);
foreach($res1 as $res2) {
if ($res2['type'] == 'file') $list[] = $file['name'].'/'.$res2['name'];
}
} else $list[] = $file['name'];
}
foreach($list as $entry)
echo $entry.'</br>';
Try this:
function traverse_directory($dir) {
$res = OC_Files::getDirectoryContent($dir);
$list = array();
foreach($res as $file) {
if ($file['type'] == 'dir') {
traverse_directory($dir . '/' . $file['name'])
} else {
$list[] = $file['name'];
}
}
foreach($list as $entry) {
echo $entry.'</br>';
}
}
traverse_directory('');

Categories