Skip to content
Merged

Dev #19

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
113 changes: 64 additions & 49 deletions .github/workflows/phpapp.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: PHP CI/CD

on:
push:
branches: [ main ]
branches: [ main, dev ]
pull_request:
branches: [ main ]

Expand All @@ -26,7 +26,7 @@ jobs:
--health-retries=3

steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4

- name: Setup PHP
uses: shivammathur/setup-php@v2
Expand All @@ -43,59 +43,53 @@ jobs:
display_startup_errors=On
error_reporting=E_ALL

- name: Check PHP installation
run: |
php -v
php -m
php --ini

- name: Validate composer.json
run: composer validate --strict

- name: Install dependencies
run: composer install --prefer-dist --no-progress

- name: Lint PHP files
run: |
find . \
-path ./core/vendor -prune -o \
-path ./node_modules -prune -o \
-name '*.php' -print0 \
| xargs -0 -n1 -P4 php -l > /dev/null
echo "All PHP files lint clean"

- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: '22'
cache: 'npm'

- name: Build frontend assets
run: |
npm ci
npm run build

- name: Create .env file
run: |
cp .env.example .env
sed -i 's/^DB_HOST=.*/DB_HOST=127.0.0.1/' .env
sed -i 's/^DB_DATABASE=.*/DB_DATABASE=phpapp/' .env
sed -i 's/^DB_USERNAME=.*/DB_USERNAME=root/' .env
sed -i 's/^DB_PASSWORD=.*/DB_PASSWORD=root/' .env
sed -i 's/^DB_HOST=.*/DB_HOST="127.0.0.1"/' .env
sed -i 's/^DB_NAME=.*/DB_NAME="phpapp"/' .env
sed -i 's/^DB_USER=.*/DB_USER="root"/' .env
sed -i 's/^DB_PASS=.*/DB_PASS="root"/' .env
cat .env

- name: Verify project structure
- name: Run migrations
run: |
echo "Current directory structure:"
ls -la
if [ -d "public" ]; then
echo "Contents of public directory:"
ls -la public
else
echo "public directory not found!"
echo "Contents of root directory:"
ls -la
fi
php migrate.php migrate
php migrate.php status

- name: Start PHP server
run: |
# Determine the correct document root
if [ -d "public" ]; then
DOCROOT="public"
elif [ -d "www" ]; then
DOCROOT="www"
else
DOCROOT="."
fi
echo "Using document root: $DOCROOT"

# Start PHP server with error logging
php -S 127.0.0.1:8000 -t $DOCROOT > php-server.log 2>&1 &
php -S 127.0.0.1:8000 -t www > php-server.log 2>&1 &
echo $! > php-server.pid

# Give it a moment to start

sleep 2

# Check if process is running

if ps -p $(cat php-server.pid) > /dev/null; then
echo "PHP server process is running"
else
Expand All @@ -104,31 +98,52 @@ jobs:
exit 1
fi

- name: Wait for server and check health
- name: Wait for server
run: |
max_attempts=30
attempt=1

while [ $attempt -le $max_attempts ]; do
echo "Attempt $attempt of $max_attempts"

if curl -s -f http://127.0.0.1:8000 > /dev/null 2>&1; then
echo "Server is responding!"
exit 0
fi

# Check if server is still running

if ! ps -p $(cat php-server.pid) > /dev/null; then
echo "PHP server has died. Server log:"
cat php-server.log
exit 1
fi

sleep 1
attempt=$((attempt + 1))
done

echo "Server failed to respond after $max_attempts attempts"
echo "PHP Server log:"
cat php-server.log
exit 1
exit 1

- name: Smoke test pages
run: |
check() {
url="$1"
expected="$2"
code=$(curl -s --path-as-is -o /dev/null -w '%{http_code}' "http://127.0.0.1:8000$url")
if [ "$code" != "$expected" ]; then
echo "FAIL: $url returned $code, expected $expected"
cat php-server.log
exit 1
fi
echo "OK: $url -> $code"
}

check "/" 200
check "/catalog/csrf_protection" 200
check "/catalog/pagination" 200
check "/this-page-does-not-exist" 404
check "/../config/web" 404
check "/index/__get" 404

