ArticleApiController.php 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845
  1. <?php
  2. namespace Typemill\Controllers;
  3. use Slim\Http\Request;
  4. use Slim\Http\Response;
  5. use Typemill\Models\Folder;
  6. use Typemill\Models\Write;
  7. use Typemill\Models\WriteYaml;
  8. use Typemill\Models\WriteMeta;
  9. use Typemill\Extensions\ParsedownExtension;
  10. use Typemill\Events\OnPagePublished;
  11. use Typemill\Events\OnPageUnpublished;
  12. use Typemill\Events\OnPageDeleted;
  13. use Typemill\Events\OnPageSorted;
  14. use \URLify;
  15. class ArticleApiController extends ContentController
  16. {
  17. public function publishArticle(Request $request, Response $response, $args)
  18. {
  19. # get params from call
  20. $this->params = $request->getParams();
  21. $this->uri = $request->getUri();
  22. # validate input only if raw mode
  23. if($this->params['raw'])
  24. {
  25. if(!$this->validateEditorInput()){ return $response->withJson($this->errors,422); }
  26. }
  27. # set structure
  28. if(!$this->setStructure($draft = true)){ return $response->withJson($this->errors, 404); }
  29. # set item
  30. if(!$this->setItem()){ return $response->withJson($this->errors, 404); }
  31. # set the status for published and drafted
  32. $this->setPublishStatus();
  33. # set path
  34. $this->setItemPath($this->item->fileType);
  35. # if raw mode, use the content from request
  36. if($this->params['raw'])
  37. {
  38. $this->content = '# ' . $this->params['title'] . "\r\n\r\n" . $this->params['content'];
  39. }
  40. else
  41. {
  42. # read content from file
  43. if(!$this->setContent()){ return $response->withJson(array('data' => false, 'errors' => $this->errors), 404); }
  44. # If it is a draft, then create clean markdown content
  45. if(is_array($this->content))
  46. {
  47. # initialize parsedown extension
  48. $parsedown = new ParsedownExtension();
  49. # turn markdown into an array of markdown-blocks
  50. $this->content = $parsedown->arrayBlocksToMarkdown($this->content);
  51. }
  52. }
  53. # set path for the file (or folder)
  54. $this->setItemPath('md');
  55. # update the file
  56. if($this->write->writeFile($this->settings['contentFolder'], $this->path, $this->content))
  57. {
  58. # update the file
  59. $delete = $this->deleteContentFiles(['txt']);
  60. # update the internal structure
  61. $this->setStructure($draft = true, $cache = false);
  62. # update the public structure
  63. $this->setStructure($draft = false, $cache = false);
  64. # complete the page meta if title or description not set
  65. $writeMeta = new WriteMeta();
  66. $meta = $writeMeta->completePageMeta($this->content, $this->settings, $this->item);
  67. # dispatch event
  68. $page = ['content' => $this->content, 'meta' => $meta, 'item' => $this->item];
  69. $this->c->dispatcher->dispatch('onPagePublished', new OnPagePublished($page));
  70. return $response->withJson(['success' => true, 'meta' => $meta], 200);
  71. }
  72. else
  73. {
  74. return $response->withJson(['errors' => ['message' => 'Could not write to file. Please check if the file is writable']], 404);
  75. }
  76. }
  77. public function unpublishArticle(Request $request, Response $response, $args)
  78. {
  79. # get params from call
  80. $this->params = $request->getParams();
  81. $this->uri = $request->getUri();
  82. # set structure
  83. if(!$this->setStructure($draft = true)){ return $response->withJson($this->errors, 404); }
  84. # set item
  85. if(!$this->setItem()){ return $response->withJson($this->errors, 404); }
  86. # set the status for published and drafted
  87. $this->setPublishStatus();
  88. # check if draft exists, if not, create one.
  89. if(!$this->item->drafted)
  90. {
  91. # set path for the file (or folder)
  92. $this->setItemPath('md');
  93. # set content of markdown-file
  94. if(!$this->setContent()){ return $response->withJson($this->errors, 404); }
  95. # initialize parsedown extension
  96. $parsedown = new ParsedownExtension();
  97. # turn markdown into an array of markdown-blocks
  98. $contentArray = $parsedown->markdownToArrayBlocks($this->content);
  99. # encode the content into json
  100. $contentJson = json_encode($contentArray);
  101. # set path for the file (or folder)
  102. $this->setItemPath('txt');
  103. /* update the file */
  104. if(!$this->write->writeFile($this->settings['contentFolder'], $this->path, $contentJson))
  105. {
  106. return $response->withJson(['errors' => ['message' => 'Could not create a draft of the page. Please check if the folder is writable']], 404);
  107. }
  108. }
  109. # check if it is a folder and if the folder has published pages.
  110. $message = false;
  111. if($this->item->elementType == 'folder')
  112. {
  113. foreach($this->item->folderContent as $folderContent)
  114. {
  115. if($folderContent->status == 'published')
  116. {
  117. $message = 'There are published pages within this folder. The pages are not visible on your website anymore.';
  118. }
  119. }
  120. }
  121. # update the file
  122. $delete = $this->deleteContentFiles(['md']);
  123. if($delete)
  124. {
  125. # update the internal structure
  126. $this->setStructure($draft = true, $cache = false);
  127. # update the live structure
  128. $this->setStructure($draft = false, $cache = false);
  129. # dispatch event
  130. $this->c->dispatcher->dispatch('onPageUnpublished', new OnPageUnpublished($this->item));
  131. return $response->withJson(['success' => ['message' => $message]], 200);
  132. }
  133. else
  134. {
  135. return $response->withJson(['errors' => ['message' => "Could not delete some files. Please check if the files exists and are writable"]], 404);
  136. }
  137. }
  138. public function discardArticleChanges(Request $request, Response $response, $args)
  139. {
  140. # get params from call
  141. $this->params = $request->getParams();
  142. $this->uri = $request->getUri();
  143. # set structure
  144. if(!$this->setStructure($draft = true)){ return $response->withJson($this->errors, 404); }
  145. # set item
  146. if(!$this->setItem()){ return $response->withJson($this->errors, 404); }
  147. # remove the unpublished changes
  148. $delete = $this->deleteContentFiles(['txt']);
  149. # set redirect url to edit page
  150. $url = $this->uri->getBaseUrl() . '/tm/content/' . $this->settings['editor'];
  151. if(isset($this->item->urlRelWoF))
  152. {
  153. $url = $url . $this->item->urlRelWoF;
  154. }
  155. # remove the unpublished changes
  156. $delete = $this->deleteContentFiles(['txt']);
  157. if($delete)
  158. {
  159. # update the backend structure
  160. $this->setStructure($draft = true, $cache = false);
  161. return $response->withJson(['data' => $this->structure, 'errors' => false, 'url' => $url], 200);
  162. }
  163. else
  164. {
  165. return $response->withJson(['data' => $this->structure, 'errors' => $this->errors], 404);
  166. }
  167. }
  168. public function deleteArticle(Request $request, Response $response, $args)
  169. {
  170. # get params from call
  171. $this->params = $request->getParams();
  172. $this->uri = $request->getUri();
  173. # set url to base path initially
  174. $url = $this->uri->getBaseUrl() . '/tm/content/' . $this->settings['editor'];
  175. # set structure
  176. if(!$this->setStructure($draft = true)){ return $response->withJson($this->errors, 404); }
  177. # set item
  178. if(!$this->setItem()){ return $response->withJson($this->errors, 404); }
  179. if($this->item->elementType == 'file')
  180. {
  181. $delete = $this->deleteContentFiles(['md','txt', 'yaml']);
  182. }
  183. elseif($this->item->elementType == 'folder')
  184. {
  185. $delete = $this->deleteContentFolder();
  186. }
  187. if($delete)
  188. {
  189. # check if it is a subfile or subfolder and set the redirect-url to the parent item
  190. if(count($this->item->keyPathArray) > 1)
  191. {
  192. # get the parent item
  193. $parentItem = Folder::getParentItem($this->structure, $this->item->keyPathArray);
  194. if($parentItem)
  195. {
  196. # an active file has been moved to another folder
  197. $url .= $parentItem->urlRelWoF;
  198. }
  199. }
  200. # update the live structure
  201. $this->setStructure($draft = false, $cache = false);
  202. # update the backend structure
  203. $this->setStructure($draft = true, $cache = false);
  204. # check if page is in extended structure and delete it
  205. $this->deleteFromExtended();
  206. # dispatch event
  207. $this->c->dispatcher->dispatch('onPageDeleted', new OnPageDeleted($this->item));
  208. return $response->withJson(array('data' => $this->structure, 'errors' => false, 'url' => $url), 200);
  209. }
  210. else
  211. {
  212. return $response->withJson(array('data' => $this->structure, 'errors' => $this->errors), 422);
  213. }
  214. }
  215. public function updateArticle(Request $request, Response $response, $args)
  216. {
  217. # get params from call
  218. $this->params = $request->getParams();
  219. $this->uri = $request->getUri();
  220. # validate input
  221. if(!$this->validateEditorInput()){ return $response->withJson($this->errors,422); }
  222. # set structure
  223. if(!$this->setStructure($draft = true)){ return $response->withJson($this->errors, 404); }
  224. # set item
  225. if(!$this->setItem()){ return $response->withJson($this->errors, 404); }
  226. # set path for the file (or folder)
  227. $this->setItemPath('txt');
  228. # merge title with content for complete markdown document
  229. $updatedContent = '# ' . $this->params['title'] . "\r\n\r\n" . $this->params['content'];
  230. # initialize parsedown extension
  231. $parsedown = new ParsedownExtension();
  232. # turn markdown into an array of markdown-blocks
  233. $contentArray = $parsedown->markdownToArrayBlocks($updatedContent);
  234. # encode the content into json
  235. $contentJson = json_encode($contentArray);
  236. /* update the file */
  237. if($this->write->writeFile($this->settings['contentFolder'], $this->path, $contentJson))
  238. {
  239. # update the internal structure
  240. $this->setStructure($draft = true, $cache = false);
  241. return $response->withJson(['success'], 200);
  242. }
  243. else
  244. {
  245. return $response->withJson(['errors' => ['message' => 'Could not write to file. Please check if the file is writable']], 404);
  246. }
  247. }
  248. public function sortArticle(Request $request, Response $response, $args)
  249. {
  250. # get params from call
  251. $this->params = $request->getParams();
  252. $this->uri = $request->getUri();
  253. # url is only needed, if an active page is moved to another folder, so user has to be redirected to the new url
  254. $url = false;
  255. # set structure
  256. if(!$this->setStructure($draft = true)){ return $response->withJson(array('data' => false, 'errors' => $this->errors, 'url' => $url), 404); }
  257. # validate input
  258. if(!$this->validateNavigationSort()){ return $response->withJson(array('data' => $this->structure, 'errors' => 'Data not valid. Please refresh the page and try again.', 'url' => $url), 422); }
  259. # get the ids (key path) for item, old folder and new folder
  260. $itemKeyPath = explode('.', $this->params['item_id']);
  261. $parentKeyFrom = explode('.', $this->params['parent_id_from']);
  262. $parentKeyTo = explode('.', $this->params['parent_id_to']);
  263. # get the item from structure
  264. $item = Folder::getItemWithKeyPath($this->structure, $itemKeyPath);
  265. if(!$item){ return $response->withJson(array('data' => $this->structure, 'errors' => 'We could not find this page. Please refresh and try again.', 'url' => $url), 404); }
  266. # if an item is moved to the first level
  267. if($this->params['parent_id_to'] == 'navi')
  268. {
  269. # create empty and default values so that the logic below still works
  270. $newFolder = new \stdClass();
  271. $newFolder->path = '';
  272. $folderContent = $this->structure;
  273. }
  274. else
  275. {
  276. # get the target folder from structure
  277. $newFolder = Folder::getItemWithKeyPath($this->structure, $parentKeyTo);
  278. # get the content of the target folder
  279. $folderContent = $newFolder->folderContent;
  280. }
  281. # if the item has been moved within the same folder
  282. if($this->params['parent_id_from'] == $this->params['parent_id_to'])
  283. {
  284. # get key of item
  285. $itemKey = end($itemKeyPath);
  286. reset($itemKeyPath);
  287. # delete item from folderContent
  288. unset($folderContent[$itemKey]);
  289. }
  290. else
  291. {
  292. # rename links in extended file
  293. $this->renameExtended($item, $newFolder);
  294. # an active file has been moved to another folder, so send new url with response
  295. if($this->params['active'] == 'active')
  296. {
  297. $url = $this->uri->getBaseUrl() . '/tm/content/' . $this->settings['editor'] . $newFolder->urlRelWoF . '/' . $item->slug;
  298. }
  299. }
  300. # add item to newFolder
  301. array_splice($folderContent, $this->params['index_new'], 0, array($item));
  302. # initialize index
  303. $index = 0;
  304. # initialise write object
  305. $write = new Write();
  306. # iterate through the whole content of the new folder to rename the files
  307. $writeError = false;
  308. foreach($folderContent as $folderItem)
  309. {
  310. if(!$write->moveElement($folderItem, $newFolder->path, $index))
  311. {
  312. $writeError = true;
  313. }
  314. $index++;
  315. }
  316. if($writeError){ return $response->withJson(array('data' => $this->structure, 'errors' => 'Something went wrong. Please refresh the page and check, if all folders and files are writable.', 'url' => $url), 404); }
  317. # update the structure for editor
  318. $this->setStructure($draft = true, $cache = false);
  319. # get item for url and set it active again
  320. if(isset($this->params['url']))
  321. {
  322. $activeItem = Folder::getItemForUrl($this->structure, $this->params['url'], $this->uri->getBaseUrl());
  323. }
  324. # keep the internal structure for response
  325. $internalStructure = $this->structure;
  326. # update the structure for website
  327. $this->setStructure($draft = false, $cache = false);
  328. # dispatch event
  329. $this->c->dispatcher->dispatch('onPageSorted', new OnPageSorted($this->params));
  330. return $response->withJson(array('data' => $internalStructure, 'errors' => false, 'url' => $url));
  331. }
  332. public function createPost(Request $request, Response $response, $args)
  333. {
  334. # get params from call
  335. $this->params = $request->getParams();
  336. $this->uri = $request->getUri();
  337. # url is only needed, if an active page is moved
  338. $url = false;
  339. # set structure
  340. if(!$this->setStructure($draft = true)){ return $response->withJson(array('data' => false, 'errors' => $this->errors, 'url' => $url), 404); }
  341. # validate input
  342. if(!$this->validateNaviItem()){ return $response->withJson(array('data' => $this->structure, 'errors' => 'Special Characters not allowed. Length between 1 and 60 chars.', 'url' => $url), 422); }
  343. # get the ids (key path) for item, old folder and new folder
  344. $folderKeyPath = explode('.', $this->params['folder_id']);
  345. # get the item from structure
  346. $folder = Folder::getItemWithKeyPath($this->structure, $folderKeyPath);
  347. if(!$folder){ return $response->withJson(array('data' => $this->structure, 'errors' => 'We could not find this page. Please refresh and try again.', 'url' => $url), 404); }
  348. $name = $this->params['item_name'];
  349. $slug = URLify::filter(iconv(mb_detect_encoding($this->params['item_name'], mb_detect_order(), true), "UTF-8", $this->params['item_name']));
  350. $namePath = date("YmdHi") . '-' . $slug;
  351. $folderPath = 'content' . $folder->path;
  352. $content = json_encode(['# ' . $name, 'Content']);
  353. # initialise write object
  354. $write = new WriteYaml();
  355. # check, if name exists
  356. if($write->checkFile($folderPath, $namePath . '.txt') OR $write->checkFile($folderPath, $namePath . '.md'))
  357. {
  358. return $response->withJson(array('data' => $this->structure, 'errors' => 'There is already a page with this name. Please choose another name.', 'url' => $url), 404);
  359. }
  360. if(!$write->writeFile($folderPath, $namePath . '.txt', $content))
  361. {
  362. return $response->withJson(array('data' => $this->structure, 'errors' => 'We could not create the file. Please refresh the page and check, if all folders and files are writable.', 'url' => $url), 404);
  363. }
  364. # get extended structure
  365. $extended = $write->getYaml('cache', 'structure-extended.yaml');
  366. # create the url for the item
  367. $urlWoF = $folder->urlRelWoF . '/' . $slug;
  368. # add the navigation name to the item htmlspecialchars needed for french language
  369. $extended[$urlWoF] = ['hide' => false, 'navtitle' => $name];
  370. # store the extended structure
  371. $write->updateYaml('cache', 'structure-extended.yaml', $extended);
  372. # update the structure for editor
  373. $this->setStructure($draft = true, $cache = false);
  374. $folder = Folder::getItemWithKeyPath($this->structure, $folderKeyPath);
  375. # activate this if you want to redirect after creating the page...
  376. # $url = $this->uri->getBaseUrl() . '/tm/content/' . $this->settings['editor'] . $folder->urlRelWoF . '/' . $slug;
  377. return $response->withJson(array('posts' => $folder, $this->structure, 'errors' => false, 'url' => $url));
  378. }
  379. public function createArticle(Request $request, Response $response, $args)
  380. {
  381. # get params from call
  382. $this->params = $request->getParams();
  383. $this->uri = $request->getUri();
  384. # url is only needed, if an active page is moved
  385. $url = false;
  386. # set structure
  387. if(!$this->setStructure($draft = true)){ return $response->withJson(array('data' => false, 'errors' => $this->errors, 'url' => $url), 404); }
  388. # validate input
  389. if(!$this->validateNaviItem()){ return $response->withJson(array('data' => $this->structure, 'errors' => 'Special Characters not allowed. Length between 1 and 60 chars.', 'url' => $url), 422); }
  390. # get the ids (key path) for item, old folder and new folder
  391. $folderKeyPath = explode('.', $this->params['folder_id']);
  392. # get the item from structure
  393. $folder = Folder::getItemWithKeyPath($this->structure, $folderKeyPath);
  394. if(!$folder){ return $response->withJson(array('data' => $this->structure, 'errors' => 'We could not find this page. Please refresh and try again.', 'url' => $url), 404); }
  395. # Rename all files within the folder to make sure, that namings and orders are correct
  396. # get the content of the target folder
  397. $folderContent = $folder->folderContent;
  398. $name = $this->params['item_name'];
  399. $slug = URLify::filter(iconv(mb_detect_encoding($this->params['item_name'], mb_detect_order(), true), "UTF-8", $this->params['item_name']));
  400. # create the name for the new item
  401. # $nameParts = Folder::getStringParts($this->params['item_name']);
  402. # $name = implode("-", $nameParts);
  403. # $slug = $name;
  404. # initialize index
  405. $index = 0;
  406. # initialise write object
  407. $write = new WriteYaml();
  408. # iterate through the whole content of the new folder
  409. $writeError = false;
  410. foreach($folderContent as $folderItem)
  411. {
  412. # check, if the same name as new item, then return an error
  413. if($folderItem->slug == $slug)
  414. {
  415. return $response->withJson(array('data' => $this->structure, 'errors' => 'There is already a page with this name. Please choose another name.', 'url' => $url), 404);
  416. }
  417. if(!$write->moveElement($folderItem, $folder->path, $index))
  418. {
  419. $writeError = true;
  420. }
  421. $index++;
  422. }
  423. if($writeError){ return $response->withJson(array('data' => $this->structure, 'errors' => 'Something went wrong. Please refresh the page and check, if all folders and files are writable.', 'url' => $url), 404); }
  424. # add prefix number to the name
  425. $namePath = $index > 9 ? $index . '-' . $slug : '0' . $index . '-' . $slug;
  426. $folderPath = 'content' . $folder->path;
  427. # create default content
  428. $content = json_encode(['# ' . $name, 'Content']);
  429. if($this->params['type'] == 'file')
  430. {
  431. if(!$write->writeFile($folderPath, $namePath . '.txt', $content))
  432. {
  433. return $response->withJson(array('data' => $this->structure, 'errors' => 'We could not create the file. Please refresh the page and check, if all folders and files are writable.', 'url' => $url), 404);
  434. }
  435. }
  436. elseif($this->params['type'] == 'folder')
  437. {
  438. if(!$write->checkPath($folderPath . DIRECTORY_SEPARATOR . $namePath))
  439. {
  440. return $response->withJson(array('data' => $this->structure, 'errors' => 'We could not create the folder. Please refresh the page and check, if all folders and files are writable.', 'url' => $url), 404);
  441. }
  442. $write->writeFile($folderPath . DIRECTORY_SEPARATOR . $namePath, 'index.txt', $content);
  443. # always redirect to a folder
  444. $url = $this->uri->getBaseUrl() . '/tm/content/' . $this->settings['editor'] . $folder->urlRelWoF . '/' . $slug;
  445. }
  446. # get extended structure
  447. $extended = $write->getYaml('cache', 'structure-extended.yaml');
  448. # create the url for the item
  449. $urlWoF = $folder->urlRelWoF . '/' . $slug;
  450. # add the navigation name to the item htmlspecialchars needed for french language
  451. $extended[$urlWoF] = ['hide' => false, 'navtitle' => $name];
  452. # store the extended structure
  453. $write->updateYaml('cache', 'structure-extended.yaml', $extended);
  454. # update the structure for editor
  455. $this->setStructure($draft = true, $cache = false);
  456. # get item for url and set it active again
  457. if(isset($this->params['url']))
  458. {
  459. $activeItem = Folder::getItemForUrl($this->structure, $this->params['url'], $this->uri->getBaseUrl());
  460. }
  461. # activate this if you want to redirect after creating the page...
  462. # $url = $this->uri->getBaseUrl() . '/tm/content/' . $this->settings['editor'] . $folder->urlRelWoF . '/' . $slug;
  463. return $response->withJson(array('data' => $this->structure, 'errors' => false, 'url' => $url));
  464. }
  465. public function createBaseItem(Request $request, Response $response, $args)
  466. {
  467. # get params from call
  468. $this->params = $request->getParams();
  469. $this->uri = $request->getUri();
  470. # url is only needed, if an active page is moved
  471. $url = false;
  472. # set structure
  473. if(!$this->setStructure($draft = true)){ return $response->withJson(array('data' => false, 'errors' => $this->errors, 'url' => $url), 404); }
  474. # validate input
  475. if(!$this->validateBaseNaviItem()){ return $response->withJson(array('data' => $this->structure, 'errors' => 'Special Characters not allowed. Length between 1 and 20 chars.', 'url' => $url), 422); }
  476. # create the name for the new item
  477. # $nameParts = Folder::getStringParts($this->params['item_name']);
  478. # $name = implode("-", $nameParts);
  479. # $slug = $name;
  480. $name = $this->params['item_name'];
  481. $slug = URLify::filter(iconv(mb_detect_encoding($this->params['item_name'], mb_detect_order(), true), "UTF-8", $this->params['item_name']));
  482. # initialize index
  483. $index = 0;
  484. # initialise write object
  485. $write = new WriteYaml();
  486. # iterate through the whole content of the new folder
  487. $writeError = false;
  488. foreach($this->structure as $item)
  489. {
  490. # check, if the same name as new item, then return an error
  491. if($item->slug == $slug)
  492. {
  493. return $response->withJson(array('data' => $this->structure, 'errors' => 'There is already a page with this name. Please choose another name.', 'url' => $url), 422);
  494. }
  495. if(!$write->moveElement($item, '', $index))
  496. {
  497. $writeError = true;
  498. }
  499. $index++;
  500. }
  501. if($writeError){ return $response->withJson(array('data' => $this->structure, 'errors' => 'Something went wrong. Please refresh the page and check, if all folders and files are writable.', 'url' => $url), 422); }
  502. # add prefix number to the name
  503. $namePath = $index > 9 ? $index . '-' . $slug : '0' . $index . '-' . $slug;
  504. $folderPath = 'content';
  505. # create default content
  506. # $content = json_encode(['# Add Title', 'Add Content']);
  507. $content = json_encode(['# ' . $name, 'Content']);
  508. if($this->params['type'] == 'file')
  509. {
  510. if(!$write->writeFile($folderPath, $namePath . '.txt', $content))
  511. {
  512. return $response->withJson(array('data' => $this->structure, 'errors' => 'We could not create the file. Please refresh the page and check, if all folders and files are writable.', 'url' => $url), 422);
  513. }
  514. }
  515. elseif($this->params['type'] == 'folder')
  516. {
  517. if(!$write->checkPath($folderPath . DIRECTORY_SEPARATOR . $namePath))
  518. {
  519. return $response->withJson(array('data' => $this->structure, 'errors' => 'We could not create the folder. Please refresh the page and check, if all folders and files are writable.', 'url' => $url), 422);
  520. }
  521. $write->writeFile($folderPath . DIRECTORY_SEPARATOR . $namePath, 'index.txt', $content);
  522. # activate this if you want to redirect after creating the page...
  523. $url = $this->uri->getBaseUrl() . '/tm/content/' . $this->settings['editor'] . '/' . $slug;
  524. }
  525. # get extended structure
  526. $extended = $write->getYaml('cache', 'structure-extended.yaml');
  527. # create the url for the item
  528. $urlWoF = '/' . $slug;
  529. # add the navigation name to the item htmlspecialchars needed for frensh language
  530. $extended[$urlWoF] = ['hide' => false, 'navtitle' => $name];
  531. # store the extended structure
  532. $write->updateYaml('cache', 'structure-extended.yaml', $extended);
  533. # update the structure for editor
  534. $this->setStructure($draft = true, $cache = false);
  535. # get item for url and set it active again
  536. if(isset($this->params['url']))
  537. {
  538. $activeItem = Folder::getItemForUrl($this->structure, $this->params['url'], $this->uri->getBaseUrl());
  539. }
  540. return $response->withJson(array('data' => $this->structure, 'errors' => false, 'url' => $url));
  541. }
  542. public function getNavigation(Request $request, Response $response, $args)
  543. {
  544. # get params from call
  545. $this->params = $request->getParams();
  546. $this->uri = $request->getUri();
  547. # set structure
  548. if(!$this->setStructure($draft = true, $cache = false)){ return $response->withJson(array('data' => false, 'errors' => $this->errors, 'url' => $url), 404); }
  549. # set information for homepage
  550. $this->setHomepage();
  551. # get item for url and set it active again
  552. if(isset($this->params['url']))
  553. {
  554. $activeItem = Folder::getItemForUrl($this->structure, $this->params['url'], $this->uri->getBaseUrl());
  555. }
  556. return $response->withJson(array('data' => $this->structure, 'homepage' => $this->homepage, 'errors' => false));
  557. }
  558. public function getArticleMarkdown(Request $request, Response $response, $args)
  559. {
  560. /* get params from call */
  561. $this->params = $request->getParams();
  562. $this->uri = $request->getUri();
  563. # set structure
  564. if(!$this->setStructure($draft = true)){ return $response->withJson(array('data' => false, 'errors' => $this->errors), 404); }
  565. /* set item */
  566. if(!$this->setItem()){ return $response->withJson($this->errors, 404); }
  567. # set the status for published and drafted
  568. $this->setPublishStatus();
  569. # set path
  570. $this->setItemPath($this->item->fileType);
  571. # read content from file
  572. if(!$this->setContent()){ return $response->withJson(array('data' => false, 'errors' => $this->errors), 404); }
  573. $content = $this->content;
  574. if($content == '')
  575. {
  576. $content = [];
  577. }
  578. # if content is not an array, then transform it
  579. if(!is_array($content))
  580. {
  581. # initialize parsedown extension
  582. $parsedown = new ParsedownExtension();
  583. # turn markdown into an array of markdown-blocks
  584. $content = $parsedown->markdownToArrayBlocks($content);
  585. }
  586. # delete markdown from title
  587. if(isset($content[0]))
  588. {
  589. $content[0] = trim($content[0], "# ");
  590. }
  591. return $response->withJson(array('data' => $content, 'errors' => false));
  592. }
  593. public function getArticleHtml(Request $request, Response $response, $args)
  594. {
  595. /* get params from call */
  596. $this->params = $request->getParams();
  597. $this->uri = $request->getUri();
  598. # set structure
  599. if(!$this->setStructure($draft = true)){ return $response->withJson(array('data' => false, 'errors' => $this->errors), 404); }
  600. /* set item */
  601. if(!$this->setItem()){ return $response->withJson($this->errors, 404); }
  602. # set the status for published and drafted
  603. $this->setPublishStatus();
  604. # set path
  605. $this->setItemPath($this->item->fileType);
  606. # read content from file
  607. if(!$this->setContent()){ return $response->withJson(array('data' => false, 'errors' => $this->errors), 404); }
  608. $content = $this->content;
  609. if($content == '')
  610. {
  611. $content = [];
  612. }
  613. # initialize parsedown extension
  614. $parsedown = new ParsedownExtension();
  615. # fix footnotes in parsedown, might break with complicated footnotes
  616. $parsedown->setVisualMode();
  617. # if content is not an array, then transform it
  618. if(!is_array($content))
  619. {
  620. # turn markdown into an array of markdown-blocks
  621. $content = $parsedown->markdownToArrayBlocks($content);
  622. }
  623. # needed for ToC links
  624. $relurl = '/tm/content/' . $this->settings['editor'] . '/' . $this->item->urlRel;
  625. # flag for TOC
  626. $toc = false;
  627. # loop through mardkown-array and create html-blocks
  628. foreach($content as $key => $block)
  629. {
  630. # parse markdown-file to content-array
  631. $contentArray = $parsedown->text($block);
  632. if($block == '[TOC]')
  633. {
  634. $toc = $key;
  635. }
  636. # parse markdown-content-array to content-string
  637. $content[$key] = ['id' => $key, 'html' => $parsedown->markup($contentArray, $relurl)];
  638. }
  639. if($toc)
  640. {
  641. $tocMarkup = $parsedown->buildTOC($parsedown->headlines);
  642. $content[$toc] = ['id' => $toc, 'html' => $tocMarkup];
  643. }
  644. return $response->withJson(array('data' => $content, 'errors' => false));
  645. }
  646. }