This commit is contained in:
2026-04-30 21:31:32 +02:00
parent e22bbde336
commit e6a09f6c95
16 changed files with 1929 additions and 1 deletions

View File

@@ -0,0 +1,391 @@
# Conventions & Quality
**Analysis Date:** 2026-04-30
**Framework:** Kohana (legacy PHP MVC, version ~2.x)
---
## Naming Conventions
### Classes
- **PascalCase** with framework-mandated `_Controller`, `_Model` suffix:
- Controllers: `Page_Controller`, `User_Controller`, `Base_Admin_Controller`
- Models: `Page_Model`, `User_Model`, `Gallery_Model`, `News_Model`
- Abstract base controllers: `Base_Admin_Controller`, `Base_Front_Controller`
- Helper extensions: `MY_` prefix on filename and class — e.g. `MY_form.php``class form extends form_Core`
### Methods
- **camelCase** throughout: `uniqueKey()`, `uniqueKeyExists()`, `nameNotExists()`, `emailNotExists()`
- Exception: Kohana routing maps URL segments to method names, so public controller action methods use lowercase: `index()`, `login()`, `logout()`, `edit()`, `show()`, `contact()`, `password()`
- Fluent/utility methods follow framework conventions: `set_flash()`, `get_once()`
### Variables & Properties
- **snake_case** for local variables and view variables:
- `$page_view`, `$password_view`, `$path_arrow`, `$admin_title`
- **camelCase** for object properties that mirror DB columns (Kohana ORM maps directly to column names):
- `$user->sha1_password`, `$user->last_success`, `$user->is_active`
- Session data keys: lowercase with underscores — `'admin'`, `'admin_redirect'`, `'message'`, `'tiny_mce_public_html_dir'`
### Files
- Controllers: `lowercase.php` matching class name without `_Controller` suffix
- `application/controllers/admin/page.php``Page_Controller`
- `application/controllers/front/page.php``Page_Controller` (different namespace via directory)
- Models: `lowercase.php` matching class name without `_Model` suffix
- `application/models/page.php``Page_Model`
- Views: `lowercase_with_underscores.php`
- `application/views/admin/page_edit.php`
- `application/views/front/page_show.php`
- Helper/library extensions: `MY_` prefix
- `application/helpers/MY_form.php`, `application/helpers/MY_html.php`, `application/helpers/MY_valid.php`
- `application/libraries/MY_Database.php`
- Layout views: `*_layout.php``default_layout.php`, `admin_layout.php`
### Database Columns (ORM)
- **snake_case**: `sha1_password`, `last_success`, `last_failed`, `password_date`, `is_active`, `parent_id`, `created_at`
---
## Code Style
### Indentation
- **2-space indentation** throughout — consistent across all application files
- Example from `application/controllers/admin/page.php`:
```php
if($this->input->post())
{
$page->title = $this->input->post('page_title');
```
### Brace Style
- **Allman style** (opening brace on new line) for class and method bodies
- Kohana-era convention — does NOT follow PSR-2/PSR-12 which requires same-line braces
### PSR Compliance
- **Not PSR-12 compliant.** The codebase predates PSR and uses Kohana framework's own style:
- Allman braces (PSR-12 requires same-line)
- 2-space indent (PSR-12 requires 4-space)
- No strict types declaration
- CLAUDE.md states PSR-12 as a goal — this is aspirational, not current practice
### Comments
- `#` style used for inline disabled code and short notes (Kohana convention):
```php
#$this->profiler = new Profiler();
# TODO ? zastosowac parametr GET...
```
- `//` style for explanatory comments:
```php
// Load the class extension
```
- `/* */` for blocks of temporarily disabled code
- PHPDoc (`/** */`) only in framework library files, not in application code
- Comments explain "why" rarely — most comments are disabled code blocks
### Direct Access Guard
Every PHP file starts with:
```php
<?php defined('SYSPATH') OR die('No direct access allowed.');
```
---
## Common Patterns
### Controller Structure
Controllers always extend a base class (never `Controller` directly for app controllers):
```php
class Page_Controller extends Base_Admin_Controller
{
public function __construct()
{
parent::__construct();
$this->view->path = 'Strony'; // breadcrumb label
}
public function edit($name = null)
{
// 1. Build view object
$page_view = new View('admin/page_edit');
// 2. Load model via ORM
$page = ORM::factory('page')->where('name', $name)->find();
// 3. Guard - 404 if not found
if (!$page->loaded) { return $this->error404(); }
// 4. Handle POST
if($this->input->post()) { ... $page->save(); url::redirect(...); }
// 5. Pass data to view, render
$page_view->page = $page;
$this->view->content = $page_view;
$this->view->render(true);
}
}
```
### ORM / Database Queries
The app uses Kohana ORM (Active Record pattern). All queries go through `ORM::factory()`:
```php
// Find by primary key (integer) or unique key (string, via unique_key() override)
ORM::factory('page')->where('name', $name)->find();
ORM::factory('user')->find($this->input->post('username'));
ORM::factory('user', 1); // find by PK integer
// Save after setting properties
$page->title = $this->input->post('page_title');
$page->save();
if ($page->saved) { ... } // check success
```
Raw DB builder (used in `install.php` and models):
```php
$this->db->set(array('col' => $val))->insert('table');
$this->db->where('id', 1)->update('user')->count();
$this->db->where($this->unique_key($value), $value)->count_records($this->table_name);
```
Models define uniqueness helpers to support string-based lookup:
- `unique_key($id)` — returns column name for string vs. integer lookup
- `unique_key_exists($value)` — boolean uniqueness check
- `name_not_exists()`, `username_not_exists()`, `email_not_exists()` — used before save
### View Rendering Pattern
Two-layer view system: content view embedded in layout view.
```php
// In controller:
$page_view = new View('admin/page_edit'); // inner/content view
$page_view->page = $page; // pass data to inner view
$this->view->content = $page_view; // embed in layout
$this->view->render(true); // render layout with output buffering
```
In layout view (`admin_layout.php`, `default_layout.php`):
```php
<?php echo $content ?> // renders the inner view
```
### Form Handling Pattern
Forms use Kohana's `form::` helper class for HTML generation:
```php
echo form::open(null, array('id' => 'edit_form'), array('id' => $page->id));
echo form::label('page_title', 'Tytuł strony: ');
echo form::input(array('name'=>'page_title', 'size'=>75, 'maxlength' => 95), $page->title);
echo form::textarea(array('name' => 'page_content', 'class' => 'mceEditor'), $page->content);
echo form::submit('save','Zapisz');
echo form::close();
```
POST handling in controller (pattern: check → validate → save → flash message → redirect):
```php
if($this->input->post())
{
$page->title = $this->input->post('page_title');
$page->save();
if ($page->saved) {
$this->session->set_flash('message','Strona została zapisana.');
}
url::redirect(url::current()); // PRG pattern
}
```
### Flash Messages
Flash messages are set in the controller and displayed in the layout:
```php
// Set (controller):
$this->session->set_flash('message', 'Strona została zapisana.');
// Read (base controller constructor):
$this->view->message = $this->session->get('message');
// Display (layout view):
<?php if ($message): ?><div class="message"><?php echo $message ?></div><?php endif; ?>
```
### Validation Pattern
For complex forms, Kohana `Validation` object is used:
```php
$post = new Validation($this->input->post());
$post->pre_filter('trim')
->add_rules('username', 'required', 'length[3,20]', 'chars[a-zA-Z0-9_.]')
->add_rules('email', 'required', 'length[5,50]', 'valid::email')
->add_rules('password', 'required', 'length[3,40]')
->add_rules('password2', 'matches[password]');
if($post->validate()) { ... }
```
For simple forms (page editing), no server-side validation — JavaScript regex validation only.
### URL/Routing
Routes defined in `application/config/routes.php` as regex → controller/method:
```php
$config['admin/page/(.*)'] = 'admin/page/edit/$1';
$config['(.+)'] = 'front/page/$1'; // catch-all for front pages
```
Redirects: `url::redirect('admin/login')` — always relative path strings.
---
## Error Handling
### Strategy
- **No try/catch in application code.** Error handling is framework-delegated.
- Kohana catches exceptions via its own error handler and logs them.
- 404 is handled explicitly via controller method:
```php
public function error404()
{
header('HTTP/1.1 404 File Not Found');
$error_view = new View('admin/error404');
$error_view->page_name = Router::$current_uri . Router::$url_suffix;
$this->view->content = $error_view;
$this->view->render(true);
}
// Magic method catches all undefined actions:
public function __call($method, $arguments)
{
return $this->error404();
}
```
### Logging
- Kohana framework logging used in system/library code (`Kohana::log('debug', '...')`)
- Application log threshold set to `1` (errors and exceptions only) in `application/config/config.php`
- Log files written to `application/logs/`
- No application-level `Kohana::log()` calls in custom code
### Model Not Found
Pattern: check `$model->loaded` (boolean) after `->find()`:
```php
$page = ORM::factory('page')->where('name', $name)->find();
if (!$page->loaded) {
return $this->error404();
}
```
---
## Authentication & Sessions
### Auth Mechanism
Custom session-based authentication. No auth module used (Kohana auth module is commented out in config).
**Login flow** (`application/controllers/admin/user.php`):
1. Find user by username via ORM
2. Check `$user->is_active` flag
3. Compare `sha1($user->salt . $plaintext_password)` against stored `$user->sha1_password`
4. On success: store user data array in session under key `'admin'`
5. Redirect to saved `admin_redirect` URL
**Password storage:** SHA1 with random MD5 salt — weak by modern standards (SHA1 is not suitable for passwords; bcrypt/argon2 should be used).
**Auth check** (in `Base_Admin_Controller::__construct()`):
```php
if(!$this->session->get('admin') && Router::$method != 'login' && Router::$method != 'logout')
{
url::redirect('admin/login');
}
```
**Admin session payload:**
```php
$admin = array(
'id' => $user->id,
'role' => $user->role,
'username' => $user->username,
'email' => $user->email,
'last_success' => $user->last_success,
'last_failed' => $user->last_failed,
);
$this->session->set('admin', $admin);
```
### Session Configuration (`application/config/session.php`)
- Driver: `native` (PHP native sessions)
- Session name: `Frisson_session`
- Expiration: 1800 seconds (30 minutes)
- Validation: `user_agent` (ties session to browser UA)
- Encryption: disabled
- Session regeneration: disabled (`'regenerate' => 0`) — CSRF risk
### Redirect After Login
```php
// Save intended URL before redirect to login:
$this->session->set('admin_redirect', url::current());
// After login, consume and redirect:
$redirect = $this->session->get_once('admin_redirect', 'admin');
url::redirect($redirect);
```
### Security Gap: Force Login Controller
`application/controllers/admin/force.php` contains a `Force_Controller::login()` method that logs in user ID 1 **without any password check**. This is a development backdoor left in production code.
---
## Testing
**No test framework is configured or used.**
- Kohana's `unit_test` module exists in `modules/` but is commented out in `application/config/config.php`:
```php
// MODPATH.'unit_test', // Unit testing
```
- No test files found (no `*.test.php`, `*_test.php`, `*Test.php`, `*spec*` files)
- No PHPUnit, Pest, or any other testing tool detected
- No test directory (`tests/`, `spec/`, etc.) exists
**Current state:** Zero automated test coverage.
---
## Security Practices
### Input Handling
- POST data accessed via `$this->input->post('field')` — Kohana's Input class applies global XSS filtering when `global_xss_filtering = true` (set in `application/config/config.php`):
```php
$config['global_xss_filtering'] = true;
```
- This provides baseline XSS protection on GET/POST/SERVER data
### Output Escaping
- Layout files escape page metadata with `html::specialchars()`:
```php
echo html::specialchars($title);
echo html::specialchars($meta_description);
```
- Content output in views is **unescaped** — `echo $page->content` — intentional for HTML content managed via TinyMCE editor, but trusts admin input fully
- Front-end user-controlled output: none currently (public site is read-only display)
### SQL Injection Prevention
- ORM query builder with `'escape' => TRUE` in database config handles escaping automatically
- Raw `$this->db->query()` calls exist only in `install.php` with hardcoded strings
- No prepared statements with `bindParam()` in app code — reliance on ORM escaping
### CSRF
- **No CSRF protection.** No CSRF tokens on any form. Kohana's form helpers do not add tokens automatically.
- Admin panel forms are vulnerable to CSRF attacks
### Cookie Security (`application/config/cookie.php`)
- `'secure' => FALSE` — cookies sent over HTTP as well as HTTPS
- `'httponly' => FALSE` — cookies accessible to JavaScript (XSS risk)
- Both should be `TRUE` in production
### Session Security
- Session ID regeneration disabled (`'regenerate' => 0`) — increases session fixation risk
- No HTTPS enforcement detected in application code
### Database Credentials
- Plaintext credentials stored in `application/config/database.php` — committed to git (critical issue)
### Significant Security Issues (summary)
| Issue | Location |
|-------|----------|
| Development backdoor login (no password) | `application/controllers/admin/force.php` |
| SHA1 password hashing (not bcrypt/argon2) | `application/controllers/admin/user.php` |
| No CSRF tokens on admin forms | All admin views |
| `print_r($_POST)` left in production code | `application/controllers/admin/user.php:29` |
| DB credentials in version control | `application/config/database.php` |
| HttpOnly and Secure cookie flags disabled | `application/config/cookie.php` |
| Session ID never regenerated | `application/config/session.php` |