Issues (438)

Security Analysis    not enabled

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

classes/Util.php (13 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
/* zKillboard
3
 * Copyright (C) 2012-2015 EVE-KILL Team and EVSCO.
4
 *
5
 * This program is free software: you can redistribute it and/or modify
6
 * it under the terms of the GNU Affero General Public License as published by
7
 * the Free Software Foundation, either version 3 of the License, or
8
 * (at your option) any later version.
9
 *
10
 * This program is distributed in the hope that it will be useful,
11
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13
 * GNU Affero General Public License for more details.
14
 *
15
 * You should have received a copy of the GNU Affero General Public License
16
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
17
 */
18
class Util
19
{
20
	public static function isMaintenanceMode()
21
	{
22
		return "true" == Db::queryField("select contents from zz_storage where locker = 'maintenance'", "contents", array(), 0);
23
	}
24
25
	public static function getMaintenanceReason()
26
	{
27
		return Storage::retrieve("MaintenanceReason", "");
28
	}
29
30
	public static function getNotification()
31
	{
32
		return Storage::retrieve("notification", null);
33
	}
34
35
	public static function is904Error()
36
	{
37
		$stop904 = Db::queryField("select count(*) count from zz_storage where locker = 'ApiStop904' and contents > now()", "count", array(), 1);
38
		return $stop904 > 0;
39
	}
40
41
	public static function getCrest($url)
42
	{
43
		StatsD::increment("crest_calls");
44
		\Perry\Setup::$fetcherOptions = ["connect_timeout" => 15, "timeout" => 30];
45
		return \Perry\Perry::fromUrl($url);
46
	}
47
48
	/**
49
	 * @param integer $keyID
50
	 * @param string $vCode
51
	 */
52
	public static function getPheal($keyID = null, $vCode = null)
53
	{
54
		global $phealCacheLocation, $apiServer, $baseAddr, $ipsAvailable;
55
56
		if (static::is904Error()) 
57
		{
58
			// Web requests shouldn't be hitting the API...
59
			if (php_sapi_name() == 'cli')
60
				exit();
0 ignored issues
show
Coding Style Compatibility introduced by
The method getPheal() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
61
62
			return null;
63
		}
64
65
		\Pheal\Core\Config::getInstance()->http_method = "curl";
0 ignored issues
show
The property http_method does not seem to exist in Pheal\Core\Config.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
66
		\Pheal\Core\Config::getInstance()->http_user_agent = "API Fetcher for http://$baseAddr";
67
		if(!empty($ipsAvailable))
68
		{
69
			$max = count($ipsAvailable)-1;
70
			$ipID = mt_rand(0, $max);
71
			\Pheal\Core\Config::getInstance()->http_interface_ip = $ipsAvailable[$ipID];
72
		}
73
		\Pheal\Core\Config::getInstance()->http_post = false;
74
		\Pheal\Core\Config::getInstance()->http_keepalive = true; // default 15 seconds
75
		\Pheal\Core\Config::getInstance()->http_keepalive = 10; // KeepAliveTimeout in seconds
76
		\Pheal\Core\Config::getInstance()->http_timeout = 30;
77
		\Pheal\Core\Config::getInstance()->http_ssl_verifypeer = false;
78
79
		if ($phealCacheLocation != null)
80
			\Pheal\Core\Config::getInstance()->cache = new \Pheal\Cache\FileStorage($phealCacheLocation); // Implement own cache class that calls statsD
81
		\Pheal\Core\Config::getInstance()->log = new PhealLogger();
82
		\Pheal\Core\Config::getInstance()->api_customkeys = true;
83
		\Pheal\Core\Config::getInstance()->api_base = $apiServer;
84
85
		if ($keyID != null && $vCode != null)
0 ignored issues
show
Bug Best Practice introduced by
It seems like you are loosely comparing $keyID of type integer|null against null; this is ambiguous if the integer can be zero. Consider using a strict comparison !== instead.
Loading history...
It seems like you are loosely comparing $vCode of type string|null against null; this is ambiguous if the string can be empty. Consider using a strict comparison !== instead.
Loading history...
86
			$pheal = new \Pheal\Pheal($keyID, $vCode);
87
		else
88
			$pheal = new \Pheal\Pheal();
89
90
		// Stats gathering, sadly phealng has no way of telling us if we're hitting the cache or not.
91
		StatsD::increment("ccp_api");
92
93
		// Return the API data to whomever requested it.
94
		return $pheal;
95
	}
96
97
	public static function pluralize($string)
98
	{
99
		if (!self::endsWith($string, "s")) return $string . "s";
100
		else return $string . "es";
101
	}
102
103
	/**
104
	 * @param string $haystack
105
	 * @param string $needle
106
	 */
107
	public static function startsWith($haystack, $needle)
108
	{
109
		$length = strlen($needle);
110
		return (substr($haystack, 0, $length) === $needle);
111
	}
112
113
	public static function endsWith($haystack, $needle)
114
	{
115
		return substr($haystack, -strlen($needle)) === $needle;
116
	}
117
118
	public static function getKillHash($killID = null, $kill = null)
119
	{
120
		if ($killID != null) {
121
			$json = Killmail::get($killID);
122
			if ($json === null) throw new Exception("Cannot find kill $killID");
123
			$kill = json_decode($json);
124
			if ($kill === null) throw new Exception("Cannot json_decode $killID");
125
		}
126
		if ($kill === null) throw new Exception("Can't hash an empty kill");
127
128
		$hashStr = "";
129
		$hashStr .= ":$kill->killTime:$kill->solarSystemID:$kill->moonID:";
130
		$victim = $kill->victim;
131
		$hashStr .= ":$victim->characterID:$victim->shipTypeID:$victim->damageTaken:";
132
133
		return hash("sha256", $hashStr);
134
	}
135
136
	public static function calcX($slot, $size)
137
	{
138
		$angle = $slot * (360 / 32) - 4;
139
		$rad = deg2rad($angle);
140
		$radius = $size / 2;
141
		return (int)(($radius * cos($rad)));
142
	}
143
144
	public static function calcY($slot, $size)
145
	{
146
		$angle = $slot * (360 / 32) - 4;
147
		$rad = deg2rad($angle);
148
		$radius = $size / 2;
149
		return (int)(($radius * sin($rad)));
150
	}
151
152
	private static $formatIskIndexes = array("", "k", "m", "b", "t", "tt", "ttt");
153
154
	public static function formatIsk($value)
155
	{
156
		$numDecimals = (((int)$value) == $value) && $value < 10000 ? 0 : 2;
157
		if ($value == 0) return number_format(0, $numDecimals);
158
		if ($value < 10000) return number_format($value, $numDecimals);
159
		$iskIndex = 0;
160
		while ($value > 999.99) {
161
			$value /= 1000;
162
			$iskIndex++;
163
		}
164
		return number_format($value, $numDecimals) . self::$formatIskIndexes[$iskIndex];
165
	}
166
167
	public static function convertUriToParameters($additionalParameters = array(), $addExtraParameters = true)
168
	{
169
		$parameters = array();
170
		@$uri = $_SERVER["REQUEST_URI"];
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
171
		$split = explode("/", $uri);
172
		$currentIndex = 0;
173
		foreach ($split as $key)
174
		{
175
			$value = $currentIndex + 1 < count($split) ? $split[$currentIndex + 1] : null;
176
			switch ($key) {
177
				case "groupID":
178
					// do nothing
179
				break;
180
				case "kills":
181
				case "losses":
182
				case "w-space":
183
				case "lowsec":
184
				case "nullsec":
185
				case "highsec":
186
				case "solo":
187
					$parameters[$key] = true;
188
				break;
189
				case "characterID":
190
				case "corporationID":
191
				case "allianceID":
192
				case "factionID":
193
				case "shipID":
194
				case "shipTypeID":
195
				case "solarSystemID":
196
				case "systemID":
197
				case "regionID":
198
					if ($value != null) {
199
						if (strpos($key, "ID") === false) $key = $key . "ID";
200
						if ($key == "systemID") $key = "solarSystemID";
201
						else if ($key == "shipID") $key = "shipTypeID";
202
						$exploded = explode(",", $value);
203
						foreach($exploded as $aValue)
204
						{
205
							if ($aValue != (int) $aValue || ((int) $aValue) == 0) throw new Exception("Invalid ID passed: $aValue");
206
						}
207
						if (sizeof($exploded) > 10) throw new Exception("Too many IDs! Max: 10");
208
						$parameters[$key] = $exploded;
209
					}
210
				break;
211
				case "page":
212
					$value = (int)$value;
213
					if ($value < 1) $value = 1;
214
					$parameters[$key] = $value;
215
				break;
216
				case "orderDirection":
217
					if (!($value == "asc" || $value == "desc")) throw new Exception("Invalid orderDirection!  Allowed: asc, desc");
218
					$parameters[$key] = "desc"; // only desc
219
					//$parameters[$key] = $value;
220
				break;
221
				case "pastSeconds":
222
					$value = (int) $value;
223
					if (($value / 86400) > 7) throw new Exception("pastSeconds is limited to a max of 7 days");
224
					$parameters[$key] = $value;
225
				break;
226
				case "startTime":
227
				case "endTime":
228
					$time = strtotime($value);
229
					if($time < 0) throw new Exception("$value is not a valid time format");
230
					$parameters[$key] = $value;
231
				break;
232
				case "limit":
233
					$value = (int) $value;
234
					if ($value <= 1000) $parameters["limit"] = $value;
235
					elseif($value > 1000) $parameters["limit"] = 1000;
236
					elseif($value <= 0) $parameters["limit"] = 1;
237
				break;
238
				case "beforeKillID":
239
				case "afterKillID":
240
				case "killID":
241
					if (!is_numeric($value)) throw new Exception("$value is not a valid entry for $key");
242
					$parameters[$key] = (int) $value;
243
				break;
244
				case "iskValue":
245
					if (!is_numeric($value)) throw new Exception("$value is not a valid entry for $key");
246
					$parameters[$key] = (int) $value;
247
				break;
248
				case "xml":
249
					$parameters[$key] = true;
250
				break;
251
				case "pretty":
252
					$parameters[$key] = true;
253
				break;
254
				case "no-attackers":
255
					$parameters[$key] = true;
256
				break;
257
				case "no-items":
258
					$parameters[$key] = true;
259
				break;
260
				case "finalblow-only":
261
					$parameters[$key] = true;
262
				break;
263
				case "api":
264
					$parameters[$key] = true;
265
				break;
266
				default:
267
					if($addExtraParameters == true)
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
268
					{
269
						if (is_numeric($value) && $value < 0) continue; //throw new Exception("$value is not a valid entry for $key");
270
						if ($key != "" && $value != "") $parameters[$key] = $value;
271
					}
272
273
					// Add more parameters to the $parameters array
274
					if(!empty($additionalParameters))
275
					{
276
						foreach($additionalParameters as $extra)
277
							if($extra == $key)
278
								$parameters[$key] = $value;
279
					}
280
				break;
281
			}
282
			$currentIndex++;
283
		}
284
285
		if (isset($parameters["page"]) && $parameters["page"] > 10 && isset($parameters["api"])) {
286
			// Verify that the request is for a character, corporation, or alliance
287
			// This will prevent scrape attempts against regions, ships, systems, etc. which
288
			// are very hard against the database
289
			$legitEntities = array("characterID", "corporationID", "allianceID");
290
			$legit = false;
291
			foreach ($legitEntities as $entity) {
292
				$legit |= in_array($entity, array_keys($parameters));
293
			}
294
			// The API doesn't handle Exceptions that well, so we have to output json/xml for them..
295
			if (!$legit)
296
			{
297
				$date = date("Y-m-d H:i:s");
298
				$cachedUntil = date("Y-m-d H:i:s", time() + 3600);
299
				if(stristr($_SERVER["REQUEST_URI"], "xml"))
300
				{
301
					$data = "<?xml version=\"1.0\" encoding=\"UTF-8\"?" . ">"; // separating the ? and > allows vi to still color format code nicely
302
					$data .= "<eveapi version=\"2\" zkbapi=\"1\">";
303
					$data .= "<currentTime>$date</currentTime>";
304
					$data .= "<result>";
305
					$data .= "<error>A maximum of 10 pages is allowed for the modifier type you are using.</error>";
306
					$data .= "</result>";
307
					$data .= "<cachedUntil>$cachedUntil</cachedUntil>";
308
					$data .= "</eveapi>";
309
					header("Content-type: text/xml; charset=utf-8");
310
				}
311
				else
312
				{
313
					header("Content-type: application/json; charset=utf-8");
314
					$data = json_encode(array("Error" => "A maximum of 10 pages is allowed for the modifier type you are using.", "cachedUntil" => $cachedUntil));
315
				}
316
				header("Retry-After: " . $cachedUntil . " GMT");
317
				header("HTTP/1.1 409 Conflict");
318
				header("Etag: ".(md5(serialize($data))));
319
				echo $data;
320
				die();
0 ignored issues
show
Coding Style Compatibility introduced by
The method convertUriToParameters() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
321
			}
322
		}
323
		return $parameters;
324
	}
325
326
	public static function shortString($string, $maxLength = 8)
0 ignored issues
show
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
327
	{
328
		if (strlen($string) <= $maxLength) return $string;
329
		return substr($string, 0, $maxLength - 3) . "...";
330
	}
331
332
	public static function truncate($str, $length = 200, $trailing = "...")
0 ignored issues
show
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
333
	{
334
		$length -= mb_strlen($trailing);
335
		if (mb_strlen($str) > $length) {
336
			// string exceeded length, truncate and add trailing dots
337
			return mb_substr($str, 0, $length) . $trailing;
338
		}
339
		else
340
		{
341
			// string was already short enough, return the string
342
			$res = $str;
343
		}
344
		return $res;
345
	}
346
347
	public static function pageTimer()
0 ignored issues
show
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
348
	{
349
		global $timer;
350
		return $timer->stop();
351
	}
352
353
	public static function isActive($pageType, $currentPage, $retValue = "active")
354
	{
355
		return strtolower($pageType) == strtolower($currentPage) ? $retValue : "";
356
	}
357
358
	private static $months = array("", "JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC");
359
360
	public static function getMonth($month)
0 ignored issues
show
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
361
	{
362
		return self::$months[$month];
363
	}
364
365
	private static $longMonths = array("", "January", "February", "March", "April", "May", "June", "July", "August",
366
			"September", "October", "November", "December");
367
368
	public static function getLongMonth($month)
0 ignored issues
show
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
369
	{
370
		return self::$longMonths[$month];
371
	}
372
373
	public static function deleteKill($killID)
374
	{
375
		if($killID < 0)
376
		{
377
			// Verify the kill exists
378
			$count = Db::execute("select count(*) count from zz_killmails where killID = :killID", array(":killID" => $killID));
379
			if ($count == 0) return false;
380
			// Remove it from the stats
381
			Stats::calcStats($killID, false);
382
			// Remove it from the kill tables
383
			Db::execute("delete from zz_participants where killID = :killID", array(":killID" => $killID));
384
			// Mark the kill as deleted
385
			Db::execute("update zz_killmails set processed = 2 where killID = :killID", array(":killID" => $killID));
386
			return true;
387
		}
388
		return false;
389
	}
390
391
	public static function themesAvailable()
392
	{
393
		$dir = "themes/";
394
		$avail = scandir($dir);
395
		foreach($avail as $key => $val)
396
			if($val == "." || $val == "..")
397
				unset($avail[$key]);
398
		return $avail;
399
	}
400
401
	/**
402
	 * @param string $haystack
403
	 */
404
	public static function strposa($haystack, $needles=array(), $offset=0)
0 ignored issues
show
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
405
	{
406
			$chr = array();
407
			foreach($needles as $needle) {
408
					$res = strpos($haystack, $needle, $offset);
409
					if ($res !== false) $chr[$needle] = $res;
410
			}
411
			if(empty($chr)) return false;
412
			return min($chr);
413
	}
414
415
	/**
416
	 * @param string $url
417
	 * @return string|null $result
418
	 */
419
	public static function getData($url, $cacheTime = 3600)
420
	{
421
		global $ipsAvailable, $baseAddr;
422
423
		$md5 = md5($url);
424
		$result = $cacheTime > 0 ? Cache::get($md5) : null;
425
426
		if(!$result)
427
		{
428
			$curl = curl_init();
429
			curl_setopt_array($curl, array(
430
				CURLOPT_USERAGENT 			=> "zKillboard dataGetter for site: {$baseAddr}",
431
				CURLOPT_TIMEOUT 			=> 30,
432
				CURLOPT_POST 				=> false,
433
				CURLOPT_FORBID_REUSE 		=> false,
434
				CURLOPT_ENCODING 			=> "",
435
				CURLOPT_URL 				=> $url,
436
				CURLOPT_HTTPHEADER 			=> array("Connection: keep-alive", "Keep-Alive: timeout=10, max=1000"),
437
				CURLOPT_RETURNTRANSFER 		=> true,
438
				CURLOPT_FAILONERROR			=> true
439
				)
440
			);
441
442
			if(count($ipsAvailable) > 1)
443
			{
444
				$ip = $ipsAvailable[time() % count($ipsAvailable)];
445
				curl_setopt($curl, CURLOPT_INTERFACE, $ip);
446
			}
447
			$result = curl_exec($curl);
448
			if ($cacheTime > 0) Cache::set($md5, $result, $cacheTime);
449
		}
450
451
		return $result;
452
	}
453
454
	/**
455
	 * @param string $url
456
	 * @param array
457
	 * @param array
458
	 * @return array $result
459
	 */
460
	public static function postData($url, $postData = array(), $headers = array())
461
	{
462
		global $ipsAvailable, $baseAddr;
463
		$userAgent = "zKillboard dataGetter for site: {$baseAddr}";
464
		if(!isset($headers))
465
			$headers = array("Connection: keep-alive", "Keep-Alive: timeout=10, max=1000");
466
467
		$curl = curl_init();
468
		$postLine = "";
469
470
		if(!empty($postData))
471
			foreach($postData as $key => $value)
472
				$postLine .= $key . "=" . $value . "&";
473
474
		rtrim($postLine, "&");
475
476
		curl_setopt($curl, CURLOPT_URL, $url);
477
		curl_setopt($curl, CURLOPT_USERAGENT, $userAgent);
478
		curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
479
		if(!empty($postData))
480
		{
481
			curl_setopt($curl, CURLOPT_POST, count($postData));
482
			curl_setopt($curl, CURLOPT_POSTFIELDS, $postLine);
483
		}
484
485
		if(count($ipsAvailable) > 0)
486
		{
487
			$ip = $ipsAvailable[time() % count($ipsAvailable)];
488
			curl_setopt($curl, CURLOPT_INTERFACE, $ip);
489
		}
490
		curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
491
		curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, true);
492
		curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 2);
493
494
		$result = curl_exec($curl);
495
496
		curl_close($curl);
497
		return $result;
498
	}
