update
This commit is contained in:
391
.paul/codebase/conventions.md
Normal file
391
.paul/codebase/conventions.md
Normal 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` |
|
||||
Reference in New Issue
Block a user