ContentApiController.php 41 KB

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