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