ContentController.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434
  1. <?php
  2. namespace Typemill\Controllers;
  3. use Slim\Http\Request;
  4. use Slim\Http\Response;
  5. use Psr\Container\ContainerInterface;
  6. use Typemill\Models\Validation;
  7. use Typemill\Models\Folder;
  8. use Typemill\Models\Write;
  9. use Typemill\Models\WriteCache;
  10. use Typemill\Models\WriteYaml;
  11. abstract class ContentController
  12. {
  13. # holds the pimple container
  14. protected $c;
  15. # holds the params from request
  16. protected $params;
  17. # holds the slim-uri-object
  18. protected $uri;
  19. # holds the errors to output in frontend
  20. protected $errors = false;
  21. # holds a write object to write files
  22. protected $write;
  23. # holds the structure of content folder as a serialized array of objects
  24. protected $structure;
  25. # holds the name of the structure-file with drafts for author environment
  26. protected $structureDraftName;
  27. # holds the name of the structure-file without drafts for live site
  28. protected $structureLiveName;
  29. # holds informations about the homepage
  30. protected $homepage;
  31. # hold the page-item as an object
  32. protected $item;
  33. # hold the breadcrumb as an object
  34. protected $breadcrumb;
  35. # holds the path to the requested file
  36. protected $path = false;
  37. # holds the content of the page
  38. protected $content;
  39. public function __construct(ContainerInterface $c)
  40. {
  41. $this->c = $c;
  42. $this->settings = $this->c->get('settings');
  43. $this->structureLiveName = 'structure.txt';
  44. $this->structureDraftName = 'structure-draft.txt';
  45. }
  46. protected function render($response, $route, $data)
  47. {
  48. if(isset($_SESSION['old']))
  49. {
  50. unset($_SESSION['old']);
  51. }
  52. $response = $response->withoutHeader('Server');
  53. $response = $response->withoutHeader('X-Powered-By');
  54. if($this->c->request->getUri()->getScheme() == 'https')
  55. {
  56. $response = $response->withAddedHeader('Strict-Transport-Security', 'max-age=63072000');
  57. }
  58. $response = $response->withAddedHeader('X-Content-Type-Options', 'nosniff');
  59. $response = $response->withAddedHeader('X-Frame-Options', 'SAMEORIGIN');
  60. $response = $response->withAddedHeader('X-XSS-Protection', '1;mode=block');
  61. $response = $response->withAddedHeader('Referrer-Policy', 'no-referrer-when-downgrade');
  62. return $this->c->view->render($response, $route, $data);
  63. }
  64. protected function render404($response, $data = NULL)
  65. {
  66. return $this->c->view->render($response->withStatus(404), '/404.twig', $data);
  67. }
  68. protected function renderIntern404($response, $data = NULL)
  69. {
  70. return $this->c->view->render($response->withStatus(404), '/intern404.twig', $data);
  71. }
  72. protected function getValidator()
  73. {
  74. return new Validation();
  75. }
  76. protected function validateEditorInput()
  77. {
  78. $validate = new Validation();
  79. $vResult = $validate->editorInput($this->params);
  80. if(is_array($vResult))
  81. {
  82. $message = reset($vResult);
  83. $this->errors = ['errors' => $vResult];
  84. if(isset($message[0])){ $this->errors['errors']['message'] = $message[0]; }
  85. return false;
  86. }
  87. return true;
  88. }
  89. protected function validateBlockInput()
  90. {
  91. $validate = new Validation();
  92. $vResult = $validate->blockInput($this->params);
  93. if(is_array($vResult))
  94. {
  95. $message = reset($vResult);
  96. $this->errors = ['errors' => $vResult];
  97. if(isset($message[0])){ $this->errors['errors']['message'] = $message[0]; }
  98. return false;
  99. }
  100. return true;
  101. }
  102. protected function validateNavigationSort()
  103. {
  104. $validate = new Validation();
  105. $vResult = $validate->navigationSort($this->params);
  106. if(is_array($vResult))
  107. {
  108. $message = reset($vResult);
  109. $this->errors = ['errors' => $vResult];
  110. if(isset($message[0])){ $this->errors['errors']['message'] = $message[0]; }
  111. return false;
  112. }
  113. return true;
  114. }
  115. protected function validateNaviItem()
  116. {
  117. $validate = new Validation();
  118. $vResult = $validate->navigationItem($this->params);
  119. if(is_array($vResult))
  120. {
  121. $message = reset($vResult);
  122. $this->errors = ['errors' => $vResult];
  123. if(isset($message[0])){ $this->errors['errors']['message'] = $message[0]; }
  124. return false;
  125. }
  126. return true;
  127. }
  128. protected function validateBaseNaviItem()
  129. {
  130. $validate = new Validation();
  131. $vResult = $validate->navigationBaseItem($this->params);
  132. if(is_array($vResult))
  133. {
  134. $message = reset($vResult);
  135. $this->errors = ['errors' => $vResult];
  136. if(isset($message[0])){ $this->errors['errors']['message'] = $message[0]; }
  137. return false;
  138. }
  139. return true;
  140. }
  141. protected function setStructure($draft = false, $cache = true)
  142. {
  143. # set initial structure to false
  144. $structure = false;
  145. # name of structure-file for draft or live
  146. $filename = $draft ? $this->structureDraftName : $this->structureLiveName;
  147. # set variables and objects
  148. $this->write = new writeCache();
  149. # check, if cached structure is still valid
  150. if($cache && $this->write->validate('cache', 'lastCache.txt', 600))
  151. {
  152. # get the cached structure
  153. $structure = $this->write->getCache('cache', $filename);
  154. }
  155. # if no structure was found or cache is deactivated
  156. if(!$structure)
  157. {
  158. # scan the content of the folder
  159. $structure = Folder::scanFolder($this->settings['rootPath'] . $this->settings['contentFolder'], $draft);
  160. # if there is content, then get the content details
  161. if(count($structure) > 0)
  162. {
  163. # get the extended structure files with changes like navigation title or hidden pages
  164. $yaml = new writeYaml();
  165. $extended = $yaml->getYaml('cache', 'structure-extended.yaml');
  166. # create an array of object with the whole content of the folder and changes from extended file
  167. $structure = Folder::getFolderContentDetails($structure, $extended, $this->uri->getBaseUrl(), $this->uri->getBasePath());
  168. }
  169. # cache navigation
  170. $this->write->updateCache('cache', $filename, 'lastCache.txt', $structure);
  171. }
  172. $this->structure = $structure;
  173. return true;
  174. }
  175. protected function renameExtended($item, $newFolder)
  176. {
  177. # get the extended structure files with changes like navigation title or hidden pages
  178. $yaml = new writeYaml();
  179. $extended = $yaml->getYaml('cache', 'structure-extended.yaml');
  180. if(isset($extended[$item->urlRelWoF]))
  181. {
  182. $newUrl = $newFolder->urlRelWoF . '/' . $item->slug;
  183. $entry = $extended[$item->urlRelWoF];
  184. unset($extended[$item->urlRelWoF]);
  185. $extended[$newUrl] = $entry;
  186. $yaml->updateYaml('cache', 'structure-extended.yaml', $extended);
  187. }
  188. return true;
  189. }
  190. protected function deleteFromExtended()
  191. {
  192. # get the extended structure files with changes like navigation title or hidden pages
  193. $yaml = new writeYaml();
  194. $extended = $yaml->getYaml('cache', 'structure-extended.yaml');
  195. if($this->item->elementType == "file" && isset($extended[$this->item->urlRelWoF]))
  196. {
  197. unset($extended[$this->item->urlRelWoF]);
  198. $yaml->updateYaml('cache', 'structure-extended.yaml', $extended);
  199. }
  200. if($this->item->elementType == "folder")
  201. {
  202. $changed = false;
  203. # delete all entries with that folder url
  204. foreach($extended as $url => $entries)
  205. {
  206. if( strpos($url, $this->item->urlRelWoF) !== false )
  207. {
  208. $changed = true;
  209. unset($extended[$url]);
  210. }
  211. }
  212. if($changed)
  213. {
  214. $yaml->updateYaml('cache', 'structure-extended.yaml', $extended);
  215. }
  216. }
  217. }
  218. protected function setHomepage()
  219. {
  220. $contentFolder = Folder::scanFolderFlat($this->settings['rootPath'] . $this->settings['contentFolder']);
  221. if(in_array('index.md', $contentFolder))
  222. {
  223. $md = true;
  224. $status = 'published';
  225. }
  226. if(in_array('index.txt', $contentFolder))
  227. {
  228. $txt = true;
  229. $status = 'unpublished';
  230. }
  231. if(isset($txt) && isset($md))
  232. {
  233. $status = 'modified';
  234. }
  235. $active = false;
  236. if($this->params['url'] == '/' || $this->params['url'] == $this->uri->getBasePath() )
  237. {
  238. $active = 'active';
  239. }
  240. $this->homepage = ['status' => $status, 'active' => $active];
  241. }
  242. protected function setItem()
  243. {
  244. # search for the url in the structure
  245. $item = Folder::getItemForUrl($this->structure, $this->params['url'], $this->uri->getBasePath());
  246. if($item)
  247. {
  248. $this->item = $item;
  249. return true;
  250. }
  251. $this->errors = ['errors' => ['message' => 'requested page-url not found']];
  252. return false;
  253. }
  254. # determine if you want to write to published file (md) or to draft (txt)
  255. protected function setItemPath($fileType)
  256. {
  257. $this->path = $this->item->pathWithoutType . '.' . $fileType;
  258. }
  259. protected function setPublishStatus()
  260. {
  261. $this->item->published = false;
  262. $this->item->drafted = false;
  263. if(file_exists($this->settings['rootPath'] . $this->settings['contentFolder'] . $this->item->pathWithoutType . '.md'))
  264. {
  265. $this->item->published = true;
  266. # add file-type in case it is a folder
  267. $this->item->fileType = "md";
  268. }
  269. if(file_exists($this->settings['rootPath'] . $this->settings['contentFolder'] . $this->item->pathWithoutType . '.txt'))
  270. {
  271. $this->item->drafted = true;
  272. # add file-type in case it is a folder
  273. $this->item->fileType = "txt";
  274. }
  275. if(!$this->item->drafted && !$this->item->published && $this->item->elementType == "folder")
  276. {
  277. # set txt as default for a folder, so that we can create an index.txt for a folder.
  278. $this->item->fileType = "txt";
  279. }
  280. }
  281. protected function deleteContentFiles($fileTypes, $folder = false)
  282. {
  283. $basePath = $this->settings['rootPath'] . $this->settings['contentFolder'];
  284. foreach($fileTypes as $fileType)
  285. {
  286. if(file_exists($basePath . $this->item->pathWithoutType . '.' . $fileType) && !unlink($basePath . $this->item->pathWithoutType . '.' . $fileType) )
  287. {
  288. $this->errors = ['message' => 'We could not delete the file, please check, if the file is writable.'];
  289. }
  290. }
  291. if($this->errors)
  292. {
  293. return false;
  294. }
  295. return true;
  296. }
  297. protected function deleteContentFolder()
  298. {
  299. $basePath = $this->settings['rootPath'] . $this->settings['contentFolder'];
  300. $path = $basePath . $this->item->path;
  301. if(file_exists($path))
  302. {
  303. $files = array_diff(scandir($path), array('.', '..'));
  304. # check if there are published pages or folders inside, then stop the operation
  305. foreach ($files as $file)
  306. {
  307. if(is_dir(realpath($path) . DIRECTORY_SEPARATOR . $file))
  308. {
  309. $this->errors = ['message' => 'Please delete the sub-folder first.'];
  310. }
  311. if(substr($file, -3) == '.md' )
  312. {
  313. $this->errors = ['message' => 'Please unpublish all pages in the folder first.'];
  314. }
  315. }
  316. if(!$this->errors)
  317. {
  318. foreach ($files as $file)
  319. {
  320. unlink(realpath($path) . DIRECTORY_SEPARATOR . $file);
  321. }
  322. return rmdir($path);
  323. }
  324. # delete all files from the extended file
  325. $this->deleteFromExtended();
  326. }
  327. return false;
  328. }
  329. protected function setContent()
  330. {
  331. # if the file exists
  332. if($this->item->published OR $this->item->drafted)
  333. {
  334. $content = $this->write->getFile($this->settings['contentFolder'], $this->path);
  335. if($this->item->fileType == 'txt')
  336. {
  337. # decode the json-draft to an array
  338. $content = json_decode($content);
  339. }
  340. }
  341. elseif($this->item->elementType == "folder")
  342. {
  343. $content = '';
  344. }
  345. else
  346. {
  347. $this->errors = ['errors' => ['message' => 'requested file not found']];
  348. return false;
  349. }
  350. $this->content = $content;
  351. return true;
  352. }
  353. }