ContentApiController.php 42 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344
  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\ProcessImage;
  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. class ContentApiController 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. # dispatch event
  200. $this->c->dispatcher->dispatch('onPageDeleted', new OnPageDeleted($this->item));
  201. return $response->withJson(array('data' => $this->structure, 'errors' => false, 'url' => $url), 200);
  202. }
  203. else
  204. {
  205. return $response->withJson(array('data' => $this->structure, 'errors' => $this->errors), 404);
  206. }
  207. }
  208. public function updateArticle(Request $request, Response $response, $args)
  209. {
  210. # get params from call
  211. $this->params = $request->getParams();
  212. $this->uri = $request->getUri();
  213. # validate input
  214. if(!$this->validateEditorInput()){ return $response->withJson($this->errors,422); }
  215. # set structure
  216. if(!$this->setStructure($draft = true)){ return $response->withJson($this->errors, 404); }
  217. # set item
  218. if(!$this->setItem()){ return $response->withJson($this->errors, 404); }
  219. # set path for the file (or folder)
  220. $this->setItemPath('txt');
  221. # merge title with content for complete markdown document
  222. $updatedContent = '# ' . $this->params['title'] . "\r\n\r\n" . $this->params['content'];
  223. # initialize parsedown extension
  224. $parsedown = new ParsedownExtension();
  225. # turn markdown into an array of markdown-blocks
  226. $contentArray = $parsedown->markdownToArrayBlocks($updatedContent);
  227. # encode the content into json
  228. $contentJson = json_encode($contentArray);
  229. /* update the file */
  230. if($this->write->writeFile($this->settings['contentFolder'], $this->path, $contentJson))
  231. {
  232. # update the internal structure
  233. $this->setStructure($draft = true, $cache = false);
  234. return $response->withJson(['success'], 200);
  235. }
  236. else
  237. {
  238. return $response->withJson(['errors' => ['message' => 'Could not write to file. Please check if the file is writable']], 404);
  239. }
  240. }
  241. public function sortArticle(Request $request, Response $response, $args)
  242. {
  243. # get params from call
  244. $this->params = $request->getParams();
  245. $this->uri = $request->getUri();
  246. # url is only needed, if an active page is moved to another folder, so user has to be redirected to the new url
  247. $url = false;
  248. # set structure
  249. if(!$this->setStructure($draft = true)){ return $response->withJson(array('data' => false, 'errors' => $this->errors, 'url' => $url), 404); }
  250. # validate input
  251. if(!$this->validateNavigationSort()){ return $response->withJson(array('data' => $this->structure, 'errors' => 'Data not valid. Please refresh the page and try again.', 'url' => $url), 422); }
  252. # get the ids (key path) for item, old folder and new folder
  253. $itemKeyPath = explode('.', $this->params['item_id']);
  254. $parentKeyFrom = explode('.', $this->params['parent_id_from']);
  255. $parentKeyTo = explode('.', $this->params['parent_id_to']);
  256. /*
  257. echo '<pre>';
  258. print_r(array($itemKeyPath 0,$parentKeyFrom navi,$parentKeyTo 2));
  259. die();
  260. */
  261. # get the item from structure
  262. $item = Folder::getItemWithKeyPath($this->structure, $itemKeyPath);
  263. if(!$item){ return $response->withJson(array('data' => $this->structure, 'errors' => 'We could not find this page. Please refresh and try again.', 'url' => $url), 404); }
  264. # if an item is moved to the first level
  265. if($this->params['parent_id_to'] == 'navi')
  266. {
  267. # create empty and default values so that the logic below still works
  268. $newFolder = new \stdClass();
  269. $newFolder->path = '';
  270. $folderContent = $this->structure;
  271. }
  272. else
  273. {
  274. # get the target folder from structure
  275. $newFolder = Folder::getItemWithKeyPath($this->structure, $parentKeyTo);
  276. # get the content of the target folder
  277. $folderContent = $newFolder->folderContent;
  278. }
  279. # if the item has been moved within the same folder
  280. if($this->params['parent_id_from'] == $this->params['parent_id_to'])
  281. {
  282. # get key of item
  283. $itemKey = end($itemKeyPath);
  284. reset($itemKeyPath);
  285. # delete item from folderContent
  286. unset($folderContent[$itemKey]);
  287. }
  288. elseif($this->params['active'] == 'active')
  289. {
  290. # an active file has been moved to another folder, so send new url with response
  291. $url = $this->uri->getBaseUrl() . '/tm/content/' . $this->settings['editor'] . $newFolder->urlRelWoF . '/' . $item->slug;
  292. }
  293. # add item to newFolder
  294. array_splice($folderContent, $this->params['index_new'], 0, array($item));
  295. # initialize index
  296. $index = 0;
  297. # initialise write object
  298. $write = new Write();
  299. # iterate through the whole content of the new folder to rename the files
  300. $writeError = false;
  301. foreach($folderContent as $folderItem)
  302. {
  303. if(!$write->moveElement($folderItem, $newFolder->path, $index))
  304. {
  305. $writeError = true;
  306. }
  307. $index++;
  308. }
  309. 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); }
  310. # update the structure for editor
  311. $this->setStructure($draft = true, $cache = false);
  312. # get item for url and set it active again
  313. if(isset($this->params['url']))
  314. {
  315. $activeItem = Folder::getItemForUrl($this->structure, $this->params['url'], $this->uri->getBaseUrl());
  316. }
  317. # keep the internal structure for response
  318. $internalStructure = $this->structure;
  319. # update the structure for website
  320. $this->setStructure($draft = false, $cache = false);
  321. # dispatch event
  322. $this->c->dispatcher->dispatch('onPageSorted', new OnPageSorted($this->params));
  323. return $response->withJson(array('data' => $internalStructure, 'errors' => false, 'url' => $url));
  324. }
  325. public function createArticle(Request $request, Response $response, $args)
  326. {
  327. # get params from call
  328. $this->params = $request->getParams();
  329. $this->uri = $request->getUri();
  330. # url is only needed, if an active page is moved
  331. $url = false;
  332. # set structure
  333. if(!$this->setStructure($draft = true)){ return $response->withJson(array('data' => false, 'errors' => $this->errors, 'url' => $url), 404); }
  334. # validate input
  335. if(!$this->validateNaviItem()){ return $response->withJson(array('data' => $this->structure, 'errors' => 'Special Characters not allowed. Length between 1 and 20 chars.', 'url' => $url), 422); }
  336. # get the ids (key path) for item, old folder and new folder
  337. $folderKeyPath = explode('.', $this->params['folder_id']);
  338. # get the item from structure
  339. $folder = Folder::getItemWithKeyPath($this->structure, $folderKeyPath);
  340. if(!$folder){ return $response->withJson(array('data' => $this->structure, 'errors' => 'We could not find this page. Please refresh and try again.', 'url' => $url), 404); }
  341. # Rename all files within the folder to make sure, that namings and orders are correct
  342. # get the content of the target folder
  343. $folderContent = $folder->folderContent;
  344. # create the name for the new item
  345. $nameParts = Folder::getStringParts($this->params['item_name']);
  346. $name = implode("-", $nameParts);
  347. $slug = $name;
  348. # initialize index
  349. $index = 0;
  350. # initialise write object
  351. $write = new Write();
  352. # iterate through the whole content of the new folder
  353. $writeError = false;
  354. foreach($folderContent as $folderItem)
  355. {
  356. # check, if the same name as new item, then return an error
  357. if($folderItem->slug == $slug)
  358. {
  359. return $response->withJson(array('data' => $this->structure, 'errors' => 'There is already a page with this name. Please choose another name.', 'url' => $url), 404);
  360. }
  361. if(!$write->moveElement($folderItem, $folder->path, $index))
  362. {
  363. $writeError = true;
  364. }
  365. $index++;
  366. }
  367. 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); }
  368. # add prefix number to the name
  369. $namePath = $index > 9 ? $index . '-' . $name : '0' . $index . '-' . $name;
  370. $folderPath = 'content' . $folder->path;
  371. $title = implode(" ", $nameParts);
  372. # create default content
  373. $content = json_encode(['# ' . $title, 'Content']);
  374. if($this->params['type'] == 'file')
  375. {
  376. if(!$write->writeFile($folderPath, $namePath . '.txt', $content))
  377. {
  378. 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);
  379. }
  380. }
  381. elseif($this->params['type'] == 'folder')
  382. {
  383. if(!$write->checkPath($folderPath . DIRECTORY_SEPARATOR . $namePath))
  384. {
  385. 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);
  386. }
  387. $write->writeFile($folderPath . DIRECTORY_SEPARATOR . $namePath, 'index.txt', $content);
  388. }
  389. # update the structure for editor
  390. $this->setStructure($draft = true, $cache = false);
  391. # get item for url and set it active again
  392. if(isset($this->params['url']))
  393. {
  394. $activeItem = Folder::getItemForUrl($this->structure, $this->params['url'], $this->uri->getBaseUrl());
  395. }
  396. # activate this if you want to redirect after creating the page...
  397. # $url = $this->uri->getBaseUrl() . '/tm/content' . $folder->urlRelWoF . '/' . $name;
  398. return $response->withJson(array('data' => $this->structure, 'errors' => false, 'url' => $url));
  399. }
  400. public function createBaseItem(Request $request, Response $response, $args)
  401. {
  402. # get params from call
  403. $this->params = $request->getParams();
  404. $this->uri = $request->getUri();
  405. # url is only needed, if an active page is moved
  406. $url = false;
  407. # set structure
  408. if(!$this->setStructure($draft = true)){ return $response->withJson(array('data' => false, 'errors' => $this->errors, 'url' => $url), 404); }
  409. # validate input
  410. if(!$this->validateBaseNaviItem()){ return $response->withJson(array('data' => $this->structure, 'errors' => 'Special Characters not allowed. Length between 1 and 20 chars.', 'url' => $url), 422); }
  411. # create the name for the new item
  412. $nameParts = Folder::getStringParts($this->params['item_name']);
  413. $name = implode("-", $nameParts);
  414. $slug = $name;
  415. # initialize index
  416. $index = 0;
  417. # initialise write object
  418. $write = new Write();
  419. # iterate through the whole content of the new folder
  420. $writeError = false;
  421. foreach($this->structure as $item)
  422. {
  423. # check, if the same name as new item, then return an error
  424. if($item->slug == $slug)
  425. {
  426. return $response->withJson(array('data' => $this->structure, 'errors' => 'There is already a page with this name. Please choose another name.', 'url' => $url), 404);
  427. }
  428. if(!$write->moveElement($item, '', $index))
  429. {
  430. $writeError = true;
  431. }
  432. $index++;
  433. }
  434. 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); }
  435. # add prefix number to the name
  436. $namePath = $index > 9 ? $index . '-' . $name : '0' . $index . '-' . $name;
  437. $folderPath = 'content';
  438. # create default content
  439. $content = json_encode(['# Add Title', 'Add Content']);
  440. if($this->params['type'] == 'file')
  441. {
  442. if(!$write->writeFile($folderPath, $namePath . '.txt', $content))
  443. {
  444. 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);
  445. }
  446. }
  447. elseif($this->params['type'] == 'folder')
  448. {
  449. if(!$write->checkPath($folderPath . DIRECTORY_SEPARATOR . $namePath))
  450. {
  451. 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);
  452. }
  453. $write->writeFile($folderPath . DIRECTORY_SEPARATOR . $namePath, 'index.txt', $content);
  454. }
  455. # update the structure for editor
  456. $this->setStructure($draft = true, $cache = false);
  457. # get item for url and set it active again
  458. if(isset($this->params['url']))
  459. {
  460. $activeItem = Folder::getItemForUrl($this->structure, $this->params['url'], $this->uri->getBaseUrl());
  461. }
  462. return $response->withJson(array('data' => $this->structure, 'errors' => false, 'url' => $url));
  463. }
  464. public function getNavigation(Request $request, Response $response, $args)
  465. {
  466. # get params from call
  467. $this->params = $request->getParams();
  468. $this->uri = $request->getUri();
  469. # set structure
  470. if(!$this->setStructure($draft = true, $cache = false)){ return $response->withJson(array('data' => false, 'errors' => $this->errors, 'url' => $url), 404); }
  471. # set information for homepage
  472. $this->setHomepage();
  473. # get item for url and set it active again
  474. if(isset($this->params['url']))
  475. {
  476. $activeItem = Folder::getItemForUrl($this->structure, $this->params['url'], $this->uri->getBaseUrl());
  477. }
  478. return $response->withJson(array('data' => $this->structure, 'homepage' => $this->homepage, 'errors' => false));
  479. }
  480. public function getArticleMarkdown(Request $request, Response $response, $args)
  481. {
  482. /* get params from call */
  483. $this->params = $request->getParams();
  484. $this->uri = $request->getUri();
  485. # set structure
  486. if(!$this->setStructure($draft = true)){ return $response->withJson(array('data' => false, 'errors' => $this->errors), 404); }
  487. /* set item */
  488. if(!$this->setItem()){ return $response->withJson($this->errors, 404); }
  489. # set the status for published and drafted
  490. $this->setPublishStatus();
  491. # set path
  492. $this->setItemPath($this->item->fileType);
  493. # read content from file
  494. if(!$this->setContent()){ return $response->withJson(array('data' => false, 'errors' => $this->errors), 404); }
  495. $content = $this->content;
  496. if($content == '')
  497. {
  498. $content = [];
  499. }
  500. # if content is not an array, then transform it
  501. if(!is_array($content))
  502. {
  503. # initialize parsedown extension
  504. $parsedown = new ParsedownExtension();
  505. # turn markdown into an array of markdown-blocks
  506. $content = $parsedown->markdownToArrayBlocks($content);
  507. }
  508. # delete markdown from title
  509. if(isset($content[0]))
  510. {
  511. $content[0] = trim($content[0], "# ");
  512. }
  513. return $response->withJson(array('data' => $content, 'errors' => false));
  514. }
  515. public function getArticleHtml(Request $request, Response $response, $args)
  516. {
  517. /* get params from call */
  518. $this->params = $request->getParams();
  519. $this->uri = $request->getUri();
  520. # set structure
  521. if(!$this->setStructure($draft = true)){ return $response->withJson(array('data' => false, 'errors' => $this->errors), 404); }
  522. /* set item */
  523. if(!$this->setItem()){ return $response->withJson($this->errors, 404); }
  524. # set the status for published and drafted
  525. $this->setPublishStatus();
  526. # set path
  527. $this->setItemPath($this->item->fileType);
  528. # read content from file
  529. if(!$this->setContent()){ return $response->withJson(array('data' => false, 'errors' => $this->errors), 404); }
  530. $content = $this->content;
  531. if($content == '')
  532. {
  533. $content = [];
  534. }
  535. # initialize parsedown extension
  536. $parsedown = new ParsedownExtension();
  537. # fix footnotes in parsedown, might break with complicated footnotes
  538. $parsedown->setVisualMode();
  539. # if content is not an array, then transform it
  540. if(!is_array($content))
  541. {
  542. # turn markdown into an array of markdown-blocks
  543. $content = $parsedown->markdownToArrayBlocks($content);
  544. }
  545. # needed for ToC links
  546. $relurl = '/tm/content/' . $this->settings['editor'] . '/' . $this->item->urlRel;
  547. # flag for TOC
  548. $toc = false;
  549. # loop through mardkown-array and create html-blocks
  550. foreach($content as $key => $block)
  551. {
  552. # parse markdown-file to content-array
  553. $contentArray = $parsedown->text($block);
  554. if($block == '[TOC]')
  555. {
  556. $toc = $key;
  557. }
  558. # parse markdown-content-array to content-string
  559. $content[$key] = ['id' => $key, 'html' => $parsedown->markup($contentArray, $relurl)];
  560. }
  561. if($toc)
  562. {
  563. $tocMarkup = $parsedown->buildTOC($parsedown->headlines);
  564. $content[$toc] = ['id' => $toc, 'html' => $tocMarkup];
  565. }
  566. return $response->withJson(array('data' => $content, 'errors' => false));
  567. }
  568. public function addBlock(Request $request, Response $response, $args)
  569. {
  570. /* get params from call */
  571. $this->params = $request->getParams();
  572. $this->uri = $request->getUri();
  573. /* validate input */
  574. if(!$this->validateBlockInput()){ return $response->withJson($this->errors,422); }
  575. # set structure
  576. if(!$this->setStructure($draft = true)){ return $response->withJson(array('data' => false, 'errors' => $this->errors), 404); }
  577. /* set item */
  578. if(!$this->setItem()){ return $response->withJson($this->errors, 404); }
  579. # set the status for published and drafted
  580. $this->setPublishStatus();
  581. # set path
  582. $this->setItemPath($this->item->fileType);
  583. # read content from file
  584. if(!$this->setContent()){ return $response->withJson(array('data' => false, 'errors' => $this->errors), 404); }
  585. # make it more clear which content we have
  586. $pageMarkdown = $this->content;
  587. $blockMarkdown = $this->params['markdown'];
  588. # standardize line breaks
  589. $blockMarkdown = str_replace(array("\r\n", "\r"), "\n", $blockMarkdown);
  590. # remove surrounding line breaks
  591. $blockMarkdown = trim($blockMarkdown, "\n");
  592. if($pageMarkdown == '')
  593. {
  594. $pageMarkdown = [];
  595. }
  596. # initialize parsedown extension
  597. $parsedown = new ParsedownExtension();
  598. # if content is not an array, then transform it
  599. if(!is_array($pageMarkdown))
  600. {
  601. # turn markdown into an array of markdown-blocks
  602. $pageMarkdown = $parsedown->markdownToArrayBlocks($pageMarkdown);
  603. }
  604. # if it is a new content-block
  605. if($this->params['block_id'] == 99999)
  606. {
  607. # set the id of the markdown-block (it will be one more than the actual array, so count is perfect)
  608. $id = count($pageMarkdown);
  609. # add the new markdown block to the page content
  610. $pageMarkdown[] = $blockMarkdown;
  611. }
  612. elseif(($this->params['block_id'] == 0) OR !isset($pageMarkdown[$this->params['block_id']]))
  613. {
  614. # if the block does not exists, return an error
  615. return $response->withJson(array('data' => false, 'errors' => 'The ID of the content-block is wrong.'), 404);
  616. }
  617. else
  618. {
  619. # insert new markdown block
  620. array_splice( $pageMarkdown, $this->params['block_id'], 0, $blockMarkdown );
  621. $id = $this->params['block_id'];
  622. }
  623. # encode the content into json
  624. $pageJson = json_encode($pageMarkdown);
  625. # set path for the file (or folder)
  626. $this->setItemPath('txt');
  627. /* update the file */
  628. if($this->write->writeFile($this->settings['contentFolder'], $this->path, $pageJson))
  629. {
  630. # update the internal structure
  631. $this->setStructure($draft = true, $cache = false);
  632. $this->content = $pageMarkdown;
  633. }
  634. else
  635. {
  636. return $response->withJson(['errors' => ['message' => 'Could not write to file. Please check if the file is writable']], 404);
  637. }
  638. /* set safe mode to escape javascript and html in markdown */
  639. $parsedown->setSafeMode(true);
  640. /* parse markdown-file to content-array */
  641. $blockArray = $parsedown->text($blockMarkdown);
  642. # we assume that toc is not relevant
  643. $toc = false;
  644. # needed for ToC links
  645. $relurl = '/tm/content/' . $this->settings['editor'] . '/' . $this->item->urlRel;
  646. if($blockMarkdown == '[TOC]')
  647. {
  648. # if block is table of content itself, then generate the table of content
  649. $tableofcontent = $this->generateToc();
  650. # and only use the html-markup
  651. $blockHTML = $tableofcontent['html'];
  652. }
  653. else
  654. {
  655. # parse markdown-content-array to content-string
  656. $blockHTML = $parsedown->markup($blockArray, $relurl);
  657. # if it is a headline
  658. if($blockMarkdown[0] == '#')
  659. {
  660. # then the TOC holds either false (if no toc used in the page) or it holds an object with the id and toc-markup
  661. $toc = $this->generateToc();
  662. }
  663. }
  664. return $response->withJson(array('content' => [ 'id' => $id, 'html' => $blockHTML ] , 'markdown' => $blockMarkdown, 'id' => $id, 'toc' => $toc, 'errors' => false));
  665. }
  666. protected function generateToc()
  667. {
  668. # we assume that page has no table of content
  669. $toc = false;
  670. # make sure $this->content is updated
  671. $content = $this->content;
  672. if($content == '')
  673. {
  674. $content = [];
  675. }
  676. # initialize parsedown extension
  677. $parsedown = new ParsedownExtension();
  678. # if content is not an array, then transform it
  679. if(!is_array($content))
  680. {
  681. # turn markdown into an array of markdown-blocks
  682. $content = $parsedown->markdownToArrayBlocks($content);
  683. }
  684. # needed for ToC links
  685. $relurl = '/tm/content/' . $this->settings['editor'] . '/' . $this->item->urlRel;
  686. # loop through mardkown-array and create html-blocks
  687. foreach($content as $key => $block)
  688. {
  689. # parse markdown-file to content-array
  690. $contentArray = $parsedown->text($block);
  691. if($block == '[TOC]')
  692. {
  693. # toc is true and holds the key of the table of content now
  694. $toc = $key;
  695. }
  696. # parse markdown-content-array to content-string
  697. $content[$key] = ['id' => $key, 'html' => $parsedown->markup($contentArray, $relurl)];
  698. }
  699. # if page has a table of content
  700. if($toc)
  701. {
  702. # generate the toc markup
  703. $tocMarkup = $parsedown->buildTOC($parsedown->headlines);
  704. # toc holds the id of the table of content and the html-markup now
  705. $toc = ['id' => $toc, 'html' => $tocMarkup];
  706. }
  707. return $toc;
  708. }
  709. public function updateBlock(Request $request, Response $response, $args)
  710. {
  711. /* get params from call */
  712. $this->params = $request->getParams();
  713. $this->uri = $request->getUri();
  714. /* validate input */
  715. if(!$this->validateBlockInput()){ return $response->withJson($this->errors,422); }
  716. # set structure
  717. if(!$this->setStructure($draft = true)){ return $response->withJson(array('data' => false, 'errors' => $this->errors), 404); }
  718. /* set item */
  719. if(!$this->setItem()){ return $response->withJson($this->errors, 404); }
  720. # set the status for published and drafted
  721. $this->setPublishStatus();
  722. # set path
  723. $this->setItemPath($this->item->fileType);
  724. # read content from file
  725. if(!$this->setContent()){ return $response->withJson(array('data' => false, 'errors' => $this->errors), 404); }
  726. # make it more clear which content we have
  727. $pageMarkdown = $this->content;
  728. $blockMarkdown = $this->params['markdown'];
  729. # standardize line breaks
  730. $blockMarkdown = str_replace(array("\r\n", "\r"), "\n", $blockMarkdown);
  731. # remove surrounding line breaks
  732. $blockMarkdown = trim($blockMarkdown, "\n");
  733. if($pageMarkdown == '')
  734. {
  735. $pageMarkdown = [];
  736. }
  737. # initialize parsedown extension
  738. $parsedown = new ParsedownExtension();
  739. $parsedown->setVisualMode();
  740. # if content is not an array, then transform it
  741. if(!is_array($pageMarkdown))
  742. {
  743. # turn markdown into an array of markdown-blocks
  744. $pageMarkdown = $parsedown->markdownToArrayBlocks($pageMarkdown);
  745. }
  746. if(!isset($pageMarkdown[$this->params['block_id']]))
  747. {
  748. # if the block does not exists, return an error
  749. return $response->withJson(array('data' => false, 'errors' => 'The ID of the content-block is wrong.'), 404);
  750. }
  751. elseif($this->params['block_id'] == 0)
  752. {
  753. # if it is the title, then delete the "# " if it exists
  754. $blockMarkdown = trim($blockMarkdown, "# ");
  755. # store the markdown-headline in a separate variable
  756. $blockMarkdownTitle = '# ' . $blockMarkdown;
  757. # add the markdown-headline to the page-markdown
  758. $pageMarkdown[0] = $blockMarkdownTitle;
  759. $id = 0;
  760. }
  761. else
  762. {
  763. # update the markdown block in the page content
  764. $pageMarkdown[$this->params['block_id']] = $blockMarkdown;
  765. $id = $this->params['block_id'];
  766. }
  767. # encode the content into json
  768. $pageJson = json_encode($pageMarkdown);
  769. # set path for the file (or folder)
  770. $this->setItemPath('txt');
  771. /* update the file */
  772. if($this->write->writeFile($this->settings['contentFolder'], $this->path, $pageJson))
  773. {
  774. # update the internal structure
  775. $this->setStructure($draft = true, $cache = false);
  776. # updated the content variable
  777. $this->content = $pageMarkdown;
  778. }
  779. else
  780. {
  781. return $response->withJson(['errors' => ['message' => 'Could not write to file. Please check if the file is writable']], 404);
  782. }
  783. /* set safe mode to escape javascript and html in markdown */
  784. $parsedown->setSafeMode(true);
  785. /* parse markdown-file to content-array, if title parse title. */
  786. if($this->params['block_id'] == 0)
  787. {
  788. $blockArray = $parsedown->text($blockMarkdownTitle);
  789. }
  790. else
  791. {
  792. $blockArray = $parsedown->text($blockMarkdown);
  793. }
  794. # we assume that toc is not relevant
  795. $toc = false;
  796. # needed for ToC links
  797. $relurl = '/tm/content/' . $this->settings['editor'] . '/' . $this->item->urlRel;
  798. if($blockMarkdown == '[TOC]')
  799. {
  800. # if block is table of content itself, then generate the table of content
  801. $tableofcontent = $this->generateToc();
  802. # and only use the html-markup
  803. $blockHTML = $tableofcontent['html'];
  804. }
  805. else
  806. {
  807. # parse markdown-content-array to content-string
  808. $blockHTML = $parsedown->markup($blockArray, $relurl);
  809. # if it is a headline
  810. if($blockMarkdown[0] == '#')
  811. {
  812. # then the TOC holds either false (if no toc used in the page) or it holds an object with the id and toc-markup
  813. $toc = $this->generateToc();
  814. }
  815. }
  816. return $response->withJson(array('content' => [ 'id' => $id, 'html' => $blockHTML ] , 'markdown' => $blockMarkdown, 'id' => $id, 'toc' => $toc, 'errors' => false));
  817. }
  818. public function moveBlock(Request $request, Response $response, $args)
  819. {
  820. # get params from call
  821. $this->params = $request->getParams();
  822. $this->uri = $request->getUri();
  823. # validate input
  824. # if(!$this->validateBlockInput()){ return $response->withJson($this->errors,422); }
  825. # set structure
  826. if(!$this->setStructure($draft = true)){ return $response->withJson(array('data' => false, 'errors' => $this->errors), 404); }
  827. # set item
  828. if(!$this->setItem()){ return $response->withJson($this->errors, 404); }
  829. # set the status for published and drafted
  830. $this->setPublishStatus();
  831. # set path
  832. $this->setItemPath($this->item->fileType);
  833. # read content from file
  834. if(!$this->setContent()){ return $response->withJson(array('data' => false, 'errors' => $this->errors), 404); }
  835. # make it more clear which content we have
  836. $pageMarkdown = $this->content;
  837. if($pageMarkdown == '')
  838. {
  839. $pageMarkdown = [];
  840. }
  841. # initialize parsedown extension
  842. $parsedown = new ParsedownExtension();
  843. # if content is not an array, then transform it
  844. if(!is_array($pageMarkdown))
  845. {
  846. # turn markdown into an array of markdown-blocks
  847. $pageMarkdown = $parsedown->markdownToArrayBlocks($pageMarkdown);
  848. }
  849. $oldIndex = ($this->params['old_index'] + 1);
  850. $newIndex = ($this->params['new_index'] + 1);
  851. if(!isset($pageMarkdown[$oldIndex]))
  852. {
  853. # if the block does not exists, return an error
  854. return $response->withJson(array('data' => false, 'errors' => 'The ID of the content-block is wrong.'), 404);
  855. }
  856. $extract = array_splice($pageMarkdown, $oldIndex, 1);
  857. array_splice($pageMarkdown, $newIndex, 0, $extract);
  858. # encode the content into json
  859. $pageJson = json_encode($pageMarkdown);
  860. # set path for the file (or folder)
  861. $this->setItemPath('txt');
  862. /* update the file */
  863. if($this->write->writeFile($this->settings['contentFolder'], $this->path, $pageJson))
  864. {
  865. # update the internal structure
  866. $this->setStructure($draft = true, $cache = false);
  867. # update this content
  868. $this->content = $pageMarkdown;
  869. }
  870. else
  871. {
  872. return $response->withJson(['errors' => ['message' => 'Could not write to file. Please check if the file is writable']], 404);
  873. }
  874. # we assume that toc is not relevant
  875. $toc = false;
  876. # needed for ToC links
  877. $relurl = '/tm/content/' . $this->settings['editor'] . '/' . $this->item->urlRel;
  878. # if the moved item is a headline
  879. if($extract[0][0] == '#')
  880. {
  881. $toc = $this->generateToc();
  882. }
  883. # if it is the title, then delete the "# " if it exists
  884. $pageMarkdown[0] = trim($pageMarkdown[0], "# ");
  885. return $response->withJson(array('markdown' => $pageMarkdown, 'toc' => $toc, 'errors' => false));
  886. }
  887. public function deleteBlock(Request $request, Response $response, $args)
  888. {
  889. /* get params from call */
  890. $this->params = $request->getParams();
  891. $this->uri = $request->getUri();
  892. $errors = false;
  893. # set structure
  894. if(!$this->setStructure($draft = true)){ return $response->withJson(array('data' => false, 'errors' => $this->errors), 404); }
  895. # set item
  896. if(!$this->setItem()){ return $response->withJson($this->errors, 404); }
  897. # set the status for published and drafted
  898. $this->setPublishStatus();
  899. # set path
  900. $this->setItemPath($this->item->fileType);
  901. # read content from file
  902. if(!$this->setContent()){ return $response->withJson(array('data' => false, 'errors' => $this->errors), 404); }
  903. # get content
  904. $this->content;
  905. if($this->content == '')
  906. {
  907. $this->content = [];
  908. }
  909. # initialize parsedown extension
  910. $parsedown = new ParsedownExtension();
  911. # if content is not an array, then transform it
  912. if(!is_array($this->content))
  913. {
  914. # turn markdown into an array of markdown-blocks
  915. $this->content = $parsedown->markdownToArrayBlocks($this->content);
  916. }
  917. # check if id exists
  918. if(!isset($this->content[$this->params['block_id']])){ return $response->withJson(array('data' => false, 'errors' => 'The ID of the content-block is wrong.'), 404); }
  919. # check if block is image
  920. $contentBlock = $this->content[$this->params['block_id']];
  921. $contentBlockStart = substr($contentBlock, 0, 2);
  922. if($contentBlockStart == '[!' OR $contentBlockStart == '![')
  923. {
  924. # extract image path
  925. preg_match("/\((.*?)\)/",$contentBlock,$matches);
  926. if(isset($matches[1]))
  927. {
  928. $imageBaseName = explode('-', $matches[1]);
  929. $imageBaseName = str_replace('media/live/', '', $imageBaseName[0]);
  930. $processImage = new ProcessImage();
  931. if(!$processImage->deleteImage($imageBaseName))
  932. {
  933. $errors = 'Could not delete some of the images, please check manually';
  934. }
  935. }
  936. }
  937. # delete the block
  938. unset($this->content[$this->params['block_id']]);
  939. $this->content = array_values($this->content);
  940. $pageMarkdown = $this->content;
  941. # delete markdown from title
  942. if(isset($pageMarkdown[0]))
  943. {
  944. $pageMarkdown[0] = trim($pageMarkdown[0], "# ");
  945. }
  946. # encode the content into json
  947. $pageJson = json_encode($this->content);
  948. # set path for the file (or folder)
  949. $this->setItemPath('txt');
  950. /* update the file */
  951. if($this->write->writeFile($this->settings['contentFolder'], $this->path, $pageJson))
  952. {
  953. # update the internal structure
  954. $this->setStructure($draft = true, $cache = false);
  955. }
  956. else
  957. {
  958. return $response->withJson(['errors' => ['message' => 'Could not write to file. Please check if the file is writable']], 404);
  959. }
  960. $toc = false;
  961. if($contentBlock[0] == '#')
  962. {
  963. $toc = $this->generateToc();
  964. }
  965. return $response->withJson(array('markdown' => $pageMarkdown, 'toc' => $toc, 'errors' => $errors));
  966. }
  967. public function createImage(Request $request, Response $response, $args)
  968. {
  969. /* get params from call */
  970. $this->params = $request->getParams();
  971. $this->uri = $request->getUri();
  972. $imageProcessor = new ProcessImage();
  973. if($imageProcessor->createImage($this->params['image'], $this->settings['images']))
  974. {
  975. return $response->withJson(array('errors' => false));
  976. }
  977. return $response->withJson(array('errors' => 'could not store image to temporary folder'));
  978. }
  979. public function publishImage(Request $request, Response $response, $args)
  980. {
  981. $params = $request->getParsedBody();
  982. $imageProcessor = new ProcessImage();
  983. $imageUrl = $imageProcessor->publishImage($this->settings['images'], $name = false);
  984. if($imageUrl)
  985. {
  986. $params['markdown'] = str_replace('imgplchldr', $imageUrl, $params['markdown']);
  987. $request = $request->withParsedBody($params);
  988. return $this->addBlock($request, $response, $args);
  989. }
  990. return $response->withJson(array('errors' => 'could not store image to media folder'));
  991. }
  992. public function saveVideoImage(Request $request, Response $response, $args)
  993. {
  994. /* get params from call */
  995. $this->params = $request->getParams();
  996. $this->uri = $request->getUri();
  997. $class = false;
  998. $imageUrl = $this->params['markdown'];
  999. if(strpos($imageUrl, 'https://www.youtube.com/watch?v=') !== false)
  1000. {
  1001. $videoID = str_replace('https://www.youtube.com/watch?v=', '', $imageUrl);
  1002. $videoID = strpos($videoID, '&') ? substr($videoID, 0, strpos($videoID, '&')) : $videoID;
  1003. $class = 'youtube';
  1004. }
  1005. if(strpos($imageUrl, 'https://youtu.be/') !== false)
  1006. {
  1007. $videoID = str_replace('https://youtu.be/', '', $imageUrl);
  1008. $videoID = strpos($videoID, '?') ? substr($videoID, 0, strpos($videoID, '?')) : $videoID;
  1009. $class = 'youtube';
  1010. }
  1011. if($class == 'youtube')
  1012. {
  1013. $videoURLmaxres = 'https://i1.ytimg.com/vi/' . $videoID . '/maxresdefault.jpg';
  1014. $videoURL0 = 'https://i1.ytimg.com/vi/' . $videoID . '/0.jpg';
  1015. }
  1016. $ctx = stream_context_create(array(
  1017. 'https' => array(
  1018. 'timeout' => 1
  1019. )
  1020. )
  1021. );
  1022. $imageData = @file_get_contents($videoURLmaxres, 0, $ctx);
  1023. if($imageData === false)
  1024. {
  1025. $imageData = @file_get_contents($videoURL0, 0, $ctx);
  1026. if($imageData === false)
  1027. {
  1028. return $response->withJson(array('errors' => 'could not get the video image'));
  1029. }
  1030. }
  1031. $imageData64 = 'data:image/jpeg;base64,' . base64_encode($imageData);
  1032. $desiredSizes = ['live' => ['width' => 560, 'height' => 315]];
  1033. $imageProcessor = new ProcessImage();
  1034. $tmpImage = $imageProcessor->createImage($imageData64, $desiredSizes);
  1035. if(!$tmpImage)
  1036. {
  1037. return $response->withJson(array('errors' => 'could not create temporary image'));
  1038. }
  1039. $imageUrl = $imageProcessor->publishImage($desiredSizes, $videoID);
  1040. if($imageUrl)
  1041. {
  1042. $this->params['markdown'] = '![' . $class . '-video](' . $imageUrl . ' "click to load video"){#' . $videoID. ' .' . $class . '}';
  1043. $request = $request->withParsedBody($this->params);
  1044. return $this->addBlock($request, $response, $args);
  1045. }
  1046. return $response->withJson(array('errors' => 'could not store the preview image'));
  1047. }
  1048. }