Folder.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466
  1. <?php
  2. namespace Typemill\Models;
  3. use \URLify;
  4. class Folder
  5. {
  6. /*
  7. * scans content of a folder (without recursion)
  8. * vars: folder path as string
  9. * returns: one-dimensional array with names of folders and files
  10. */
  11. public static function scanFolderFlat($folderPath)
  12. {
  13. $folderItems = scandir($folderPath);
  14. $folderContent = array();
  15. foreach ($folderItems as $key => $item)
  16. {
  17. if (!in_array($item, array(".","..")))
  18. {
  19. $nameParts = self::getStringParts($item);
  20. $fileType = array_pop($nameParts);
  21. if($fileType == 'md' OR $fileType == 'txt' )
  22. {
  23. $folderContent[] = $item;
  24. }
  25. }
  26. }
  27. return $folderContent;
  28. }
  29. /*
  30. * scans content of a folder recursively
  31. * vars: folder path as string
  32. * returns: multi-dimensional array with names of folders and files
  33. */
  34. public static function scanFolder($folderPath, $draft = false)
  35. {
  36. $folderItems = scandir($folderPath);
  37. $folderContent = array();
  38. # if it is the live version and if it is a folder that is not published, then do not show the folder and its content.
  39. if(!$draft && !in_array('index.md', $folderItems)){ return false; }
  40. foreach ($folderItems as $key => $item)
  41. {
  42. if (!in_array($item, array(".","..")))
  43. {
  44. if (is_dir($folderPath . DIRECTORY_SEPARATOR . $item))
  45. {
  46. $subFolder = $item;
  47. $folderPublished = file_exists($folderPath . DIRECTORY_SEPARATOR . $item . DIRECTORY_SEPARATOR . 'index.md');
  48. # scan that folder only if it is a draft or if the folder is published (contains index.md)
  49. if($draft OR $folderPublished)
  50. {
  51. $folderContent[$subFolder] = self::scanFolder($folderPath . DIRECTORY_SEPARATOR . $subFolder, $draft);
  52. }
  53. }
  54. else
  55. {
  56. $nameParts = self::getStringParts($item);
  57. $fileType = array_pop($nameParts);
  58. if($fileType == 'md')
  59. {
  60. $folderContent[] = $item;
  61. }
  62. if($draft === true && $fileType == 'txt')
  63. {
  64. if(isset($last) && ($last == implode($nameParts)) )
  65. {
  66. array_pop($folderContent);
  67. $item = $item . 'md';
  68. }
  69. $folderContent[] = $item;
  70. }
  71. /* store the name of the last file */
  72. $last = implode($nameParts);
  73. }
  74. }
  75. }
  76. return $folderContent;
  77. }
  78. /*
  79. * Transforms array of folder item into an array of item-objects with additional information for each item
  80. * vars: multidimensional array with folder- and file-names
  81. * returns: array of objects. Each object contains information about an item (file or folder).
  82. */
  83. public static function getFolderContentDetails(array $folderContent, $baseUrl, $fullSlugWithFolder = NULL, $fullSlugWithoutFolder = NULL, $fullPath = NULL, $keyPath = NULL, $chapter = NULL)
  84. {
  85. $contentDetails = [];
  86. $iteration = 0;
  87. $chapternr = 1;
  88. foreach($folderContent as $key => $name)
  89. {
  90. $item = new \stdClass();
  91. if(is_array($name))
  92. {
  93. $nameParts = self::getStringParts($key);
  94. $fileType = '';
  95. if(in_array('index.md', $name))
  96. {
  97. $fileType = 'md';
  98. $status = 'published';
  99. }
  100. if(in_array('index.txt', $name))
  101. {
  102. $fileType = 'txt';
  103. $status = 'unpublished';
  104. }
  105. if(in_array('index.txtmd', $name))
  106. {
  107. $fileType = 'txt';
  108. $status = 'modified';
  109. }
  110. $item->originalName = $key;
  111. $item->elementType = 'folder';
  112. $item->status = $status;
  113. $item->fileType = $fileType;
  114. $item->order = count($nameParts) > 1 ? array_shift($nameParts) : NULL;
  115. $item->name = implode(" ",$nameParts);
  116. $item->name = iconv(mb_detect_encoding($item->name, mb_detect_order(), true), "UTF-8", $item->name);
  117. $item->slug = implode("-",$nameParts);
  118. $item->slug = URLify::filter(iconv(mb_detect_encoding($item->slug, mb_detect_order(), true), "UTF-8", $item->slug));
  119. $item->path = $fullPath . DIRECTORY_SEPARATOR . $key;
  120. $item->pathWithoutType = $fullPath . DIRECTORY_SEPARATOR . $key . DIRECTORY_SEPARATOR . 'index';
  121. $item->urlRelWoF = $fullSlugWithoutFolder . '/' . $item->slug;
  122. $item->urlRel = $fullSlugWithFolder . '/' . $item->slug;
  123. $item->urlAbs = $baseUrl . $fullSlugWithoutFolder . '/' . $item->slug;
  124. $item->key = $iteration;
  125. $item->keyPath = isset($keyPath) ? $keyPath . '.' . $iteration : $iteration;
  126. $item->keyPathArray = explode('.', $item->keyPath);
  127. $item->chapter = $chapter ? $chapter . '.' . $chapternr : $chapternr;
  128. $item->active = false;
  129. $item->activeParent = false;
  130. $item->folderContent = self::getFolderContentDetails($name, $baseUrl, $item->urlRel, $item->urlRelWoF, $item->path, $item->keyPath, $item->chapter);
  131. }
  132. else
  133. {
  134. # do not use files in base folder (only folders are allowed)
  135. # if(!isset($keyPath)) continue;
  136. # do not use index files
  137. if($name == 'index.md' || $name == 'index.txt' || $name == 'index.txtmd' ) continue;
  138. $nameParts = self::getStringParts($name);
  139. $fileType = array_pop($nameParts);
  140. $nameWithoutType = self::getNameWithoutType($name);
  141. if($fileType == 'md')
  142. {
  143. $status = 'published';
  144. }
  145. elseif($fileType == 'txt')
  146. {
  147. $status = 'unpublished';
  148. }
  149. else
  150. {
  151. $fileType = 'txt';
  152. $status = 'modified';
  153. }
  154. $item->originalName = $name;
  155. $item->elementType = 'file';
  156. $item->status = $status;
  157. $item->fileType = $fileType;
  158. $item->order = count($nameParts) > 1 ? array_shift($nameParts) : NULL;
  159. $item->name = implode(" ",$nameParts);
  160. $item->name = iconv(mb_detect_encoding($item->name, mb_detect_order(), true), "UTF-8", $item->name);
  161. $item->slug = implode("-",$nameParts);
  162. $item->slug = URLify::filter(iconv(mb_detect_encoding($item->slug, mb_detect_order(), true), "UTF-8", $item->slug));
  163. $item->path = $fullPath . DIRECTORY_SEPARATOR . $name;
  164. $item->pathWithoutType = $fullPath . DIRECTORY_SEPARATOR . $nameWithoutType;
  165. $item->key = $iteration;
  166. $item->keyPath = isset($keyPath) ? $keyPath . '.' . $iteration : $iteration;
  167. $item->keyPathArray = explode('.',$item->keyPath);
  168. $item->chapter = $chapter . '.' . $chapternr;
  169. $item->urlRelWoF = $fullSlugWithoutFolder . '/' . $item->slug;
  170. $item->urlRel = $fullSlugWithFolder . '/' . $item->slug;
  171. $item->urlAbs = $baseUrl . $fullSlugWithoutFolder . '/' . $item->slug;
  172. $item->active = false;
  173. $item->activeParent = false;
  174. }
  175. $iteration++;
  176. $chapternr++;
  177. $contentDetails[] = $item;
  178. }
  179. return $contentDetails;
  180. }
  181. public static function getItemForUrl($folderContentDetails, $url, $baseUrl, $result = NULL)
  182. {
  183. # if we are on the homepage
  184. if($url == '/' OR $url == $baseUrl)
  185. {
  186. # return a standard item-object
  187. $item = new \stdClass;
  188. $item->elementType = 'folder';
  189. $item->path = '';
  190. $item->urlRel = '/';
  191. $item->pathWithoutType = DIRECTORY_SEPARATOR . 'index';
  192. return $item;
  193. }
  194. foreach($folderContentDetails as $key => $item)
  195. {
  196. if($item->urlRel === $url)
  197. {
  198. # set item active, needed for move item in navigation
  199. $item->active = true;
  200. $result = $item;
  201. }
  202. elseif($item->elementType === "folder")
  203. {
  204. $result = self::getItemForUrl($item->folderContent, $url, $baseUrl, $result);
  205. }
  206. }
  207. return $result;
  208. }
  209. public static function getPagingForItem($content, $item)
  210. {
  211. $keyPos = count($item->keyPathArray)-1;
  212. $thisChapArray = $item->keyPathArray;
  213. $nextItemArray = $item->keyPathArray;
  214. $prevItemArray = $item->keyPathArray;
  215. $item->thisChapter = false;
  216. $item->prevItem = false;
  217. $item->nextItem = false;
  218. /************************
  219. * ADD THIS CHAPTER *
  220. ************************/
  221. if($keyPos > 0)
  222. {
  223. array_pop($thisChapArray);
  224. $item->thisChapter = self::getItemWithKeyPath($content, $thisChapArray);
  225. }
  226. /************************
  227. * ADD NEXT ITEM *
  228. ************************/
  229. if($item->elementType == 'folder')
  230. {
  231. /* get the first element in the folder */
  232. $item->nextItem = isset($item->folderContent[0]) ? clone($item->folderContent[0]) : false;
  233. }
  234. if(!$item->nextItem)
  235. {
  236. $nextItemArray[$keyPos]++;
  237. $item->nextItem = self::getItemWithKeyPath($content, $nextItemArray);
  238. }
  239. while(!$item->nextItem)
  240. {
  241. array_pop($nextItemArray);
  242. if(empty($nextItemArray)) break;
  243. $newKeyPos = count($nextItemArray)-1;
  244. $nextItemArray[$newKeyPos]++;
  245. $item->nextItem = self::getItemWithKeyPath($content, $nextItemArray);
  246. }
  247. /************************
  248. * ADD PREVIOUS ITEM *
  249. ************************/
  250. if($prevItemArray[$keyPos] > 0)
  251. {
  252. $prevItemArray[$keyPos]--;
  253. $item->prevItem = self::getItemWithKeyPath($content, $prevItemArray);
  254. if($item->prevItem && $item->prevItem->elementType == 'folder' && !empty($item->prevItem->folderContent))
  255. {
  256. /* get last item in folder */
  257. $item->prevItem = self::getLastItemOfFolder($item->prevItem);
  258. }
  259. }
  260. else
  261. {
  262. $item->prevItem = $item->thisChapter;
  263. }
  264. if($item->prevItem && $item->prevItem->elementType == 'folder'){ unset($item->prevItem->folderContent); }
  265. if($item->nextItem && $item->nextItem->elementType == 'folder'){ unset($item->nextItem->folderContent); }
  266. if($item->thisChapter){unset($item->thisChapter->folderContent); }
  267. return $item;
  268. }
  269. /*
  270. * Gets a copy of an item with a key
  271. * @param array $content with the full structure of the content as multidimensional array
  272. * @param array $searchArray with the key as a one-dimensional array like array(0,3,4)
  273. * @return array $item
  274. */
  275. public static function getItemWithKeyPath($content, array $searchArray)
  276. {
  277. $item = false;
  278. foreach($searchArray as $key => $itemKey)
  279. {
  280. $item = isset($content[$itemKey]) ? clone($content[$itemKey]) : false;
  281. unset($searchArray[$key]);
  282. if(!empty($searchArray) && $item)
  283. {
  284. return self::getItemWithKeyPath($item->folderContent, $searchArray);
  285. }
  286. }
  287. return $item;
  288. }
  289. # https://www.quora.com/Learning-PHP-Is-there-a-way-to-get-the-value-of-multi-dimensional-array-by-specifying-the-key-with-a-variable
  290. # NOT IN USE
  291. public static function getItemWithKeyPathNew($array, array $keys)
  292. {
  293. $item = $array;
  294. foreach ($keys as $key)
  295. {
  296. $item = isset($item[$key]->folderContent) ? $item[$key]->folderContent : $item[$key];
  297. }
  298. return $item;
  299. }
  300. /*
  301. * Extracts an item with a key https://stackoverflow.com/questions/52097092/php-delete-value-of-array-with-dynamic-key
  302. * @param array $content with the full structure of the content as multidimensional array
  303. * @param array $searchArray with the key as a one-dimensional array like array(0,3,4)
  304. * @return array $item
  305. * NOT IN USE ??
  306. */
  307. public static function extractItemWithKeyPath($structure, array $keys)
  308. {
  309. $result = &$structure;
  310. $last = array_pop($keys);
  311. foreach ($keys as $key) {
  312. if(isset($result[$key]->folderContent))
  313. {
  314. $result = &$result[$key]->folderContent;
  315. }
  316. else
  317. {
  318. $result = &$result[$key];
  319. }
  320. }
  321. $item = $result[$last];
  322. unset($result[$last]);
  323. return array('structure' => $structure, 'item' => $item);
  324. }
  325. /* get breadcrumb as copied array, set elements active in original and mark parent element in original */
  326. public static function getBreadcrumb($content, $searchArray, $i = NULL, $breadcrumb = NULL)
  327. {
  328. # if it is the first round, create an empty array
  329. if(!$i){ $i = 0; $breadcrumb = array();}
  330. while($i < count($searchArray))
  331. {
  332. $item = $content[$searchArray[$i]];
  333. if($i == count($searchArray)-1)
  334. {
  335. $item->active = true;
  336. }
  337. else
  338. {
  339. $item->activeParent = true;
  340. }
  341. /*
  342. $item->active = true;
  343. if($i == count($searchArray)-2)
  344. {
  345. $item->activeParent = true;
  346. }
  347. */
  348. $copy = clone($item);
  349. if($copy->elementType == 'folder')
  350. {
  351. unset($copy->folderContent);
  352. $content = $item->folderContent;
  353. }
  354. $breadcrumb[] = $copy;
  355. $i++;
  356. return self::getBreadcrumb($content, $searchArray, $i++, $breadcrumb);
  357. }
  358. return $breadcrumb;
  359. }
  360. public static function getParentItem($content, $searchArray, $iteration = NULL)
  361. {
  362. if(!$iteration){ $iteration = 0; }
  363. while($iteration < count($searchArray)-2)
  364. {
  365. $content = $content[$searchArray[$iteration]]->folderContent;
  366. $iteration++;
  367. return self::getParentItem($content, $searchArray, $iteration);
  368. }
  369. return $content[$searchArray[$iteration]];
  370. }
  371. private static function getLastItemOfFolder($folder)
  372. {
  373. $lastItem = end($folder->folderContent);
  374. if(is_object($lastItem) && $lastItem->elementType == 'folder' && !empty($lastItem->folderContent))
  375. {
  376. return self::getLastItemOfFolder($lastItem);
  377. }
  378. return $lastItem;
  379. }
  380. public static function getStringParts($name)
  381. {
  382. return preg_split('/[\-\.\_\=\+\?\!\*\#\(\)\/ ]/',$name);
  383. }
  384. public static function getFileType($fileName)
  385. {
  386. $parts = preg_split('/\./',$fileName);
  387. return end($parts);
  388. }
  389. public static function splitFileName($fileName)
  390. {
  391. $parts = preg_split('/\./',$fileName);
  392. return $parts;
  393. }
  394. public static function getNameWithoutType($fileName)
  395. {
  396. $parts = preg_split('/\./',$fileName);
  397. return $parts[0];
  398. }
  399. }