ArticleApiController.php 29 KB

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