blogController.php 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459
  1. <?php
  2. namespace nabu\controllers\blogController;
  3. defined('NABU') || exit;
  4. require_once 'libs/csrf.php';
  5. require_once 'libs/validations.php';
  6. require_once 'models/blogModel.php';
  7. require_once 'libs/utils.php';
  8. require_once 'libs/parsedown-1.7.4/Parsedown.php';
  9. use nabu\libs\csrf\csrf,
  10. nabu\libs\validations\validations,
  11. nabu\models\blogModel\blogModel,
  12. nabu\libs\utils\utils,
  13. Parsedown\Parsedown;
  14. // Administra el flujo de datos de los artículos.
  15. class blogController {
  16. private const num_articles = 9;
  17. private static function default_route() {
  18. $GLOBALS['messages'] -> redirect(NABU_ROUTES['home']);
  19. }
  20. // Renderiza la página web de errores.
  21. public static function errors() {
  22. if (empty($_SESSION['errors']) || !is_array($_SESSION['errors']))
  23. self::default_route();
  24. if (empty($_SESSION['errors']['message']) || empty($_SESSION['errors']['code']))
  25. $GLOBALS['messages'] -> errors('Set the error message and code', 400);
  26. http_response_code($_SESSION['errors']['code']);
  27. $head_title = 'Error';
  28. $token = csrf::generate_token();
  29. $error = $_SESSION['errors']['message'];
  30. unset($_SESSION['errors'], $_SESSION['messages']);
  31. require_once 'views/layouts/head.php';
  32. require_once 'views/layouts/navbar.php';
  33. require_once 'views/pages/errors.php';
  34. require_once 'views/layouts/footer.php';
  35. }
  36. // Renderiza la página web principal.
  37. public static function home() {
  38. global $messages;
  39. if (empty($_POST['search_submit'])) {
  40. $token = csrf::generate_token();
  41. $messages = $messages -> messages();
  42. $blogModel = new blogModel();
  43. // Obtiene los artículos más valorados y recientes.
  44. $articles = $blogModel -> popular_articles(5);
  45. $recent_articles = $blogModel -> recent_articles(self::num_articles);
  46. unset($blogModel);
  47. // Formatea los datos de los artículos durante la marcha.
  48. self::escape_articles($articles);
  49. self::escape_articles($recent_articles);
  50. require_once 'views/layouts/head.php';
  51. require_once 'views/layouts/navbar.php';
  52. require_once 'views/pages/home.php';
  53. require_once 'views/layouts/footer.php';
  54. }
  55. else {
  56. csrf::validate_token($_POST['csrf']);
  57. $query = self::validate_search($_POST);
  58. // Solicita una búsqueda de artículos.
  59. $messages -> redirect(NABU_ROUTES['search'] . '&q=' . urlencode($query));
  60. }
  61. }
  62. // Renderiza la página web para publicar artículos.
  63. public static function post() {
  64. if (!utils::session_exists())
  65. self::default_route();
  66. if (empty($_POST['post_submit'])) {
  67. $head_title = 'Publicar artículo';
  68. $token = csrf::generate_token();
  69. $messages = $GLOBALS['messages'] -> messages();
  70. require_once 'views/layouts/head.php';
  71. require_once 'views/layouts/navbar.php';
  72. require_once 'views/pages/post.php';
  73. require_once 'views/layouts/footer.php';
  74. }
  75. else {
  76. csrf::validate_token($_POST['csrf']);
  77. self::post_article($_POST);
  78. }
  79. }
  80. // Formatea los datos de los artículos.
  81. private static function escape_articles(&$articles) {
  82. foreach ($articles as &$article) {
  83. $article['title'] = utils::str_escape($article['title']);
  84. $article['synopsis'] = utils::str_escape($article['synopsis']);
  85. $article['author'] = utils::str_escape($article['author']);
  86. // Defina la ruta completa de la portada del artículo.
  87. if (empty($article['cover']))
  88. $article['cover'] = NABU_DEFAULT['cover'];
  89. else {
  90. // Valida si la imagen está disponible.
  91. if (file_exists(NABU_DIRECTORY['storage_covers'] . '/' . $article['cover']))
  92. $article['cover'] = NABU_DIRECTORY['covers'] . '/' . $article['cover'];
  93. else
  94. $article['cover'] = NABU_DEFAULT['cover'];
  95. }
  96. }
  97. unset($article);
  98. }
  99. // Renderiza la página web de todos los artículos publicados.
  100. public static function all_articles() {
  101. $head_title = 'Todos lo artículos';
  102. $token = csrf::generate_token();
  103. $blogModel = new blogModel();
  104. // Obtiene el número total de artículos autorizados.
  105. $total = $blogModel -> count_articles();
  106. if ($total === false)
  107. self::default_route();
  108. // Página de resultado inicial.
  109. $page = 1;
  110. // Calcula el número total de páginas de resultados.
  111. $total_results = ceil($total/self::num_articles);
  112. if (isset($_GET['page']) && is_numeric($_GET['page']))
  113. $page = $_GET['page'];
  114. global $messages;
  115. $view = NABU_ROUTES['all_articles'];
  116. // Administra la paginación.
  117. if ($page < 1)
  118. $messages -> redirect($view);
  119. // Número total de resultados si no hay artículos publicados.
  120. if ($total_results == 0)
  121. $total_results = 1;
  122. if ($page > $total_results)
  123. $messages -> redirect($view . '&page=' . $total_results);
  124. // Calcula el número de artículos acumulados por paginación de resultados.
  125. $accumulation = ($page - 1) * self::num_articles;
  126. // Lista todos los artículos autorizados.
  127. $articles = $blogModel -> all_articles(self::num_articles, $accumulation);
  128. unset($blogModel, $total, $total_results, $accumulation);
  129. // Formatea los datos de los artículos durante la marcha.
  130. self::escape_articles($articles);
  131. require_once 'views/layouts/head.php';
  132. require_once 'views/layouts/navbar.php';
  133. require_once 'views/pages/all_articles.php';
  134. require_once 'views/layouts/footer.php';
  135. }
  136. // Renderiza la página web de artículos en espera de aprobación de un usuario.
  137. public static function sent() {
  138. if (!utils::session_exists())
  139. self::default_route();
  140. $head_title = 'Artículos enviados';
  141. $token = csrf::generate_token();
  142. $messages = $GLOBALS['messages'] -> messages();
  143. $blogModel = new blogModel();
  144. // Lista todos los artículos enviados.
  145. $articles = $blogModel -> sent_articles($_SESSION['user']['id']);
  146. // Formatea los datos de los artículos durante la marcha.
  147. foreach($articles as &$article) {
  148. $article['title'] = utils::str_escape($article['title']);
  149. $article['synopsis'] = utils::str_escape($article['synopsis']);
  150. }
  151. unset($blogModel, $article);
  152. require_once 'views/layouts/head.php';
  153. require_once 'views/layouts/navbar.php';
  154. require_once 'views/pages/sent.php';
  155. require_once 'views/layouts/footer.php';
  156. }
  157. // Renderiza la página web de resultados de búsqueda de artículos.
  158. public static function search() {
  159. if (empty($_GET['q']))
  160. self::default_route();
  161. $head_title = 'Búsqueda';
  162. $token = csrf::generate_token();
  163. $query = self::validate_search($_GET);
  164. $blogModel = new blogModel();
  165. // Obtiene el número total de artículos buscados.
  166. $total = $blogModel -> count_search($query);
  167. if ($total === false)
  168. self::default_route();
  169. // Página de resultado inicial.
  170. $page = 1;
  171. // Calcula el número total de páginas de resultados.
  172. $total_results = ceil($total/self::num_articles);
  173. if (isset($_GET['page']) && is_numeric($_GET['page']))
  174. $page = $_GET['page'];
  175. global $messages;
  176. $view = NABU_ROUTES['search'] . '&q=' . urlencode($query);
  177. // Administra la paginación.
  178. if ($page < 1)
  179. $messages -> redirect($view);
  180. // Número total de resultados si no existen coincidencias de búsqueda.
  181. if ($total_results == 0)
  182. $total_results = 1;
  183. if ($page > $total_results)
  184. $messages -> redirect($view . '&page=' . $total_results);
  185. // Calcula el número de artículos acumulados por paginación de resultados.
  186. $accumulation = ($page - 1) * self::num_articles;
  187. // Lista de artículos con coincidencia de patrón de búsqueda.
  188. $articles = $blogModel -> search_articles($query, self::num_articles, $accumulation);
  189. unset($blogModel, $total, $total_results, $accumulation);
  190. $messages = $GLOBALS['messages'] -> messages();
  191. // Escapa todos los caracteres de una búsqueda.
  192. $query = utils::str_escape($query);
  193. // Formatea los datos de los artículos durante la marcha.
  194. self::escape_articles($articles);
  195. require_once 'views/layouts/head.php';
  196. require_once 'views/layouts/navbar.php';
  197. require_once 'views/pages/search.php';
  198. require_once 'views/layouts/footer.php';
  199. }
  200. // Renderiza la página web de un artículo.
  201. public static function article() {
  202. if (empty($_GET['slug']))
  203. self::default_route();
  204. $blogModel = new blogModel();
  205. $article = $blogModel -> get_article($_GET['slug']);
  206. if ($article == false)
  207. self::default_route();
  208. // Formatea los datos del artículo.
  209. $article['title'] = utils::str_escape($article['title']);
  210. $article['author'] = utils::str_escape($article['author']);
  211. $author_profile = NABU_ROUTES['profile'] . '&user=' . urlencode($article['author_username']);
  212. $article['author_username'] = utils::str_escape($article['author_username']);
  213. // Define una descripción por defecto del autor.
  214. if (empty($article['author_description']))
  215. $article['author_description'] = 'Compartiendo conocimiento...';
  216. else
  217. $article['author_description'] = utils::str_escape($article['author_description']);
  218. $date_article = date_parse($article['date']);
  219. switch ($date_article['month']) {
  220. case 1:
  221. $month = 'Enero';
  222. break;
  223. case 2:
  224. $month = 'Febrero';
  225. break;
  226. case 3:
  227. $month = 'Marzo';
  228. break;
  229. case 4:
  230. $month = 'Abril';
  231. break;
  232. case 5:
  233. $month = 'Mayo';
  234. break;
  235. case 6:
  236. $month = 'Junio';
  237. break;
  238. case 7:
  239. $month = 'Julio';
  240. break;
  241. case 8:
  242. $month = 'Agosto';
  243. break;
  244. case 9:
  245. $month = 'Septiembre';
  246. break;
  247. case 10:
  248. $month = 'Octubre';
  249. break;
  250. case 11:
  251. $month = 'Noviembre';
  252. break;
  253. case 12:
  254. $month = 'Diciembre';
  255. break;
  256. }
  257. $article['date'] = $date_article['day'] . ' de ' . $month . ' del ' . $date_article['year'];
  258. // Defina la ruta completa de la portada del artículo.
  259. if (empty($article['cover']))
  260. $article['cover'] = NABU_DEFAULT['cover'];
  261. else {
  262. // Valida si la imagen está disponible.
  263. if (file_exists(NABU_DIRECTORY['storage_covers'] . '/' . $article['cover']))
  264. $article['cover'] = NABU_DIRECTORY['covers'] . '/' . $article['cover'];
  265. else
  266. $article['cover'] = NABU_DEFAULT['cover'];
  267. }
  268. // Defina la ruta completa del avatar del autor.
  269. if (empty($article['author_avatar']))
  270. $article['author_avatar'] = NABU_DEFAULT['avatars'];
  271. else {
  272. // Valida si la imagen está disponible.
  273. if (file_exists(NABU_DIRECTORY['storage_avatars'] . '/' . $article['author_avatar']))
  274. $article['author_avatar'] = NABU_DIRECTORY['avatars'] . '/' . $article['author_avatar'];
  275. else
  276. $article['author_avatar'] = NABU_DEFAULT['avatar'];
  277. }
  278. $Parsedown = new Parsedown();
  279. $Parsedown -> setSafeMode(true);
  280. // Convierte el artículo en Markdown a HTML.
  281. $article['content'] = $Parsedown -> text($article['content']);
  282. unset($blogModel, $Parsedown, $date_article, $month);
  283. $head_title = 'Artículo';
  284. $token = csrf::generate_token();
  285. require_once 'views/layouts/head.php';
  286. require_once 'views/layouts/navbar.php';
  287. require_once 'views/pages/article.php';
  288. require_once 'views/layouts/comments.php';
  289. require_once 'views/layouts/footer.php';
  290. }
  291. // Renderiza la página web de artículos por categoría.
  292. public static function category() {
  293. $head_title = 'Categoría';
  294. $token = csrf::generate_token();
  295. $articles = array();
  296. require_once 'views/layouts/head.php';
  297. require_once 'views/layouts/navbar.php';
  298. require_once 'views/pages/category.php';
  299. require_once 'views/layouts/footer.php';
  300. }
  301. // Renderiza la página web de artículos favoritos.
  302. public static function favorites() {
  303. if (!utils::session_exists())
  304. self::default_route();
  305. $head_title = 'Favoritos';
  306. $token = csrf::generate_token();
  307. $articles = array();
  308. require_once 'views/layouts/head.php';
  309. require_once 'views/layouts/navbar.php';
  310. require_once 'views/pages/favorites.php';
  311. require_once 'views/layouts/footer.php';
  312. }
  313. // Valida los campos del formulario de búsqueda.
  314. private static function validate_search($form) {
  315. $validations = new validations(NABU_ROUTES['home']);
  316. $data = $validations -> validate_form($form, array(
  317. // 20210717-TÍTULO = 255 caracteres.
  318. array('q', 'exist' => true, 'max' => 246, 'trim_all' => true)
  319. ));
  320. return $data['q'];
  321. }
  322. // Valida los campos de publicación de un artículo y envía un artículo para su aprobación.
  323. private static function post_article($form) {
  324. $validations = new validations(NABU_ROUTES['post']);
  325. $data = $validations -> validate_form($form, array(
  326. array('title', 'exist' => true, 'max' => 246, 'trim_all' => true),
  327. array('synopsis', 'exist' => true, 'trim_all' => true),
  328. array('content', 'exist' => true, 'max' => NABU_DEFAULT['article_size'], 'trim' => true)
  329. ));
  330. $data['slug'] = utils::url_slug($data['title']);
  331. // Valida la longitud de la URL.
  332. $validations -> validate_form($data, array(
  333. array('slug', 'exist' => true)
  334. ));
  335. $blogModel = new blogModel();
  336. // Obtiene el id de un artículo dado su URL.
  337. $article = $blogModel -> article_id($data['slug']);
  338. global $messages;
  339. // Valida si el título del artículo es único en el día.
  340. if ($article) {
  341. $messages -> add_message('Por favor define un título diferente o espera máximo un día para enviar tu artículo');
  342. $messages -> redirect(NABU_ROUTES['post']);
  343. }
  344. $data['user_id'] = $_SESSION['user']['id'];
  345. // Registra el artículo en la base de datos..
  346. $blogModel -> save_article($data);
  347. $messages -> add_message('Tu artículo se ha enviado correctamente, en breve autorizaremos tu publicación');
  348. $messages -> redirect(NABU_ROUTES['sent']);
  349. }
  350. }