docs: map existing codebase

- stack.md (68 lines) - PHP/MySQL/Apache stack, vendored libraries
- architecture.md (131 lines) - Custom MVC CMS, dual-layer (front/admin)
- structure.md (170 lines) - Directory layout and conventions
- conventions.md (98 lines) - PHP snake_case, SCSS $c/$f prefixes, jQuery patterns
- testing.md (49 lines) - No automated tests detected
- integrations.md (111 lines) - Google Maps, PHPMailer, Pixieset, Facebook
- concerns.md (150 lines) - Critical security issues: hardcoded creds, MD5, unserialize
- db_schema.md (260 lines) - ~32 tables with pp_ prefix, inferred from source
- tech_changelog.md (9 lines) - Initial log entry

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-05-05 22:02:04 +02:00
parent 2d3bb66d42
commit cf1a0adb0b
10 changed files with 1377 additions and 0 deletions

318
.paul/codebase/db_schema.md Normal file
View File

@@ -0,0 +1,318 @@
# Database Schema
**Updated:** 2026-05-05 | **Total tables:** ~32 | **Engine:** MySQL InnoDB | **Prefix:** `pp_`
Schema inferred from Medoo ORM queries in PHP source files. No migration files found — schema managed manually.
---
## Content — Articles
**`pp_articles`** — Main article/content records
| Column | Type | Notes |
|--------|------|-------|
| `id` | INT | PK, AUTO_INCREMENT |
| `hash` | VARCHAR | Unique identifier for external reference |
| `status` | TINYINT | Published/draft/archived |
| `date_add` | DATETIME | Creation timestamp |
| `views` | INT | View counter |
| `password` | VARCHAR | Optional password protection |
| `pixieset` | VARCHAR | Pixieset gallery identifier |
**`pp_articles_langs`** — Language-specific article content
| Column | Type | Notes |
|--------|------|-------|
| `article_id` | INT | FK → `pp_articles.id` |
| `lang_id` | INT | FK → `pp_langs.id` |
| `title` | VARCHAR | Article title |
| `content` | LONGTEXT | Rich text content |
| `seo_link` | VARCHAR | URL slug |
| `noindex` | TINYINT | SEO noindex flag |
**`pp_articles_images`** — Article gallery images
| Column | Type | Notes |
|--------|------|-------|
| `id` | INT | PK, AUTO_INCREMENT |
| `article_id` | INT | FK → `pp_articles.id` |
| `src` | VARCHAR | Image filename/path |
| `o` | INT | Sort order |
| `favorite` | TINYINT | Flagged as favorite |
| `to_delete` | TINYINT | Soft-delete flag |
**`pp_articles_files`** — Downloadable file attachments
| Column | Type | Notes |
|--------|------|-------|
| `id` | INT | PK, AUTO_INCREMENT |
| `article_id` | INT | FK → `pp_articles.id` |
| `src` | VARCHAR | File path |
| `name` | VARCHAR | Display name |
| `to_delete` | TINYINT | Soft-delete flag |
**`pp_articles_pages`** — Article-to-Page assignments
| Column | Type | Notes |
|--------|------|-------|
| `article_id` | INT | FK → `pp_articles.id` |
| `page_id` | INT | FK → `pp_pages.id` |
| `o` | INT | Sort order on page |
| `status` | TINYINT | Visibility on this page |
**`pp_articles_tags`** — Many-to-many: Articles ↔ Tags
| Column | Type | Notes |
|--------|------|-------|
| `article_id` | INT | FK → `pp_articles.id` |
| `tag_id` | INT | FK → `pp_tags.id` |
**`pp_articles_additional_params`** — Custom field definitions per article
| Column | Type | Notes |
|--------|------|-------|
| `id` | INT | PK, AUTO_INCREMENT |
| `article_id` | INT | FK → `pp_articles.id` |
| `name` | VARCHAR | Field name |
| `language` | VARCHAR | Language code |
| `status` | TINYINT | Active/inactive |
**`pp_articles_additional_values`** — Custom field values
| Column | Type | Notes |
|--------|------|-------|
| `param_id` | INT | FK → `pp_articles_additional_params.id` |
| `article_id` | INT | FK → `pp_articles.id` |
| `value` | TEXT | Field value |
| `language_id` | INT | FK → `pp_langs.id` |
---
## Content — Pages & Layouts
**`pp_pages`** — Website page hierarchy
| Column | Type | Notes |
|--------|------|-------|
| `id` | INT | PK, AUTO_INCREMENT |
| `menu_id` | INT | FK → `pp_menus.id` |
| `parent_id` | INT | Self-referencing for nested pages |
| `page_type` | VARCHAR | Type identifier |
| `sort_type` | VARCHAR | Article sort order |
| `status` | TINYINT | Published/hidden |
| `start` | TINYINT | Homepage flag |
| `o` | INT | Sort order in navigation |
| `cache` | TINYINT | Page-level cache toggle |
**`pp_pages_langs`** — Page language translations
| Column | Type | Notes |
|--------|------|-------|
| `page_id` | INT | FK → `pp_pages.id` |
| `lang_id` | INT | FK → `pp_langs.id` |
| `title` | VARCHAR | Page title |
| `seo_link` | VARCHAR | URL slug |
**`pp_layouts`** — Template layout containers
| Column | Type | Notes |
|--------|------|-------|
| `id` | INT | PK, AUTO_INCREMENT |
| `layout_id` | INT | Parent layout reference |
| `name` | VARCHAR | Layout name |
| `html` | LONGTEXT | Desktop HTML with `[PLACEHOLDER:id]` tags |
| `m_html` | LONGTEXT | Mobile HTML |
| `css` | TEXT | Inline CSS |
| `m_css` | TEXT | Mobile inline CSS |
| `js` | TEXT | Inline JS |
| `m_js` | TEXT | Mobile inline JS |
| `status` | TINYINT | Active/inactive |
**`pp_layouts_pages`** — Layout-to-page assignments
| Column | Type | Notes |
|--------|------|-------|
| `layout_id` | INT | FK → `pp_layouts.id` |
| `page_id` | INT | FK → `pp_pages.id` |
---
## Content — Static Containers & Banners
**`pp_scontainers`** — Reusable static content blocks
| Column | Type | Notes |
|--------|------|-------|
| `id` | INT | PK, AUTO_INCREMENT |
| `status` | TINYINT | Active/inactive |
**`pp_scontainers_langs`** — Static container translations
| Column | Type | Notes |
|--------|------|-------|
| `id` | INT | PK, AUTO_INCREMENT |
| `scontainer_id` | INT | FK → `pp_scontainers.id` |
| `lang_id` | INT | FK → `pp_langs.id` |
| `html` | LONGTEXT | Content HTML |
**`pp_banners`** — Promotional banners
| Column | Type | Notes |
|--------|------|-------|
| `id` | INT | PK, AUTO_INCREMENT |
| `name` | VARCHAR | Banner name |
| `status` | TINYINT | Active/inactive |
**`pp_banners_langs`** — Banner language versions
| Column | Type | Notes |
|--------|------|-------|
| `id` | INT | PK, AUTO_INCREMENT |
| `id_banner` | INT | FK → `pp_banners.id` |
| `id_lang` | INT | FK → `pp_langs.id` |
| `title` | VARCHAR | Banner title |
| `content` | TEXT | Banner content/HTML |
---
## Navigation & Taxonomy
**`pp_menus`** — Navigation menu definitions
| Column | Type | Notes |
|--------|------|-------|
| `id` | INT | PK, AUTO_INCREMENT |
| `name` | VARCHAR | Menu name |
**`pp_tags`** — Article tags/categories
| Column | Type | Notes |
|--------|------|-------|
| `id` | INT | PK, AUTO_INCREMENT |
| `name` | VARCHAR | Tag name |
**`pp_authors`** — Article author profiles
| Column | Type | Notes |
|--------|------|-------|
| `id` | INT | PK, AUTO_INCREMENT |
**`pp_authors_langs`** — Author translations
| Column | Type | Notes |
|--------|------|-------|
| `author_id` | INT | FK → `pp_authors.id` |
| `lang_id` | INT | FK → `pp_langs.id` |
---
## Users & Access Control
**`pp_users`** — Admin panel users
| Column | Type | Notes |
|--------|------|-------|
| `id` | INT | PK, AUTO_INCREMENT |
| `login` | VARCHAR | Username |
| `password` | VARCHAR | MD5 hash (security concern — see concerns.md) |
| `status` | TINYINT | Active/inactive |
| `active_to` | DATE | Account expiry date |
| `admin` | TINYINT | Super-admin flag |
| `error_logged_count` | INT | Failed login counter |
**`pp_users_privileges`** — User permission assignments
| Column | Type | Notes |
|--------|------|-------|
| `id_user` | INT | FK → `pp_users.id` |
| `name` | VARCHAR | Privilege name (string key) |
---
## Multi-language
**`pp_langs`** — Available language configurations
| Column | Type | Notes |
|--------|------|-------|
| `id` | INT | PK, AUTO_INCREMENT |
| `start` | TINYINT | Default language flag |
| `domain` | VARCHAR | Domain for this language |
| `main_domain` | VARCHAR | Primary domain |
| `o` | INT | Sort order |
| `status` | TINYINT | Active/inactive |
**`pp_langs_translations`** — UI string translations
| Column | Type | Notes |
|--------|------|-------|
| Dynamic columns per language ID | VARCHAR | Translation strings |
---
## Newsletter
**`pp_newsletter`** — Email subscribers
| Column | Type | Notes |
|--------|------|-------|
| `id` | INT | PK, AUTO_INCREMENT |
| `email` | VARCHAR | Subscriber email |
| `status` | TINYINT | Active/unsubscribed |
**`pp_newsletter_send`** — Sent campaign log
| Column | Type | Notes |
|--------|------|-------|
| `id` | INT | PK, AUTO_INCREMENT |
| `email_template_id` | INT | FK → `pp_newsletter_templates.id` |
**`pp_newsletter_templates`** — Email campaign templates
| Column | Type | Notes |
|--------|------|-------|
| `id` | INT | PK, AUTO_INCREMENT |
| `name` | VARCHAR | Template name |
| `subject` | VARCHAR | Email subject |
| `html` | LONGTEXT | Email HTML body |
---
## Contacts & Locations
**`pp_contacts_maps`** — Location/contact point entries
| Column | Type | Notes |
|--------|------|-------|
| `id` | INT | PK, AUTO_INCREMENT |
**`pp_contacts_maps_products`** — Product associations for locations
| Column | Type | Notes |
|--------|------|-------|
| `map_id` | INT | FK → `pp_contacts_maps.id` |
| `product_id` | INT | Product reference |
**`pp_contacts_maps_provinces`** — Province associations for locations
| Column | Type | Notes |
|--------|------|-------|
| `map_id` | INT | FK → `pp_contacts_maps.id` |
| `province_id` | INT | Province reference |
**`pp_contact_emails`** — Contact form submissions
| Column | Type | Notes |
|--------|------|-------|
| `email` | VARCHAR | Sender email |
| `phone` | VARCHAR | Sender phone |
| `title` | VARCHAR | Subject |
| `mail` | TEXT | Message body |
| `add_date` | DATETIME | Submission timestamp |
---
## SEO & Settings
**`pp_seo_additional`** — Per-article SEO metadata
| Column | Type | Notes |
|--------|------|-------|
| `article_id` | INT | FK → `pp_articles.id` |
| `lang_id` | INT | FK → `pp_langs.id` |
**`pp_settings`** — Global CMS configuration (key-value store)
| Column | Type | Notes |
|--------|------|-------|
| `param` | VARCHAR | Setting key (e.g., `google_map_key`, `email_host`) |
| `value` | TEXT | Setting value |
---
## Schema Characteristics
| Property | Value |
|----------|-------|
| Engine | MySQL InnoDB |
| Table prefix | `pp_` |
| Soft deletes | `to_delete` column on `pp_articles_images`, `pp_articles_files` |
| Multi-language | All content tables have `_langs` counterpart |
| Migrations | None — schema managed manually |
| Timestamps | `date_add` on articles; not standardized across all tables |
## Key Relationships
- **1:N** — `pp_articles``pp_articles_images`, `pp_articles_files`, `pp_articles_langs`
- **N:N** — `pp_articles``pp_tags` (via `pp_articles_tags`)
- **N:N** — `pp_articles``pp_pages` (via `pp_articles_pages`)
- **Hierarchical** — `pp_pages.parent_id` self-reference for nested page tree
- **Language join** — all content tables join `pp_langs` on `lang_id` for translation