499
500
	/**
501
	 * Gets post data, and returns it
502
	 * @param  string $var The variable you can to return
503
	 * @return string|null
504
	 */
505
	public static function getPost($var)
506
	{
507
		return isset($_POST[$var]) ? $_POST[$var] : null;
508
	}
509
510
	public static function informationPages()
511
	{
512
		global $baseDir, $theme;
513
		$tDir = $baseDir . "themes/" . $theme . "/information/";
514
		$data = null;
515
		$pages = array();
516
517
		if(is_dir($tDir))
518
			$data = scandir($tDir);
519
520
		if($data)
521
		{
522
			foreach($data as $key =>  $file)
523
			{
524
				if($file == "." || $file == "..")
525
					continue;
526
527
				if(is_dir($tDir . $file))
528
				{
529
					$subData = scandir($tDir . $file);
530
					foreach($subData as $key => $subDir)
531
					{
532
						if($subDir == "." || $subDir == "..")
533
							continue;
534
535
						$pages[$file][] = array("name" => strtolower(str_replace(".md", "", $subDir)), "path" => "$tDir$file/$subDir");
536
					}
537
				}
538
				else
539
					$pages[strtolower(str_replace(".md", "", $file))][] = array("name" => strtolower(str_replace(".md", "", $file)), "path" => "$tDir$file");
540
			}
541
		}
542
		return $pages;
543
	}
544
}
545