01 Doing the files and folders - HugoEsparzaC/CiCompOverflow GitHub Wiki
Make the following folders:
- controllers
- includes
- models
- public
- src
- img
- js
- scss
- base
- views
void
DB_HOST =
DB_USER =
DB_PASS =
DB_NAME =
<?php
use Model\ActiveRecord;
require __DIR__ . '/../vendor/autoload.php';
$dotenv = Dotenv\Dotenv::createImmutable(__DIR__);
$dotenv->safeLoad();
require 'functions.php';
require 'database.php';
// Connect to the database
ActiveRecord::setDB($db);
<?php
$db = mysqli_connect(
$_ENV['DB_HOST'],
$_ENV['DB_USER'],
$_ENV['DB_PASS'],
$_ENV['DB_NAME'],
);
$db->set_charset('utf8');
if (!$db) {
echo "Error: Failed to connect to MySQL.";
echo "Debugging errno: " . mysqli_connect_errno();
echo "Debugging error: " . mysqli_connect_error();
exit;
}
<?php
function debuguear($variable) : string {
echo "<pre>";
var_dump($variable);
echo "</pre>";
exit;
}
// Escape / Sanitize HTML
function s($html) : string {
$s = htmlspecialchars($html);
return $s;
}
<?php
namespace Model;
class ActiveRecord {
public $id;
// Database
protected static $db;
protected static $table = '';
protected static $dbColumns = [];
// Alerts and Messages
protected static $alerts = [];
// Define the connection to the DB - includes/database.php
public static function setDB($database) {
self::$db = $database;
}
public static function setAlert($type, $message) {
static::$alerts[$type][] = $message;
}
// Validation
public static function getAlerts() {
return static::$alerts;
}
public function validate() {
static::$alerts = [];
return static::$alerts;
}
// Execute the SQL query to get the results
public static function querySQL($query) {
// Execute the query
$result = self::$db->query($query);
// Iterate the results
$array = [];
while($record = $result->fetch_assoc()) {
$array[] = static::createObject($record);
}
// Free the memory
$result->free();
// Return the results
return $array;
}
// Create the object in memory that is equal to the DB
protected static function createObject($record) {
$object = new static;
foreach($record as $key => $value ) {
if(property_exists( $object, $key )) {
$object->$key = $value;
}
}
return $object;
}
// Identify and join the attributes of the DB
public function attributes() {
$attributes = [];
foreach(static::$dbColumns as $column) {
if($column === 'id') continue;
$attributes[$column] = $this->$column;
}
return $attributes;
}
// Sanitize the attributes before saving them to the DB
public function sanitizeAttributes() {
$attributes = $this->attributes();
$sanitized = [];
foreach($attributes as $key => $value) {
$sanitized[$key] = self::$db->escape_string($value);
}
return $sanitized;
}
// Synchronize the object in memory with the changes made by the user
public function synchronize($args = []) {
foreach($args as $key => $value) {
if(property_exists($this, $key) && !is_null($value)) {
$this->$key = $value;
}
}
}
// Save a record
public function save() {
$result = '';
if (!is_null($this->id)) {
// Update
$result = $this->update();
} else {
// Creating a new record
$result = $this->create();
}
return $result;
}
// Get all records
public static function all() {
$query = "SELECT * FROM " . static::$table;
$result = self::querySQL($query);
return $result;
}
// Search for a record by its id
public static function find($id) {
$query = "SELECT * FROM " . static::$table ." WHERE id = {$id}";
$result = self::querySQL($query);
return array_shift( $result ) ;
}
// Get a limited number of records
public static function get($limit) {
$query = "SELECT * FROM " . static::$table . " LIMIT {$limit}";
$result = self::querySQL($query);
return array_shift($result);
}
// Create a new record
public function create() {
// Sanitize the data
$attributes = $this->sanitizeAttributes();
// Insert into the database
$query = "INSERT INTO " . static::$table . " (";
$query .= join(', ', array_keys($attributes));
$query .= ") VALUES ('";
$query .= join("', '", array_values($attributes));
$query .= "')";
// Result of the query
$result = self::$db->query($query);
return [
'result' => $result,
'id' => self::$db->insert_id
];
}
// Update the record
public function update() {
// Sanitize the data
$attributes = $this->sanitizeAttributes();
// Iterate to add each field to the DB
$values = [];
foreach ($attributes as $key => $value) {
$values[] = "{$key}='{$value}'";
}
// SQL query
$query = "UPDATE " . static::$table . " SET ";
$query .= join(', ', $values);
$query .= " WHERE id = '" . self::$db->escape_string($this->id) . "' ";
$query .= " LIMIT 1";
// Update DB
$result = self::$db->query($query);
return $result;
}
// Delete a record by its ID
public function delete() {
$query = "DELETE FROM " . static::$table . " WHERE id = " . self::$db->escape_string($this->id) . " LIMIT 1";
$result = self::$db->query($query);
return $result;
}
}
<?php
require_once __DIR__ . '/../includes/app.php';
use MVC\Router;
$router = new Router();
// Checks and validates the routes that exist and assigns them the Controller functions
$router->checkRoutes();
Images
void
@forward 'base';
@use 'variables' as v;
html {
font-size: 62.5%;
box-sizing: border-box;
height: 100%;
}
body {
font-family: v.$main_font;
font-size: 1.6rem;
font-style: normal;
}
*, *:before, *:after {
box-sizing: inherit;
}
.contenedor {
width: 95%;
max-width: 120rem;
margin: 0 auto;
}
a {
text-decoration: none;
}
h1, h2, h3 {
margin: 0 0 5rem 0;
font-weight: 900;
}
h1 {
font-size: 4rem;
}
h2 {
font-size: 4.6rem;
}
h3 {
font-size: 6rem;
text-align: center;
}
img {
max-width: 100%;
width: 100%;
height: auto;
display: block;
}
@forward 'globals';
@forward 'mixins';
@forward 'normalize';
@forward 'variables';
@use 'variables' as v;
/** Media Queries **/
@mixin phone {
@media (min-width: v.$phone) {
@content;
}
}
@mixin tablet {
@media (min-width: v.$tablet) {
@content;
}
}
@mixin desktop {
@media (min-width: v.$desktop) {
@content;
}
}
@mixin grid($columns: 1, $spacing: 5rem) {
display: grid;
grid-template-columns: repeat($columns, 1fr);
gap: $spacing;
}
// Font
$main_font: "Montserrat", sans-serif;
// Media Query Sizes
$phone: 480px;
$tablet: 768px;
$desktop: 1024px;
// Computer Science ColorMagic Colors
$darknavyblue: #191d4d;
$navyblue: #505086;
$indigo:#8787b5;
$grey: #d6d6e0;
$clearindigo: #b0b0c9;
// Basic Colors
$black: #000000;
$white: #FFFFFF;
$spacing: 5rem;
// Weights
$thin: 300;
$regular: 400;
$bold: 700;
$black: 900;
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>CiComp Overflow</title>
<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=Montserrat:ital,wght@0,100..900;1,100..900&display=swap" rel="stylesheet">
<link rel="preload" href="build/css/app.css" as="style">
<link rel="stylesheet" href="build/css/app.css">
</head>
<body>
<?php echo $content; ?>
</body>
</html>
# Dependencias de Composer
vendor/
# Dependencias de Node.js
node_modules/
# Variables de entorno
.env
# Archivos de logs y temporales
*.log
*.tmp
import path from 'path'
import fs from 'fs'
import { glob } from 'glob'
import { src, dest, watch, series } from 'gulp'
import * as dartSass from 'sass'
import gulpSass from 'gulp-sass'
import terser from 'gulp-terser'
import sharp from 'sharp'
const sass = gulpSass(dartSass);
const paths = {
scss: 'src/scss/**/*.scss',
js: 'src/js/**/*.js',
imagenes: 'src/img/**/*'
}
export function css( done ) {
src(paths.scss, {sourcemaps: true})
.pipe( sass({
outputStyle: 'compressed'
}).on('error', sass.logError) )
.pipe( dest('./public/build/css', {sourcemaps: '.'}) );
done()
}
export function js( done ) {
src(paths.js, {sourcemaps: true})
.pipe(terser())
.pipe(dest('./public/build/js', {sourcemaps: '.'}))
done()
}
/*
export async function crop(done) {
const inputFolder = 'src/img/gallery/full'
const outputFolder = 'src/img/gallery/thumb';
const width = 250;
const height = 180;
if (!fs.existsSync(outputFolder)) {
fs.mkdirSync(outputFolder, { recursive: true })
}
const images = fs.readdirSync(inputFolder).filter(file => {
return /\.(jpg)$/i.test(path.extname(file));
});
try {
images.forEach(file => {
const inputFile = path.join(inputFolder, file)
const outputFile = path.join(outputFolder, file)
sharp(inputFile)
.resize(width, height, {
position: 'centre'
})
.toFile(outputFile)
});
done()
} catch (error) {
console.log(error)
}
}
*/
export async function imagenes(done) {
const srcDir = './src/img';
const buildDir = './public/build/img';
const images = await glob('./src/img/**/*{jpg,png}')
images.forEach(file => {
const relativePath = path.relative(srcDir, path.dirname(file));
const outputSubDir = path.join(buildDir, relativePath);
procesarImagenes(file, outputSubDir);
});
done();
}
function procesarImagenes(file, outputSubDir) {
if (!fs.existsSync(outputSubDir)) {
fs.mkdirSync(outputSubDir, { recursive: true })
}
const baseName = path.basename(file, path.extname(file))
const extName = path.extname(file)
if (extName.toLowerCase() === '.svg') {
// If it's an SVG file, move it to the output directory
const outputFile = path.join(outputSubDir, `${baseName}${extName}`);
fs.copyFileSync(file, outputFile);
} else {
// For other image formats, process them with sharp
const outputFile = path.join(outputSubDir, `${baseName}${extName}`);
const outputFileWebp = path.join(outputSubDir, `${baseName}.webp`);
const outputFileAvif = path.join(outputSubDir, `${baseName}.avif`);
const options = { quality: 80 };
sharp(file).jpeg(options).toFile(outputFile);
sharp(file).webp(options).toFile(outputFileWebp);
sharp(file).avif().toFile(outputFileAvif);
}
}
export function dev( done ) {
watch( paths.scss, css );
watch( paths.js, js );
watch( paths.imagenes + '{png,jpg}', imagenes)
done()
}
export default series( js, css, imagenes, dev )
<?php
namespace MVC;
class Router
{
public array $getRoutes = [];
public array $postRoutes = [];
public function get($url, $fn)
{
$this->getRoutes[$url] = $fn;
}
public function post($url, $fn)
{
$this->postRoutes[$url] = $fn;
}
public function checkRoutes()
{
// Protect Routes...
session_start();
// Array of protected routes...
// $protected_routes = ['/admin', '/properties/create', '/properties/update', '/properties/delete', '/sellers/create', '/sellers/update', '/sellers/delete'];
// $auth = $_SESSION['login'] ?? null;
$currentUrl = $_SERVER['PATH_INFO'] ?? '/';
$method = $_SERVER['REQUEST_METHOD'];
if ($method === 'GET') {
$fn = $this->getRoutes[$currentUrl] ?? null;
} else {
$fn = $this->postRoutes[$currentUrl] ?? null;
}
if ( $fn ) {
// Call user fn will call a function when we don't know which one it will be
call_user_func($fn, $this); // This is to pass arguments
} else {
echo "Page Not Found or Invalid Route";
}
}
public function render($view, $datos = [])
{
// Read what we pass to the view
foreach ($datos as $key => $value) {
$$key = $value; // Double dollar sign means: variable variable, basically our variable remains the original, but when assigning it to another it does not overwrite it, it keeps its value, this way the variable name is assigned dynamically
}
ob_start(); // Store in memory for a moment...
// then include the view in the layout
include_once __DIR__ . "/views/$view.php";
$content = ob_get_clean(); // Clean the Buffer
include_once __DIR__ . '/views/layout.php';
}
}