Merge with dev

This commit is contained in:
francisco-solis99 2021-09-29 18:30:07 -05:00
commit a35a3b685a
45 changed files with 608 additions and 75 deletions

2
NOTICE
View file

@ -1,4 +1,4 @@
Nabu - A content management system (CMS) for Markdown articles, written in HTML, CSS, Javascript and PHP.
Nabu - A content management system (CMS) for Markdown articles, written in HTML, CSS, JavaScript and PHP.
Copyright (C) 2021 Ricardo García Jiménez <ricardogj08@riseup.net>
Juan José Ramírez López <juan.ramirez.j99@gmail.com>

View file

@ -0,0 +1,44 @@
# Nabu
Un sistema gestor de contenido (CMS) para artículos en `Markdown`, escrito en `HTML`, `CSS`, `JavaScript` y `PHP`.
## Archivo de configuración de la base de datos
Por defecto, `Nabu` escanea el archivo `database-config.json` dentro de la carpeta raíz del proyecto.
Estructura del archivo de configuración:
```json
{
"dbms": "mysql",
"host": "localhost",
"database": "nabu",
"user": "root",
"password": "root",
"charset": "utf8mb4"
}
```
## Licencia
```text
Nabu - Un sistema gestor de contenido (CMS) para artículos en Markdown, escrito en HTML, CSS, JavaScript y PHP.
Copyright (C) 2021 Ricardo García Jiménez <ricardogj08@riseup.net>,
Juan José Ramírez López <juan.ramirez.j99@gmail.com>,
Francisco Solís Martínez <franciscosolism08@gmail.com>,
Fernando Andrés Chávez Gavaldón <fernandochg26@gmail.com>
Este programa es software libre: puedes redistribuirlo y/o modificarlo
bajo los términos de la Licencia Pública General de GNU Affero publicada por
la Free Software Foundation, ya sea la versión 3 de la Licencia, o
(a su elección) cualquier versión posterior.
Este programa se distribuye con la esperanza de que sea de utilidad,
pero SIN NINGUNA GARANTÍA; incluso sin la garantía implícita de
COMERCIABILIDAD o APTITUD PARA UN PROPÓSITO PARTICULAR. Consulte la
Licencia Pública General de GNU Affero para obtener más detalles.
Debería haber recibido una copia de la Licencia Pública General de GNU Affero
junto con este programa. De lo contrario, consulte <https://www.gnu.org/licenses/>.
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

BIN
assets/images/hero.png Executable file → Normal file

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 11 KiB

BIN
assets/images/hero1_5x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

BIN
assets/images/hero2x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

View file

@ -23,6 +23,9 @@ class adminController {
case 'published':
self::published_articles();
break;
case 'users':
self::users();
break;
default:
utils::redirect(NABU_ROUTES['admin']);
}
@ -48,4 +51,8 @@ class adminController {
static private function published_articles() {
require_once 'views/admin/published-articles.php';
}
static private function users() {
require_once 'views/admin/users.php';
}
}

View file

@ -5,6 +5,9 @@ defined('NABU') || exit;
require_once 'models/usersModel.php';
class usersController {
private const cost = array('cost' => 12);
private const hash = 'sha256';
static public function login() {
require_once 'views/pages/login.php';
}
@ -14,23 +17,57 @@ class usersController {
}
// Renderiza la página de registro de usuarios
// y registra un usuario por el método POST.
// y registra un usuario con el método POST.
static public function signup() {
if (empty($_POST['signup-submit'])) {
$token = csrf::generate();
$messages = messages::get();
require_once 'views/pages/signup.php';
}
else {
csrf::validate($_POST['csrf']);
$validations = new validations(NABU_ROUTES['signup']);
$user = $validations -> validate_form($_POST, array(
$data = $validations -> validate_form($_POST, array(
array('name', 'exists' => true, 'trim_all' => true, 'min_lenght' => 5, 'max_lenght' => 255),
array('username', 'exists' => true, 'trim' => true, 'min_lenght' => 1, 'max_lenght' => 255, 'not_spaces' => true),
array('email', 'exists' => true, 'is_email' => true, 'trim' => true, 'min_lenght' => 5, 'max_lenght' => 255, 'not_spaces' => true),
array('password', 'exists' => true, 'min_lenght' => 6, 'max_lenght' => 255, 'not_spaces' => true, 'equal' => $_POST['confirm-password'])
));
$user['email'] = strtolower($user['email']);
$usersModel = new usersModel();
$users = $usersModel -> find($data['username'], $data['email']);
// Genera una llave aleatoria de verificación de dirección de e-mail.
$key = bin2hex(random_bytes(32));
// Formatea en minúsculas la dirección de e-mail.
$data['email'] = strtolower($data['email']);
// Hash de verificación de e-mail.
$hash = hash_hmac(self::hash, $data['email'], $key);
// Cifra la contraseña.
$data['password'] = password_hash($data['password'], PASSWORD_DEFAULT, self::cost);
// Define la fecha de registro.
$data['creation_date'] = utils::current_date();
// Registra el nuevo usuario.
$usersModel -> save($data);
$user = $usersModel -> get('username', $data['username']);
$verification = array(
'id' => $user['id'],
'hash' => $hash,
'expiration' => time() + 60 * 60
);
// Registra el hash de verificación de dirección de e-mail.
$usersModel -> verification($verification);
}
}
}

View file

@ -7,7 +7,9 @@ session_start();
require_once 'core/config.php';
require_once 'libs/utils.php';
require_once 'libs/messages.php';
require_once 'libs/csrf.php';
require_once 'libs/validations.php';
require_once 'database/connection.php';
$components = require 'core/routes.php';

View file

@ -17,13 +17,14 @@ return array(
'home' => array('route' => 'home', 'controller' => 'blogController', 'view' => 'home'),
'favorites' => array('route' => 'favorites', 'controller' => 'communityController', 'view' => 'favorites'),
'like' => array('route' => 'like', 'controller' => 'communityController', 'view' => 'like'),
'published-articles' => array('route' => 'admin&section=published', 'controller' => 'adminController', 'view' => 'admin'),
'login' => array('route' => 'login', 'controller' => 'usersController', 'view' => 'login'),
'logout' => array('route' => 'logout', 'controller' => 'usersController', 'view' => 'logout'),
'post-article' => array('route' => 'post-article', 'controller' => 'articlesController', 'view' => 'post_article'),
'profile' => array('route' => 'profile', 'controller' => 'profilesController', 'view' => 'profile'),
'published-articles' => array('route' => 'admin&section=published', 'controller' => 'adminController', 'view' => 'admin'),
'search' => array('route' => 'search', 'controller' => 'searchController', 'view' => 'search'),
'sent-articles' => array('route' => 'sent-articles', 'controller' => 'articlesController', 'view' => 'sent_articles'),
'signup' => array('route' => 'signup', 'controller' => 'usersController', 'view' => 'signup'),
'users' => array('route' => 'admin&section=users', 'controller' => 'adminController', 'view' => 'admin'),
'verifications' => array('route' => 'verifications', 'controller' => 'verificationsController', 'view' => 'verifications'),
);

View file

@ -0,0 +1,83 @@
<?php
defined('NABU') || exit;
// Realiza la conexión con la base de datos.
class connection {
protected $pdo;
public function __construct() {
if (!file_exists(NABU_DIRECTORY['database'])) {
exit('Create a database config file.');
}
// Carga el archivo de configuración de la base de datos.
$config = file_get_contents(NABU_DIRECTORY['database']);
if ($config === false) {
exit('The database config file is invalid.');
}
$config = json_decode($config, true);
$keys = array('dbms', 'host', 'database', 'user', 'password', 'charset');
foreach ($keys as $key) {
if (empty($config[$key])) {
exit('Set "' . $key . '" in the database config file.');
}
}
// Parámetros de configuración de la conexión de la base de datos.
$options = array(
// Mantiene el nombre de las columnas como en la base de datos.
PDO::ATTR_CASE => PDO::CASE_NATURAL,
// Define el manejador de errores de 'PDO' por excepciones, utiliza el objeto 'PDOException'.
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
// Deshabilita las consultas preparadas de 'PDO' y utiliza el sistema nativo del SGBD.
PDO::ATTR_EMULATE_PREPARES => false,
// Define por defecto los resultados de las consultas como arrays asociativos.
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
);
// Origen de la base de datos.
$dsn = $config['dbms'] . ':host=' . $config['host'] . ';dbname=' . $config['database'];
try {
// Configuración y conexión de la base de datos.
$this -> pdo = new PDO($dsn, $config['user'], $config['password'], $options);
// Define la codificación de caracteres para el cliente del SGBD y los resultados de las consultas.
$this -> pdo -> exec('SET CHARSET ' . $config['charset']);
}
catch(PDOException $e) {
exit($e -> getMessage());
}
}
protected function errors(string $exception, string $error) {
// error_log($exception);
messages::errors('¡Lo sentimos mucho! &#x1F61E;, ' . $error . ', por favor inténtelo más tarde', 500);
}
// @return el alias de un 'id de role'.
protected function role_format($id) {
$role = 'user';
if ($id == 1) {
$role = 'admin';
}
if ($id == 2) {
$role = 'moderator';
}
return $role;
}
// Finaliza la conexión con la base de datos.
public function __destruct() {
$this -> pdo = null;
}
}

View file

@ -0,0 +1,111 @@
/*
DROP DATABASE IF EXISTS nabu;
*/
CREATE DATABASE IF NOT EXISTS nabu
CHARACTER SET = 'utf8mb4'
COLLATE = 'utf8mb4_general_ci';
USE nabu;
CREATE TABLE IF NOT EXISTS `roles` (
`id` TINYINT UNSIGNED NOT NULL,
`name` VARCHAR(20) NOT NULL,
CONSTRAINT roles_id_pk PRIMARY KEY(id),
CONSTRAINT roles_name_uk UNIQUE(name)
);
CREATE TABLE IF NOT EXISTS `users` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`role_id` TINYINT UNSIGNED NOT NULL DEFAULT 3,
`name` VARCHAR(255) NOT NULL,
`username` VARCHAR(255) NOT NULL,
`email` VARCHAR(255),
`password` VARCHAR(255) NOT NULL,
`activated` TINYINT(1) NOT NULL DEFAULT FALSE,
`creation_date` DATETIME(0) NOT NULL,
CONSTRAINT users_id_pk PRIMARY KEY(id),
CONSTRAINT users_username_uk UNIQUE(username),
CONSTRAINT users_email_uk UNIQUE(email),
CONSTRAINT users_role_id_fk FOREIGN KEY(role_id) REFERENCES roles(id) ON UPDATE RESTRICT ON DELETE RESTRICT
);
CREATE TABLE IF NOT EXISTS `verifications` (
`id` INT UNSIGNED NOT NULL,
`hash` VARCHAR(255) NOT NULL,
`expiration` INT UNSIGNED NOT NULL,
CONSTRAINT verifications_id_pk PRIMARY KEY(id),
CONSTRAINT verifications_id_fk FOREIGN KEY(id) REFERENCES users(id) ON UPDATE CASCADE ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS `profiles` (
`id` INT UNSIGNED NOT NULL,
`avatar` VARCHAR(255),
`background` VARCHAR(255),
`description` VARCHAR(255),
CONSTRAINT profiles_id_pk PRIMARY KEY(id),
CONSTRAINT profiles_id_fk FOREIGN KEY(id) REFERENCES users(id) ON UPDATE RESTRICT ON DELETE RESTRICT,
CONSTRAINT profiles_avatar_uk UNIQUE(avatar),
CONSTRAINT profiles_background_uk UNIQUE(background)
);
CREATE TABLE IF NOT EXISTS `articles` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`user_id` INT UNSIGNED NOT NULL,
`title` VARCHAR(246) NOT NULL,
`synopsis` VARCHAR(255) NOT NULL,
`content` MEDIUMTEXT NOT NULL,
`slug` VARCHAR(255) NOT NULL,
`cover` VARCHAR(255),
`authorized` TINYINT(1) NOT NULL DEFAULT FALSE,
`creation_date` DATETIME(0) NOT NULL,
`modification_date` DATETIME(0),
CONSTRAINT articles_id_pk PRIMARY KEY(id),
CONSTRAINT articles_user_id_fk FOREIGN KEY(user_id) REFERENCES users(id) ON UPDATE RESTRICT ON DELETE RESTRICT,
CONSTRAINT articles_slug_uk UNIQUE(slug),
CONSTRAINT articles_cover_uk UNIQUE(cover)
);
CREATE TABLE IF NOT EXISTS `categories` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`name` VARCHAR(50) NOT NULL,
CONSTRAINT categories_id_pk PRIMARY KEY(id),
CONSTRAINT categories_name_uk UNIQUE(name)
);
CREATE TABLE IF NOT EXISTS `categories_article` (
`article_id` INT UNSIGNED NOT NULL,
`categorie_id` INT UNSIGNED NOT NULL,
CONSTRAINT categories_article_pk PRIMARY KEY(article_id, categorie_id),
CONSTRAINT categories_article_article_id_fk FOREIGN KEY(article_id) REFERENCES articles(id) ON UPDATE RESTRICT ON DELETE RESTRICT,
CONSTRAINT categories_categorie_id_fk FOREIGN KEY(categorie_id) REFERENCES categories(id) ON UPDATE RESTRICT ON DELETE RESTRICT
);
CREATE TABLE IF NOT EXISTS `authorizations` (
`article_id` INT UNSIGNED NOT NULL,
`user_id` INT UNSIGNED NOT NULL,
`authorization_date` DATETIME(0) NOT NULL,
CONSTRAINT authorizations_pk PRIMARY KEY(article_id, authorization_date),
CONSTRAINT authorizations_article_id_fk FOREIGN KEY(article_id) REFERENCES articles(id) ON UPDATE RESTRICT ON DELETE RESTRICT,
CONSTRAINT authorizations_user_id_fk FOREIGN KEY(user_id) REFERENCES users(id) ON UPDATE RESTRICT ON DELETE RESTRICT
);
CREATE TABLE IF NOT EXISTS `comments` (
`user_id` INT UNSIGNED NOT NULL,
`article_id` INT UNSIGNED NOT NULL,
`body` VARCHAR(255) NOT NULL,
`comment_date` DATETIME(0) NOT NULL,
CONSTRAINT comments_pk PRIMARY KEY(user_id, comment_date),
CONSTRAINT comments_user_id_fk FOREIGN KEY(user_id) REFERENCES users(id) ON UPDATE RESTRICT ON DELETE RESTRICT,
CONSTRAINT comments_article_id_fk FOREIGN KEY(article_id) REFERENCES articles(id) ON UPDATE RESTRICT ON DELETE RESTRICT
);
CREATE TABLE IF NOT EXISTS `favorites` (
`user_id` INT UNSIGNED NOT NULL,
`article_id` INT UNSIGNED NOT NULL,
CONSTRAINT favorites_pk PRIMARY KEY(user_id, article_id),
CONSTRAINT favorites_user_id_fk FOREIGN KEY(user_id) REFERENCES users(id) ON UPDATE RESTRICT ON DELETE RESTRICT,
CONSTRAINT favorites_article_id_fk FOREIGN KEY(article_id) REFERENCES articles(id) ON UPDATE RESTRICT ON DELETE RESTRICT
);
INSERT INTO roles(id, name) VALUES(1, 'ADMIN'), (2, 'MODERATOR'), (3, 'USER');

View file

@ -0,0 +1,50 @@
<?php
defined('NABU') || exit;
// Valida formularios contra ataques CSRF.
class csrf {
private const size = 32;
private const hash = 'sha256';
private const secret = 'rWO!KJ9&*Wk@';
private const expiration = 4; // Horas.
// Elimina la variable de sesión.
private static function destroy() {
unset($_SESSION['csrf']);
}
private static function errors(string $error) {
self::destroy();
messages::errors($error, 400);
}
// Genera un token en base a bytes aleatorios.
public static function generate() {
$key = bin2hex(random_bytes(self::size));
$_SESSION['csrf'] = array(
'token' => hash_hmac(self::hash, self::secret, $key),
'expiration' => time() + (60 * 60 * self::expiration)
);
return $_SESSION['csrf']['token'];
}
// Valida si un token no está expirado y es igual al generado.
public static function validate($token2) {
if (empty($_SESSION['csrf']) || empty($token2)) {
self::errors('Token inválido');
}
if (time() > $_SESSION['csrf']['expiration']) {
self::errors('El formulario ha expirado, por favor recargue la página web');
}
if (!hash_equals($_SESSION['csrf']['token'], $token2)) {
self::errors('Los tokens no coinciden');
}
self::destroy();
}
}

View file

@ -2,7 +2,7 @@
defined('NABU') || exit;
// Colecciòn de herramientas propias de Nabu.
// Colección de herramientas propias de Nabu.
class utils {
// Redirecciona a una página web y termina la ejecución de todos los scripts de PHP.
static public function redirect(string $route) {
@ -10,7 +10,12 @@ class utils {
exit;
}
static public function escape(string $str) {
// @return la fecha actual.
static public function current_date() {
return date('Y-m-d H:i:s');
}
static public function escape($str) {
//
}
}

View file

@ -96,9 +96,6 @@ class validations {
$data = array();
foreach ($options as $option) {
if (!is_array($option))
$this -> errors('The validation options are not an array');
if (empty($option[0]) || !is_string($option[0]))
$this -> errors('Not found field name');

View file

@ -2,6 +2,13 @@
defined('NABU') || exit;
class adminModel {
class adminModel extends connection {
public function __construct() {
parent::__construct();
}
public function __destruct() {
parent::__destruct();
$this -> pdo = null;
}
}

View file

@ -2,6 +2,13 @@
defined('NABU') || exit;
class articlesModel {
class articlesModel extends connection {
public function __construct() {
parent::__construct();
}
public function __destruct() {
parent::__destruct();
$this -> pdo = null;
}
}

View file

@ -2,6 +2,13 @@
defined('NABU') || exit;
class blogModel {
class blogModel extends connection {
public function __construct() {
parent::__construct();
}
public function __destruct() {
parent::__destruct();
$this -> pdo = null;
}
}

View file

@ -2,6 +2,13 @@
defined('NABU') || exit;
class communityModel {
class communityModel extends connection {
public function __construct() {
parent::__construct();
}
public function __destruct() {
parent::__destruct();
$this -> pdo = null;
}
}

View file

@ -2,6 +2,13 @@
defined('NABU') || exit;
class profilesModel {
class profilesModel extends connection {
public function __construct() {
parent::__construct();
}
public function __destruct() {
parent::__destruct();
$this -> pdo = null;
}
}

View file

@ -2,6 +2,13 @@
defined('NABU') || exit;
class searchModel {
class searchModel extends connection {
public function __construct() {
parent::__construct();
}
public function __destruct() {
parent::__destruct();
$this -> pdo = null;
}
}

View file

@ -2,6 +2,88 @@
defined('NABU') || exit;
class usersModel {
class usersModel extends connection {
public function __construct() {
parent::__construct();
}
// @return un lista de arrays asociativos con los datos de usuarios.
public function find(string $username, string $email) {
$query = 'SELECT u.id, u.role_id AS role, u.username, u.email, u.password, u.activated, u.creation_date,' .
'v.hash, v.expiration AS hash_expiration FROM users AS u ' .
'LEFT JOIN verifications AS v on u.id = v.id ' .
'WHERE u.username = ? OR u.email = ? LIMIT 2';
try {
$prepare = $this -> pdo -> prepare($query);
$prepare -> execute(array($username, $email));
$users = $prepare -> fetchAll();
if (empty($users)) {
return array();
}
return $users;
}
catch(PDOException $e) {
$this -> errors($e -> getMessage(), 'tuvimos un problema para validar si tu apodo y dirección de correo electrónico son únicos');
}
}
// Registra un nuevo usuario.
public function save(array $data) {
$query = 'INSERT INTO users(name, username, email, password, creation_date) ' .
'VALUES(:name, :username, :email, :password, :creation_date)';
try {
$this -> pdo -> prepare($query) -> execute($data);
}
catch(PDOException $e) {
$this -> errors($e -> getMessage(), 'tuvimos un problema para registrar tu cuenta de usuario');
}
}
// @return un array asociativo con los datos de un solo usuario.
public function get(string $column, $pattern) {
$query = 'SELECT u.id, u.role_id AS role, u.username, u.email, u.password, u.activated, u.creation_date,' .
'v.hash, v.expiration AS hash_expiration FROM users AS u ' .
'LEFT JOIN verifications AS v on u.id = v.id ' .
'WHERE u.' . $column . ' = ? LIMIT 1';
try {
$prepare = $this -> pdo -> prepare($query);
$prepare -> execute(array($pattern));
$user = $prepare -> fetch();
if ($user !== false) {
$user['role'] = $this -> role_format($user['role']);
}
return $user;
}
catch(PDOException $e) {
$this -> errors($e -> getMessage(), 'tuvimos un problema para buscar tu cuenta de usuario');
}
}
// Registra el 'hash de verificación de dirección de e-mail' con tiempo de expiración.
public function verification(array $verification) {
$query = 'INSERT INTO verifications(id, hash, expiration) VALUES(:id, :hash, :expiration)';
try {
$this -> pdo -> prepare($query) -> execute($verification);
}
catch(PDOException $e) {
$this -> errors($e -> getMessage(), 'tuvimos un problema para registrar tu clave de verificación de dirección de correo electrónico');
}
}
public function __destruct() {
parent::__destruct();
$this -> pdo = null;
}
}

View file

@ -2,6 +2,13 @@
defined('NABU') || exit;
class verificationsModel {
class verificationsModel extends connection {
public function __construct() {
parent::__construct();
}
public function __destruct() {
parent::__destruct();
$this -> pdo = null;
}
}

View file

@ -1,9 +1,12 @@
<?php defined('NABU') || exit ?>
<?php $head_title = 'Administración' ?>
<?php $styles = array(
NABU_DIRECTORY['styles'] . '/admin/dashboard/dashboard.css',
NABU_DIRECTORY['styles'] . '/admin/dashboard/dashboard-desktop.css',
'admin/dashboard/dashboard.css',
) ?>
<?php $desktop_styles = array(
array('admin/dashboard/dashboard-desktop.css', 'attributes' => ''),
) ?>
<?php $scripts = array() ?>
<?php require_once 'views/components/head.php' ?>
<?php require_once 'views/components/admin-navbar.php' ?>

View file

@ -1,16 +1,17 @@
<?php defined('NABU') || exit ?>
<?php $head_title = 'Editar artículo' ?>
<?php $styles = array(
NABU_DIRECTORY['styles'] . '/admin/edit-article/edit-article.css',
NABU_DIRECTORY['styles'] . '/admin/edit-article/edit-article-desktop.css',
'admin/edit-article/edit-article.css',
) ?>
<?php $desktop_styles = array(
array('admin/edit-article/edit-article-desktop.css', 'attributes' => ''),
) ?>
<?php $scripts = array() ?>
<?php require_once 'views/components/head.php' ?>
<?php require_once 'views/components/admin-navbar.php' ?>
<h1>Editar artículo</h1>
<p><a href="<?= NABU_ROUTES['admin'] ?>">Volver</a></p>
<?php require_once 'views/components/messages.php' ?>
<form method="POST" action="<?= NABU_ROUTES['edit-article'] . '&slug=' . $article['slug'] ?>" enctype="multipart/form-data">

View file

@ -1,9 +1,12 @@
<?php defined('NABU') || exit ?>
<?php $head_title = 'Artículos publicados' ?>
<?php $styles = array(
NABU_DIRECTORY['styles'] . '/admin/published-articles/published-articles.css',
NABU_DIRECTORY['styles'] . '/admin/published-articles/published-articles-desktop.css',
'admin/published-articles/published-articles.css',
) ?>
<?php $desktop_styles = array(
array('admin/published-articles/published-articles-desktop.css', 'attributes' => ''),
) ?>
<?php $scripts = array() ?>
<?php require_once 'views/components/head.php' ?>
<?php require_once 'views/components/admin-navbar.php' ?>

View file

@ -1,9 +1,12 @@
<?php defined('NABU') || exit ?>
<?php $head_title = 'Usuarios registrados' ?>
<?php $styles = array(
NABU_DIRECTORY['styles'] . '/admin/users/users.css',
NABU_DIRECTORY['styles'] . '/admin/users/users-desktop.css',
'admin/users/users.css',
) ?>
<?php $desktop_styles = array(
array('admin/users/users-desktop.css', 'attributes' => ''),
) ?>
<?php $scripts = array() ?>
<?php require_once 'views/components/head.php' ?>
<?php require_once 'views/components/admin-navbar.php' ?>

View file

@ -7,6 +7,7 @@
<ul>
<li><a href="<?= NABU_ROUTES['admin'] ?>">Artículos envíados</a></li>
<li><a href="<?= NABU_ROUTES['published-articles'] ?>">Artículos publicados</a></li>
<li><a href="<?= NABU_ROUTES['users'] ?>">Usuarios registrados</a></li>
<li><a href="<?= NABU_ROUTES['home'] ?>"><?= NABU_DEFAULT['website-name'] ?></a></li>
<li><a href="<?= NABU_ROUTES['profile'] . '&user=' . urlencode($username) ?>"><?= utils::escape($username) ?></a></li>
<li><a href="<?= NABU_ROUTES['logout'] ?>">Cerrar sesión</a></li>

View file

@ -5,18 +5,25 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- CSS files -->
<!-- Base CSS files -->
<link rel="icon" href="<?= NABU_DIRECTORY['images'] ?>/buho.svg" type="image/svg+xml" sizes="any">
<link rel="stylesheet" href="<?= NABU_DIRECTORY['styles'] ?>/normalize.css">
<!-- Mobile CSS files -->
<?php foreach ($styles as $style): ?>
<link rel="stylesheet" href="<?= $style ?>">
<link rel="stylesheet" href="<?= NABU_DIRECTORY['styles'] . '/' . $style ?>">
<?php endforeach ?>
<!-- Desktop CSS files -->
<?php foreach ($desktop_styles as $style): ?>
<link rel="stylesheet" href="<?= NABU_DIRECTORY['styles'] . '/' . $style[0] ?>" <?= $style['attributes'] ?>>
<?php endforeach ?>
<!-- Fonts -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;600;700&display=swap" rel="stylesheet">
<!-- JS files -->
<script src="<?= NABU_DIRECTORY['scripts'] ?>/main.js" defer></script>
<?php foreach ($scripts as $script): ?>
<script src="<?= NABU_DIRECTORY['scripts'] . '/' . $script ?>" defer></script>
<?php endforeach ?>
<title><?= $head_title ?></title>
</head>
<body>

View file

@ -1,10 +1,13 @@
<?php defined('NABU') || exit ?>
<?php $head_title = 'Muro' ?>
<?php $styles = array(
NABU_DIRECTORY['styles'] . '/pages/all-articles/all-articles.css',
NABU_DIRECTORY['styles'] . '/pages/all-articles/all-articles-desktop.css',
NABU_DIRECTORY['styles'] . '/components/articles/articles.css',
'pages/all-articles/all-articles.css',
'components/articles/articles.css',
) ?>
<?php $desktop_styles = array(
array('pages/all-articles/all-articles-desktop.css', 'attributes' => ''),
) ?>
<?php $scripts = array() ?>
<?php require_once 'views/components/head.php' ?>
<?php require_once 'views/components/navbar.php' ?>

View file

@ -1,10 +1,13 @@
<?php defined('NABU') || exit ?>
<?php $head_title = 'Artículo' ?>
<?php $styles = array(
NABU_DIRECTORY['styles'] . '/pages/article/article.css',
NABU_DIRECTORY['styles'] . '/pages/article/article-desktop.css',
NABU_DIRECTORY['styles'] . '/components/articles/articles.css',
'pages/article/article.css',
'components/articles/articles.css',
) ?>
<?php $desktop_styles = array(
array('pages/article/article-desktop.css', 'attributes' => ''),
) ?>
<?php $scripts = array() ?>
<?php require_once 'views/components/head.php' ?>
<h1><?= $article['title'] ?></h1>

View file

@ -1,10 +1,13 @@
<?php defined('NABU') || exit ?>
<?php $head_title = 'Categoría' ?>
<?php $styles = array(
NABU_DIRECTORY['styles'] . '/pages/category/category.css',
NABU_DIRECTORY['styles'] . '/pages/category/category-desktop.css',
NABU_DIRECTORY['styles'] . '/components/articles/articles.css',
'pages/category/category.css',
'components/articles/articles.css',
) ?>
<?php $desktop_styles = array(
array('pages/category/category-desktop.css', 'attributes' => '')
) ?>
<?php $scripts = array() ?>
<?php require_once 'views/components/head.php' ?>
<?php require_once 'views/components/navbar.php' ?>

View file

@ -1,15 +1,16 @@
<?php defined('NABU') || exit ?>
<?php $head_title = 'Confirmar contraseña' ?>
<?php $styles = array(
NABU_DIRECTORY['styles'] . '/pages/confirm-password/confirm-password.css',
NABU_DIRECTORY['styles'] . '/pages/confirm-password/confirm-password-desktop.css',
'pages/confirm-password/confirm-password.css',
) ?>
<?php $desktop_styles = array(
array('pages/confirm-password/confirm-password-desktop.css', 'attributes' => '')
) ?>
<?php $scripts = array() ?>
<?php require_once 'views/components/head.php' ?>
<h1>Confirmar contraseña</h1>
<p><a href="<?= $back ?>">Volver</a></p>
<?php require_once 'views/components/messages.php' ?>
<p>Ingresa tu contraseña para completar la operación.</p>

View file

@ -1,9 +1,12 @@
<?php defined('NABU') || exit ?>
<?php $head_title = 'Editar artículo' ?>
<?php $styles = array(
NABU_DIRECTORY['styles'] . '/pages/edit-article/edit-article.css',
NABU_DIRECTORY['styles'] . '/pages/edit-article/edit-article-desktop.css',
'pages/edit-article/edit-article.css',
) ?>
<?php $desktop_styles = array(
array('pages/edit-article/edit-article-desktop.css', 'attributes' => '')
) ?>
<?php $scripts = array() ?>
<?php require_once 'views/components/head.php' ?>
<?php require_once 'views/components/navbar.php' ?>

View file

@ -1,15 +1,16 @@
<?php defined('NABU') || exit ?>
<?php $head_title = 'Editar perfil' ?>
<?php $styles = array(
NABU_DIRECTORY['styles'] . '/pages/edit-profile/edit-profile.css',
NABU_DIRECTORY['styles'] . '/pages/edit-profile/edit-profile-desktop.css',
'pages/edit-profile/edit-profile.css',
) ?>
<?php $desktop_styles = array(
array('pages/edit-profile/edit-profile-desktop.css', 'attributes' => '')
) ?>
<?php $scripts = array() ?>
<?php require_once 'views/components/head.php' ?>
<h1>Editar perfil</h1>
<p><a href="<?= $back ?>">Volver</a></p>
<?php require_once 'views/components/messages.php' ?>
<form method="POST" action="<?= NABU_ROUTES['edit-profile'] ?>" enctype="multipart/form-data">

View file

@ -1,13 +1,14 @@
<?php defined('NABU') || exit ?>
<?php $head_title = 'Error' ?>
<?php $styles = array(
NABU_DIRECTORY['styles'] . '/pages/errors/errors.css',
NABU_DIRECTORY['styles'] . '/pages/errors/errors-desktop.css',
'pages/errors/errors.css',
) ?>
<?php $desktop_styles = array(
array('pages/errors/errors-desktop.css', 'attributes' => '')
) ?>
<?php $scripts = array() ?>
<?php require_once 'views/components/head.php' ?>
<p><a href="<?= NABU_ROUTES['home'] ?>">Volver</a></p>
<p><mark><?= $error ?>.</mark></p>
<?php require_once 'views/components/footer.php' ?>

View file

@ -1,10 +1,13 @@
<?php defined('NABU') || exit ?>
<?php $head_title = 'Artículos favoritos' ?>
<?php $styles = array(
NABU_DIRECTORY['styles'] . '/pages/favorites/favorites.css',
NABU_DIRECTORY['styles'] . '/pages/favorites/favorites-desktop.css',
NABU_DIRECTORY['styles'] . '/components/articles/articles.css',
'pages/favorites/favorites.css',
'components/articles/articles.css',
) ?>
<?php $desktop_styles = array(
array('pages/favorites/favorites-desktop.css', 'attributes' => '')
) ?>
<?php $scripts = array() ?>
<?php require_once 'views/components/head.php' ?>
<?php require_once 'views/components/navbar.php' ?>

View file

@ -1,10 +1,15 @@
<?php defined('NABU') || exit ?>
<?php $head_title = NABU_DEFAULT['website-name'] ?>
<?php $styles = array(
NABU_DIRECTORY['styles'] . '/pages/home/home.css',
NABU_DIRECTORY['styles'] . '/pages/home/home-desktop.css',
NABU_DIRECTORY['styles'] . '/components/articles/articles.css',
NABU_DIRECTORY['styles'] . '/components/footer/footer.css',
<?php $head_title = NABU_DEFAULT['website-name'] ?>
<?php $styles = array(
'pages/home/home.css',
'components/articles/articles.css',
'components/footer/footer.css',
) ?>
<?php $desktop_styles = array(
array('pages/home/home-desktop.css', 'attributes' => 'media="(max-width: 720px)"'),
) ?>
<?php $scripts = array(
'home.js',
) ?>
<?php require_once 'views/components/head.php' ?>
@ -12,6 +17,7 @@
<?php require_once 'views/components/navbar.php' ?>
<div class="hero">
<picture class="hero__img-wrapper">
<source srcset="<?= NABU_DIRECTORY['images'] ?>/hero-desktop.png" media="(min-width: 600px)">
<img src="<?= NABU_DIRECTORY['images'] ?>/hero.png" alt="Imagen inspiración en cualquier momento y cualquier lugar" class="hero__img">
</picture>
<h1 class="hero__CTA">Lee, inspírate y escribe</h1>

View file

@ -1,9 +1,12 @@
<?php defined('NABU') || exit ?>
<?php $head_title = 'Iniciar sesión' ?>
<?php $styles = array(
NABU_DIRECTORY['styles'] . '/pages/login/login.css',
NABU_DIRECTORY['styles'] . '/pages/login/login-desktop.css',
'pages/login/login.css',
) ?>
<?php $desktop_styles = array(
array('pages/login/login-desktop.css', 'attributes' => ''),
) ?>
<?php $scripts = array() ?>
<?php require_once 'views/components/head.php' ?>
<h1>Iniciar sesión</h1>

View file

@ -1,9 +1,12 @@
<?php defined('NABU') || exit ?>
<?php $head_title = 'Publicar un artículo' ?>
<?php $styles = array(
NABU_DIRECTORY['styles'] . '/pages/post-article/post-article.css',
NABU_DIRECTORY['styles'] . '/pages/post-article/post-article-desktop.css',
'pages/post-article/post-article.css',
) ?>
<?php $desktop_styles = array(
array('pages/post-article/post-article-desktop.css', 'attributes' => ''),
) ?>
<?php $scripts = array() ?>
<?php require_once 'views/components/head.php' ?>
<?php require_once 'views/components/navbar.php' ?>

View file

@ -1,10 +1,13 @@
<?php defined('NABU') || exit ?>
<?php $head_title = 'Perfil' ?>
<?php $styles = array(
NABU_DIRECTORY['styles'] . '/pages/profile/profile.css',
NABU_DIRECTORY['styles'] . '/pages/profile/profile-desktop.css',
NABU_DIRECTORY['styles'] . '/components/articles/articles.css',
'pages/profile/profile.css',
'components/articles/articles.css',
) ?>
<?php $desktop_styles = array(
array('pages/profile/profile-desktop.css', 'attributes' => ''),
) ?>
<?php $scripts = array() ?>
<?php require_once 'views/components/head.php' ?>
<?php require_once 'views/components/navbar.php' ?>

View file

@ -1,10 +1,13 @@
<?php defined('NABU') || exit ?>
<?php $head_title = 'Búsquedas' ?>
<?php $styles = array(
NABU_DIRECTORY['styles'] . '/pages/search/search.css',
NABU_DIRECTORY['styles'] . '/pages/search/search-desktop.css',
NABU_DIRECTORY['styles'] . '/components/articles/articles.css',
'pages/search/search.css',
'components/articles/articles.css',
) ?>
<?php $desktop_styles = array(
array('pages/search/search-desktop.css', 'attributes' => ''),
) ?>
<?php $scripts = array() ?>
<?php require_once 'views/components/head.php' ?>
<?php require_once 'views/components/navbar.php' ?>

View file

@ -1,9 +1,12 @@
<?php defined('NABU') || exit ?>
<?php $head_title = 'Artículos enviados' ?>
<?php $styles = array(
NABU_DIRECTORY['styles'] . '/pages/sent-articles/sent-articles.css',
NABU_DIRECTORY['styles'] . '/pages/sent-articles/sent-articles-desktop.css',
'pages/sent-articles/sent-articles.css',
) ?>
<?php $desktop_styles = array(
array('pages/sent-articles/sent-articles-desktop.css', 'attributes' => ''),
) ?>
<?php $scripts = array() ?>
<?php require_once 'views/components/head.php' ?>
<?php require_once 'views/components/navbar.php' ?>

View file

@ -1,10 +1,18 @@
<?php defined('NABU') || exit ?>
<?php $head_title = 'Crea una cuenta' ?>
<?php $styles = array(
<<<<<<< HEAD
NABU_DIRECTORY['styles'] . '/pages/signup/signup.css',
NABU_DIRECTORY['styles'] . '/pages/signup/signup-desktop.css',
NABU_DIRECTORY['styles'] . '/components/footer/footer.css',
=======
'pages/signup/signup.css',
>>>>>>> dev
) ?>
<?php $desktop_styles = array(
array('pages/signup/signup-desktop.css', 'attributes' => ''),
) ?>
<?php $scripts = array() ?>
<?php require_once 'views/components/head.php' ?>