Files
centrumcopy.com.pl/.paul/codebase/conventions.md
2026-04-30 21:31:32 +02:00

14 KiB

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.phpclass 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.phpPage_Controller
    • application/controllers/front/page.phpPage_Controller (different namespace via directory)
  • Models: lowercase.php matching class name without _Model suffix
    • application/models/page.phpPage_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.phpdefault_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:
    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):
    #$this->profiler = new Profiler();
    # TODO ? zastosowac parametr GET...
    
  • // style for explanatory comments:
    // 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 defined('SYSPATH') OR die('No direct access allowed.');

Common Patterns

Controller Structure

Controllers always extend a base class (never Controller directly for app controllers):

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():

// 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):

$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.

// 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 echo $content ?>  // renders the inner view

Form Handling Pattern

Forms use Kohana's form:: helper class for HTML generation:

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):

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:

// 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:

$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:

$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:
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():

$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()):

if(!$this->session->get('admin') && Router::$method != 'login' && Router::$method != 'logout')
{
  url::redirect('admin/login');
}

Admin session payload:

$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

// 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:
    // 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):
    $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():
    echo html::specialchars($title);
    echo html::specialchars($meta_description);
    
  • Content output in views is unescapedecho $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
  • '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