- name: Show server log
if: failure()
run: cat php-server.log || true
2 changes: 1 addition & 1 deletion app/controller/catalog/pagination.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ public function index() {
$this->meta->setTitle($this->language->get('meta_title'));
$this->meta->setDescription($this->language->get('meta_description'));

$page = isset($this->request->get['page']) ? $this->request->get['page'] : 1;
$page = isset($this->request->get['page']) ? max(1, (int)$this->request->get['page']) : 1;

$this->data['heading_title'] = $page > 1 ? $this->language->get('heading_title') . ' - ' . $page : $this->language->get('heading_title');

Expand Down
2 changes: 0 additions & 2 deletions app/controller/common/footer.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@

class ControllerCommonFooter extends Controller {
public function index() {
$this->response->addScript('https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js', 'footer');

$scripts = $this->response->getScripts();

$this->data['scripts'] = array();
Expand Down
6 changes: 3 additions & 3 deletions app/controller/common/header.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@

class ControllerCommonHeader extends Controller {
public function index() {
$this->data['meta'] = $this->meta->getMetaTags();
$this->data['meta'] = $this->view->raw($this->meta->getMetaTags());

$this->data['styles'] = $this->response->getStyles();

$this->response->addScript('/assets/js/app.js');
$this->response->addScript($this->staticfile->getAssetUri('js/app.js'));

$scripts = $this->response->getScripts();

Expand All @@ -20,7 +20,7 @@ public function index() {

$this->data['menu'] = $this->load->controller('common/menu');

$this->response->addStyle('/assets/css/app.css');
$this->response->addStyle($this->staticfile->getAssetUri('css/app.css'));

$this->data['styles'] = $this->response->getStyles();

Expand Down
2 changes: 0 additions & 2 deletions app/view/www/assets/css/app

This file was deleted.

1 change: 0 additions & 1 deletion app/view/www/assets/js/app.js

This file was deleted.

24 changes: 14 additions & 10 deletions core/autoload.php
Original file line number Diff line number Diff line change
@@ -1,29 +1,33 @@
<?php

function vendor_autoloader($class_name) {
$file = DIR_CORE . 'vendor/autoload.php';

if (file_exists($file)) {
require_once $file;
}
// Composer autoloader: load once instead of probing on every class miss
if (is_file(DIR_CORE . 'vendor/autoload.php')) {
require_once DIR_CORE . 'vendor/autoload.php';
}

function classes_autoloader($class_name) {
if (!preg_match('/^[a-zA-Z0-9_]+$/', $class_name)) {
return;
}

$file = DIR_CORE . 'classes/' . strtolower($class_name) . '.php';

if (file_exists($file)) {
if (is_file($file)) {
require_once $file;
}
}

function library_autoloader($class_name) {
if (!preg_match('/^[a-zA-Z0-9_]+$/', $class_name)) {
return;
}

$file = DIR_CORE . 'library/' . strtolower($class_name) . '.php';

if (file_exists($file)) {
if (is_file($file)) {
require_once $file;
}
}

spl_autoload_register('vendor_autoloader');
spl_autoload_register('classes_autoloader');
spl_autoload_register('library_autoloader');
spl_autoload_register('library_autoloader');
19 changes: 17 additions & 2 deletions core/bootstrap.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,33 @@

require_once DIR_CORE . 'registry.php';
require_once DIR_CORE . 'autoload.php';
require_once DIR_CORE . 'router.php';

$registry = new Registry();

$log = new Log('phpapp', 'php.log');

set_error_handler(function ($errno, $errstr, $errfile, $errline) use ($log) {
if (!(error_reporting() & $errno)) {
return false;
}

$log->error("PHP Error: [$errno] $errstr in $errfile on line $errline");
return false;

return true;
});

set_exception_handler(function ($exception) use ($log) {
$log->exception($exception);

if (!headers_sent()) {
http_response_code(500);
}

// Never leak exception details to the client
echo 'Internal Server Error';
});

ini_set('display_errors', '0');
ini_set('log_errors', '1');
ini_set('error_log', DIR_LOG . 'php_errors.log');

Expand All @@ -34,9 +46,12 @@
$registry->set('pagination', new Pagination());
$registry->set('google_auth', new google_auth($registry));
$registry->set('meta', new Meta());
$registry->set('cache', new Cache());

$registry->set('view', new View($registry));

require_once DIR_CORE . 'router.php';

$route = new Router($registry);

$route->start();
20 changes: 15 additions & 5 deletions core/classes/view.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
class View {
protected $registry;
protected $twig;

public function __construct($registry) {
$this->registry = $registry;

Expand All @@ -12,8 +12,8 @@ public function __construct($registry) {
$this->twig = new \Twig\Environment($loader, [
'cache' => DIR_CACHE . 'twig',
'auto_reload' => true,
'debug' => true,
'autoescape' => false
'debug' => false,
'autoescape' => 'html'
]);
}

Expand All @@ -25,7 +25,17 @@ public function template($template, $data = []) {
return $template->render($data);
}

/**
* Wrap a trusted HTML string so autoescaping does not escape it,
* e.g. $this->data['header'] = $this->view->raw($html);
*/
public function raw($html) {
return new \Twig\Markup((string)$html, 'UTF-8');
}

protected function filter(&$data) {
$data['csrf'] = '<input type="hidden" name="csrf" value="' . $this->registry->get('session')->get('token') . '" />';
$token = htmlspecialchars((string)$this->registry->get('session')->get('token'), ENT_QUOTES, 'UTF-8');

$data['csrf'] = $this->raw('<input type="hidden" name="csrf" value="' . $token . '" />');
}
}
}
Loading
Loading