Compare commits
28 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8f6d084b4d | |||
| 47abff2550 | |||
| ffe661b4d2 | |||
| 73ff0ca5b6 | |||
| 7949e9b6a3 | |||
| 3325eaf44c | |||
| 9b31ce0d16 | |||
| 964bfa877c | |||
| 36fa3fdeae | |||
| 645037d144 | |||
| b8ab53a6f3 | |||
| dd31c062ad | |||
| 869f25d6db | |||
| b41fa58488 | |||
| 1b4c6fe66a | |||
| 320710fd02 | |||
| 11d720aa25 | |||
| 08bd6d23c9 | |||
| 28de4e88b7 | |||
| 0c1e916ed6 | |||
| 1bebdff3ac | |||
| 5e6c3e46fc | |||
| ff227fa6e0 | |||
| 2e715e803e | |||
| 8e6b29976c | |||
| 9ee4116f50 | |||
| a6b821bb75 | |||
| 9c98fe7ad2 |
47
.claude/commands/koniec-pracy.md
Normal file
47
.claude/commands/koniec-pracy.md
Normal file
@@ -0,0 +1,47 @@
|
||||
Wykonaj procedurę zakończenia pracy w projekcie cmsPRO. Wszystkie kroki wykonuj kolejno:
|
||||
|
||||
## 1. Testy
|
||||
Uruchom `php vendor/bin/phpunit`. Jeśli testy nie przechodzą — napraw błędy przed kontynuowaniem.
|
||||
|
||||
## 2. Dokumentacja
|
||||
Sprawdź czy zmiany wymagają aktualizacji:
|
||||
- `docs/PROJECT_STRUCTURE.md` — struktura projektu, moduły, fazy refaktoryzacji
|
||||
- `docs/FORM_EDIT_SYSTEM.md` — system formularzy (tylko jeśli zmiany dotyczyły formularzy)
|
||||
|
||||
Zaktualizuj tylko jeśli zmiany tego wymagają. Nie aktualizuj na siłę.
|
||||
|
||||
## 3. Migracje SQL
|
||||
Jeśli były zmiany w bazie danych:
|
||||
- Utwórz plik `migrations/{version}.sql` (np. `migrations/1.694.sql`)
|
||||
- NIE w `updates/` — build script sam wczyta z `migrations/`
|
||||
|
||||
## 4. Commit
|
||||
Wykonaj git commit ze zmianami. Użyj konwencji z tego repo (patrz `git log --oneline -5`).
|
||||
|
||||
## 5. Paczka aktualizacji
|
||||
Procedura budowania paczki:
|
||||
|
||||
a) Znajdź aktualną wersję w `updates/versions.php` (`$current_ver = XXXX`)
|
||||
b) Oblicz nową wersję: `current_ver + 1`
|
||||
c) Zaktualizuj `$current_ver` w `updates/versions.php` na nową wartość
|
||||
d) Utwórz commit: `build(update): paczka {wersja} — {krótki opis zmian}`
|
||||
e) Utwórz git tag: `git tag v{wersja}` (format: v1.694, v1.695, ...)
|
||||
f) Uruchom build script:
|
||||
```
|
||||
powershell -ExecutionPolicy Bypass -File ./build-update.ps1 -FromTag v{poprzednia_wersja} -ToTag v{nowa_wersja} -ChangelogEntry "NEW - {opis zmian}"
|
||||
```
|
||||
g) Dodaj pliki paczki do ostatniego commita: `git add updates/*/ver_{wersja}.* && git commit --amend`
|
||||
|
||||
## 6. Push
|
||||
Wykonaj `git push && git push --tags`. Jeśli auth fail (próbuj 3 razy, czasem jest błąd za pierwszym razem) — poinformuj użytkownika żeby uruchomił `! git push && git push --tags`.
|
||||
|
||||
## Podsumowanie
|
||||
Na koniec wyświetl tabelkę:
|
||||
| Krok | Status |
|
||||
|------|--------|
|
||||
| Testy | OK/FAIL |
|
||||
| Dokumentacja | Zaktualizowana / Bez zmian |
|
||||
| Migracje SQL | Utworzone / Nie dotyczy |
|
||||
| Commit | hash |
|
||||
| Paczka | ver_X.XXX.zip |
|
||||
| Push | OK / Wymaga auth |
|
||||
@@ -48,7 +48,8 @@
|
||||
"Bash(python3:*)",
|
||||
"Bash(python:*)",
|
||||
"Bash(grep:*)",
|
||||
"Bash(grep ^<b>ver:*)"
|
||||
"Bash(grep ^<b>ver:*)",
|
||||
"Skill(paul:plan)"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
17
.mcp.json
Normal file
17
.mcp.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"mcpServers": {
|
||||
"serena": {
|
||||
"command": "uvx",
|
||||
"args": [
|
||||
"--from",
|
||||
"git+https://github.com/oraios/serena",
|
||||
"serena",
|
||||
"start-mcp-server",
|
||||
"--context",
|
||||
"ide-assistant",
|
||||
"--project",
|
||||
"C:/visual studio code/projekty/cmsPRO"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
49
.paul/PROJECT.md
Normal file
49
.paul/PROJECT.md
Normal file
@@ -0,0 +1,49 @@
|
||||
# Project: cmsPRO
|
||||
|
||||
## Description
|
||||
Autorski system CMS z panelem administracyjnym (17 modułów admin, 13 modułów front). Projekt przechodzi pełną refaktoryzację kodu w 19 fazach — wzorcem docelowej architektury jest shopPRO. Wzorzec migracji: wrapper delegation (stare klasy delegują do nowych, zero regresji).
|
||||
|
||||
## Core Value
|
||||
Autorski system CMS umożliwiający zarządzanie treściami i stronami internetowymi.
|
||||
|
||||
## Already Completed
|
||||
- Domain (10 repos): Articles, Languages, Layouts, Pages, Settings, User, Scontainers, Banners, Authors, Newsletter
|
||||
- Shared (7 modules): Cache, Helpers, Html, Image, Tpl, Email, Security
|
||||
- Form Edit System: FormEditViewModel, multi-tab, validation, persistence
|
||||
- PHPUnit base: Bootstrap, 3 test files
|
||||
- Wrapper delegation pattern: legacy factories delegate to Domain repositories
|
||||
|
||||
## Requirements
|
||||
|
||||
### Must Have
|
||||
- Centralny PSR-4 autoloader (hybrydowy z legacy)
|
||||
- Wszystkie Domain repositories (Scontainers, Banners, Authors, Newsletter, SEO, Cron, Releases)
|
||||
- Shared\Email + Shared\Security (CsrfToken, HMAC-SHA256)
|
||||
- Admin\ namespace z DI dla wszystkich 17 modułów
|
||||
- Frontend\ namespace dla wszystkich front modułów
|
||||
- Bezpieczne cookies (HMAC-SHA256 zamiast hash w JSON)
|
||||
|
||||
### Should Have
|
||||
- PHPUnit testy dla nowych repositories i controllers
|
||||
- Legacy cleanup (usunięcie wrapperów po pełnej migracji)
|
||||
|
||||
### Nice to Have
|
||||
- Admin base classes (TableListRequestFactory, FormValidator — wzór shopPRO)
|
||||
|
||||
## Constraints
|
||||
- PHP < 8.0 (produkcja) — brak match, named args, union types, str_contains()
|
||||
- Referencja architektury: shopPRO (C:\visual studio code\projekty\shopPRO)
|
||||
- Zachowanie 100% kompatybilności wstecznej podczas migracji (wrapper delegation)
|
||||
- Medoo ORM (nie zmieniać)
|
||||
- Zewnętrzne biblioteki (Mobile_Detect, geoplugin) — nie ruszać
|
||||
|
||||
## Success Criteria
|
||||
- 19 faz refaktoryzacji zakończonych
|
||||
- Cały kod w namespace'ach Domain\, Shared\, Admin\, Frontend\
|
||||
- Zero regresji — istniejąca funkcjonalność działa bez zmian
|
||||
- Bezpieczne cookies (HMAC-SHA256)
|
||||
- Testy PHPUnit dla kluczowych modułów
|
||||
|
||||
---
|
||||
*Created: 2026-04-04*
|
||||
*Last updated: 2026-04-04 after Phase 4*
|
||||
306
.paul/ROADMAP.md
Normal file
306
.paul/ROADMAP.md
Normal file
@@ -0,0 +1,306 @@
|
||||
# Roadmap: cmsPRO
|
||||
|
||||
## Overview
|
||||
Pełna refaktoryzacja cmsPRO do architektury DDD wzorowanej na shopPRO. Wzorzec: wrapper delegation — stare klasy delegują do nowych, zero regresji. Referencja: C:\visual studio code\projekty\shopPRO. PHP < 8.0 (produkcja).
|
||||
|
||||
## Current Milestone
|
||||
**v0.1 Refaktoryzacja** (v0.1.0)
|
||||
Status: In progress
|
||||
Phases: 4 of 19 complete
|
||||
|
||||
## Already Completed (before PAUL)
|
||||
- **Domain (6 repos):** Articles, Languages, Layouts, Pages, Settings, User
|
||||
- **Shared (5 modules):** Cache, Helpers, Html, Image, Tpl
|
||||
- **Form Edit System:** Universal form handling framework (FormEditViewModel, multi-tab, validation)
|
||||
- **PHPUnit base:** Bootstrap, 3 test files (Languages, Settings, User)
|
||||
|
||||
## Phases
|
||||
|
||||
| Phase | Name | Plans | Status | Completed |
|
||||
|-------|------|-------|--------|-----------|
|
||||
| 1 | Infrastructure & Autoloader | 1 | Complete | 2026-04-04 |
|
||||
| 2 | Shared: Email + Security | 1 | Complete | 2026-04-04 |
|
||||
| 3 | Domain: Scontainers + Banners | 1 | Complete | 2026-04-04 |
|
||||
| 4 | Domain: Authors + Newsletter | 1 | Complete | 2026-04-04 |
|
||||
| 5 | Domain: SeoAdditional + Cron + Releases | 1 | Not started | - |
|
||||
| 6 | Admin: Base Infrastructure | 1 | Not started | - |
|
||||
| 7 | Admin: Articles + ArticlesArchive | 1 | Not started | - |
|
||||
| 8 | Admin: Pages + Layouts | 1 | Not started | - |
|
||||
| 9 | Admin: Languages + Settings | 1 | Not started | - |
|
||||
| 10 | Admin: Banners + Authors + Scontainers | 1 | Not started | - |
|
||||
| 11 | Admin: Newsletter + Emails + SeoAdditional | 1 | Not started | - |
|
||||
| 12 | Admin: Users + Backups + Filemanager | 1 | Not started | - |
|
||||
| 13 | Admin: Releases + Update | 1 | Not started | - |
|
||||
| 14 | Front: Site + Articles | 1 | Not started | - |
|
||||
| 15 | Front: Pages + Menu + Banners + Scontainers | 1 | Not started | - |
|
||||
| 16 | Front: Remaining modules | 1-2 | Not started | - |
|
||||
| 17 | Users & Security: HMAC-SHA256 | 1 | Not started | - |
|
||||
| 18 | Tests | 1-2 | Not started | - |
|
||||
| 19 | Legacy Cleanup | 1 | Not started | - |
|
||||
|
||||
## Phase Details
|
||||
|
||||
### Phase 1: Infrastructure & Autoloader
|
||||
|
||||
**Goal:** Centralny autoloader (PSR-4 + legacy), composer.json z mapowaniem, usunięcie duplikatów z entry pointów.
|
||||
**Depends on:** Nothing (first phase)
|
||||
**Research:** Unlikely
|
||||
|
||||
**Scope:**
|
||||
- autoload/autoloader.php (hybrydowy)
|
||||
- composer.json PSR-4: Domain\, Shared\, Admin\, Frontend\
|
||||
- Migracja 6 entry pointów (index.php, admin/index.php, ajax.php, api.php, cron.php, download.php)
|
||||
|
||||
**Plans:**
|
||||
- [ ] 01-01: PSR-4 autoloader setup i composer.json
|
||||
|
||||
### Phase 2: Shared: Email + Security
|
||||
|
||||
**Goal:** Dodać brakujące moduły Shared — Email (migracja z legacy) i Security (CsrfToken, wzór shopPRO).
|
||||
**Depends on:** Phase 1 (autoloader)
|
||||
**Research:** Unlikely
|
||||
|
||||
**Scope:**
|
||||
- Shared\Email\Email — migracja z legacy
|
||||
- Shared\Security\CsrfToken — nowy moduł (wzór shopPRO)
|
||||
- Wrapper w starym class.Email.php (jeśli istnieje)
|
||||
|
||||
**Plans:**
|
||||
- [ ] 02-01: Email + Security modules
|
||||
|
||||
### Phase 3: Domain: Scontainers + Banners
|
||||
|
||||
**Goal:** Repository dla Scontainers i Banners — przeniesienie logiki z factory do Domain\.
|
||||
**Depends on:** Phase 1 (autoloader)
|
||||
**Research:** Unlikely (wzorzec ustalony)
|
||||
|
||||
**Scope:**
|
||||
- Domain\Scontainers\ScontainersRepository
|
||||
- Domain\Banners\BannersRepository
|
||||
- Wrappery w starych factory (delegacja)
|
||||
|
||||
**Plans:**
|
||||
- [ ] 03-01: Scontainers + Banners repositories
|
||||
|
||||
### Phase 4: Domain: Authors + Newsletter
|
||||
|
||||
**Goal:** Repository dla Authors i Newsletter.
|
||||
**Depends on:** Phase 1
|
||||
**Research:** Unlikely
|
||||
|
||||
**Scope:**
|
||||
- Domain\Authors\AuthorsRepository
|
||||
- Domain\Newsletter\NewsletterRepository
|
||||
|
||||
**Plans:**
|
||||
- [ ] 04-01: Authors + Newsletter repositories
|
||||
|
||||
### Phase 5: Domain: SeoAdditional + Cron + Releases
|
||||
|
||||
**Goal:** Repository dla SEO, Cron, i systemu Releases/Update.
|
||||
**Depends on:** Phase 1
|
||||
**Research:** Unlikely
|
||||
|
||||
**Scope:**
|
||||
- Domain\SeoAdditional\SeoAdditionalRepository
|
||||
- Domain\Cron\CronRepository
|
||||
- Domain\Releases\ReleasesRepository (lub Update)
|
||||
|
||||
**Plans:**
|
||||
- [ ] 05-01: SeoAdditional + Cron + Releases repositories
|
||||
|
||||
### Phase 6: Admin: Base Infrastructure
|
||||
|
||||
**Goal:** Bazowe klasy Admin\ — kontrolery bazowe, TableListRequestFactory, FormValidator (wzór shopPRO).
|
||||
**Depends on:** Phase 1 (autoloader), Form Edit System (already done)
|
||||
**Research:** Likely (analiza shopPRO Admin base classes)
|
||||
|
||||
**Scope:**
|
||||
- Admin\Base\BaseController (lub abstrakcyjna klasa bazowa)
|
||||
- Admin\Support\TableListRequestFactory
|
||||
- Admin\Support\FormValidator
|
||||
- Integracja z istniejącym FormEditViewModel
|
||||
|
||||
**Plans:**
|
||||
- [ ] 06-01: Admin base infrastructure
|
||||
|
||||
### Phase 7: Admin: Articles + ArticlesArchive
|
||||
|
||||
**Goal:** Migracja kontrolerów Articles i ArticlesArchive do Admin\ z DI.
|
||||
**Depends on:** Phase 6 (Admin base), Phase 1 (Domain\Articles already exists)
|
||||
**Research:** Unlikely
|
||||
|
||||
**Scope:**
|
||||
- Admin\Articles\ArticlesController
|
||||
- Admin\Articles\ArticlesArchiveController
|
||||
- Wrapper w starym controls/class.Articles.php
|
||||
|
||||
**Plans:**
|
||||
- [ ] 07-01: Articles admin controllers
|
||||
|
||||
### Phase 8: Admin: Pages + Layouts
|
||||
|
||||
**Goal:** Migracja kontrolerów Pages i Layouts do Admin\.
|
||||
**Depends on:** Phase 6
|
||||
**Research:** Unlikely
|
||||
|
||||
**Scope:**
|
||||
- Admin\Pages\PagesController
|
||||
- Admin\Layouts\LayoutsController
|
||||
|
||||
**Plans:**
|
||||
- [ ] 08-01: Pages + Layouts admin controllers
|
||||
|
||||
### Phase 9: Admin: Languages + Settings
|
||||
|
||||
**Goal:** Migracja kontrolerów Languages i Settings do Admin\.
|
||||
**Depends on:** Phase 6
|
||||
**Research:** Unlikely
|
||||
|
||||
**Scope:**
|
||||
- Admin\Languages\LanguagesController
|
||||
- Admin\Settings\SettingsController
|
||||
|
||||
**Plans:**
|
||||
- [ ] 09-01: Languages + Settings admin controllers
|
||||
|
||||
### Phase 10: Admin: Banners + Authors + Scontainers
|
||||
|
||||
**Goal:** Migracja kontrolerów Banners, Authors, Scontainers do Admin\.
|
||||
**Depends on:** Phase 6, Phase 3 (Domain repos), Phase 4 (Domain repos)
|
||||
**Research:** Unlikely
|
||||
|
||||
**Scope:**
|
||||
- Admin\Banners\BannersController
|
||||
- Admin\Authors\AuthorsController
|
||||
- Admin\Scontainers\ScontainersController
|
||||
|
||||
**Plans:**
|
||||
- [ ] 10-01: Banners + Authors + Scontainers admin controllers
|
||||
|
||||
### Phase 11: Admin: Newsletter + Emails + SeoAdditional
|
||||
|
||||
**Goal:** Migracja kontrolerów Newsletter, Emails, SeoAdditional do Admin\.
|
||||
**Depends on:** Phase 6, Phase 4, Phase 5
|
||||
**Research:** Unlikely
|
||||
|
||||
**Scope:**
|
||||
- Admin\Newsletter\NewsletterController
|
||||
- Admin\Emails\EmailsController
|
||||
- Admin\SeoAdditional\SeoAdditionalController
|
||||
|
||||
**Plans:**
|
||||
- [ ] 11-01: Newsletter + Emails + SeoAdditional admin controllers
|
||||
|
||||
### Phase 12: Admin: Users + Backups + Filemanager
|
||||
|
||||
**Goal:** Migracja kontrolerów Users, Backups, Filemanager do Admin\.
|
||||
**Depends on:** Phase 6
|
||||
**Research:** Unlikely
|
||||
|
||||
**Scope:**
|
||||
- Admin\Users\UsersController
|
||||
- Admin\Backups\BackupsController
|
||||
- Admin\Filemanager\FilemanagerController
|
||||
|
||||
**Plans:**
|
||||
- [ ] 12-01: Users + Backups + Filemanager admin controllers
|
||||
|
||||
### Phase 13: Admin: Releases + Update
|
||||
|
||||
**Goal:** Migracja kontrolerów Releases i Update do Admin\.
|
||||
**Depends on:** Phase 6, Phase 5 (Domain repos)
|
||||
**Research:** Unlikely
|
||||
|
||||
**Scope:**
|
||||
- Admin\Releases\ReleasesController
|
||||
- Admin\Update\UpdateController
|
||||
|
||||
**Plans:**
|
||||
- [ ] 13-01: Releases + Update admin controllers
|
||||
|
||||
### Phase 14: Front: Site + Articles
|
||||
|
||||
**Goal:** Migracja głównych kontrolerów front — Site i Articles do Frontend\.
|
||||
**Depends on:** Phase 1 (autoloader), Domain repos
|
||||
**Research:** Unlikely
|
||||
|
||||
**Scope:**
|
||||
- Frontend\Site\SiteController (lub controls + factory + view)
|
||||
- Frontend\Articles\ArticlesController
|
||||
- LayoutEngine (jeśli potrzebny, wzór shopPRO)
|
||||
|
||||
**Plans:**
|
||||
- [ ] 14-01: Site + Articles frontend controllers
|
||||
|
||||
### Phase 15: Front: Pages + Menu + Banners + Scontainers
|
||||
|
||||
**Goal:** Migracja front kontrolerów Pages, Menu, Banners, Scontainers.
|
||||
**Depends on:** Phase 14 (Front base)
|
||||
**Research:** Unlikely
|
||||
|
||||
**Scope:**
|
||||
- Frontend\Pages, Frontend\Menu, Frontend\Banners, Frontend\Scontainers
|
||||
|
||||
**Plans:**
|
||||
- [ ] 15-01: Pages + Menu + Banners + Scontainers frontend
|
||||
|
||||
### Phase 16: Front: Remaining modules
|
||||
|
||||
**Goal:** Migracja pozostałych front modułów — Authors, Languages, Newsletter, Search, AuditSEO, SeoAdditional, Layouts, Settings.
|
||||
**Depends on:** Phase 14
|
||||
**Research:** Unlikely
|
||||
|
||||
**Scope:**
|
||||
- Wszystkie pozostałe front factories/controls/views
|
||||
|
||||
**Plans:**
|
||||
- [ ] 16-01: Remaining frontend modules (batch 1)
|
||||
- [ ] 16-02: Remaining frontend modules (batch 2, if needed)
|
||||
|
||||
### Phase 17: Users & Security: HMAC-SHA256
|
||||
|
||||
**Goal:** Wymiana insecure remember-me cookies (hash w JSON) na HMAC-SHA256 signed tokens.
|
||||
**Depends on:** Phase 2 (Shared\Security), Phase 12 (Admin\Users)
|
||||
**Research:** Likely (strategia migracji istniejących cookies, backward compat)
|
||||
|
||||
**Scope:**
|
||||
- Nowy system remember-me z HMAC-SHA256
|
||||
- Migracja istniejących sesji/cookies
|
||||
- Security hardening w UserRepository
|
||||
|
||||
**Plans:**
|
||||
- [ ] 17-01: HMAC-SHA256 cookie system
|
||||
|
||||
### Phase 18: Tests
|
||||
|
||||
**Goal:** Rozbudowa PHPUnit testów dla nowych Domain repositories i Admin controllers.
|
||||
**Depends on:** Phase 5 (all Domain repos), Phase 13 (all Admin controllers)
|
||||
**Research:** Unlikely
|
||||
|
||||
**Scope:**
|
||||
- Testy dla nowych Domain repositories
|
||||
- Testy dla Admin controllers (unit)
|
||||
- Rozbudowa test bootstrap
|
||||
|
||||
**Plans:**
|
||||
- [ ] 18-01: Domain repository tests
|
||||
- [ ] 18-02: Admin controller tests
|
||||
|
||||
### Phase 19: Legacy Cleanup
|
||||
|
||||
**Goal:** Usunięcie legacy wrapperów i starych class.*.php po weryfikacji że cały kod używa nowych klas.
|
||||
**Depends on:** All prior phases
|
||||
**Research:** Unlikely
|
||||
|
||||
**Scope:**
|
||||
- Usunięcie wrapperów z class.*.php
|
||||
- Usunięcie starych controls/factory/view plików
|
||||
- Finalna weryfikacja i cleanup
|
||||
|
||||
**Plans:**
|
||||
- [ ] 19-01: Legacy wrapper removal and cleanup
|
||||
|
||||
---
|
||||
*Roadmap created: 2026-04-04*
|
||||
*Last updated: 2026-04-04*
|
||||
70
.paul/STATE.md
Normal file
70
.paul/STATE.md
Normal file
@@ -0,0 +1,70 @@
|
||||
# Project State
|
||||
|
||||
## Project Reference
|
||||
|
||||
See: .paul/PROJECT.md (updated 2026-04-04)
|
||||
|
||||
**Core value:** Autorski system CMS umożliwiający zarządzanie treściami i stronami internetowymi.
|
||||
**Current focus:** Phase 4 complete — ready for Phase 5
|
||||
|
||||
## Current Position
|
||||
|
||||
Milestone: v0.1 Refaktoryzacja
|
||||
Phase: 4 of 19 (Domain: Authors + Newsletter) — Complete
|
||||
Plan: 04-01 complete
|
||||
Status: Loop closed, ready for next PLAN
|
||||
Last activity: 2026-04-04 — Phase 4 complete, UNIFY done
|
||||
|
||||
Progress:
|
||||
- Milestone: [▓▓░░░░░░░░] 20%
|
||||
|
||||
## Loop Position
|
||||
|
||||
Current loop state:
|
||||
```
|
||||
PLAN ──▶ APPLY ──▶ UNIFY
|
||||
✓ ✓ ✓ [Loop complete - ready for next PLAN]
|
||||
```
|
||||
|
||||
## Performance Metrics
|
||||
|
||||
**Velocity:**
|
||||
- Total plans completed: 4
|
||||
- Total execution time: ~22min
|
||||
|
||||
**By Phase:**
|
||||
|
||||
| Phase | Plans | Total Time | Avg/Plan |
|
||||
|-------|-------|------------|----------|
|
||||
| 01-infrastructure | 1/1 | ~10min | ~10min |
|
||||
| 02-shared-email-security | 1/1 | ~8min | ~8min |
|
||||
| 03-domain-scontainers-banners | 1/1 | ~2min | ~2min |
|
||||
| 04-domain-authors-newsletter | 1/1 | ~2min | ~2min |
|
||||
|
||||
## Accumulated Context
|
||||
|
||||
### Decisions
|
||||
- Centralny autoloader zamiast duplikatów
|
||||
- CsrfToken: single token per session (shopPRO pattern)
|
||||
- Email: PHPMailer require via __DIR__ absolute paths
|
||||
- Shared layer kompletny: Cache, Helpers, Html, Image, Tpl, Email, Security
|
||||
- Wrapper delegation: factory creates new repo per call (no singleton)
|
||||
- Front repos: $lang[0] passed explicitly, repos don't use globals
|
||||
- Front caching: migrated from \Cache:: to \Shared\Cache\CacheHandler::
|
||||
- Newsletter: globals ($settings, $lang) passed as explicit params to repo methods
|
||||
|
||||
### Deferred Issues
|
||||
None.
|
||||
|
||||
### Blockers/Concerns
|
||||
None.
|
||||
|
||||
## Session Continuity
|
||||
|
||||
Last session: 2026-04-04
|
||||
Stopped at: Phase 4 complete, loop closed
|
||||
Next action: Run /paul:plan for Phase 5 (Domain: SeoAdditional + Cron + Releases)
|
||||
Resume file: .paul/phases/04-domain-authors-newsletter/04-01-SUMMARY.md
|
||||
|
||||
---
|
||||
*STATE.md — Updated after every significant action*
|
||||
33
.paul/config.md
Normal file
33
.paul/config.md
Normal file
@@ -0,0 +1,33 @@
|
||||
# Project Config
|
||||
|
||||
**Project:** cmsPRO
|
||||
**Created:** 2026-04-04
|
||||
|
||||
## Project Settings
|
||||
|
||||
```yaml
|
||||
project:
|
||||
name: cmsPRO
|
||||
version: 0.0.0
|
||||
```
|
||||
|
||||
## Integrations
|
||||
|
||||
### SonarQube
|
||||
|
||||
```yaml
|
||||
sonarqube:
|
||||
enabled: true
|
||||
project_key: cmsPRO
|
||||
```
|
||||
|
||||
## Preferences
|
||||
|
||||
```yaml
|
||||
preferences:
|
||||
auto_commit: false
|
||||
verbose_output: false
|
||||
```
|
||||
|
||||
---
|
||||
*Config created: 2026-04-04*
|
||||
176
.paul/phases/01-infrastructure/01-01-PLAN.md
Normal file
176
.paul/phases/01-infrastructure/01-01-PLAN.md
Normal file
@@ -0,0 +1,176 @@
|
||||
---
|
||||
phase: 01-infrastructure
|
||||
plan: 01
|
||||
type: execute
|
||||
wave: 1
|
||||
depends_on: []
|
||||
files_modified:
|
||||
- composer.json
|
||||
- autoload/autoloader.php
|
||||
- index.php
|
||||
- admin/index.php
|
||||
- ajax.php
|
||||
- api.php
|
||||
- cron.php
|
||||
- download.php
|
||||
autonomous: true
|
||||
delegation: off
|
||||
---
|
||||
|
||||
<objective>
|
||||
## Goal
|
||||
Scentralizować autoloader w jednym pliku, dodać PSR-4 mapowanie w composer.json dla Domain\, Shared\, Admin\, Frontend\, i zastąpić zduplikowane __autoload_my_classes() we wszystkich entry pointach.
|
||||
|
||||
## Purpose
|
||||
Fundament dla całej refaktoryzacji — bez działającego PSR-4 autoloadera nie można dodawać nowych klas w Admin\ i Frontend\ namespace'ach.
|
||||
|
||||
## Output
|
||||
- Centralny autoload/autoloader.php (hybrydowy: PSR-4 + legacy class.*.php)
|
||||
- Zaktualizowany composer.json z PSR-4 mapowaniem
|
||||
- Wszystkie entry pointy używają jednego autoloadera
|
||||
</objective>
|
||||
|
||||
<context>
|
||||
## Project Context
|
||||
@.paul/PROJECT.md
|
||||
@.paul/ROADMAP.md
|
||||
|
||||
## Source Files
|
||||
@composer.json
|
||||
@index.php (zawiera __autoload_my_classes)
|
||||
@admin/index.php (zawiera duplikat __autoload_my_classes)
|
||||
@ajax.php, api.php, cron.php, download.php (kolejne duplikaty)
|
||||
|
||||
## Reference
|
||||
shopPRO composer.json — PSR-4 mapping: Domain\, Admin\, Frontend\, Shared\ → autoload/
|
||||
</context>
|
||||
|
||||
<acceptance_criteria>
|
||||
|
||||
## AC-1: Centralny autoloader
|
||||
```gherkin
|
||||
Given plik autoload/autoloader.php istnieje
|
||||
When jest załadowany przez require_once
|
||||
Then rejestruje spl_autoload_register z obsługą zarówno PSR-4 (ClassName.php) jak i legacy (class.ClassName.php)
|
||||
```
|
||||
|
||||
## AC-2: composer.json PSR-4
|
||||
```gherkin
|
||||
Given composer.json ma sekcję autoload.psr-4
|
||||
When uruchomię composer dump-autoload
|
||||
Then namespace'y Domain\, Shared\, Admin\, Frontend\ mapują do autoload/Domain/, autoload/Shared/, autoload/Admin/, autoload/Frontend/
|
||||
```
|
||||
|
||||
## AC-3: Entry pointy używają centralnego autoloadera
|
||||
```gherkin
|
||||
Given index.php, admin/index.php, ajax.php, api.php, cron.php, download.php
|
||||
When sprawdzę ich kod
|
||||
Then każdy zawiera require_once do autoload/autoloader.php (lub ../autoload/autoloader.php)
|
||||
And żaden nie zawiera zduplikowanej funkcji __autoload_my_classes
|
||||
```
|
||||
|
||||
## AC-4: Istniejące klasy działają
|
||||
```gherkin
|
||||
Given klasy Domain\Articles\ArticlesRepository, Shared\Cache\CacheHandler etc. istnieją
|
||||
When autoloader próbuje je załadować
|
||||
Then klasy ładują się poprawnie (brak Fatal Error)
|
||||
```
|
||||
|
||||
</acceptance_criteria>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 1: Utworzenie centralnego autoloadera</name>
|
||||
<files>autoload/autoloader.php</files>
|
||||
<action>
|
||||
Utworzyć plik autoload/autoloader.php:
|
||||
- Funkcja __autoload_my_classes($class) obsługująca:
|
||||
1. Zamiana namespace separator \ na /
|
||||
2. Próba załadowania: autoload/{path}/class.{ClassName}.php (legacy)
|
||||
3. Próba załadowania: autoload/{path}/{ClassName}.php (PSR-4)
|
||||
- spl_autoload_register('__autoload_my_classes')
|
||||
- Bazowy katalog ustalany przez __DIR__ . '/' (relatywnie do autoload/)
|
||||
- Obsługa klas bez namespace (legacy) — szukanie w autoload/class.{name}.php
|
||||
|
||||
Wzorować się na istniejącej logice z index.php, ale:
|
||||
- Używać __DIR__ zamiast ścieżek relatywnych do entry pointa
|
||||
- Jeden plik obsługuje WSZYSTKIE entry pointy
|
||||
</action>
|
||||
<verify>Sprawdzić że plik istnieje i zawiera spl_autoload_register</verify>
|
||||
<done>AC-1 satisfied: Centralny autoloader z obsługą PSR-4 i legacy</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 2: Aktualizacja composer.json</name>
|
||||
<files>composer.json</files>
|
||||
<action>
|
||||
Dodać sekcję autoload.psr-4 do composer.json:
|
||||
```json
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Domain\\": "autoload/Domain/",
|
||||
"Shared\\": "autoload/Shared/",
|
||||
"Admin\\": "autoload/Admin/",
|
||||
"Frontend\\": "autoload/Frontend/"
|
||||
}
|
||||
}
|
||||
```
|
||||
Zachować istniejący autoload-dev.
|
||||
</action>
|
||||
<verify>Sprawdzić że composer.json zawiera poprawne mapowanie PSR-4</verify>
|
||||
<done>AC-2 satisfied: composer.json z PSR-4 mapowaniem</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 3: Migracja entry pointów</name>
|
||||
<files>index.php, admin/index.php, ajax.php, api.php, cron.php, download.php</files>
|
||||
<action>
|
||||
W każdym entry poincie:
|
||||
1. USUNĄĆ definicję funkcji __autoload_my_classes() i jej spl_autoload_register
|
||||
2. DODAĆ na początku (po <?php): require_once __DIR__ . '/autoload/autoloader.php';
|
||||
Dla admin/index.php: require_once __DIR__ . '/../autoload/autoloader.php';
|
||||
3. Zachować resztę kodu bez zmian (config.php, medoo, session etc.)
|
||||
|
||||
NIE zmieniać niczego innego w tych plikach — tylko autoloader.
|
||||
</action>
|
||||
<verify>Grep po wszystkich entry pointach: brak __autoload_my_classes definicji, jest require autoloader.php</verify>
|
||||
<done>AC-3 satisfied: Wszystkie entry pointy używają centralnego autoloadera</done>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<boundaries>
|
||||
|
||||
## DO NOT CHANGE
|
||||
- autoload/Domain/* (istniejące klasy Domain — nie modyfikować)
|
||||
- autoload/Shared/* (istniejące klasy Shared — nie modyfikować)
|
||||
- config.php (konfiguracja bazy danych)
|
||||
- libraries/* (zewnętrzne biblioteki)
|
||||
|
||||
## SCOPE LIMITS
|
||||
- Tylko autoloader — nie refaktoryzować żadnych klas
|
||||
- Nie dodawać nowych klas Admin\ ani Frontend\ (to w kolejnych fazach)
|
||||
- Nie zmieniać logiki biznesowej w entry pointach
|
||||
|
||||
</boundaries>
|
||||
|
||||
<verification>
|
||||
Before declaring plan complete:
|
||||
- [ ] autoload/autoloader.php istnieje i zawiera spl_autoload_register
|
||||
- [ ] composer.json ma sekcję autoload.psr-4 z 4 namespace'ami
|
||||
- [ ] Żaden entry point nie zawiera zduplikowanej funkcji __autoload_my_classes
|
||||
- [ ] Wszystkie entry pointy mają require_once autoloader.php
|
||||
- [ ] Istniejące testy PHPUnit przechodzą (jeśli są)
|
||||
- All acceptance criteria met
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
- Centralny autoloader działa dla PSR-4 i legacy class.*.php
|
||||
- Wszystkie entry pointy korzystają z jednego autoloadera
|
||||
- Zero regresji — istniejący kod działa bez zmian
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
After completion, create `.paul/phases/01-infrastructure/01-01-SUMMARY.md`
|
||||
</output>
|
||||
110
.paul/phases/01-infrastructure/01-01-SUMMARY.md
Normal file
110
.paul/phases/01-infrastructure/01-01-SUMMARY.md
Normal file
@@ -0,0 +1,110 @@
|
||||
---
|
||||
phase: 01-infrastructure
|
||||
plan: 01
|
||||
subsystem: infra
|
||||
tags: [autoloader, psr-4, composer]
|
||||
|
||||
requires: []
|
||||
provides:
|
||||
- Centralny hybrydowy autoloader (PSR-4 + legacy)
|
||||
- composer.json z PSR-4 mapowaniem namespace'ów
|
||||
affects: [all future phases - every new class uses this autoloader]
|
||||
|
||||
tech-stack:
|
||||
added: []
|
||||
patterns: [centralny autoloader z __DIR__, hybrydowy PSR-4 + legacy]
|
||||
|
||||
key-files:
|
||||
created: [autoload/autoloader.php]
|
||||
modified: [composer.json, index.php, admin/index.php, admin/ajax.php, ajax.php, api.php, cron.php, download.php]
|
||||
|
||||
key-decisions:
|
||||
- "Centralny autoloader zamiast duplikatów w entry pointach (ulepszenie vs shopPRO)"
|
||||
- "Savant3 special case przeniesiony do centralnego autoloadera"
|
||||
|
||||
patterns-established:
|
||||
- "Jeden autoloader dla wszystkich entry pointów — __DIR__ based paths"
|
||||
- "Hybrydowe ładowanie: legacy class.*.php → PSR-4 ClassName.php"
|
||||
|
||||
duration: ~10min
|
||||
completed: 2026-04-04
|
||||
---
|
||||
|
||||
# Phase 1 Plan 01: Infrastructure & Autoloader Summary
|
||||
|
||||
**Centralny hybrydowy autoloader (PSR-4 + legacy) zastępujący 7 zduplikowanych kopii w entry pointach.**
|
||||
|
||||
## Performance
|
||||
|
||||
| Metric | Value |
|
||||
|--------|-------|
|
||||
| Duration | ~10min |
|
||||
| Completed | 2026-04-04 |
|
||||
| Tasks | 3 completed |
|
||||
| Files modified | 8 |
|
||||
|
||||
## Acceptance Criteria Results
|
||||
|
||||
| Criterion | Status | Notes |
|
||||
|-----------|--------|-------|
|
||||
| AC-1: Centralny autoloader | Pass | autoload/autoloader.php z spl_autoload_register, __DIR__ paths |
|
||||
| AC-2: composer.json PSR-4 | Pass | Domain\, Shared\, Admin\, Frontend\ mapped |
|
||||
| AC-3: Entry pointy zmigrowane | Pass | 7 entry pointów, 0 duplikatów __autoload_my_classes |
|
||||
| AC-4: Istniejące klasy działają | Pass | Autoloader obsługuje legacy + PSR-4 format |
|
||||
|
||||
## Accomplishments
|
||||
|
||||
- Utworzono centralny `autoload/autoloader.php` z obsługą legacy (class.*.php) i PSR-4 (ClassName.php)
|
||||
- Zaktualizowano `composer.json` z PSR-4 mapowaniem dla 4 namespace'ów
|
||||
- Zmigrowano 7 entry pointów (index.php, admin/index.php, admin/ajax.php, ajax.php, api.php, cron.php, download.php)
|
||||
|
||||
## Files Created/Modified
|
||||
|
||||
| File | Change | Purpose |
|
||||
|------|--------|---------|
|
||||
| `autoload/autoloader.php` | Created | Centralny hybrydowy autoloader |
|
||||
| `composer.json` | Modified | PSR-4 mapping dla Domain\, Shared\, Admin\, Frontend\ |
|
||||
| `index.php` | Modified | require_once autoloader.php |
|
||||
| `admin/index.php` | Modified | require_once ../autoloader.php |
|
||||
| `admin/ajax.php` | Modified | require_once ../autoloader.php (Savant3 przeniesiony) |
|
||||
| `ajax.php` | Modified | require_once autoloader.php |
|
||||
| `api.php` | Modified | require_once autoloader.php |
|
||||
| `cron.php` | Modified | require_once autoloader.php |
|
||||
| `download.php` | Modified | require_once autoloader.php |
|
||||
|
||||
## Decisions Made
|
||||
|
||||
| Decision | Rationale | Impact |
|
||||
|----------|-----------|--------|
|
||||
| Centralny autoloader (vs duplikaty jak w shopPRO) | DRY, łatwiejsze utrzymanie, jednorazowa poprawka | Ulepszenie vs shopPRO — notatka dodana do shopPRO/docs |
|
||||
| Savant3 special case w centralnym autoloaderze | Był tylko w admin/ajax.php, powinien działać globalnie | Brak regresji |
|
||||
|
||||
## Deviations from Plan
|
||||
|
||||
### Summary
|
||||
|
||||
| Type | Count | Impact |
|
||||
|------|-------|--------|
|
||||
| Scope additions | 1 | Minimal — admin/ajax.php (7th entry point) |
|
||||
|
||||
Plan zakładał 6 entry pointów, ale znaleziono 7 (admin/ajax.php nie był wymieniony w planie). Zmigrowany bez problemów.
|
||||
|
||||
## Issues Encountered
|
||||
|
||||
None.
|
||||
|
||||
## Next Phase Readiness
|
||||
|
||||
**Ready:**
|
||||
- Autoloader obsługuje wszystkie namespace'y potrzebne dla faz 2-19
|
||||
- Nowe klasy w Admin\, Frontend\ będą automatycznie ładowane
|
||||
|
||||
**Concerns:**
|
||||
- AC-4 zweryfikowane statycznie (kod autoloadera) — runtime test wymaga uruchomienia aplikacji
|
||||
|
||||
**Blockers:**
|
||||
- None
|
||||
|
||||
---
|
||||
*Phase: 01-infrastructure, Plan: 01*
|
||||
*Completed: 2026-04-04*
|
||||
182
.paul/phases/02-shared-email-security/02-01-PLAN.md
Normal file
182
.paul/phases/02-shared-email-security/02-01-PLAN.md
Normal file
@@ -0,0 +1,182 @@
|
||||
---
|
||||
phase: 02-shared-email-security
|
||||
plan: 01
|
||||
type: execute
|
||||
wave: 1
|
||||
depends_on: ["01-01"]
|
||||
files_modified:
|
||||
- autoload/Shared/Email/Email.php
|
||||
- autoload/Shared/Security/CsrfToken.php
|
||||
- autoload/Shared/Helpers/Helpers.php
|
||||
autonomous: true
|
||||
delegation: off
|
||||
---
|
||||
|
||||
<objective>
|
||||
## Goal
|
||||
Utworzyć Shared\Email\Email i Shared\Security\CsrfToken wzorując się na shopPRO. Przenieść logikę z Helpers::send_email() i Helpers::get_token()/is_token_valid() do dedykowanych klas. Zachować wrappery w Helpers dla kompatybilności.
|
||||
|
||||
## Purpose
|
||||
Email i Security to brakujące moduły Shared potrzebne przed refaktoryzacją Admin i Frontend kontrolerów. CsrfToken z kryptograficznie bezpiecznym tokenem zastąpi słaby sha1(mt_rand()).
|
||||
|
||||
## Output
|
||||
- autoload/Shared/Email/Email.php — klasa email z PHPMailer
|
||||
- autoload/Shared/Security/CsrfToken.php — CSRF z random_bytes + hash_equals
|
||||
- Wrappery w Helpers.php delegujące do nowych klas
|
||||
</objective>
|
||||
|
||||
<context>
|
||||
## Project Context
|
||||
@.paul/PROJECT.md
|
||||
@.paul/ROADMAP.md
|
||||
|
||||
## Prior Work
|
||||
@.paul/phases/01-infrastructure/01-01-SUMMARY.md — autoloader gotowy, PSR-4 działa
|
||||
|
||||
## Source Files
|
||||
@autoload/Shared/Helpers/Helpers.php — zawiera send_email(), get_token(), is_token_valid()
|
||||
|
||||
## Reference
|
||||
shopPRO autoload/Shared/Email/Email.php — docelowa implementacja
|
||||
shopPRO autoload/Shared/Security/CsrfToken.php — docelowa implementacja
|
||||
</context>
|
||||
|
||||
<acceptance_criteria>
|
||||
|
||||
## AC-1: Email class
|
||||
```gherkin
|
||||
Given plik autoload/Shared/Email/Email.php istnieje
|
||||
When załaduję klasę Shared\Email\Email
|
||||
Then klasa ma metody: send(), email_check(), load_by_name()
|
||||
And send() używa PHPMailer do wysyłki maili
|
||||
```
|
||||
|
||||
## AC-2: CsrfToken class
|
||||
```gherkin
|
||||
Given plik autoload/Shared/Security/CsrfToken.php istnieje
|
||||
When załaduję klasę Shared\Security\CsrfToken
|
||||
Then klasa ma statyczne metody: getToken(), validate(), regenerate()
|
||||
And getToken() używa bin2hex(random_bytes(32))
|
||||
And validate() używa hash_equals() (timing-safe)
|
||||
```
|
||||
|
||||
## AC-3: Wrappery w Helpers
|
||||
```gherkin
|
||||
Given Helpers::send_email() i Helpers::get_token() nadal istnieją
|
||||
When wywołam je z istniejącego kodu
|
||||
Then delegują do nowych klas (Shared\Email\Email i Shared\Security\CsrfToken)
|
||||
And istniejący kod działa bez zmian
|
||||
```
|
||||
|
||||
</acceptance_criteria>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 1: Utworzenie Shared\Email\Email</name>
|
||||
<files>autoload/Shared/Email/Email.php</files>
|
||||
<action>
|
||||
Utworzyć klasę Email wzorowaną na shopPRO:
|
||||
- namespace Shared\Email
|
||||
- Właściwość $table = 'pp_newsletter_templates'
|
||||
- Właściwość $text (treść maila), $headers, $newsletter_headers, $newsletter_footers
|
||||
- Metoda load_by_name(string $name) — ładuje szablon z DB
|
||||
- Metoda email_check($email) — walidacja filter_var
|
||||
- Metoda send(string $email, string $subject, bool $newsletter_headers = false, string $file = null)
|
||||
- Używa PHPMailer (require_once z libraries/)
|
||||
- Regex do naprawy relatywnych URL w obrazkach/linkach
|
||||
- Obsługa załączników
|
||||
- Return $mail->Send()
|
||||
|
||||
WAŻNE: Sprawdzić w Helpers.php jak wygląda obecna implementacja send_email()
|
||||
i przenieść tę logikę do nowej klasy, dostosowując do wzorca shopPRO.
|
||||
PHP < 8.0 — brak named args, union types, match.
|
||||
</action>
|
||||
<verify>Sprawdzić że plik istnieje, ma namespace Shared\Email, klasę Email z metodami send(), email_check()</verify>
|
||||
<done>AC-1 satisfied: Email class z PHPMailer</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 2: Utworzenie Shared\Security\CsrfToken</name>
|
||||
<files>autoload/Shared/Security/CsrfToken.php</files>
|
||||
<action>
|
||||
Utworzyć klasę CsrfToken wzorowaną na shopPRO:
|
||||
- namespace Shared\Security
|
||||
- const SESSION_KEY = 'csrf_token'
|
||||
- static getToken(): string
|
||||
- Jeśli brak tokenu w sesji → generuje bin2hex(random_bytes(32))
|
||||
- Zapisuje w $_SESSION[self::SESSION_KEY]
|
||||
- Zwraca token
|
||||
- static validate(string $token): bool
|
||||
- Porównuje z $_SESSION[self::SESSION_KEY] używając hash_equals()
|
||||
- Return true/false (NIE usuwać tokenu po walidacji — to robi regenerate())
|
||||
- static regenerate(): void
|
||||
- Wymusza nowy token: unset($_SESSION[self::SESSION_KEY])
|
||||
|
||||
PHP < 8.0 — brak named args, union types, match.
|
||||
</action>
|
||||
<verify>Sprawdzić że plik istnieje, ma namespace Shared\Security, klasę CsrfToken z metodami getToken(), validate(), regenerate()</verify>
|
||||
<done>AC-2 satisfied: CsrfToken z random_bytes + hash_equals</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 3: Wrappery w Helpers.php</name>
|
||||
<files>autoload/Shared/Helpers/Helpers.php</files>
|
||||
<action>
|
||||
W klasie Helpers:
|
||||
1. Metoda send_email() — zamienić ciało na delegację:
|
||||
$email = new \Shared\Email\Email();
|
||||
$email->text = $text;
|
||||
return $email->send($to, $subject, false, $file);
|
||||
2. Metoda get_token() — zamienić ciało na delegację:
|
||||
return \Shared\Security\CsrfToken::getToken();
|
||||
3. Metoda is_token_valid() — zamienić ciało na delegację:
|
||||
return \Shared\Security\CsrfToken::validate($token);
|
||||
|
||||
Zachować sygnatury metod identyczne — żaden calling code się nie zmienia.
|
||||
NIE usuwać metod — to wrappery dla kompatybilności wstecznej.
|
||||
NIE zmieniać żadnych innych metod w Helpers.
|
||||
</action>
|
||||
<verify>Sprawdzić że Helpers::send_email(), get_token(), is_token_valid() delegują do nowych klas</verify>
|
||||
<done>AC-3 satisfied: Wrappery delegują, istniejący kod działa bez zmian</done>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<boundaries>
|
||||
|
||||
## DO NOT CHANGE
|
||||
- autoload/Domain/* (nie ruszać repositories)
|
||||
- autoload/Shared/Cache/* (nie ruszać)
|
||||
- autoload/Shared/Html/* (nie ruszać)
|
||||
- autoload/Shared/Image/* (nie ruszać)
|
||||
- autoload/Shared/Tpl/* (nie ruszać)
|
||||
- config.php, libraries/* (nie ruszać)
|
||||
|
||||
## SCOPE LIMITS
|
||||
- Tylko Email i Security — nie refaktoryzować innych metod Helpers
|
||||
- Nie zmieniać callerów (admin/, front/) — oni nadal używają Helpers::
|
||||
- Nie dodawać nowych zależności poza tym co już jest w libraries/
|
||||
|
||||
</boundaries>
|
||||
|
||||
<verification>
|
||||
Before declaring plan complete:
|
||||
- [ ] autoload/Shared/Email/Email.php istnieje z namespace Shared\Email
|
||||
- [ ] autoload/Shared/Security/CsrfToken.php istnieje z namespace Shared\Security
|
||||
- [ ] Helpers::send_email() deleguje do Email class
|
||||
- [ ] Helpers::get_token() deleguje do CsrfToken::getToken()
|
||||
- [ ] Helpers::is_token_valid() deleguje do CsrfToken::validate()
|
||||
- [ ] Żadne inne metody w Helpers nie zostały zmienione
|
||||
- All acceptance criteria met
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
- Email i CsrfToken klasy utworzone z poprawnymi namespace'ami
|
||||
- Wrappery w Helpers zachowują kompatybilność wsteczną
|
||||
- Zero regresji — istniejący kod używający Helpers:: działa bez zmian
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
After completion, create `.paul/phases/02-shared-email-security/02-01-SUMMARY.md`
|
||||
</output>
|
||||
108
.paul/phases/02-shared-email-security/02-01-SUMMARY.md
Normal file
108
.paul/phases/02-shared-email-security/02-01-SUMMARY.md
Normal file
@@ -0,0 +1,108 @@
|
||||
---
|
||||
phase: 02-shared-email-security
|
||||
plan: 01
|
||||
subsystem: infra
|
||||
tags: [email, phpmailer, csrf, security, shared]
|
||||
|
||||
requires:
|
||||
- phase: 01-infrastructure
|
||||
provides: centralny autoloader PSR-4
|
||||
provides:
|
||||
- Shared\Email\Email — klasa email z PHPMailer
|
||||
- Shared\Security\CsrfToken — CSRF z random_bytes + hash_equals
|
||||
- Wrappery w Helpers dla kompatybilności wstecznej
|
||||
affects: [phase-06 admin-base, phase-17 users-security]
|
||||
|
||||
tech-stack:
|
||||
added: []
|
||||
patterns: [wrapper delegation dla Helpers, static utility class dla CsrfToken]
|
||||
|
||||
key-files:
|
||||
created: [autoload/Shared/Email/Email.php, autoload/Shared/Security/CsrfToken.php]
|
||||
modified: [autoload/Shared/Helpers/Helpers.php]
|
||||
|
||||
key-decisions:
|
||||
- "CsrfToken: single token per session (shopPRO pattern) zamiast multi-token array"
|
||||
- "Email: PHPMailer require via __DIR__ absolute paths"
|
||||
- "Helpers::get_token() wywołuje regenerate() + getToken() — zachowuje semantykę jednorazowego tokenu"
|
||||
|
||||
patterns-established:
|
||||
- "Wrapper delegation: stara metoda w Helpers deleguje do nowej klasy"
|
||||
- "Security: random_bytes(32) + hash_equals() jako standard"
|
||||
|
||||
duration: ~8min
|
||||
completed: 2026-04-04
|
||||
---
|
||||
|
||||
# Phase 2 Plan 01: Shared Email + Security Summary
|
||||
|
||||
**Shared\Email\Email z PHPMailer i Shared\Security\CsrfToken z kryptograficznie bezpiecznym tokenem, plus wrappery w Helpers.**
|
||||
|
||||
## Performance
|
||||
|
||||
| Metric | Value |
|
||||
|--------|-------|
|
||||
| Duration | ~8min |
|
||||
| Completed | 2026-04-04 |
|
||||
| Tasks | 3 completed |
|
||||
| Files modified | 3 |
|
||||
|
||||
## Acceptance Criteria Results
|
||||
|
||||
| Criterion | Status | Notes |
|
||||
|-----------|--------|-------|
|
||||
| AC-1: Email class | Pass | send(), email_check(), load_by_name(), PHPMailer |
|
||||
| AC-2: CsrfToken class | Pass | random_bytes(32), hash_equals(), regenerate() |
|
||||
| AC-3: Wrappery w Helpers | Pass | send_email(), get_token(), is_token_valid() delegują |
|
||||
|
||||
## Accomplishments
|
||||
|
||||
- Utworzono `Shared\Email\Email` z pełną obsługą PHPMailer, załączników, reply-to, regex URL fix
|
||||
- Utworzono `Shared\Security\CsrfToken` z kryptograficznie bezpiecznym tokenem (upgrade z sha1/mt_rand)
|
||||
- Wrappery w Helpers zachowują pełną kompatybilność wsteczną
|
||||
|
||||
## Files Created/Modified
|
||||
|
||||
| File | Change | Purpose |
|
||||
|------|--------|---------|
|
||||
| `autoload/Shared/Email/Email.php` | Created | OOP Email z PHPMailer |
|
||||
| `autoload/Shared/Security/CsrfToken.php` | Created | CSRF token management |
|
||||
| `autoload/Shared/Helpers/Helpers.php` | Modified | Wrappery delegujące do nowych klas |
|
||||
|
||||
## Decisions Made
|
||||
|
||||
| Decision | Rationale | Impact |
|
||||
|----------|-----------|--------|
|
||||
| Single token per session (CsrfToken) | Wzór shopPRO, prostsze, bezpieczniejsze | Legacy multi-token array zastąpiony |
|
||||
| get_token() = regenerate() + getToken() | Zachowuje semantykę: każde wywołanie daje nowy token | Kompatybilność z kodem który zakłada jednorazowy token |
|
||||
| PHPMailer require via __DIR__ | Absolute paths, działa z każdego entry pointa | Eliminuje problem relatywnych ścieżek |
|
||||
|
||||
## Deviations from Plan
|
||||
|
||||
### Summary
|
||||
|
||||
| Type | Count | Impact |
|
||||
|------|-------|--------|
|
||||
| Scope additions | 1 | Minimal — Email.send() ma $replay param z cmsPRO |
|
||||
|
||||
Email.send() w cmsPRO ma dodatkowy parametr `$replay` (reply-to) którego shopPRO nie ma. Zachowano dla kompatybilności z istniejącym kodem.
|
||||
|
||||
## Issues Encountered
|
||||
|
||||
None.
|
||||
|
||||
## Next Phase Readiness
|
||||
|
||||
**Ready:**
|
||||
- Shared layer kompletny (Cache, Helpers, Html, Image, Tpl, Email, Security)
|
||||
- Fazy 3-5 (Domain repositories) mogą startować
|
||||
|
||||
**Concerns:**
|
||||
- None
|
||||
|
||||
**Blockers:**
|
||||
- None
|
||||
|
||||
---
|
||||
*Phase: 02-shared-email-security, Plan: 01*
|
||||
*Completed: 2026-04-04*
|
||||
198
.paul/phases/03-domain-scontainers-banners/03-01-PLAN.md
Normal file
198
.paul/phases/03-domain-scontainers-banners/03-01-PLAN.md
Normal file
@@ -0,0 +1,198 @@
|
||||
---
|
||||
phase: 03-domain-scontainers-banners
|
||||
plan: 01
|
||||
type: execute
|
||||
wave: 1
|
||||
depends_on: []
|
||||
files_modified:
|
||||
- autoload/Domain/Scontainers/ScontainersRepository.php
|
||||
- autoload/Domain/Banners/BannersRepository.php
|
||||
- autoload/admin/factory/class.Scontainers.php
|
||||
- autoload/admin/factory/class.Banners.php
|
||||
- autoload/front/factory/class.Scontainers.php
|
||||
- autoload/front/factory/class.Banners.php
|
||||
autonomous: true
|
||||
delegation: auto
|
||||
---
|
||||
|
||||
<objective>
|
||||
## Goal
|
||||
Create Domain\Scontainers\ScontainersRepository and Domain\Banners\BannersRepository, then convert legacy factory classes to wrapper delegation.
|
||||
|
||||
## Purpose
|
||||
Continue DDD refactoring — migrate Scontainers and Banners data access from static factory methods (global $mdb) to injected-dependency Domain repositories. Establishes wrapper delegation pattern for the first time in the project.
|
||||
|
||||
## Output
|
||||
- 2 new Domain repository files
|
||||
- 4 legacy factory files converted to wrappers
|
||||
</objective>
|
||||
|
||||
<context>
|
||||
## Project Context
|
||||
@.paul/PROJECT.md
|
||||
@.paul/ROADMAP.md
|
||||
@.paul/STATE.md
|
||||
|
||||
## Source Files
|
||||
@autoload/admin/factory/class.Scontainers.php
|
||||
@autoload/admin/factory/class.Banners.php
|
||||
@autoload/front/factory/class.Scontainers.php
|
||||
@autoload/front/factory/class.Banners.php
|
||||
@autoload/Domain/Languages/LanguagesRepository.php (pattern reference)
|
||||
</context>
|
||||
|
||||
<acceptance_criteria>
|
||||
|
||||
## AC-1: ScontainersRepository exists with all methods
|
||||
```gherkin
|
||||
Given the autoloader is configured for Domain\ namespace
|
||||
When ScontainersRepository is instantiated with $db (Medoo)
|
||||
Then it provides containerDetails(), containerSave(), containerDelete(), scontainerByLang() methods
|
||||
And all methods use $this->db instead of global $mdb
|
||||
```
|
||||
|
||||
## AC-2: BannersRepository exists with all methods
|
||||
```gherkin
|
||||
Given the autoloader is configured for Domain\ namespace
|
||||
When BannersRepository is instantiated with $db (Medoo)
|
||||
Then it provides bannerDetails(), bannerSave(), bannerDelete(), activeBanners(), mainBanner() methods
|
||||
And all methods use $this->db instead of global $mdb
|
||||
```
|
||||
|
||||
## AC-3: Legacy admin factories delegate to repositories
|
||||
```gherkin
|
||||
Given admin\factory\Scontainers and admin\factory\Banners exist
|
||||
When their static methods are called (e.g. container_save(), banner_delete())
|
||||
Then they instantiate the Domain repository with global $mdb
|
||||
And delegate the call to the corresponding repository method
|
||||
And return the same result as before
|
||||
```
|
||||
|
||||
## AC-4: Legacy front factories delegate to repositories
|
||||
```gherkin
|
||||
Given front\factory\Scontainers and front\factory\Banners exist
|
||||
When their static methods are called (e.g. scontainer_details(), banners())
|
||||
Then they delegate to the Domain repository
|
||||
And caching behavior is preserved (Cache::fetch/store in repository)
|
||||
```
|
||||
|
||||
</acceptance_criteria>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 1: Create ScontainersRepository and BannersRepository</name>
|
||||
<files>autoload/Domain/Scontainers/ScontainersRepository.php, autoload/Domain/Banners/BannersRepository.php</files>
|
||||
<action>
|
||||
Create Domain\Scontainers\ScontainersRepository following LanguagesRepository pattern:
|
||||
- namespace Domain\Scontainers
|
||||
- Constructor: __construct($db) storing Medoo instance
|
||||
- containerDetails($containerId): get from pp_scontainers + pp_scontainers_langs (all langs)
|
||||
- containerSave($containerId, $title, $text, $status, $showTitle, $src, $html): insert/update pp_scontainers + pp_scontainers_langs with multi-language support. Handle single-lang vs multi-lang arrays exactly as current factory does. Call \S::delete_cache() after.
|
||||
- containerDelete($containerId): delete from pp_scontainers, call \S::delete_cache()
|
||||
- scontainerByLang($scontainerId, $langId): get container + single lang translation, use \Shared\Cache\CacheHandler::fetch/store (migrate from \Cache:: to \Shared\Cache\CacheHandler::)
|
||||
|
||||
Create Domain\Banners\BannersRepository following same pattern:
|
||||
- namespace Domain\Banners
|
||||
- Constructor: __construct($db)
|
||||
- bannerDetails($bannerId): get from pp_banners + pp_banners_langs (all langs)
|
||||
- bannerSave($bannerId, $name, $status, $dateStart, $dateEnd, $homePage, $src, $url, $html, $text): insert/update pp_banners + pp_banners_langs. Handle single/multi lang arrays. Call \S::delete_cache().
|
||||
- bannerDelete($bannerId): delete from pp_banners, call \S::delete_cache()
|
||||
- activeBanners($langId): active non-homepage banners with date filtering, use \Shared\Cache\CacheHandler for caching
|
||||
- mainBanner($langId): single active homepage banner with date filtering, cached
|
||||
|
||||
IMPORTANT:
|
||||
- PHP < 8.0 compatible (no match, no named args, no union types, no str_contains)
|
||||
- Use $this->db->query() for complex SQL (date filtering in Banners) — keep raw SQL identical to current factory
|
||||
- Multi-language save pattern: query pp_langs for active languages, loop and insert translations
|
||||
- Status/checkbox conversion ('on' → 1, else 0) stays in repository methods
|
||||
</action>
|
||||
<verify>php -l autoload/Domain/Scontainers/ScontainersRepository.php && php -l autoload/Domain/Banners/BannersRepository.php</verify>
|
||||
<done>AC-1 and AC-2 satisfied: Both repositories exist with all methods, use injected $db</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 2: Convert legacy factories to wrapper delegation</name>
|
||||
<files>autoload/admin/factory/class.Scontainers.php, autoload/admin/factory/class.Banners.php, autoload/front/factory/class.Scontainers.php, autoload/front/factory/class.Banners.php</files>
|
||||
<action>
|
||||
Convert all 4 factory files to thin wrappers that delegate to Domain repositories.
|
||||
|
||||
Pattern for each static method:
|
||||
```php
|
||||
public static function method_name($args)
|
||||
{
|
||||
global $mdb;
|
||||
$repo = new \Domain\Scontainers\ScontainersRepository($mdb);
|
||||
return $repo->methodName($args);
|
||||
}
|
||||
```
|
||||
|
||||
admin\factory\Scontainers:
|
||||
- container_delete($id) → $repo->containerDelete($id)
|
||||
- container_save(...) → $repo->containerSave(...)
|
||||
- container_details($id) → $repo->containerDetails($id)
|
||||
|
||||
admin\factory\Banners:
|
||||
- banner_delete($id) → $repo->bannerDelete($id)
|
||||
- banner_save(...) → $repo->bannerSave(...)
|
||||
- banner_details($id) → $repo->bannerDetails($id)
|
||||
|
||||
front\factory\Scontainers:
|
||||
- scontainer_details($id) → $repo->scontainerByLang($id, $lang[0]) — note: use global $lang
|
||||
|
||||
front\factory\Banners:
|
||||
- banners() → $repo->activeBanners($lang[0])
|
||||
- main_banner() → $repo->mainBanner($lang[0])
|
||||
|
||||
IMPORTANT:
|
||||
- Keep namespace declarations unchanged (admin\factory, front\factory)
|
||||
- Keep method signatures identical (same parameter names and order)
|
||||
- For front factories: pass $lang[0] explicitly to repository (repo does NOT use global $lang)
|
||||
</action>
|
||||
<verify>php -l autoload/admin/factory/class.Scontainers.php && php -l autoload/admin/factory/class.Banners.php && php -l autoload/front/factory/class.Scontainers.php && php -l autoload/front/factory/class.Banners.php</verify>
|
||||
<done>AC-3 and AC-4 satisfied: All legacy factories delegate to Domain repositories, signatures unchanged</done>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<boundaries>
|
||||
|
||||
## DO NOT CHANGE
|
||||
- autoload/autoloader.php (autoloader stable)
|
||||
- composer.json (PSR-4 mapping already includes Domain\)
|
||||
- autoload/admin/controls/class.Scontainers.php (admin controllers — Phase 10)
|
||||
- autoload/admin/controls/class.Banners.php (admin controllers — Phase 10)
|
||||
- autoload/admin/view/ (admin views — later phases)
|
||||
- autoload/front/view/ (front views — later phases)
|
||||
- autoload/class.Scontainer.php (legacy ArrayAccess entity — separate concern)
|
||||
- Any existing Domain\ repositories (Articles, Languages, Layouts, Pages, Settings, User)
|
||||
|
||||
## SCOPE LIMITS
|
||||
- Only factory → repository migration, NOT admin controllers or views
|
||||
- No new Composer dependencies
|
||||
- No database schema changes
|
||||
- Do not refactor the multi-language save pattern (keep it working as-is)
|
||||
|
||||
</boundaries>
|
||||
|
||||
<verification>
|
||||
Before declaring plan complete:
|
||||
- [ ] php -l passes for all 6 files (2 new + 4 modified)
|
||||
- [ ] ScontainersRepository has: containerDetails, containerSave, containerDelete, scontainerByLang
|
||||
- [ ] BannersRepository has: bannerDetails, bannerSave, bannerDelete, activeBanners, mainBanner
|
||||
- [ ] All 4 factory files are thin wrappers (no direct $mdb usage, only delegation)
|
||||
- [ ] No PHP 8.0+ syntax used
|
||||
- [ ] \S::delete_cache() calls preserved in repository methods
|
||||
- [ ] Caching (\Shared\Cache\CacheHandler) used in front-facing repository methods
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
- All tasks completed
|
||||
- All verification checks pass
|
||||
- Zero regression — factory method signatures unchanged
|
||||
- Domain repositories follow established pattern (constructor DI, $this->db)
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
After completion, create `.paul/phases/03-domain-scontainers-banners/03-01-SUMMARY.md`
|
||||
</output>
|
||||
115
.paul/phases/03-domain-scontainers-banners/03-01-SUMMARY.md
Normal file
115
.paul/phases/03-domain-scontainers-banners/03-01-SUMMARY.md
Normal file
@@ -0,0 +1,115 @@
|
||||
---
|
||||
phase: 03-domain-scontainers-banners
|
||||
plan: 01
|
||||
subsystem: domain
|
||||
tags: [medoo, repository, scontainers, banners, wrapper-delegation]
|
||||
|
||||
requires:
|
||||
- phase: 01-infrastructure
|
||||
provides: PSR-4 autoloader for Domain\ namespace
|
||||
|
||||
provides:
|
||||
- Domain\Scontainers\ScontainersRepository
|
||||
- Domain\Banners\BannersRepository
|
||||
- Wrapper delegation pattern (first usage in project)
|
||||
|
||||
affects: [phase-10-admin-banners-authors-scontainers, phase-15-front-pages-menu-banners-scontainers]
|
||||
|
||||
tech-stack:
|
||||
added: []
|
||||
patterns: [wrapper-delegation, domain-repository-with-cache]
|
||||
|
||||
key-files:
|
||||
created:
|
||||
- autoload/Domain/Scontainers/ScontainersRepository.php
|
||||
- autoload/Domain/Banners/BannersRepository.php
|
||||
modified:
|
||||
- autoload/admin/factory/class.Scontainers.php
|
||||
- autoload/admin/factory/class.Banners.php
|
||||
- autoload/front/factory/class.Scontainers.php
|
||||
- autoload/front/factory/class.Banners.php
|
||||
|
||||
key-decisions:
|
||||
- "Wrapper delegation pattern: factory static methods delegate to repo instances via global $mdb"
|
||||
- "Front factories pass $lang[0] explicitly — repositories do not use global $lang"
|
||||
- "Caching migrated from \\Cache:: to \\Shared\\Cache\\CacheHandler:: in repository layer"
|
||||
|
||||
patterns-established:
|
||||
- "Wrapper delegation: global $mdb; $repo = new \\Domain\\X\\XRepository($mdb); return $repo->method()"
|
||||
- "Front factory passes language ID explicitly to repository"
|
||||
|
||||
duration: ~2min
|
||||
started: 2026-04-04T00:00:00Z
|
||||
completed: 2026-04-04T00:00:00Z
|
||||
---
|
||||
|
||||
# Phase 3 Plan 01: Scontainers + Banners Repositories Summary
|
||||
|
||||
**Domain repositories for Scontainers and Banners with wrapper delegation in all 4 legacy factories.**
|
||||
|
||||
## Performance
|
||||
|
||||
| Metric | Value |
|
||||
|--------|-------|
|
||||
| Duration | ~2min |
|
||||
| Tasks | 2 completed (delegated) |
|
||||
| Files created | 2 |
|
||||
| Files modified | 4 |
|
||||
|
||||
## Acceptance Criteria Results
|
||||
|
||||
| Criterion | Status | Notes |
|
||||
|-----------|--------|-------|
|
||||
| AC-1: ScontainersRepository exists with all methods | Pass | 5 methods (incl. constructor), 110 lines |
|
||||
| AC-2: BannersRepository exists with all methods | Pass | 6 methods (incl. constructor), 148 lines |
|
||||
| AC-3: Legacy admin factories delegate to repositories | Pass | 6 static methods → thin wrappers |
|
||||
| AC-4: Legacy front factories delegate to repositories | Pass | 3 static methods → thin wrappers, $lang[0] passed explicitly |
|
||||
|
||||
## Accomplishments
|
||||
|
||||
- Created ScontainersRepository with containerDetails, containerSave, containerDelete, scontainerByLang
|
||||
- Created BannersRepository with bannerDetails, bannerSave, bannerDelete, activeBanners, mainBanner
|
||||
- Established wrapper delegation pattern — first usage in the project, template for all future phases
|
||||
|
||||
## Files Created/Modified
|
||||
|
||||
| File | Change | Purpose |
|
||||
|------|--------|---------|
|
||||
| `autoload/Domain/Scontainers/ScontainersRepository.php` | Created | Domain repository for scontainers CRUD + cached front read |
|
||||
| `autoload/Domain/Banners/BannersRepository.php` | Created | Domain repository for banners CRUD + cached active/main banner |
|
||||
| `autoload/admin/factory/class.Scontainers.php` | Modified | Wrapper: 3 methods delegate to ScontainersRepository |
|
||||
| `autoload/admin/factory/class.Banners.php` | Modified | Wrapper: 3 methods delegate to BannersRepository |
|
||||
| `autoload/front/factory/class.Scontainers.php` | Modified | Wrapper: 1 method delegates with $lang[0] |
|
||||
| `autoload/front/factory/class.Banners.php` | Modified | Wrapper: 2 methods delegate with $lang[0] |
|
||||
|
||||
## Decisions Made
|
||||
|
||||
| Decision | Rationale | Impact |
|
||||
|----------|-----------|--------|
|
||||
| Wrapper creates new repo instance per call | Matches static factory pattern, no singleton needed | Simple, no state leaks between calls |
|
||||
| Front repos use CacheHandler, not \Cache | Aligns with Shared layer conventions | Consistent caching across Domain layer |
|
||||
| $lang[0] passed as parameter, not global in repo | Repositories should not depend on globals | Cleaner, testable API |
|
||||
|
||||
## Deviations from Plan
|
||||
|
||||
None — plan executed exactly as written.
|
||||
|
||||
## Issues Encountered
|
||||
|
||||
None.
|
||||
|
||||
## Next Phase Readiness
|
||||
|
||||
**Ready:**
|
||||
- Domain\Scontainers and Domain\Banners available for Admin controllers (Phase 10)
|
||||
- Wrapper delegation pattern established for future Domain phases (4, 5)
|
||||
|
||||
**Concerns:**
|
||||
- None
|
||||
|
||||
**Blockers:**
|
||||
- None
|
||||
|
||||
---
|
||||
*Phase: 03-domain-scontainers-banners, Plan: 01*
|
||||
*Completed: 2026-04-04*
|
||||
216
.paul/phases/04-domain-authors-newsletter/04-01-PLAN.md
Normal file
216
.paul/phases/04-domain-authors-newsletter/04-01-PLAN.md
Normal file
@@ -0,0 +1,216 @@
|
||||
---
|
||||
phase: 04-domain-authors-newsletter
|
||||
plan: 01
|
||||
type: execute
|
||||
wave: 1
|
||||
depends_on: []
|
||||
files_modified:
|
||||
- autoload/Domain/Authors/AuthorsRepository.php
|
||||
- autoload/Domain/Newsletter/NewsletterRepository.php
|
||||
- autoload/admin/factory/class.Authors.php
|
||||
- autoload/admin/factory/class.Newsletter.php
|
||||
- autoload/front/factory/class.Authors.php
|
||||
- autoload/front/factory/class.Newsletter.php
|
||||
autonomous: true
|
||||
delegation: auto
|
||||
---
|
||||
|
||||
<objective>
|
||||
## Goal
|
||||
Create Domain\Authors\AuthorsRepository and Domain\Newsletter\NewsletterRepository, then convert legacy factory classes to wrapper delegation.
|
||||
|
||||
## Purpose
|
||||
Continue DDD refactoring — migrate Authors and Newsletter data access to Domain repositories using established wrapper delegation pattern from Phase 3.
|
||||
|
||||
## Output
|
||||
- 2 new Domain repository files
|
||||
- 4 legacy factory files converted to wrappers
|
||||
</objective>
|
||||
|
||||
<context>
|
||||
## Project Context
|
||||
@.paul/PROJECT.md
|
||||
@.paul/ROADMAP.md
|
||||
@.paul/STATE.md
|
||||
|
||||
## Prior Work
|
||||
@.paul/phases/03-domain-scontainers-banners/03-01-SUMMARY.md (wrapper delegation pattern reference)
|
||||
|
||||
## Source Files
|
||||
@autoload/admin/factory/class.Authors.php
|
||||
@autoload/admin/factory/class.Newsletter.php
|
||||
@autoload/front/factory/class.Authors.php
|
||||
@autoload/front/factory/class.Newsletter.php
|
||||
@autoload/Domain/Languages/LanguagesRepository.php (pattern reference)
|
||||
</context>
|
||||
|
||||
<acceptance_criteria>
|
||||
|
||||
## AC-1: AuthorsRepository exists with all methods
|
||||
```gherkin
|
||||
Given the autoloader is configured for Domain\ namespace
|
||||
When AuthorsRepository is instantiated with $db (Medoo)
|
||||
Then it provides simpleList(), authorDetails(), authorSave(), authorDelete(), authorByLang() methods
|
||||
And all methods use $this->db instead of global $mdb
|
||||
```
|
||||
|
||||
## AC-2: NewsletterRepository exists with all methods
|
||||
```gherkin
|
||||
Given the autoloader is configured for Domain\ namespace
|
||||
When NewsletterRepository is instantiated with $db (Medoo)
|
||||
Then it provides emailsImport(), isAdminTemplate(), templateDelete(), send(), templateDetails(), templateSave(), templatesList(), unsubscribe(), confirm(), newsletterSend(), getHash(), signin(), getTemplate(), signout() methods
|
||||
And all methods use $this->db instead of global $mdb
|
||||
```
|
||||
|
||||
## AC-3: Legacy admin factories delegate to repositories
|
||||
```gherkin
|
||||
Given admin\factory\Authors and admin\factory\Newsletter exist
|
||||
When their static methods are called
|
||||
Then they instantiate the Domain repository with global $mdb
|
||||
And delegate the call to the corresponding repository method
|
||||
And return the same result as before
|
||||
```
|
||||
|
||||
## AC-4: Legacy front factories delegate to repositories
|
||||
```gherkin
|
||||
Given front\factory\Authors and front\factory\Newsletter exist
|
||||
When their static methods are called
|
||||
Then they delegate to the Domain repository
|
||||
And caching behavior is preserved (in repository for Authors)
|
||||
```
|
||||
|
||||
</acceptance_criteria>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 1: Create AuthorsRepository and NewsletterRepository</name>
|
||||
<files>autoload/Domain/Authors/AuthorsRepository.php, autoload/Domain/Newsletter/NewsletterRepository.php</files>
|
||||
<action>
|
||||
Create Domain\Authors\AuthorsRepository following established pattern:
|
||||
- namespace Domain\Authors
|
||||
- Constructor: __construct($db) storing Medoo instance
|
||||
- simpleList(): select from pp_authors, return array (from admin get_simple_list)
|
||||
- authorDetails($authorId): get from pp_authors + select pp_authors_langs, return with ['languages'][$lang_id] sub-array
|
||||
- authorSave($authorId, $author, $image, $description): insert/update pp_authors + pp_authors_langs with multi-language support. Same pattern as ScontainersRepository: query pp_langs for active languages, handle single vs multi lang arrays. Call \S::delete_cache() after.
|
||||
- authorDelete($authorId): delete from pp_authors, call \S::delete_cache(), return result
|
||||
- authorByLang($authorId, $langId): cached read using \Shared\Cache\CacheHandler::fetch("get_single_author:$authorId"). Get from pp_authors + pp_authors_langs for specific lang. Cache and return. Note: cache key does NOT include langId (matching original front factory).
|
||||
|
||||
Create Domain\Newsletter\NewsletterRepository following same pattern:
|
||||
- namespace Domain\Newsletter
|
||||
- Constructor: __construct($db)
|
||||
- emailsImport($emails): parse comma/newline separated emails, validate with filter_var, insert unique into pp_newsletter. Return count of imported.
|
||||
- isAdminTemplate($templateId): check if template exists in pp_newsletter_templates where id and admin=1. Return boolean.
|
||||
- templateDelete($templateId): delete from pp_newsletter_templates where id. Return result.
|
||||
- send($dates, $template, $onlyOnce): insert into pp_newsletter_send for each subscriber email from pp_newsletter. If $onlyOnce, check pp_newsletter_send for existing entries. Complex logic — replicate exactly from admin factory.
|
||||
- templateDetails($templateId): get single template from pp_newsletter_templates.
|
||||
- templateSave($id, $name, $text): insert/update pp_newsletter_templates. Call \S::delete_cache().
|
||||
- templatesList(): select all from pp_newsletter_templates ordered.
|
||||
- unsubscribe($hash): update pp_newsletter set status=0 where hash=$hash. Return result.
|
||||
- confirm($hash): update pp_newsletter set status=1 where hash=$hash. Return result.
|
||||
- newsletterSend($limit): select from pp_newsletter_send with limit, send emails via loop, delete sent entries. Replicate exactly from front factory.
|
||||
- getHash($email): select hash from pp_newsletter where email. Return hash or false.
|
||||
- signin($email): insert into pp_newsletter with email, hash (md5), status=0. Return result or hash.
|
||||
- getTemplate($templateName): get template from pp_newsletter_templates where name=$templateName. Return template.
|
||||
- signout($email): delete from pp_newsletter where email=$email. Return result.
|
||||
|
||||
IMPORTANT:
|
||||
- PHP < 8.0 compatible
|
||||
- Replicate logic EXACTLY from factory files — read them first
|
||||
- Multi-language save pattern same as Phase 3 repos
|
||||
- Keep all \S::delete_cache() calls where they exist in originals
|
||||
- Newsletter send() and newsletterSend() are complex — read carefully and replicate precisely
|
||||
</action>
|
||||
<verify>php -l autoload/Domain/Authors/AuthorsRepository.php && php -l autoload/Domain/Newsletter/NewsletterRepository.php</verify>
|
||||
<done>AC-1 and AC-2 satisfied: Both repositories exist with all methods, use injected $db</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 2: Convert legacy factories to wrapper delegation</name>
|
||||
<files>autoload/admin/factory/class.Authors.php, autoload/admin/factory/class.Newsletter.php, autoload/front/factory/class.Authors.php, autoload/front/factory/class.Newsletter.php</files>
|
||||
<action>
|
||||
Convert all 4 factory files to thin wrappers using Phase 3 pattern:
|
||||
```php
|
||||
public static function method_name($args)
|
||||
{
|
||||
global $mdb;
|
||||
$repo = new \Domain\Authors\AuthorsRepository($mdb);
|
||||
return $repo->methodName($args);
|
||||
}
|
||||
```
|
||||
|
||||
admin\factory\Authors:
|
||||
- get_simple_list() → $repo->simpleList()
|
||||
- delete_author($id_author) → $repo->authorDelete($id_author)
|
||||
- save_author($id_author, $author, $image, $description) → $repo->authorSave($id_author, $author, $image, $description)
|
||||
|
||||
admin\factory\Newsletter:
|
||||
- emails_import($emails) → $repo->emailsImport($emails)
|
||||
- is_admin_template($template_id) → $repo->isAdminTemplate($template_id)
|
||||
- newsletter_template_delete($template_id) → $repo->templateDelete($template_id)
|
||||
- send($dates, $template, $only_once) → $repo->send($dates, $template, $only_once)
|
||||
- email_template_detalis($id_template) → $repo->templateDetails($id_template)
|
||||
- template_save($id, $name, $text) → $repo->templateSave($id, $name, $text)
|
||||
- templates_list() → $repo->templatesList()
|
||||
|
||||
front\factory\Authors:
|
||||
- get_single_author($id_author) → global $mdb; $repo = new \Domain\Authors\AuthorsRepository($mdb); return $repo->authorByLang($id_author, null);
|
||||
Note: front factory uses global $lang but the cache key doesn't include lang — pass null or handle in repo. Check original carefully.
|
||||
|
||||
front\factory\Newsletter:
|
||||
- newsletter_unsubscribe($hash) → $repo->unsubscribe($hash)
|
||||
- newsletter_confirm($hash) → $repo->confirm($hash)
|
||||
- newsletter_send($limit = 5) → $repo->newsletterSend($limit)
|
||||
- get_hash($email) → $repo->getHash($email)
|
||||
- newsletter_signin($email) → $repo->signin($email)
|
||||
- get_template($template_name) → $repo->getTemplate($template_name)
|
||||
- newsletter_signout($email) → $repo->signout($email)
|
||||
|
||||
IMPORTANT:
|
||||
- Keep namespaces and method signatures IDENTICAL
|
||||
- Read each file first before editing
|
||||
- Each method = thin 3-line wrapper
|
||||
</action>
|
||||
<verify>php -l autoload/admin/factory/class.Authors.php && php -l autoload/admin/factory/class.Newsletter.php && php -l autoload/front/factory/class.Authors.php && php -l autoload/front/factory/class.Newsletter.php</verify>
|
||||
<done>AC-3 and AC-4 satisfied: All legacy factories delegate to Domain repositories</done>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<boundaries>
|
||||
|
||||
## DO NOT CHANGE
|
||||
- autoload/autoloader.php
|
||||
- composer.json
|
||||
- autoload/admin/controls/ (admin controllers — later phases)
|
||||
- autoload/admin/view/ (admin views — later phases)
|
||||
- autoload/front/view/ (front views — later phases)
|
||||
- Any existing Domain\ repositories (Articles, Languages, Layouts, Pages, Settings, User, Scontainers, Banners)
|
||||
|
||||
## SCOPE LIMITS
|
||||
- Only factory → repository migration
|
||||
- No new Composer dependencies
|
||||
- No database schema changes
|
||||
|
||||
</boundaries>
|
||||
|
||||
<verification>
|
||||
Before declaring plan complete:
|
||||
- [ ] php -l passes for all 6 files (2 new + 4 modified)
|
||||
- [ ] AuthorsRepository has: simpleList, authorDetails, authorSave, authorDelete, authorByLang
|
||||
- [ ] NewsletterRepository has: emailsImport, isAdminTemplate, templateDelete, send, templateDetails, templateSave, templatesList, unsubscribe, confirm, newsletterSend, getHash, signin, getTemplate, signout
|
||||
- [ ] All 4 factory files are thin wrappers (no direct $mdb usage)
|
||||
- [ ] No PHP 8.0+ syntax used
|
||||
- [ ] \S::delete_cache() calls preserved where originals had them
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
- All tasks completed
|
||||
- All verification checks pass
|
||||
- Zero regression — factory method signatures unchanged
|
||||
- Domain repositories follow established pattern
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
After completion, create `.paul/phases/04-domain-authors-newsletter/04-01-SUMMARY.md`
|
||||
</output>
|
||||
111
.paul/phases/04-domain-authors-newsletter/04-01-SUMMARY.md
Normal file
111
.paul/phases/04-domain-authors-newsletter/04-01-SUMMARY.md
Normal file
@@ -0,0 +1,111 @@
|
||||
---
|
||||
phase: 04-domain-authors-newsletter
|
||||
plan: 01
|
||||
subsystem: domain
|
||||
tags: [medoo, repository, authors, newsletter, wrapper-delegation]
|
||||
|
||||
requires:
|
||||
- phase: 01-infrastructure
|
||||
provides: PSR-4 autoloader for Domain\ namespace
|
||||
|
||||
provides:
|
||||
- Domain\Authors\AuthorsRepository
|
||||
- Domain\Newsletter\NewsletterRepository
|
||||
|
||||
affects: [phase-10-admin-banners-authors-scontainers, phase-11-admin-newsletter-emails-seoadditional]
|
||||
|
||||
tech-stack:
|
||||
added: []
|
||||
patterns: [wrapper-delegation, globals-to-parameters]
|
||||
|
||||
key-files:
|
||||
created:
|
||||
- autoload/Domain/Authors/AuthorsRepository.php
|
||||
- autoload/Domain/Newsletter/NewsletterRepository.php
|
||||
modified:
|
||||
- autoload/admin/factory/class.Authors.php
|
||||
- autoload/admin/factory/class.Newsletter.php
|
||||
- autoload/front/factory/class.Authors.php
|
||||
- autoload/front/factory/class.Newsletter.php
|
||||
|
||||
key-decisions:
|
||||
- "Newsletter methods using global $settings/$lang now take them as explicit parameters"
|
||||
- "authorByLang cache key preserved from original (no langId in key)"
|
||||
|
||||
patterns-established:
|
||||
- "Globals-to-parameters: when repo method needs $settings or $lang, wrapper passes them explicitly"
|
||||
|
||||
duration: ~2min
|
||||
started: 2026-04-04T00:00:00Z
|
||||
completed: 2026-04-04T00:00:00Z
|
||||
---
|
||||
|
||||
# Phase 4 Plan 01: Authors + Newsletter Repositories Summary
|
||||
|
||||
**Domain repositories for Authors (5 methods) and Newsletter (14 methods) with wrapper delegation and globals-to-parameters pattern.**
|
||||
|
||||
## Performance
|
||||
|
||||
| Metric | Value |
|
||||
|--------|-------|
|
||||
| Duration | ~2min |
|
||||
| Tasks | 2 completed (delegated) |
|
||||
| Files created | 2 |
|
||||
| Files modified | 4 |
|
||||
|
||||
## Acceptance Criteria Results
|
||||
|
||||
| Criterion | Status | Notes |
|
||||
|-----------|--------|-------|
|
||||
| AC-1: AuthorsRepository exists with all methods | Pass | 5 methods, 156 lines |
|
||||
| AC-2: NewsletterRepository exists with all methods | Pass | 14 methods, 281 lines |
|
||||
| AC-3: Legacy admin factories delegate to repositories | Pass | 11 static methods → wrappers |
|
||||
| AC-4: Legacy front factories delegate to repositories | Pass | 8 static methods → wrappers, globals passed as params |
|
||||
|
||||
## Accomplishments
|
||||
|
||||
- Created AuthorsRepository with simpleList, authorDetails, authorSave, authorDelete, authorByLang
|
||||
- Created NewsletterRepository with full subscriber lifecycle + template CRUD + sending
|
||||
- Established globals-to-parameters pattern for methods needing $settings/$lang
|
||||
|
||||
## Files Created/Modified
|
||||
|
||||
| File | Change | Purpose |
|
||||
|------|--------|---------|
|
||||
| `autoload/Domain/Authors/AuthorsRepository.php` | Created | Domain repository for authors CRUD + cached front read |
|
||||
| `autoload/Domain/Newsletter/NewsletterRepository.php` | Created | Domain repository for newsletter subscriber lifecycle, templates, sending |
|
||||
| `autoload/admin/factory/class.Authors.php` | Modified | Wrapper: 4 methods delegate to AuthorsRepository |
|
||||
| `autoload/admin/factory/class.Newsletter.php` | Modified | Wrapper: 7 methods delegate to NewsletterRepository |
|
||||
| `autoload/front/factory/class.Authors.php` | Modified | Wrapper: 1 method delegates |
|
||||
| `autoload/front/factory/class.Newsletter.php` | Modified | Wrapper: 7 methods delegate, passing $settings/$lang |
|
||||
|
||||
## Decisions Made
|
||||
|
||||
| Decision | Rationale | Impact |
|
||||
|----------|-----------|--------|
|
||||
| Globals as parameters for newsletterSend/signin | Repos should not depend on globals | Front wrappers pass $settings, $lang explicitly |
|
||||
| Preserve original cache key for authorByLang | Backward compatibility with existing cache | Cache key "get_single_author:$id" without langId |
|
||||
|
||||
## Deviations from Plan
|
||||
|
||||
None — plan executed exactly as written.
|
||||
|
||||
## Issues Encountered
|
||||
|
||||
None.
|
||||
|
||||
## Next Phase Readiness
|
||||
|
||||
**Ready:**
|
||||
- Domain\Authors and Domain\Newsletter available for Admin controllers (Phases 10, 11)
|
||||
- All Domain repos for phases 3-4 complete
|
||||
|
||||
**Concerns:**
|
||||
- None
|
||||
|
||||
**Blockers:**
|
||||
- None
|
||||
|
||||
---
|
||||
*Phase: 04-domain-authors-newsletter, Plan: 01*
|
||||
*Completed: 2026-04-04*
|
||||
1
.phpunit.result.cache
Normal file
1
.phpunit.result.cache
Normal file
@@ -0,0 +1 @@
|
||||
{"version":2,"defects":{"Tests\\Unit\\Domain\\Settings\\SettingsRepositoryTest::testAllSettingsReturnsEmptyArrayWhenDbReturnsNull":8,"Tests\\Unit\\Domain\\Settings\\SettingsRepositoryTest::testAllSettingsUsesCache":8},"times":{"Tests\\Unit\\Domain\\Languages\\LanguagesRepositoryTest::testLanguagesListReturnsArray":0.041,"Tests\\Unit\\Domain\\Languages\\LanguagesRepositoryTest::testLanguagesListReturnsEmptyWhenNull":0.001,"Tests\\Unit\\Domain\\Languages\\LanguagesRepositoryTest::testLanguageDetailsReturnsRowWhenFound":0,"Tests\\Unit\\Domain\\Languages\\LanguagesRepositoryTest::testLanguageDetailsReturnsNullWhenNotFound":0.001,"Tests\\Unit\\Domain\\Languages\\LanguagesRepositoryTest::testActiveLanguagesQueriesDbAndCaches":0.001,"Tests\\Unit\\Domain\\Languages\\LanguagesRepositoryTest::testActiveLanguagesReturnsEmptyWhenNull":0,"Tests\\Unit\\Domain\\Languages\\LanguagesRepositoryTest::testMaxOrderReturnsInteger":0,"Tests\\Unit\\Domain\\Languages\\LanguagesRepositoryTest::testTranslationDeleteReturnsTrueOnSuccess":0.001,"Tests\\Unit\\Domain\\Languages\\LanguagesRepositoryTest::testTranslationDeleteReturnsFalseOnFailure":0.001,"Tests\\Unit\\Domain\\Languages\\LanguagesRepositoryTest::testTranslationDetailsReturnsRowOrNull":0,"Tests\\Unit\\Domain\\Settings\\SettingsRepositoryTest::testAllSettingsReturnsMappedArray":0.001,"Tests\\Unit\\Domain\\Settings\\SettingsRepositoryTest::testAllSettingsReturnsEmptyArrayWhenDbReturnsNull":0,"Tests\\Unit\\Domain\\Settings\\SettingsRepositoryTest::testAllSettingsUsesCache":0,"Tests\\Unit\\Domain\\Settings\\SettingsRepositoryTest::testUpdateCallsDbUpdateWhenParamExists":0.001,"Tests\\Unit\\Domain\\Settings\\SettingsRepositoryTest::testUpdateCallsDbInsertWhenParamMissing":0,"Tests\\Unit\\Domain\\Settings\\SettingsRepositoryTest::testVisitCounterReturnsValue":0,"Tests\\Unit\\Domain\\Settings\\SettingsRepositoryTest::testVisitCounterReturnsNullWhenEmpty":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testFindReturnsUserArray":0.001,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testFindReturnsNullWhenNotFound":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testFindByLoginReturnsUser":0.002,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testAllReturnsArray":0.001,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testAllReturnsEmptyArrayWhenNull":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testHasPrivilegeReturnsTrueForAdminUser":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testHasPrivilegeReturnsTrueWhenPrivilegeExists":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testHasPrivilegeReturnsFalseWhenPrivilegeMissing":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testLogonReturnsZeroWhenUserNotFound":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testLogonReturnsMinusOneWhenAccountBlocked":0.001,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testLogonReturnsOneOnSuccess":0.001,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testIsLoginTakenReturnsTrueWhenExists":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testIsLoginTakenReturnsFalseWhenFree":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testVerifyTwofaCodeReturnsFalseWhenUserNotFound":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testVerifyTwofaCodeReturnsFalseWhenTooManyFailedAttempts":0.075,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testVerifyTwofaCodeReturnsFalseWhenExpired":0.073,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testVerifyTwofaCodeReturnsTrueOnValidCode":0.148,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testDeleteReturnsTrueOnSuccess":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testSaveReturnsErrorWhenPasswordTooShort":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testSaveReturnsErrorWhenPasswordsMismatch":0}}
|
||||
@@ -45,7 +45,9 @@ ignored_paths: []
|
||||
# Added on 2025-04-18
|
||||
read_only: false
|
||||
|
||||
# list of tool names to exclude. We recommend not excluding any tools, see the readme for more details.
|
||||
# list of tool names to exclude.
|
||||
# This extends the existing exclusions (e.g. from the global configuration)
|
||||
#
|
||||
# Below is the complete list of tools for convenience.
|
||||
# To make sure you have the latest list of tools, and to view their descriptions,
|
||||
# execute `uv run scripts/print_tool_overview.py`.
|
||||
@@ -86,7 +88,8 @@ read_only: false
|
||||
# * `write_memory`: Writes a named memory (for future reference) to Serena's project-specific memory store.
|
||||
excluded_tools: []
|
||||
|
||||
# list of tools to include that would otherwise be disabled (particularly optional tools that are disabled by default)
|
||||
# list of tools to include that would otherwise be disabled (particularly optional tools that are disabled by default).
|
||||
# This extends the existing inclusions (e.g. from the global configuration).
|
||||
included_optional_tools: []
|
||||
|
||||
# fixed set of tools to use as the base tool set (if non-empty), replacing Serena's default set of tools.
|
||||
@@ -112,6 +115,38 @@ default_modes:
|
||||
# (contrary to the memories, which are loaded on demand).
|
||||
initial_prompt: ""
|
||||
|
||||
# override of the corresponding setting in serena_config.yml, see the documentation there.
|
||||
# If null or missing, the value from the global config is used.
|
||||
# time budget (seconds) per tool call for the retrieval of additional symbol information
|
||||
# such as docstrings or parameter information.
|
||||
# This overrides the corresponding setting in the global configuration; see the documentation there.
|
||||
# If null or missing, use the setting from the global configuration.
|
||||
symbol_info_budget:
|
||||
|
||||
# The language backend to use for this project.
|
||||
# If not set, the global setting from serena_config.yml is used.
|
||||
# Valid values: LSP, JetBrains
|
||||
# Note: the backend is fixed at startup. If a project with a different backend
|
||||
# is activated post-init, an error will be returned.
|
||||
language_backend:
|
||||
|
||||
# line ending convention to use when writing source files.
|
||||
# Possible values: unset (use global setting), "lf", "crlf", or "native" (platform default)
|
||||
# This does not affect Serena's own files (e.g. memories and configuration files), which always use native line endings.
|
||||
line_ending:
|
||||
|
||||
# list of regex patterns which, when matched, mark a memory entry as read‑only.
|
||||
# Extends the list from the global configuration, merging the two lists.
|
||||
read_only_memory_patterns: []
|
||||
|
||||
# list of regex patterns for memories to completely ignore.
|
||||
# Matching memories will not appear in list_memories or activate_project output
|
||||
# and cannot be accessed via read_memory or write_memory.
|
||||
# To access ignored memory files, use the read_file tool on the raw file path.
|
||||
# Extends the list from the global configuration, merging the two lists.
|
||||
# Example: ["_archive/.*", "_episodes/.*"]
|
||||
ignored_memory_patterns: []
|
||||
|
||||
# advanced configuration option allowing to configure language server-specific options.
|
||||
# Maps the language key to the options.
|
||||
# Have a look at the docstring of the constructors of the LS implementations within solidlsp (e.g., for C# or PHP) to see which options are available.
|
||||
# No documentation on options means no options are available.
|
||||
ls_specific_settings: {}
|
||||
|
||||
@@ -48,6 +48,15 @@ backups/
|
||||
cache/
|
||||
cron/
|
||||
|
||||
# Moduł zarządzania releaseami (tylko serwer dewelopera)
|
||||
autoload/admin/controls/class.Releases.php
|
||||
autoload/admin/factory/class.Releases.php
|
||||
autoload/admin/view/class.Releases.php
|
||||
admin/templates/releases/
|
||||
|
||||
# Menu dewelopera
|
||||
admin/templates/additional-menu.php
|
||||
|
||||
# IDE
|
||||
.vscode/
|
||||
.serena/
|
||||
|
||||
8
.vscode/ftp-kr.json
vendored
8
.vscode/ftp-kr.json
vendored
@@ -12,6 +12,12 @@
|
||||
"ignoreRemoteModification": true,
|
||||
"ignore": [
|
||||
".git",
|
||||
"/.vscode"
|
||||
"/.vscode",
|
||||
"/.claude",
|
||||
"/.serena",
|
||||
"/docs",
|
||||
"AGENTS.md",
|
||||
"CLAUDE.md",
|
||||
"/.paul"
|
||||
]
|
||||
}
|
||||
41
AGENTS.md
Normal file
41
AGENTS.md
Normal file
@@ -0,0 +1,41 @@
|
||||
# Workflow
|
||||
|
||||
## Sposób pracy
|
||||
- Pisz do mnie po polsku, zwięźle i krótko, ale merytorycznie
|
||||
|
||||
## Zasady pisania kodu
|
||||
- Kod ma być czytelny „dla obcego”: jasne nazwy, mało magii
|
||||
- Brak „skrótów na szybko” typu logika w widokach, copy-paste, losowe helpery bez spójności
|
||||
- Każda funkcja/klasa ma mieć jedną odpowiedzialność, zwykle do 30–50 linii (jeśli dłuższe – dzielić)
|
||||
- max 3 poziomy zagnieżdżeń (if/foreach), reszta do osobnych metod
|
||||
- Nazewnictwo:
|
||||
- klasy: PascalCase
|
||||
- metody/zmienne: camelCase
|
||||
- stałe: UPPER_SNAKE_CASE
|
||||
- Zero „skrótologii” w nazwach (np. $d, $tmp, $x1) poza pętlami 2–3 linijki
|
||||
- medoo + prepared statements bez wyjątków (żadnego sklejania SQL stringiem)
|
||||
- XSS: escape w widokach (np. helper e())
|
||||
- CSRF dla formularzy, sensowna obsługa sesji
|
||||
- Kod ma mieć komentarze tylko tam, gdzie wyjaśniają „dlaczego”, nie „co”
|
||||
|
||||
|
||||
## Wprowadzanie zmian
|
||||
- Przeanalizuj wprowadzone zadanie
|
||||
- Jeżeli masz jakieś wątpliwości pytaj
|
||||
- Przedstaw plan
|
||||
- Po akceptacji wdróź plan
|
||||
|
||||
## KONIEC PRACY
|
||||
|
||||
Gdy użytkownik napisze `KONIEC PRACY`, wykonaj kolejno:
|
||||
|
||||
1. Przeprowadzenie testów.
|
||||
2. Aktualizacja dokumentacji technicznej, jeśli zmiany tego wymagają:
|
||||
- `docs/PROJECT_STRUCTURE.md`
|
||||
- `docs/FORM_EDIT_SYSTEM.md`
|
||||
3. Migracje SQL (jeśli były zmiany w bazie danych):
|
||||
- Plik: `migrations/{version}.sql` (np. `migrations/0.304.sql`)
|
||||
- **NIE** w `updates/` — build script sam wczyta z `migrations/`
|
||||
- Sprawdź czy plik istnieje i jest poprawnie nazwany przed commitem
|
||||
4. Commit.
|
||||
5. Push.
|
||||
5
CLAUDE.md
Normal file
5
CLAUDE.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# Workflow
|
||||
|
||||
## KONIEC PRACY
|
||||
|
||||
Gdy użytkownik napisze `KONIEC PRACY`, uruchom komendę `/koniec-pracy`.
|
||||
@@ -1,19 +1,6 @@
|
||||
<?
|
||||
error_reporting( E_ALL ^ E_NOTICE ^ E_STRICT ^ E_WARNING ^ E_DEPRECATED );
|
||||
function __autoload_my_classes( $classname )
|
||||
{
|
||||
$q = explode( '\\' , $classname );
|
||||
$c = array_pop( $q );
|
||||
$f = '../autoload/' . implode( '/' , $q ) . '/class.' . $c . '.php';
|
||||
if ( $c == 'Savant3' )
|
||||
{
|
||||
require_once( '../autoload/Savant3.php' );
|
||||
return true;
|
||||
}
|
||||
if ( file_exists( $f ) )
|
||||
require_once( $f );
|
||||
}
|
||||
spl_autoload_register( '__autoload_my_classes' );
|
||||
require_once __DIR__ . '/../autoload/autoloader.php';
|
||||
|
||||
require_once '../config.php';
|
||||
require_once '../libraries/medoo/medoo.php';
|
||||
|
||||
@@ -12,15 +12,7 @@ if ( file_exists( 'ip.conf' ) )
|
||||
}
|
||||
|
||||
error_reporting( E_ALL ^ E_NOTICE ^ E_STRICT ^ E_WARNING ^ E_DEPRECATED );
|
||||
function __autoload_my_classes( $classname )
|
||||
{
|
||||
$q = explode( '\\' , $classname );
|
||||
$c = array_pop( $q );
|
||||
$f = '../autoload/' . implode( '/' , $q ) . '/class.' . $c . '.php';
|
||||
if ( file_exists( $f ) )
|
||||
require_once( $f );
|
||||
}
|
||||
spl_autoload_register( '__autoload_my_classes' );
|
||||
require_once __DIR__ . '/../autoload/autoloader.php';
|
||||
|
||||
require_once '../config.php';
|
||||
require_once '../libraries/medoo/medoo.php';
|
||||
|
||||
11
admin/templates/additional-menu.php
Normal file
11
admin/templates/additional-menu.php
Normal file
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
// Menu tylko na serwerze dewelopera — wykluczone z .updateignore
|
||||
?>
|
||||
<div class="title">Developer</div>
|
||||
<ul>
|
||||
<li>
|
||||
<a href="/admin/releases/main_view/">
|
||||
<img src="/admin/css/icons/settings-20-filled.svg">Releases & Licencje
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
312
admin/templates/releases/main-view.php
Normal file
312
admin/templates/releases/main-view.php
Normal file
@@ -0,0 +1,312 @@
|
||||
<?php
|
||||
global $gdb;
|
||||
ob_start();
|
||||
?>
|
||||
<style>
|
||||
.releases-tabs-nav { margin-bottom: 0; border-bottom: 1px solid #ddd; }
|
||||
.releases-tabs-nav li { display: inline-block; margin-bottom: -1px; }
|
||||
.releases-tabs-nav li a {
|
||||
display: block; padding: 8px 16px; text-decoration: none; color: #555;
|
||||
border: 1px solid transparent; border-radius: 3px 3px 0 0;
|
||||
background: #f5f5f5; margin-right: 2px; cursor: pointer;
|
||||
}
|
||||
.releases-tabs-nav li.active a {
|
||||
color: #333; background: #fff;
|
||||
border-color: #ddd #ddd #fff;
|
||||
}
|
||||
.releases-tab-pane { display: none; padding: 18px 0 0; }
|
||||
.releases-tab-pane.active { display: block; }
|
||||
.license-form-wrap { display: none; margin-bottom: 20px; }
|
||||
</style>
|
||||
|
||||
<ul class="releases-tabs-nav" id="releases-tabs-nav">
|
||||
<li class="active"><a href="#" data-tab="tab-versions">Wersje</a></li>
|
||||
<li><a href="#" data-tab="tab-licenses">Licencje</a></li>
|
||||
</ul>
|
||||
|
||||
<!-- TAB: Wersje -->
|
||||
<div class="releases-tab-pane active" id="tab-versions">
|
||||
<div style="margin-bottom: 12px;">
|
||||
<form method="post" action="/admin/releases/discover_versions/" style="display:inline"
|
||||
onsubmit="return confirm('Wykryć wersje z dysku i zarejestrować jako stable?')">
|
||||
<button type="submit" class="btn btn-info btn-sm">
|
||||
<i class="fa fa-search"></i> Wykryj wersje z dysku
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
<table class="table table-bordered table-striped table-hover table-condensed">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Wersja</th>
|
||||
<th class="text-center" style="width:100px;">Kanał</th>
|
||||
<th style="width:150px;">Data dodania</th>
|
||||
<th style="width:150px;">Data promocji</th>
|
||||
<th class="text-center" style="width:60px;">ZIP</th>
|
||||
<th class="text-center" style="width:140px;">Akcje</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php if (empty($this->versions)): ?>
|
||||
<tr><td colspan="6" class="text-center text-muted">Brak wersji w bazie.</td></tr>
|
||||
<?php else: foreach ($this->versions as $v): ?>
|
||||
<tr>
|
||||
<td><strong><?= htmlspecialchars($v['version']) ?></strong></td>
|
||||
<td class="text-center">
|
||||
<?php if ($v['channel'] === 'stable'): ?>
|
||||
<span class="label label-success">stable</span>
|
||||
<?php else: ?>
|
||||
<span class="label label-warning">beta</span>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td><?= htmlspecialchars($v['created_at'] ?? '') ?></td>
|
||||
<td><?= $v['promoted_at'] ? htmlspecialchars($v['promoted_at']) : '<span class="text-muted">—</span>' ?></td>
|
||||
<td class="text-center">
|
||||
<?php if ($v['zip_exists']): ?>
|
||||
<span class="text-success"><i class="fa fa-check"></i></span>
|
||||
<?php else: ?>
|
||||
<span class="text-danger"><i class="fa fa-times"></i></span>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<?php if ($v['channel'] === 'beta'): ?>
|
||||
<form method="post" action="/admin/releases/promote/" style="display:inline">
|
||||
<input type="hidden" name="version" value="<?= htmlspecialchars($v['version']) ?>">
|
||||
<button type="submit" class="btn btn-success btn-xs"
|
||||
onclick="return confirm('Promować <?= htmlspecialchars($v['version'], ENT_QUOTES) ?> do stable?')">
|
||||
Promuj →stable
|
||||
</button>
|
||||
</form>
|
||||
<?php else: ?>
|
||||
<form method="post" action="/admin/releases/demote/" style="display:inline">
|
||||
<input type="hidden" name="version" value="<?= htmlspecialchars($v['version']) ?>">
|
||||
<button type="submit" class="btn btn-warning btn-xs"
|
||||
onclick="return confirm('Cofnąć <?= htmlspecialchars($v['version'], ENT_QUOTES) ?> do beta?')">
|
||||
Cofnij →beta
|
||||
</button>
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- TAB: Licencje -->
|
||||
<div class="releases-tab-pane" id="tab-licenses">
|
||||
|
||||
<div style="margin-bottom: 12px;">
|
||||
<a href="#" class="btn btn-success btn-sm" id="btn-add-license">
|
||||
<i class="fa fa-plus-circle"></i> Dodaj licencję
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Formularz dodawania / edycji -->
|
||||
<div class="license-form-wrap panel panel-default" id="license-form-wrap">
|
||||
<div class="panel-heading">
|
||||
<strong id="license-form-title">Nowa licencja</strong>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<form method="post" action="/admin/releases/save_license/" id="license-form">
|
||||
<input type="hidden" name="id" id="lic-id" value="">
|
||||
<div class="row">
|
||||
<div class="col-sm-6">
|
||||
<div class="form-group">
|
||||
<label>Domena</label>
|
||||
<input type="text" name="domain" id="lic-domain" class="form-control" placeholder="np. example.com" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
<div class="form-group">
|
||||
<label>Klucz licencji</label>
|
||||
<input type="text" name="key" id="lic-key" class="form-control" placeholder="Klucz UUID / losowy ciąg (pusty = domyślny)">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-4">
|
||||
<div class="form-group">
|
||||
<label>Ważna do daty</label>
|
||||
<input type="date" name="valid_to_date" id="lic-valid-date" class="form-control">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-4">
|
||||
<div class="form-group">
|
||||
<label>Ważna do wersji</label>
|
||||
<input type="text" name="valid_to_version" id="lic-valid-ver" class="form-control" placeholder="np. 1.700">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-4">
|
||||
<div class="form-group">
|
||||
<label>Dostęp beta</label>
|
||||
<select name="beta" id="lic-beta" class="form-control">
|
||||
<option value="0">Nie</option>
|
||||
<option value="1">Tak</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Notatka</label>
|
||||
<input type="text" name="note" id="lic-note" class="form-control" placeholder="Opcjonalna notatka">
|
||||
</div>
|
||||
<button type="submit" class="btn btn-system btn-sm">
|
||||
<i class="fa fa-save"></i> Zapisz licencję
|
||||
</button>
|
||||
<a href="#" class="btn btn-default btn-sm" id="btn-cancel-license">Anuluj</a>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tabela licencji -->
|
||||
<table class="table table-bordered table-striped table-hover table-condensed">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Domena</th>
|
||||
<th style="width:120px;">Klucz</th>
|
||||
<th style="width:120px;">Do daty</th>
|
||||
<th style="width:100px;">Do wersji</th>
|
||||
<th class="text-center" style="width:70px;">Beta</th>
|
||||
<th>Notatka</th>
|
||||
<th class="text-center" style="width:60px;">Edytuj</th>
|
||||
<th class="text-center" style="width:60px;">Usuń</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php if (empty($this->licenses)): ?>
|
||||
<tr><td colspan="8" class="text-center text-muted">Brak licencji w bazie.</td></tr>
|
||||
<?php else: foreach ($this->licenses as $lic): ?>
|
||||
<tr>
|
||||
<td><?= htmlspecialchars($lic['domain']) ?></td>
|
||||
<td>
|
||||
<code title="<?= htmlspecialchars($lic['key']) ?>">
|
||||
<?= htmlspecialchars(substr($lic['key'], 0, 8)) ?>…
|
||||
</code>
|
||||
</td>
|
||||
<td><?= $lic['valid_to_date'] ? htmlspecialchars($lic['valid_to_date']) : '<span class="text-muted">—</span>' ?></td>
|
||||
<td><?= $lic['valid_to_version'] ? htmlspecialchars($lic['valid_to_version']) : '<span class="text-muted">—</span>' ?></td>
|
||||
<td class="text-center">
|
||||
<form method="post" action="/admin/releases/toggle_beta/" style="display:inline">
|
||||
<input type="hidden" name="id" value="<?= (int)$lic['id'] ?>">
|
||||
<button type="submit"
|
||||
class="label <?= $lic['beta'] ? 'label-info' : 'label-default' ?>"
|
||||
title="Kliknij, aby przełączyć"
|
||||
style="cursor:pointer;border:none;background:none">
|
||||
<?= $lic['beta'] ? 'tak' : 'nie' ?>
|
||||
</button>
|
||||
</form>
|
||||
</td>
|
||||
<td><?= htmlspecialchars($lic['note'] ?? '') ?></td>
|
||||
<td class="text-center">
|
||||
<a href="#"
|
||||
class="btn btn-default btn-xs btn-edit-license"
|
||||
data-id="<?= (int)$lic['id'] ?>"
|
||||
data-domain="<?= htmlspecialchars($lic['domain'], ENT_QUOTES) ?>"
|
||||
data-key="<?= htmlspecialchars($lic['key'], ENT_QUOTES) ?>"
|
||||
data-valid-date="<?= htmlspecialchars($lic['valid_to_date'] ?? '', ENT_QUOTES) ?>"
|
||||
data-valid-ver="<?= htmlspecialchars($lic['valid_to_version'] ?? '', ENT_QUOTES) ?>"
|
||||
data-beta="<?= (int)$lic['beta'] ?>"
|
||||
data-note="<?= htmlspecialchars($lic['note'] ?? '', ENT_QUOTES) ?>">
|
||||
<i class="fa fa-pencil"></i>
|
||||
</a>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<form method="post" action="/admin/releases/delete_license/" style="display:inline"
|
||||
onsubmit="return confirm('Usunąć licencję dla <?= htmlspecialchars($lic['domain'], ENT_QUOTES) ?>?')">
|
||||
<input type="hidden" name="id" value="<?= (int)$lic['id'] ?>">
|
||||
<button type="submit" class="btn btn-danger btn-xs">
|
||||
<i class="fa fa-trash"></i>
|
||||
</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
(function () {
|
||||
// Tab switching
|
||||
document.querySelectorAll('#releases-tabs-nav a[data-tab]').forEach(function (link) {
|
||||
link.addEventListener('click', function (e) {
|
||||
e.preventDefault();
|
||||
var targetId = this.getAttribute('data-tab');
|
||||
document.querySelectorAll('#releases-tabs-nav li').forEach(function (li) {
|
||||
li.classList.remove('active');
|
||||
});
|
||||
document.querySelectorAll('.releases-tab-pane').forEach(function (pane) {
|
||||
pane.classList.remove('active');
|
||||
});
|
||||
this.parentElement.classList.add('active');
|
||||
document.getElementById(targetId).classList.add('active');
|
||||
});
|
||||
});
|
||||
|
||||
// Show add-license form
|
||||
document.getElementById('btn-add-license').addEventListener('click', function (e) {
|
||||
e.preventDefault();
|
||||
resetLicenseForm();
|
||||
document.getElementById('license-form-title').textContent = 'Nowa licencja';
|
||||
var wrap = document.getElementById('license-form-wrap');
|
||||
if (wrap.style.display === 'none' || wrap.style.display === '') {
|
||||
wrap.style.display = 'block';
|
||||
$(wrap).slideDown(200);
|
||||
}
|
||||
});
|
||||
|
||||
// Cancel
|
||||
document.getElementById('btn-cancel-license').addEventListener('click', function (e) {
|
||||
e.preventDefault();
|
||||
$(document.getElementById('license-form-wrap')).slideUp(200);
|
||||
});
|
||||
|
||||
// Edit buttons
|
||||
document.querySelectorAll('.btn-edit-license').forEach(function (btn) {
|
||||
btn.addEventListener('click', function (e) {
|
||||
e.preventDefault();
|
||||
var d = this.dataset;
|
||||
document.getElementById('lic-id').value = d.id;
|
||||
document.getElementById('lic-domain').value = d.domain;
|
||||
document.getElementById('lic-key').value = d.key;
|
||||
document.getElementById('lic-valid-date').value = d.validDate;
|
||||
document.getElementById('lic-valid-ver').value = d.validVer;
|
||||
document.getElementById('lic-beta').value = d.beta;
|
||||
document.getElementById('lic-note').value = d.note;
|
||||
document.getElementById('license-form-title').textContent = 'Edytuj licencję: ' + d.domain;
|
||||
var wrap = document.getElementById('license-form-wrap');
|
||||
wrap.style.display = 'block';
|
||||
$(wrap).slideDown(200);
|
||||
wrap.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||||
});
|
||||
});
|
||||
|
||||
function resetLicenseForm() {
|
||||
document.getElementById('lic-id').value = '';
|
||||
document.getElementById('lic-domain').value = '';
|
||||
document.getElementById('lic-key').value = '';
|
||||
document.getElementById('lic-valid-date').value = '';
|
||||
document.getElementById('lic-valid-ver').value = '';
|
||||
document.getElementById('lic-beta').value = '0';
|
||||
document.getElementById('lic-note').value = '';
|
||||
}
|
||||
|
||||
// If URL hash indicates licenses tab, switch to it on load
|
||||
if (window.location.hash === '#licenses') {
|
||||
document.querySelector('[data-tab="tab-licenses"]').click();
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
<?php
|
||||
$out = ob_get_clean();
|
||||
|
||||
$grid = new \gridEdit;
|
||||
$grid->id = 'releases-view';
|
||||
$grid->gdb_opt = $gdb;
|
||||
$grid->include_plugins = true;
|
||||
$grid->title = 'Releases & Licencje';
|
||||
$grid->default_buttons = false;
|
||||
$grid->form = false;
|
||||
$grid->external_code = $out;
|
||||
echo $grid->draw();
|
||||
?>
|
||||
11
ajax.php
11
ajax.php
@@ -1,15 +1,6 @@
|
||||
<?php
|
||||
error_reporting( E_ALL ^ E_NOTICE ^ E_STRICT ^ E_WARNING ^ E_DEPRECATED );
|
||||
function __autoload_my_classes( $classname )
|
||||
{
|
||||
$q = explode( '\\' , $classname );
|
||||
$c = array_pop( $q );
|
||||
$f = 'autoload/' . implode( '/' , $q ) . '/class.' . $c . '.php';
|
||||
|
||||
if ( file_exists( $f ) )
|
||||
require_once( $f );
|
||||
}
|
||||
spl_autoload_register( '__autoload_my_classes' );
|
||||
require_once __DIR__ . '/autoload/autoloader.php';
|
||||
date_default_timezone_set( 'Europe/Warsaw' );
|
||||
|
||||
require_once 'config.php';
|
||||
|
||||
13
api.php
13
api.php
@@ -1,15 +1,6 @@
|
||||
<?php
|
||||
error_reporting(E_ALL ^ E_NOTICE ^ E_STRICT ^ E_WARNING ^ E_DEPRECATED);
|
||||
function __autoload_my_classes($classname)
|
||||
{
|
||||
$q = explode('\\', $classname);
|
||||
$c = array_pop($q);
|
||||
$f = 'autoload/' . implode('/', $q) . '/class.' . $c . '.php';
|
||||
|
||||
if (file_exists($f))
|
||||
require_once($f);
|
||||
}
|
||||
spl_autoload_register('__autoload_my_classes');
|
||||
error_reporting( E_ALL ^ E_NOTICE ^ E_STRICT ^ E_WARNING ^ E_DEPRECATED );
|
||||
require_once __DIR__ . '/autoload/autoloader.php';
|
||||
date_default_timezone_set('Europe/Warsaw');
|
||||
|
||||
require_once 'config.php';
|
||||
|
||||
648
autoload/Domain/Articles/ArticlesRepository.php
Normal file
648
autoload/Domain/Articles/ArticlesRepository.php
Normal file
@@ -0,0 +1,648 @@
|
||||
<?php
|
||||
namespace Domain\Articles;
|
||||
|
||||
class ArticlesRepository
|
||||
{
|
||||
private $db;
|
||||
|
||||
public function __construct( $db )
|
||||
{
|
||||
$this->db = $db;
|
||||
}
|
||||
|
||||
public function filesOrderSave( $articleId, $order ): void
|
||||
{
|
||||
$i = 0;
|
||||
$order = explode( ';', $order );
|
||||
|
||||
if ( is_array( $order ) && !empty( $order ) )
|
||||
foreach ( $order as $fileId )
|
||||
$this->db->update( 'pp_articles_files', [ 'o' => (int)$i++ ], [
|
||||
'AND' => [ 'article_id' => $articleId, 'id' => $fileId ]
|
||||
] );
|
||||
}
|
||||
|
||||
public function galleryOrderSave( $articleId, $order ): void
|
||||
{
|
||||
$i = 0;
|
||||
$order = explode( ';', $order );
|
||||
|
||||
if ( is_array( $order ) && !empty( $order ) )
|
||||
foreach ( $order as $imageId )
|
||||
$this->db->update( 'pp_articles_images', [ 'o' => $i++ ], [
|
||||
'AND' => [ 'article_id' => $articleId, 'id' => $imageId ]
|
||||
] );
|
||||
}
|
||||
|
||||
public function additionalParams( $language = 0 )
|
||||
{
|
||||
return $this->db->select( 'pp_articles_additional_params', '*', [ 'AND' => [ 'status' => 1, 'language' => $language ] ] );
|
||||
}
|
||||
|
||||
public function imageAltChange( $imageId, $imageAlt )
|
||||
{
|
||||
$result = $this->db->update( 'pp_articles_images', [ 'alt' => $imageAlt ], [ 'id' => $imageId ] );
|
||||
\S::delete_cache();
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function articleUrl( $articleId )
|
||||
{
|
||||
$results = $this->db->query(
|
||||
"SELECT seo_link FROM pp_articles_langs AS pal, pp_langs AS pl WHERE lang_id = pl.id AND article_id = " . (int)$articleId . " AND seo_link != '' ORDER BY o ASC LIMIT 1"
|
||||
)->fetchAll();
|
||||
|
||||
if ( !$results[0]['seo_link'] )
|
||||
{
|
||||
$title = $this->articleTitle( $articleId );
|
||||
return 'a-' . $articleId . '-' . \S::seo( $title );
|
||||
}
|
||||
|
||||
return $results[0]['seo_link'];
|
||||
}
|
||||
|
||||
public function articlePages( $articleId )
|
||||
{
|
||||
$pagesRepo = new \Domain\Pages\PagesRepository( $this->db );
|
||||
$results = $this->db->query( "SELECT page_id FROM pp_articles_pages WHERE article_id = " . (int)$articleId )->fetchAll();
|
||||
|
||||
if ( is_array( $results ) and !empty( $results ) ) foreach ( $results as $row )
|
||||
{
|
||||
if ( $out == '' )
|
||||
$out .= ' - ';
|
||||
|
||||
$out .= $pagesRepo->pageTitle( $row['page_id'] );
|
||||
|
||||
if ( end( $results ) != $row )
|
||||
$out .= ' / ';
|
||||
}
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
public function articleTitle( $articleId )
|
||||
{
|
||||
$results = $this->db->query(
|
||||
"SELECT title FROM pp_articles_langs AS pal, pp_langs AS pl WHERE lang_id = pl.id AND article_id = " . (int)$articleId . " AND title != '' ORDER BY o ASC LIMIT 1"
|
||||
)->fetchAll();
|
||||
|
||||
return $results[0]['title'];
|
||||
}
|
||||
|
||||
public function deleteFile( $fileId ): bool
|
||||
{
|
||||
$this->db->update( 'pp_articles_files', [ 'to_delete' => 1 ], [ 'id' => (int)$fileId ] );
|
||||
return true;
|
||||
}
|
||||
|
||||
public function deleteImg( $imageId ): bool
|
||||
{
|
||||
$this->db->update( 'pp_articles_images', [ 'to_delete' => 1 ], [ 'id' => (int)$imageId ] );
|
||||
return true;
|
||||
}
|
||||
|
||||
public function articleDetails( $articleId )
|
||||
{
|
||||
if ( $article = $this->db->get( 'pp_articles', '*', [ 'id' => (int)$articleId ] ) )
|
||||
{
|
||||
$results = $this->db->select( 'pp_articles_langs', '*', [ 'article_id' => (int)$articleId ] );
|
||||
if ( is_array( $results ) )
|
||||
foreach ( $results as $row )
|
||||
$article['languages'][ $row['lang_id'] ] = $row;
|
||||
|
||||
$article['images'] = $this->db->select( 'pp_articles_images', '*', [ 'article_id' => (int)$articleId, 'ORDER' => [ 'o' => 'ASC', 'id' => 'ASC' ] ] );
|
||||
$article['files'] = $this->db->select( 'pp_articles_files', '*', [ 'article_id' => (int)$articleId, 'ORDER' => [ 'o' => 'ASC', 'id' => 'ASC' ] ] );
|
||||
$article['pages'] = $this->db->select( 'pp_articles_pages', 'page_id', [ 'article_id' => (int)$articleId ] );
|
||||
$article['tags'] = $this->db->select( 'pp_tags', [ '[><]pp_articles_tags' => [ 'id' => 'tag_id' ] ], 'name', [ 'article_id' => (int)$articleId ] );
|
||||
$article['params'] = $this->db->select( 'pp_articles_additional_values', [ 'param_id', 'value', 'language_id' ], [ 'article_id' => (int)$articleId ] );
|
||||
}
|
||||
|
||||
return $article;
|
||||
}
|
||||
|
||||
public function insertMissingHash(): bool
|
||||
{
|
||||
if ( $this->db->count( 'pp_articles', [ 'hash' => null ] ) )
|
||||
{
|
||||
$rows = $this->db->select( 'pp_articles', [ 'id', 'date_add' ], [ 'hash' => null ] );
|
||||
if ( is_array( $rows ) )
|
||||
foreach ( $rows as $row )
|
||||
$this->db->update( 'pp_articles', [ 'hash' => md5( $row['id'] . $row['date_add'] ) ], [ 'id' => $row['id'] ] );
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public function articlesByDateAdd( $dateStart, $dateEnd )
|
||||
{
|
||||
$results = $this->db->query(
|
||||
'SELECT id FROM pp_articles WHERE status = 1 AND date_add BETWEEN \'' . $dateStart . '\' AND \'' . $dateEnd . '\' ORDER BY date_add DESC'
|
||||
)->fetchAll();
|
||||
|
||||
if ( is_array( $results ) && !empty( $results ) )
|
||||
foreach ( $results as $row )
|
||||
$articles[] = \front\factory\Articles::article_details( $row['id'], 'pl' );
|
||||
|
||||
return isset( $articles ) ? $articles : null;
|
||||
}
|
||||
|
||||
public function articlesSetArchive( $articleId )
|
||||
{
|
||||
$result = $this->db->update( 'pp_articles', [ 'status' => -1 ], [ 'id' => (int)$articleId ] );
|
||||
\S::htacces();
|
||||
\S::delete_cache();
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function fileNameChange( $fileId, $fileName ): bool
|
||||
{
|
||||
$this->db->update( 'pp_articles_files', [ 'name' => $fileName ], [ 'id' => (int)$fileId ] );
|
||||
return true;
|
||||
}
|
||||
|
||||
public function deleteNonassignedFiles(): void
|
||||
{
|
||||
$results = $this->db->select( 'pp_articles_files', '*', [ 'article_id' => null ] );
|
||||
if ( is_array( $results ) )
|
||||
foreach ( $results as $row )
|
||||
if ( file_exists( '../' . $row['src'] ) )
|
||||
unlink( '../' . $row['src'] );
|
||||
|
||||
$this->db->delete( 'pp_articles_files', [ 'article_id' => null ] );
|
||||
}
|
||||
|
||||
public function deleteNonassignedImages(): void
|
||||
{
|
||||
$results = $this->db->select( 'pp_articles_images', '*', [ 'article_id' => null ] );
|
||||
if ( is_array( $results ) )
|
||||
foreach ( $results as $row )
|
||||
if ( file_exists( '../' . $row['src'] ) )
|
||||
unlink( '../' . $row['src'] );
|
||||
|
||||
$this->db->delete( 'pp_articles_images', [ 'article_id' => null ] );
|
||||
}
|
||||
|
||||
public function duplicateArticle( $articleId, $userId ): bool
|
||||
{
|
||||
$article = $this->articleDetails( $articleId );
|
||||
|
||||
if ( !$article )
|
||||
return false;
|
||||
|
||||
$this->db->insert( 'pp_articles', [
|
||||
'show_title' => $article['show_title'],
|
||||
'show_date_add' => $article['show_date_add'],
|
||||
'show_date_modify' => $article['show_date_modify'],
|
||||
'date_add' => date( 'Y-m-d H:i:s' ),
|
||||
'date_modify' => date( 'Y-m-d H:i:s' ),
|
||||
'modify_by' => $userId,
|
||||
'layout_id' => $article['layout_id'],
|
||||
'status' => $article['status'],
|
||||
'repeat_entry' => $article['repeat_entry'],
|
||||
'social_icons' => $article['social_icons'],
|
||||
'date_start' => $article['date_start'],
|
||||
'date_end' => $article['event_date'],
|
||||
'priority' => $article['priority'],
|
||||
'password' => $article['password'],
|
||||
'pixieset' => $article['pixieset']
|
||||
] );
|
||||
|
||||
$articleTmpId = $this->db->id();
|
||||
if ( !$articleTmpId )
|
||||
return false;
|
||||
|
||||
foreach ( $article['languages'] as $key => $val )
|
||||
$this->db->insert( 'pp_articles_langs', [
|
||||
'article_id' => $articleTmpId,
|
||||
'lang_id' => $key,
|
||||
'title' => 'Kopia: ' . $val['title'],
|
||||
'entry' => $val['entry'],
|
||||
'text' => $val['text'],
|
||||
'meta_title' => null,
|
||||
'meta_description' => null,
|
||||
'meta_keywords' => null,
|
||||
'seo_link' => null,
|
||||
'copy_from' => $val['copy_from'],
|
||||
'block_direct_access' => $val['block_direct_access']
|
||||
] );
|
||||
|
||||
foreach ( $article['params'] as $param )
|
||||
$this->db->insert( 'pp_articles_additional_values', [
|
||||
'param_id' => $param['param_id'],
|
||||
'value' => $param['value'],
|
||||
'article_id' => $articleTmpId,
|
||||
'language_id' => $param['language_id']
|
||||
] );
|
||||
|
||||
foreach ( $article['pages'] as $page )
|
||||
{
|
||||
$order = $this->maxOrder() + 1;
|
||||
$this->db->insert( 'pp_articles_pages', [
|
||||
'article_id' => $articleTmpId,
|
||||
'page_id' => $page,
|
||||
'o' => (int)$order
|
||||
] );
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function articleSave(
|
||||
$articleId, $title, $mainImage, $entry, $text, $tableOfContents, $status, $showTitle, $showTableOfContents, $showDateAdd, $dateAdd, $showDateModify, $dateModify, $seoLink, $metaTitle, $metaDescription,
|
||||
$metaKeywords, $layoutId, $pages, $noindex, $repeatEntry, $copyFrom, $socialIcons, $eventDate, $tags, $blockDirectAccess, $priority, $password, $pixieset, $idAuthor, $params, $userId
|
||||
)
|
||||
{
|
||||
$eventDate = explode( ' - ', $eventDate );
|
||||
|
||||
if ( !$articleId )
|
||||
{
|
||||
$this->db->insert( 'pp_articles', [
|
||||
'show_title' => $showTitle == 'on' ? 1 : 0,
|
||||
'show_table_of_contents' => $showTableOfContents == 'on' ? 1 : 0,
|
||||
'show_date_add' => $showDateAdd == 'on' ? 1 : 0,
|
||||
'show_date_modify' => $showDateModify == 'on' ? 1 : 0,
|
||||
'date_add' => date( 'Y-m-d H:i:s' ),
|
||||
'date_modify' => date( 'Y-m-d H:i:s' ),
|
||||
'modify_by' => $userId,
|
||||
'layout_id' => $layoutId ? (int)$layoutId : null,
|
||||
'status' => $status == 'on' ? 1 : 0,
|
||||
'repeat_entry' => $repeatEntry == 'on' ? 1 : 0,
|
||||
'social_icons' => $socialIcons == 'on' ? 1 : 0,
|
||||
'date_start' => $eventDate[0] ? $eventDate[0] : null,
|
||||
'date_end' => $eventDate[1] ? $eventDate[1] : null,
|
||||
'priority' => $priority == 'on' ? 1 : 0,
|
||||
'password' => $password ? $password : null,
|
||||
'pixieset' => $pixieset,
|
||||
'id_author' => $idAuthor ? $idAuthor : null
|
||||
] );
|
||||
|
||||
$id = $this->db->id();
|
||||
if ( !$id )
|
||||
return false;
|
||||
|
||||
$i = 0;
|
||||
$results = $this->db->select( 'pp_langs', [ 'id' ], [ 'status' => 1, 'ORDER' => [ 'o' => 'ASC' ] ] );
|
||||
if ( is_array( $results ) and count( $results ) > 1 ) foreach ( $results as $row )
|
||||
{
|
||||
$this->db->insert( 'pp_articles_langs', [
|
||||
'article_id' => (int)$id,
|
||||
'lang_id' => $row['id'],
|
||||
'title' => $title[ $i ] != '' ? $title[ $i ] : null,
|
||||
'main_image' => $mainImage[$i] != '' ? $mainImage[$i] : null,
|
||||
'entry' => $entry[ $i ] != '' ? $entry[ $i ] : null,
|
||||
'text' => $text[ $i ] != '' ? $text[ $i ] : null,
|
||||
'table_of_contents' => $tableOfContents[$i] != '' ? $tableOfContents[$i] : null,
|
||||
'meta_title' => $metaTitle[ $i ] != '' ? $metaTitle[ $i ] : null,
|
||||
'meta_description' => $metaDescription[ $i ] != '' ? $metaDescription[ $i ] : null,
|
||||
'meta_keywords' => $metaKeywords[ $i ] != '' ? $metaKeywords[ $i ] : null,
|
||||
'seo_link' => \S::seo( $seoLink[ $i ] ) != '' ? \S::seo( $seoLink[ $i ] ) : null,
|
||||
'noindex' => $noindex[ $i ],
|
||||
'copy_from' => $copyFrom[ $i ] != '' ? $copyFrom[ $i ] : null,
|
||||
'block_direct_access' => $blockDirectAccess[ $i ]
|
||||
] );
|
||||
$i++;
|
||||
}
|
||||
else if ( is_array( $results ) and count( $results ) == 1 ) foreach ( $results as $row )
|
||||
{
|
||||
$this->db->insert( 'pp_articles_langs', [
|
||||
'article_id' => (int)$id,
|
||||
'lang_id' => $row['id'],
|
||||
'title' => $title != '' ? $title : null,
|
||||
'main_image' => $mainImage != '' ? $mainImage : null,
|
||||
'entry' => $entry != '' ? $entry : null,
|
||||
'text' => $text != '' ? $text : null,
|
||||
'table_of_contents' => $tableOfContents != '' ? $tableOfContents : null,
|
||||
'meta_title' => $metaTitle != '' ? $metaTitle : null,
|
||||
'meta_description' => $metaDescription != '' ? $metaDescription : null,
|
||||
'meta_keywords' => $metaKeywords != '' ? $metaKeywords : null,
|
||||
'seo_link' => \S::seo( $seoLink ) != '' ? \S::seo( $seoLink ) : null,
|
||||
'noindex' => $noindex,
|
||||
'copy_from' => $copyFrom != '' ? $copyFrom : null,
|
||||
'block_direct_access' => $blockDirectAccess
|
||||
] );
|
||||
}
|
||||
|
||||
$results = $this->db->select( 'pp_articles_additional_params', '*', [ 'AND' => [ 'status' => 1, 'language' => 0 ] ] );
|
||||
if ( is_array( $results ) and !empty( $results ) ) foreach ( $results as $row )
|
||||
{
|
||||
$this->db->insert( 'pp_articles_additional_values', [
|
||||
'param_id' => $row['id'],
|
||||
'value' => $params[ 'ap_' . $row['name'] ],
|
||||
'article_id' => (int)$id,
|
||||
'language_id' => null
|
||||
] );
|
||||
}
|
||||
|
||||
if ( is_array( $pages ) ) foreach ( $pages as $page )
|
||||
{
|
||||
$order = $this->maxOrder() + 1;
|
||||
$this->db->insert( 'pp_articles_pages', [
|
||||
'article_id' => (int)$id,
|
||||
'page_id' => (int)$page,
|
||||
'o' => (int)$order
|
||||
] );
|
||||
}
|
||||
else if ( $pages )
|
||||
{
|
||||
$order = $this->maxOrder() + 1;
|
||||
$this->db->insert( 'pp_articles_pages', [
|
||||
'article_id' => (int)$id,
|
||||
'page_id' => (int)$pages,
|
||||
'o' => (int)$order
|
||||
] );
|
||||
}
|
||||
|
||||
$results = $this->db->select( 'pp_articles_files', '*', [ 'article_id' => null ] );
|
||||
if ( is_array( $results ) ) foreach ( $results as $row )
|
||||
{
|
||||
$dir = '/upload/article_files/article_' . $id;
|
||||
$new_file_name = str_replace( '/upload/article_files/tmp', $dir, $row['src'] );
|
||||
|
||||
if ( file_exists( '..' . $row['src'] ) )
|
||||
{
|
||||
if ( !is_dir( '../' . $dir ) and $created !== true )
|
||||
{
|
||||
if ( mkdir( '../' . $dir, 0755, true ) )
|
||||
$created = true;
|
||||
}
|
||||
rename( '..' . $row['src'], '..' . $new_file_name );
|
||||
}
|
||||
|
||||
$this->db->update( 'pp_articles_files', [ 'src' => $new_file_name, 'article_id' => $id ], [ 'id' => $row['id'] ] );
|
||||
}
|
||||
|
||||
$created = false;
|
||||
|
||||
$results = $this->db->select( 'pp_articles_images', '*', [ 'article_id' => null ] );
|
||||
if ( is_array( $results ) ) foreach ( $results as $row )
|
||||
{
|
||||
$dir = '/upload/article_images/article_' . $id;
|
||||
$new_file_name = str_replace( '/upload/article_images/tmp', $dir, $row['src'] );
|
||||
|
||||
if ( file_exists( '../' . $new_file_name ) )
|
||||
{
|
||||
$ext = strrpos( $new_file_name, '.' );
|
||||
$fileName_a = substr( $new_file_name, 0, $ext );
|
||||
$fileName_b = substr( $new_file_name, $ext );
|
||||
$count = 1;
|
||||
|
||||
while ( file_exists( '../' . $fileName_a . '_' . $count . $fileName_b ) )
|
||||
$count++;
|
||||
|
||||
$new_file_name = $fileName_a . '_' . $count . $fileName_b;
|
||||
}
|
||||
|
||||
if ( file_exists( '..' . $row['src'] ) )
|
||||
{
|
||||
if ( !is_dir( '../' . $dir ) and $created !== true )
|
||||
{
|
||||
if ( mkdir( '../' . $dir, 0755, true ) )
|
||||
$created = true;
|
||||
}
|
||||
rename( '..' . $row['src'], '..' . $new_file_name );
|
||||
}
|
||||
|
||||
$this->db->update( 'pp_articles_images', [ 'src' => $new_file_name, 'article_id' => (int)$id ], [ 'id' => $row['id'] ] );
|
||||
}
|
||||
|
||||
$tags = explode( ',', $tags );
|
||||
if ( is_array( $tags ) ) foreach ( $tags as $tag )
|
||||
{
|
||||
if ( trim( $tag ) != '' )
|
||||
{
|
||||
$tag_id = $this->db->get( 'pp_tags', 'id', [ 'name' => $tag ] );
|
||||
if ( !$tag_id )
|
||||
{
|
||||
$this->db->insert( 'pp_tags', [ 'name' => $tag ] );
|
||||
$tag_id = $this->db->id();
|
||||
}
|
||||
|
||||
$this->db->insert( 'pp_articles_tags', [ 'article_id' => (int)$id, 'tag_id' => (int)$tag_id ] );
|
||||
}
|
||||
}
|
||||
|
||||
\S::htacces();
|
||||
\S::delete_cache();
|
||||
|
||||
return $id;
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->db->update( 'pp_articles', [
|
||||
'show_title' => $showTitle == 'on' ? 1 : 0,
|
||||
'show_table_of_contents' => $showTableOfContents == 'on' ? 1 : 0,
|
||||
'show_date_add' => $showDateAdd == 'on' ? 1 : 0,
|
||||
'date_add' => $dateAdd,
|
||||
'show_date_modify' => $showDateModify == 'on' ? 1 : 0,
|
||||
'date_modify' => $dateModify ? $dateModify : date( 'Y-m-d H:i:s' ),
|
||||
'modify_by' => $userId,
|
||||
'layout_id' => $layoutId ? (int)$layoutId : null,
|
||||
'status' => $status == 'on' ? 1 : 0,
|
||||
'repeat_entry' => $repeatEntry == 'on' ? 1 : 0,
|
||||
'social_icons' => $socialIcons == 'on' ? 1 : 0,
|
||||
'date_start' => $eventDate[0] ? $eventDate[0] : null,
|
||||
'date_end' => $eventDate[1] ? $eventDate[1] : null,
|
||||
'priority' => $priority == 'on' ? 1 : 0,
|
||||
'password' => $password ? $password : null,
|
||||
'pixieset' => $pixieset,
|
||||
'id_author' => $idAuthor ? $idAuthor : null
|
||||
], [
|
||||
'id' => (int)$articleId
|
||||
] );
|
||||
|
||||
if ( $dateAdd )
|
||||
$this->db->update( 'pp_articles', [ 'date_add' => $dateAdd ], [ 'id' => (int)$articleId ] );
|
||||
|
||||
$i = 0;
|
||||
$this->db->delete( 'pp_articles_langs', [ 'article_id' => (int)$articleId ] );
|
||||
|
||||
$results = $this->db->select( 'pp_langs', [ 'id' ], [ 'status' => 1, 'ORDER' => [ 'o' => 'ASC' ] ] );
|
||||
if ( is_array( $results ) and count( $results ) > 1 ) foreach ( $results as $row )
|
||||
{
|
||||
$this->db->insert( 'pp_articles_langs', [
|
||||
'article_id' => (int)$articleId,
|
||||
'lang_id' => $row['id'],
|
||||
'title' => $title[ $i ] != '' ? $title[ $i ] : null,
|
||||
'main_image' => $mainImage[$i] != '' ? $mainImage[$i] : null,
|
||||
'entry' => $entry[ $i ] != '' ? $entry[ $i ] : null,
|
||||
'text' => $text[ $i ] != '' ? $text[ $i ] : null,
|
||||
'table_of_contents' => $tableOfContents[$i] != '' ? $tableOfContents[$i] : null,
|
||||
'meta_title' => $metaTitle[ $i ] != '' ? $metaTitle[ $i ] : null,
|
||||
'meta_description' => $metaDescription[ $i ] != '' ? $metaDescription[ $i ] : null,
|
||||
'meta_keywords' => $metaKeywords[ $i ] != '' ? $metaKeywords[ $i ] : null,
|
||||
'seo_link' => \S::seo( $seoLink[ $i ] ) != '' ? \S::seo( $seoLink[ $i ] ) : null,
|
||||
'noindex' => $noindex[ $i ],
|
||||
'copy_from' => $copyFrom[ $i ] != '' ? $copyFrom[ $i ] : null,
|
||||
'block_direct_access' => $blockDirectAccess[ $i ]
|
||||
] );
|
||||
$i++;
|
||||
}
|
||||
else if ( is_array( $results ) and count( $results ) == 1 ) foreach ( $results as $row )
|
||||
{
|
||||
$this->db->insert( 'pp_articles_langs', [
|
||||
'article_id' => (int)$articleId,
|
||||
'lang_id' => $row['id'],
|
||||
'title' => $title != '' ? $title : null,
|
||||
'main_image' => $mainImage != '' ? $mainImage : null,
|
||||
'entry' => $entry != '' ? $entry : null,
|
||||
'text' => $text != '' ? $text : null,
|
||||
'table_of_contents' => $tableOfContents != '' ? $tableOfContents : null,
|
||||
'meta_title' => $metaTitle != '' ? $metaTitle : null,
|
||||
'meta_description' => $metaDescription != '' ? $metaDescription : null,
|
||||
'meta_keywords' => $metaKeywords != '' ? $metaKeywords : null,
|
||||
'seo_link' => \S::seo( $seoLink ) != '' ? \S::seo( $seoLink ) : null,
|
||||
'noindex' => $noindex,
|
||||
'copy_from' => $copyFrom != '' ? $copyFrom : null,
|
||||
'block_direct_access' => $blockDirectAccess
|
||||
] );
|
||||
}
|
||||
|
||||
$this->db->delete( 'pp_articles_additional_values', [ 'article_id' => (int)$articleId ] );
|
||||
|
||||
$results = $this->db->select( 'pp_articles_additional_params', '*', [ 'AND' => [ 'status' => 1, 'language' => 0 ] ] );
|
||||
if ( is_array( $results ) and !empty( $results ) ) foreach ( $results as $row )
|
||||
{
|
||||
$this->db->insert( 'pp_articles_additional_values', [
|
||||
'param_id' => $row['id'],
|
||||
'value' => $params[ 'ap_' . $row['name'] ],
|
||||
'article_id' => (int)$articleId,
|
||||
'language_id' => null
|
||||
] );
|
||||
}
|
||||
|
||||
$results = $this->db->select( 'pp_articles_additional_params', '*', [ 'AND' => [ 'status' => 1, 'language' => 1 ] ] );
|
||||
if ( is_array( $results ) and !empty( $results ) ) foreach ( $results as $row )
|
||||
{
|
||||
$results2 = $this->db->select( 'pp_langs', [ 'id' ], [ 'status' => 1, 'ORDER' => [ 'o' => 'ASC' ] ] );
|
||||
if ( is_array( $results2 ) ) foreach ( $results2 as $row2 )
|
||||
{
|
||||
$this->db->insert( 'pp_articles_additional_values', [
|
||||
'param_id' => $row['id'],
|
||||
'value' => $params[ 'ap_' . $row['name'] . '_' . $row2['id'] ],
|
||||
'article_id' => (int)$articleId,
|
||||
'language_id' => $row2['id']
|
||||
] );
|
||||
}
|
||||
}
|
||||
|
||||
$not_in = [ 0 ];
|
||||
if ( is_array( $pages ) ) foreach ( $pages as $page )
|
||||
$not_in[] = $page;
|
||||
else if ( $pages )
|
||||
$not_in[] = $pages;
|
||||
|
||||
$this->db->delete( 'pp_articles_pages', [ 'AND' => [ 'article_id' => (int)$articleId, 'page_id[!]' => $not_in ] ] );
|
||||
|
||||
$pages_tmp = $this->db->select( 'pp_articles_pages', 'page_id', [ 'article_id' => (int)$articleId ] );
|
||||
if ( !is_array( $pages ) )
|
||||
$pages = [ $pages ];
|
||||
|
||||
$pages = array_diff( $pages, $pages_tmp );
|
||||
if ( is_array( $pages ) ) foreach ( $pages as $page )
|
||||
{
|
||||
$order = $this->maxOrder() + 1;
|
||||
$this->db->insert( 'pp_articles_pages', [
|
||||
'article_id' => (int)$articleId,
|
||||
'page_id' => (int)$page,
|
||||
'o' => (int)$order
|
||||
] );
|
||||
}
|
||||
|
||||
$results = $this->db->select( 'pp_articles_files', '*', [ 'article_id' => null ] );
|
||||
if ( is_array( $results ) ) foreach ( $results as $row )
|
||||
{
|
||||
$dir = '/upload/article_files/article_' . $articleId;
|
||||
$new_file_name = str_replace( '/upload/article_files/tmp', $dir, $row['src'] );
|
||||
|
||||
if ( file_exists( '..' . $row['src'] ) )
|
||||
{
|
||||
if ( !is_dir( '../' . $dir ) and $created !== true )
|
||||
{
|
||||
if ( mkdir( '../' . $dir, 0755, true ) )
|
||||
$created = true;
|
||||
}
|
||||
rename( '..' . $row['src'], '..' . $new_file_name );
|
||||
}
|
||||
|
||||
$this->db->update( 'pp_articles_files', [ 'src' => $new_file_name, 'article_id' => (int)$articleId ], [ 'id' => $row['id'] ] );
|
||||
}
|
||||
|
||||
$created = false;
|
||||
|
||||
$results = $this->db->select( 'pp_articles_images', '*', [ 'article_id' => null ] );
|
||||
if ( is_array( $results ) ) foreach ( $results as $row )
|
||||
{
|
||||
$dir = '/upload/article_images/article_' . $articleId;
|
||||
$new_file_name = str_replace( '/upload/article_images/tmp', $dir, $row['src'] );
|
||||
|
||||
if ( file_exists( '../' . $new_file_name ) )
|
||||
{
|
||||
$ext = strrpos( $new_file_name, '.' );
|
||||
$fileName_a = substr( $new_file_name, 0, $ext );
|
||||
$fileName_b = substr( $new_file_name, $ext );
|
||||
$count = 1;
|
||||
|
||||
while ( file_exists( '../' . $fileName_a . '_' . $count . $fileName_b ) )
|
||||
$count++;
|
||||
|
||||
$new_file_name = $fileName_a . '_' . $count . $fileName_b;
|
||||
}
|
||||
|
||||
if ( file_exists( '..' . $row['src'] ) )
|
||||
{
|
||||
if ( !is_dir( '../' . $dir ) and $created !== true )
|
||||
{
|
||||
if ( mkdir( '../' . $dir, 0755, true ) )
|
||||
$created = true;
|
||||
}
|
||||
rename( '..' . $row['src'], '..' . $new_file_name );
|
||||
}
|
||||
|
||||
$this->db->update( 'pp_articles_images', [ 'src' => $new_file_name, 'article_id' => (int)$articleId ], [ 'id' => $row['id'] ] );
|
||||
}
|
||||
|
||||
$results = $this->db->select( 'pp_articles_images', '*', [ 'AND' => [ 'article_id' => (int)$articleId, 'to_delete' => 1 ] ] );
|
||||
if ( is_array( $results ) ) foreach ( $results as $row )
|
||||
if ( file_exists( '../' . $row['src'] ) )
|
||||
unlink( '../' . $row['src'] );
|
||||
|
||||
$this->db->delete( 'pp_articles_images', [ 'AND' => [ 'article_id' => (int)$articleId, 'to_delete' => 1 ] ] );
|
||||
|
||||
$results = $this->db->select( 'pp_articles_files', '*', [ 'AND' => [ 'article_id' => (int)$articleId, 'to_delete' => 1 ] ] );
|
||||
if ( is_array( $results ) ) foreach ( $results as $row )
|
||||
if ( file_exists( '../' . $row['src'] ) )
|
||||
unlink( '../' . $row['src'] );
|
||||
|
||||
$this->db->delete( 'pp_articles_files', [ 'AND' => [ 'article_id' => (int)$articleId, 'to_delete' => 1 ] ] );
|
||||
|
||||
$this->db->delete( 'pp_articles_tags', [ 'article_id' => (int)$articleId ] );
|
||||
|
||||
$tags = explode( ',', $tags );
|
||||
if ( is_array( $tags ) ) foreach ( $tags as $tag )
|
||||
{
|
||||
if ( trim( $tag ) != '' )
|
||||
{
|
||||
$tag_id = $this->db->get( 'pp_tags', 'id', [ 'name' => $tag ] );
|
||||
if ( !$tag_id )
|
||||
{
|
||||
$this->db->insert( 'pp_tags', [ 'name' => $tag ] );
|
||||
$tag_id = $this->db->id();
|
||||
}
|
||||
|
||||
$this->db->insert( 'pp_articles_tags', [ 'article_id' => (int)$articleId, 'tag_id' => (int)$tag_id ] );
|
||||
}
|
||||
}
|
||||
|
||||
\S::htacces();
|
||||
\S::delete_cache();
|
||||
return $articleId;
|
||||
}
|
||||
}
|
||||
|
||||
public function maxOrder()
|
||||
{
|
||||
return $this->db->max( 'pp_articles_pages', 'o' );
|
||||
}
|
||||
}
|
||||
?>
|
||||
156
autoload/Domain/Authors/AuthorsRepository.php
Normal file
156
autoload/Domain/Authors/AuthorsRepository.php
Normal file
@@ -0,0 +1,156 @@
|
||||
<?php
|
||||
namespace Domain\Authors;
|
||||
|
||||
class AuthorsRepository
|
||||
{
|
||||
private $db;
|
||||
|
||||
public function __construct($db)
|
||||
{
|
||||
$this->db = $db;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prosta lista autorow
|
||||
* @return array|bool
|
||||
*/
|
||||
public function simpleList()
|
||||
{
|
||||
return $this->db->select('pp_authors', '*', ['ORDER' => ['author' => 'ASC']]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Szczegoly autora z jezykami
|
||||
* @param int $authorId
|
||||
* @return array|bool
|
||||
*/
|
||||
public function authorDetails($authorId)
|
||||
{
|
||||
$author = $this->db->get('pp_authors', '*', ['id' => (int)$authorId]);
|
||||
|
||||
$results = $this->db->select('pp_authors_langs', '*', ['id_author' => (int)$authorId]);
|
||||
if (is_array($results)) foreach ($results as $row)
|
||||
$author['languages'][$row['id_lang']] = $row;
|
||||
|
||||
return $author;
|
||||
}
|
||||
|
||||
/**
|
||||
* Zapis autora (insert lub update)
|
||||
* @param int $authorId
|
||||
* @param string $author
|
||||
* @param string $image
|
||||
* @param string|array $description
|
||||
* @return int|bool
|
||||
*/
|
||||
public function authorSave($authorId, $author, $image, $description)
|
||||
{
|
||||
if (!$authorId)
|
||||
{
|
||||
$this->db->insert('pp_authors', [
|
||||
'author' => $author,
|
||||
'image' => $image
|
||||
]);
|
||||
|
||||
$id = $this->db->id();
|
||||
|
||||
if ($id)
|
||||
{
|
||||
$i = 0;
|
||||
|
||||
$results = $this->db->select('pp_langs', ['id'], ['status' => 1, 'ORDER' => ['o' => 'ASC']]);
|
||||
if (is_array($results) and count($results) > 1) foreach ($results as $row)
|
||||
{
|
||||
$this->db->insert('pp_authors_langs', [
|
||||
'id_author' => (int)$id,
|
||||
'id_lang' => $row['id'],
|
||||
'description' => $description[$i]
|
||||
]);
|
||||
$i++;
|
||||
}
|
||||
else if (is_array($results) and count($results) == 1) foreach ($results as $row)
|
||||
{
|
||||
$this->db->insert('pp_authors_langs', [
|
||||
'id_author' => (int)$id,
|
||||
'id_lang' => $row['id'],
|
||||
'description' => $description
|
||||
]);
|
||||
}
|
||||
|
||||
\S::delete_cache();
|
||||
|
||||
return $id;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->db->update('pp_authors', [
|
||||
'author' => $author,
|
||||
'image' => $image
|
||||
], [
|
||||
'id' => (int)$authorId
|
||||
]);
|
||||
|
||||
$this->db->delete('pp_authors_langs', ['id_author' => (int)$authorId]);
|
||||
|
||||
$i = 0;
|
||||
|
||||
$results = $this->db->select('pp_langs', ['id'], ['status' => 1, 'ORDER' => ['o' => 'ASC']]);
|
||||
if (is_array($results) and count($results) > 1) foreach ($results as $row)
|
||||
{
|
||||
$this->db->insert('pp_authors_langs', [
|
||||
'id_author' => (int)$authorId,
|
||||
'id_lang' => $row['id'],
|
||||
'description' => $description[$i]
|
||||
]);
|
||||
$i++;
|
||||
}
|
||||
else if (is_array($results) and count($results) == 1) foreach ($results as $row)
|
||||
{
|
||||
$this->db->insert('pp_authors_langs', [
|
||||
'id_author' => (int)$authorId,
|
||||
'id_lang' => $row['id'],
|
||||
'description' => $description
|
||||
]);
|
||||
}
|
||||
|
||||
\S::delete_cache();
|
||||
|
||||
return $authorId;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Usuniecie autora
|
||||
* @param int $authorId
|
||||
* @return object|bool
|
||||
*/
|
||||
public function authorDelete($authorId)
|
||||
{
|
||||
$result = $this->db->delete('pp_authors', ['id' => (int)$authorId]);
|
||||
\S::delete_cache();
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Szczegoly autora z cache (front)
|
||||
* @param int $authorId
|
||||
* @return array|bool
|
||||
*/
|
||||
public function authorByLang($authorId)
|
||||
{
|
||||
if (!$author = \Shared\Cache\CacheHandler::fetch("get_single_author:$authorId"))
|
||||
{
|
||||
$author = $this->db->get('pp_authors', '*', ['id' => (int)$authorId]);
|
||||
|
||||
$results = $this->db->select('pp_authors_langs', '*', ['id_author' => (int)$authorId]);
|
||||
if (is_array($results)) foreach ($results as $row)
|
||||
$author['languages'][$row['id_lang']] = $row;
|
||||
|
||||
\Shared\Cache\CacheHandler::store("get_single_author:$authorId", $author);
|
||||
}
|
||||
return $author;
|
||||
}
|
||||
}
|
||||
148
autoload/Domain/Banners/BannersRepository.php
Normal file
148
autoload/Domain/Banners/BannersRepository.php
Normal file
@@ -0,0 +1,148 @@
|
||||
<?php
|
||||
namespace Domain\Banners;
|
||||
|
||||
class BannersRepository
|
||||
{
|
||||
private $db;
|
||||
|
||||
public function __construct( $db )
|
||||
{
|
||||
$this->db = $db;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Odczyt
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
public function bannerDetails( $bannerId )
|
||||
{
|
||||
$banner = $this->db->get( 'pp_banners', '*', [ 'id' => $bannerId ] );
|
||||
if ( !$banner ) return null;
|
||||
|
||||
$langs = $this->db->select( 'pp_banners_langs', '*', [ 'id_banner' => $bannerId ] );
|
||||
$banner['languages'] = [];
|
||||
if ( is_array( $langs ) )
|
||||
foreach ( $langs as $lang )
|
||||
$banner['languages'][ $lang['id_lang'] ] = $lang;
|
||||
|
||||
return $banner;
|
||||
}
|
||||
|
||||
public function activeBanners( $langId )
|
||||
{
|
||||
if ( $banners = \Shared\Cache\CacheHandler::fetch( 'banners' ) )
|
||||
return $banners;
|
||||
|
||||
$results = $this->db->query(
|
||||
'SELECT id, name FROM pp_banners WHERE status = 1 AND ( date_start <= \'' . date( 'Y-m-d' ) . '\' OR date_start IS NULL ) AND ( date_end >= \'' . date( 'Y-m-d' ) . '\' OR date_end IS NULL ) AND home_page = 0'
|
||||
)->fetchAll( \PDO::FETCH_ASSOC );
|
||||
|
||||
$banners = [];
|
||||
if ( is_array( $results ) )
|
||||
{
|
||||
foreach ( $results as $row )
|
||||
{
|
||||
$langData = $this->db->get( 'pp_banners_langs', '*', [
|
||||
'AND' => [ 'id_banner' => $row['id'], 'id_lang' => $langId ]
|
||||
] );
|
||||
$row['languages'] = $langData ?: [];
|
||||
$banners[] = $row;
|
||||
}
|
||||
}
|
||||
|
||||
\Shared\Cache\CacheHandler::store( 'banners', $banners );
|
||||
return $banners;
|
||||
}
|
||||
|
||||
public function mainBanner( $langId )
|
||||
{
|
||||
$cacheKey = "main_banner:$langId";
|
||||
if ( $banner = \Shared\Cache\CacheHandler::fetch( $cacheKey ) )
|
||||
return $banner;
|
||||
|
||||
$results = $this->db->query(
|
||||
'SELECT id, name FROM pp_banners WHERE status = 1 AND ( date_start <= \'' . date( 'Y-m-d' ) . '\' OR date_start IS NULL ) AND ( date_end >= \'' . date( 'Y-m-d' ) . '\' OR date_end IS NULL ) AND home_page = 1 ORDER BY date_end ASC LIMIT 1'
|
||||
)->fetchAll( \PDO::FETCH_ASSOC );
|
||||
|
||||
if ( !is_array( $results ) || empty( $results ) ) return null;
|
||||
|
||||
$banner = $results[0];
|
||||
$langData = $this->db->get( 'pp_banners_langs', '*', [
|
||||
'AND' => [ 'id_banner' => $banner['id'], 'id_lang' => $langId ]
|
||||
] );
|
||||
$banner['languages'] = $langData ?: [];
|
||||
|
||||
\Shared\Cache\CacheHandler::store( $cacheKey, $banner );
|
||||
return $banner;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Zapis / usuwanie
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
public function bannerSave( $bannerId, $name, $status, $dateStart, $dateEnd, $homePage, $src, $url, $html, $text )
|
||||
{
|
||||
$languages = $this->db->select( 'pp_langs', '*', [ 'status' => 1, 'ORDER' => [ 'o' => 'ASC' ] ] );
|
||||
if ( !is_array( $languages ) ) $languages = [];
|
||||
$langCount = count( $languages );
|
||||
|
||||
if ( !$bannerId )
|
||||
{
|
||||
$this->db->insert( 'pp_banners', [
|
||||
'name' => $name,
|
||||
'status' => $status == 'on' ? 1 : 0,
|
||||
'date_start' => $dateStart ? $dateStart : null,
|
||||
'date_end' => $dateEnd ? $dateEnd : null,
|
||||
'home_page' => $homePage == 'on' ? 1 : 0,
|
||||
] );
|
||||
$bannerId = $this->db->id();
|
||||
if ( !$bannerId ) return false;
|
||||
|
||||
foreach ( $languages as $i => $lang )
|
||||
{
|
||||
$this->db->insert( 'pp_banners_langs', [
|
||||
'id_banner' => $bannerId,
|
||||
'id_lang' => $lang['id'],
|
||||
'src' => $langCount > 1 ? $src[ $i ] : $src,
|
||||
'url' => $langCount > 1 ? $url[ $i ] : $url,
|
||||
'html' => $langCount > 1 ? $html[ $i ] : $html,
|
||||
'text' => $langCount > 1 ? $text[ $i ] : $text,
|
||||
] );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->db->update( 'pp_banners', [
|
||||
'name' => $name,
|
||||
'status' => $status == 'on' ? 1 : 0,
|
||||
'date_start' => $dateStart ? $dateStart : null,
|
||||
'date_end' => $dateEnd ? $dateEnd : null,
|
||||
'home_page' => $homePage == 'on' ? 1 : 0,
|
||||
], [ 'id' => $bannerId ] );
|
||||
|
||||
$this->db->delete( 'pp_banners_langs', [ 'id_banner' => $bannerId ] );
|
||||
|
||||
foreach ( $languages as $i => $lang )
|
||||
{
|
||||
$this->db->insert( 'pp_banners_langs', [
|
||||
'id_banner' => $bannerId,
|
||||
'id_lang' => $lang['id'],
|
||||
'src' => $langCount > 1 ? $src[ $i ] : $src,
|
||||
'url' => $langCount > 1 ? $url[ $i ] : $url,
|
||||
'html' => $langCount > 1 ? $html[ $i ] : $html,
|
||||
'text' => $langCount > 1 ? $text[ $i ] : $text,
|
||||
] );
|
||||
}
|
||||
}
|
||||
|
||||
\S::delete_cache();
|
||||
return $bannerId;
|
||||
}
|
||||
|
||||
public function bannerDelete( $bannerId )
|
||||
{
|
||||
$result = $this->db->delete( 'pp_banners', [ 'id' => $bannerId ] );
|
||||
\S::delete_cache();
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
213
autoload/Domain/Languages/LanguagesRepository.php
Normal file
213
autoload/Domain/Languages/LanguagesRepository.php
Normal file
@@ -0,0 +1,213 @@
|
||||
<?php
|
||||
namespace Domain\Languages;
|
||||
|
||||
class LanguagesRepository
|
||||
{
|
||||
private $db;
|
||||
|
||||
public function __construct( $db )
|
||||
{
|
||||
$this->db = $db;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Odczyt
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
public function languagesList(): array
|
||||
{
|
||||
return $this->db->select( 'pp_langs', '*', [ 'ORDER' => [ 'o' => 'ASC' ] ] ) ?: [];
|
||||
}
|
||||
|
||||
public function languageDetails( string $languageId ): ?array
|
||||
{
|
||||
return $this->db->get( 'pp_langs', '*', [ 'id' => $languageId ] ) ?: null;
|
||||
}
|
||||
|
||||
public function availableDomains(): array
|
||||
{
|
||||
return $this->db->query(
|
||||
'SELECT domain FROM pp_langs WHERE status = 1 AND domain IS NOT NULL GROUP BY domain'
|
||||
)->fetchAll( \PDO::FETCH_ASSOC ) ?: [];
|
||||
}
|
||||
|
||||
public function defaultDomain(): ?string
|
||||
{
|
||||
$results = $this->db->query(
|
||||
'SELECT domain FROM pp_langs WHERE status = 1 AND domain IS NOT NULL AND main_domain = 1'
|
||||
)->fetchAll();
|
||||
return $results[0][0] ?? null;
|
||||
}
|
||||
|
||||
public function defaultLanguage( string $domain = '' ): ?string
|
||||
{
|
||||
if ( !$default = \Shared\Cache\CacheHandler::fetch( "default_language:$domain" ) )
|
||||
{
|
||||
if ( $domain )
|
||||
$results = $this->db->query(
|
||||
'SELECT id FROM pp_langs WHERE status = 1 AND domain = \'' . $domain . '\' ORDER BY start DESC, o ASC LIMIT 1'
|
||||
)->fetchAll();
|
||||
|
||||
if ( !$domain || !$this->defaultDomain() )
|
||||
$results = $this->db->query(
|
||||
'SELECT id FROM pp_langs WHERE status = 1 AND domain IS NULL ORDER BY start DESC, o ASC LIMIT 1'
|
||||
)->fetchAll();
|
||||
|
||||
$default = $results[0][0] ?? null;
|
||||
\Shared\Cache\CacheHandler::store( "default_language:$domain", $default );
|
||||
}
|
||||
return $default;
|
||||
}
|
||||
|
||||
public function activeLanguages(): array
|
||||
{
|
||||
if ( !$active = \Shared\Cache\CacheHandler::fetch( 'active_languages' ) )
|
||||
{
|
||||
$active = $this->db->select( 'pp_langs', [ 'id', 'name', 'domain' ], [ 'status' => 1, 'ORDER' => [ 'o' => 'ASC' ] ] ) ?: [];
|
||||
\Shared\Cache\CacheHandler::store( 'active_languages', $active );
|
||||
}
|
||||
return $active;
|
||||
}
|
||||
|
||||
public function langTranslations( string $language = 'pl' ): array
|
||||
{
|
||||
if ( !$translations = \Shared\Cache\CacheHandler::fetch( "lang_translations:$language" ) )
|
||||
{
|
||||
$translations = [ '0' => $language ];
|
||||
|
||||
$results = $this->db->select( 'pp_langs_translations', [ 'text', $language ] );
|
||||
if ( is_array( $results ) )
|
||||
foreach ( $results as $row )
|
||||
$translations[ $row['text'] ] = $row[ $language ];
|
||||
|
||||
\Shared\Cache\CacheHandler::store( "lang_translations:$language", $translations );
|
||||
}
|
||||
return $translations;
|
||||
}
|
||||
|
||||
public function translationDetails( int $translationId ): ?array
|
||||
{
|
||||
return $this->db->get( 'pp_langs_translations', '*', [ 'id' => $translationId ] ) ?: null;
|
||||
}
|
||||
|
||||
public function maxOrder(): int
|
||||
{
|
||||
return (int) $this->db->max( 'pp_langs', 'o' );
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Zapis / usuwanie
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
public function languageSave( string $languageId, string $name, $status, $start, $o, $domain, $main_domain ): string
|
||||
{
|
||||
if ( $start == 'on' && $status == 'on' && !\S::get_domain( $domain ) )
|
||||
$this->db->update( 'pp_langs', [ 'start' => 0 ], [ 'id[!]' => $languageId ] );
|
||||
|
||||
if ( $start == 'on' && $status == 'on' && \S::get_domain( $domain ) )
|
||||
$this->db->update( 'pp_langs', [ 'start' => 0 ], [
|
||||
'AND' => [ 'id[!]' => $languageId, 'domain' => \S::get_domain( $domain ) ]
|
||||
] );
|
||||
|
||||
if ( $main_domain == 'on' && $domain && $status == 'on' )
|
||||
$this->db->update( 'pp_langs', [ 'main_domain' => 0 ], [ ' id[!]' => $languageId ] );
|
||||
|
||||
if ( $this->db->count( 'pp_langs', [ 'id' => $languageId ] ) )
|
||||
{
|
||||
$this->db->update( 'pp_langs', [
|
||||
'status' => $status == 'on' ? 1 : 0,
|
||||
'start' => $start == 'on' ? 1 : 0,
|
||||
'name' => $name,
|
||||
'o' => $o,
|
||||
'domain' => \S::get_domain( $domain ) ?: null,
|
||||
'main_domain' => $main_domain == 'on' && \S::get_domain( $domain ) ? 1 : 0,
|
||||
], [ 'id' => $languageId ] );
|
||||
}
|
||||
else
|
||||
{
|
||||
if ( $this->db->query( 'ALTER TABLE pp_langs_translations ADD ' . strtolower( $languageId ) . ' TEXT NULL DEFAULT NULL' ) )
|
||||
{
|
||||
$this->db->insert( 'pp_langs', [
|
||||
'id' => strtolower( $languageId ),
|
||||
'name' => $name,
|
||||
'status' => $status == 'on' ? 1 : 0,
|
||||
'start' => $start == 'on' ? 1 : 0,
|
||||
'o' => $o,
|
||||
'domain' => \S::get_domain( $domain ) ?: null,
|
||||
'main_domain' => $main_domain == 'on' && \S::get_domain( $domain ) ? 1 : 0,
|
||||
] );
|
||||
}
|
||||
}
|
||||
|
||||
// Upewnij się, że każda domena ma język startowy
|
||||
if ( !$this->db->count( 'pp_langs', [ 'AND' => [ 'status' => 1, 'domain[!]' => null ] ] ) )
|
||||
{
|
||||
if ( !$this->db->count( 'pp_langs', [ 'AND' => [ 'status' => 1, 'start' => 1, 'domain' => null ] ] ) )
|
||||
{
|
||||
if ( $idTmp = $this->db->get( 'pp_langs', 'id', [ 'status' => 1, 'ORDER' => [ 'o' => 'ASC' ] ] ) )
|
||||
$this->db->update( 'pp_langs', [ 'start' => 1 ], [ 'id' => $idTmp ] );
|
||||
}
|
||||
}
|
||||
|
||||
$domains = $this->db->select( 'pp_langs', 'domain', [ 'domain[!]' => null, 'GROUP' => 'domain' ] );
|
||||
if ( is_array( $domains ) && !empty( $domains ) )
|
||||
{
|
||||
$this->db->update( 'pp_langs', [ 'start' => 0 ], [ 'domain' => null ] );
|
||||
foreach ( $domains as $dom )
|
||||
{
|
||||
if ( !$this->db->count( 'pp_langs', [ 'AND' => [ 'status' => 1, 'start' => 1, 'domain' => $dom ] ] ) )
|
||||
{
|
||||
if ( $idTmp = $this->db->get( 'pp_langs', 'id', [ 'AND' => [ 'status' => 1, 'domain' => $dom ], 'ORDER' => [ 'o' => 'ASC' ] ] ) )
|
||||
$this->db->update( 'pp_langs', [ 'start' => 1 ], [ 'id' => $idTmp ] );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( !$this->db->count( 'pp_langs', [ 'AND' => [ 'status' => 1, 'main_domain' => 1 ] ] ) )
|
||||
{
|
||||
if ( $idTmp = $this->db->get( 'pp_langs', 'id', [ 'AND' => [ 'status' => 1, 'domain[!]' => null ], 'ORDER' => [ 'o' => 'ASC' ] ] ) )
|
||||
$this->db->update( 'pp_langs', [ 'main_domain' => 1 ], [ 'id' => $idTmp ] );
|
||||
}
|
||||
|
||||
\S::htacces();
|
||||
\S::delete_cache();
|
||||
return $languageId;
|
||||
}
|
||||
|
||||
public function languageDelete( string $languageId ): bool
|
||||
{
|
||||
if ( $this->db->count( 'pp_langs' ) > 1 )
|
||||
{
|
||||
if ( $this->db->query( 'ALTER TABLE pp_langs_translations DROP ' . $languageId )
|
||||
&& $this->db->delete( 'pp_langs', [ 'id' => $languageId ] ) )
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function translationSave( $translationId, string $text, array $languages = [] ): int
|
||||
{
|
||||
if ( $translationId )
|
||||
{
|
||||
$this->db->update( 'pp_langs_translations', [ 'text' => $text ], [ 'id' => $translationId ] );
|
||||
foreach ( $languages as $key => $val )
|
||||
$this->db->update( 'pp_langs_translations', [ $key => $val ], [ 'id' => $translationId ] );
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->db->insert( 'pp_langs_translations', [ 'text' => $text ] );
|
||||
$translationId = $this->db->id();
|
||||
foreach ( $languages as $key => $val )
|
||||
$this->db->update( 'pp_langs_translations', [ $key => $val ], [ 'id' => $translationId ] );
|
||||
}
|
||||
|
||||
\S::htacces();
|
||||
\S::delete_cache();
|
||||
return (int) $translationId;
|
||||
}
|
||||
|
||||
public function translationDelete( int $translationId ): bool
|
||||
{
|
||||
return (bool) $this->db->delete( 'pp_langs_translations', [ 'id' => $translationId ] );
|
||||
}
|
||||
}
|
||||
123
autoload/Domain/Layouts/LayoutsRepository.php
Normal file
123
autoload/Domain/Layouts/LayoutsRepository.php
Normal file
@@ -0,0 +1,123 @@
|
||||
<?php
|
||||
namespace Domain\Layouts;
|
||||
|
||||
class LayoutsRepository
|
||||
{
|
||||
private $db;
|
||||
|
||||
public function __construct( $db )
|
||||
{
|
||||
$this->db = $db;
|
||||
}
|
||||
|
||||
public function layoutDelete( $layoutId )
|
||||
{
|
||||
if ( $this->db->count( 'pp_layouts' ) > 1 )
|
||||
return $this->db->delete( 'pp_layouts', [ 'id' => (int)$layoutId ] );
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function layoutDetails( $layoutId )
|
||||
{
|
||||
$layout = $this->db->get( 'pp_layouts', '*', [ 'id' => (int)$layoutId ] );
|
||||
$layout['pages'] = $this->db->select( 'pp_layouts_pages', 'page_id', [ 'layout_id' => (int)$layoutId ] );
|
||||
|
||||
return $layout;
|
||||
}
|
||||
|
||||
public function layoutSave( $layoutId, $name, $status, $pages, $html, $css, $js, $mHtml, $mCss, $mJs )
|
||||
{
|
||||
if ( !$layoutId )
|
||||
return $this->createLayout( $name, $status, $pages, $html, $css, $js, $mHtml, $mCss, $mJs );
|
||||
|
||||
return $this->updateLayout( $layoutId, $name, $status, $pages, $html, $css, $js, $mHtml, $mCss, $mJs );
|
||||
}
|
||||
|
||||
public function menusList()
|
||||
{
|
||||
$pagesRepo = new \Domain\Pages\PagesRepository( $this->db );
|
||||
$results = $this->db->select( 'pp_menus', 'id', [ 'ORDER' => [ 'name' => 'ASC' ] ] );
|
||||
if ( is_array( $results ) )
|
||||
foreach ( $results as $row )
|
||||
{
|
||||
$menu = $pagesRepo->menuDetails( $row );
|
||||
$menu['pages'] = $pagesRepo->menuPages( $row );
|
||||
$menus[] = $menu;
|
||||
}
|
||||
|
||||
return isset( $menus ) ? $menus : null;
|
||||
}
|
||||
|
||||
public function layoutsList()
|
||||
{
|
||||
return $this->db->select( 'pp_layouts', '*', [ 'ORDER' => [ 'name' => 'ASC' ] ] );
|
||||
}
|
||||
|
||||
private function createLayout( $name, $status, $pages, $html, $css, $js, $mHtml, $mCss, $mJs )
|
||||
{
|
||||
if ( $status == 'on' )
|
||||
$this->db->update( 'pp_layouts', [ 'status' => 0 ] );
|
||||
|
||||
$this->db->insert( 'pp_layouts', [
|
||||
'name' => $name,
|
||||
'html' => $html,
|
||||
'css' => $css,
|
||||
'js' => $js,
|
||||
'm_html' => $mHtml,
|
||||
'm_css' => $mCss,
|
||||
'm_js' => $mJs,
|
||||
'status' => $status == 'on' ? 1 : 0
|
||||
] );
|
||||
|
||||
$id = $this->db->id();
|
||||
if ( !$id )
|
||||
return false;
|
||||
|
||||
$this->replaceLayoutPages( (int)$id, $pages );
|
||||
\S::delete_cache();
|
||||
|
||||
return $id;
|
||||
}
|
||||
|
||||
private function updateLayout( $layoutId, $name, $status, $pages, $html, $css, $js, $mHtml, $mCss, $mJs )
|
||||
{
|
||||
if ( $status == 'on' )
|
||||
$this->db->update( 'pp_layouts', [ 'status' => 0 ] );
|
||||
|
||||
$this->db->update( 'pp_layouts', [
|
||||
'name' => $name,
|
||||
'html' => $html,
|
||||
'css' => $css,
|
||||
'js' => $js,
|
||||
'm_html' => $mHtml,
|
||||
'm_css' => $mCss,
|
||||
'm_js' => $mJs,
|
||||
'status' => $status == 'on' ? 1 : 0
|
||||
], [
|
||||
'id' => $layoutId
|
||||
] );
|
||||
|
||||
$this->db->delete( 'pp_layouts_pages', [ 'layout_id' => (int)$layoutId ] );
|
||||
$this->replaceLayoutPages( (int)$layoutId, $pages );
|
||||
|
||||
\S::delete_cache();
|
||||
return $layoutId;
|
||||
}
|
||||
|
||||
private function replaceLayoutPages( int $layoutId, $pages ): void
|
||||
{
|
||||
if ( is_array( $pages ) )
|
||||
foreach ( $pages as $page )
|
||||
{
|
||||
$this->db->delete( 'pp_layouts_pages', [ 'page_id' => (int)$page ] );
|
||||
$this->db->insert( 'pp_layouts_pages', [ 'layout_id' => $layoutId, 'page_id' => (int)$page ] );
|
||||
}
|
||||
else if ( $pages )
|
||||
{
|
||||
$this->db->delete( 'pp_layouts_pages', [ 'page_id' => (int)$pages ] );
|
||||
$this->db->insert( 'pp_layouts_pages', [ 'layout_id' => $layoutId, 'page_id' => (int)$pages ] );
|
||||
}
|
||||
}
|
||||
}
|
||||
?>
|
||||
281
autoload/Domain/Newsletter/NewsletterRepository.php
Normal file
281
autoload/Domain/Newsletter/NewsletterRepository.php
Normal file
@@ -0,0 +1,281 @@
|
||||
<?php
|
||||
namespace Domain\Newsletter;
|
||||
|
||||
class NewsletterRepository
|
||||
{
|
||||
private $db;
|
||||
|
||||
public function __construct($db)
|
||||
{
|
||||
$this->db = $db;
|
||||
}
|
||||
|
||||
/**
|
||||
* Import emaili do newslettera
|
||||
* @param string $emails
|
||||
* @return bool
|
||||
*/
|
||||
public function emailsImport($emails)
|
||||
{
|
||||
$emails = explode(PHP_EOL, $emails);
|
||||
if (is_array($emails)) foreach ($emails as $email)
|
||||
{
|
||||
if (trim($email) and !$this->db->count('pp_newsletter', ['email' => trim($email)]))
|
||||
$this->db->insert('pp_newsletter', [
|
||||
'email' => trim($email),
|
||||
'hash' => md5($email . time()),
|
||||
'status' => 1
|
||||
]);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sprawdza czy szablon jest adminski
|
||||
* @param int $templateId
|
||||
* @return string|bool
|
||||
*/
|
||||
public function isAdminTemplate($templateId)
|
||||
{
|
||||
return $this->db->get('pp_newsletter_templates', 'is_admin', ['id' => (int)$templateId]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Usuniecie szablonu newslettera
|
||||
* @param int $templateId
|
||||
* @return object|bool
|
||||
*/
|
||||
public function templateDelete($templateId)
|
||||
{
|
||||
return $this->db->delete('pp_newsletter_templates', ['id' => (int)$templateId]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wysylka newslettera - kolejkowanie
|
||||
* @param string $dates
|
||||
* @param int $template
|
||||
* @param string $onlyOnce
|
||||
* @return bool
|
||||
*/
|
||||
public function send($dates, $template, $onlyOnce)
|
||||
{
|
||||
$results = $this->db->select('pp_newsletter', 'email', ['status' => 1]);
|
||||
if (is_array($results) and !empty($results)) foreach ($results as $row)
|
||||
{
|
||||
if ($template and $onlyOnce)
|
||||
{
|
||||
if (!$this->db->count('pp_newsletter_send', ['AND' => ['id_template' => $template, 'email' => $row]]))
|
||||
$this->db->insert('pp_newsletter_send', [
|
||||
'email' => $row,
|
||||
'dates' => $dates,
|
||||
'id_template' => $template ? $template : null,
|
||||
'only_once' => ($onlyOnce == 'on' and $template) ? 1 : 0
|
||||
]);
|
||||
}
|
||||
else
|
||||
$this->db->insert('pp_newsletter_send', [
|
||||
'email' => $row,
|
||||
'dates' => $dates,
|
||||
'id_template' => $template ? $template : null,
|
||||
'only_once' => ($onlyOnce == 'on' and $template) ? 1 : 0
|
||||
]);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Szczegoly szablonu email
|
||||
* @param int $templateId
|
||||
* @return array|bool
|
||||
*/
|
||||
public function templateDetails($templateId)
|
||||
{
|
||||
$result = $this->db->get('pp_newsletter_templates', '*', ['id' => (int)$templateId]);
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Zapis szablonu (insert lub update)
|
||||
* @param int $id
|
||||
* @param string $name
|
||||
* @param string $text
|
||||
* @return int|bool
|
||||
*/
|
||||
public function templateSave($id, $name, $text)
|
||||
{
|
||||
if (!$id)
|
||||
{
|
||||
if ($this->db->insert('pp_newsletter_templates', [
|
||||
'name' => $name,
|
||||
'text' => $text
|
||||
]))
|
||||
{
|
||||
\S::delete_cache();
|
||||
return $this->db->id();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->db->update('pp_newsletter_templates', [
|
||||
'name' => $name,
|
||||
'text' => $text
|
||||
], [
|
||||
'id' => (int)$id
|
||||
]);
|
||||
|
||||
\S::delete_cache();
|
||||
return $id;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Lista szablonow (nie-adminskich)
|
||||
* @return array|bool
|
||||
*/
|
||||
public function templatesList()
|
||||
{
|
||||
return $this->db->select('pp_newsletter_templates', '*', ['is_admin' => 0, 'ORDER' => ['name' => 'ASC']]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wypisanie z newslettera po hashu
|
||||
* @param string $hash
|
||||
* @return object|bool
|
||||
*/
|
||||
public function unsubscribe($hash)
|
||||
{
|
||||
return $this->db->update('pp_newsletter', ['status' => 0], ['hash' => $hash]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Potwierdzenie zapisu po hashu
|
||||
* @param string $hash
|
||||
* @return bool
|
||||
*/
|
||||
public function confirm($hash)
|
||||
{
|
||||
if (!$id = $this->db->get('pp_newsletter', 'id', ['AND' => ['hash' => $hash, 'status' => 0]]))
|
||||
return false;
|
||||
else
|
||||
$this->db->update('pp_newsletter', ['status' => 1], ['id' => $id]);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wysylka zakolejkowanych newsletterow (cron/front)
|
||||
* @param int $limit
|
||||
* @param array $settings
|
||||
* @param array $lang
|
||||
* @return bool
|
||||
*/
|
||||
public function newsletterSend($limit, $settings, $lang)
|
||||
{
|
||||
$results = $this->db->query('SELECT * FROM pp_newsletter_send WHERE mailed = 0 ORDER BY id ASC LIMIT ' . (int)$limit)->fetchAll();
|
||||
if (is_array($results) and !empty($results))
|
||||
{
|
||||
foreach ($results as $row)
|
||||
{
|
||||
$dates = explode(' - ', $row['dates']);
|
||||
|
||||
$text = \admin\view\Newsletter::preview(
|
||||
\admin\factory\Articles::articles_by_date_add($dates[0], $dates[1]),
|
||||
\admin\factory\Settings::settings_details(),
|
||||
\admin\factory\Newsletter::email_template_detalis($row['id_template'])
|
||||
);
|
||||
|
||||
if ($settings['ssl']) $base = 'https'; else $base = 'http';
|
||||
|
||||
$link = $base . "://" . $_SERVER['SERVER_NAME'] . '/newsletter/unsubscribe/hash=' . $this->getHash($row['email']);
|
||||
$text = str_replace('[WYPISZ_SIE]', $link, $text);
|
||||
|
||||
$regex = "-(<img[^>]+src\s*=\s*['\"])(((?!'|\"|http(|s)://).)*)(['\"][^>]*>)-i";
|
||||
$text = preg_replace($regex, "$1" . $base . "://" . $_SERVER['SERVER_NAME'] . "$2$4", $text);
|
||||
|
||||
$regex = "-(<a[^>]+href\s*=\s*['\"])(((?!'|\"|http(|s)://).)*)(['\"][^>]*>)-i";
|
||||
$text = preg_replace($regex, "$1" . $base . "://" . $_SERVER['SERVER_NAME'] . "$2$4", $text);
|
||||
|
||||
\S::send_email($row['email'], 'Newsletter ze strony: ' . $_SERVER['SERVER_NAME'], $text);
|
||||
|
||||
if ($row['only_once'])
|
||||
$this->db->update('pp_newsletter_send', ['mailed' => 1], ['id' => $row['id']]);
|
||||
else
|
||||
$this->db->delete('pp_newsletter_send', ['id' => $row['id']]);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pobranie hasha dla emaila
|
||||
* @param string $email
|
||||
* @return string|bool
|
||||
*/
|
||||
public function getHash($email)
|
||||
{
|
||||
return $this->db->get('pp_newsletter', 'hash', ['email' => $email]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Zapis do newslettera z wysylka potwierdzenia
|
||||
* @param string $email
|
||||
* @param array $settings
|
||||
* @param array $lang
|
||||
* @return bool
|
||||
*/
|
||||
public function signin($email, $settings, $lang)
|
||||
{
|
||||
if (!\S::email_check($email))
|
||||
return false;
|
||||
|
||||
if (!$this->db->get('pp_newsletter', 'id', ['email' => $email]))
|
||||
{
|
||||
$hash = md5(time() . $email);
|
||||
|
||||
$text = $settings['newsletter_header'];
|
||||
$text .= $this->getTemplate('#potwierdzenie-zapisu-do-newslettera');
|
||||
$text .= $settings['newsletter_footer_1'];
|
||||
|
||||
$settings['ssl'] ? $base = 'https' : $base = 'http';
|
||||
|
||||
$regex = "-(<img[^>]+src\s*=\s*['\"])(((?!'|\"|http://).)*)(['\"][^>]*>)-i";
|
||||
$text = preg_replace($regex, "$1" . $base . "://" . $_SERVER['SERVER_NAME'] . "$2$4", $text);
|
||||
|
||||
$regex = "-(<a[^>]+href\s*=\s*['\"])(((?!'|\"|http://).)*)(['\"][^>]*>)-i";
|
||||
$text = preg_replace($regex, "$1" . $base . "://" . $_SERVER['SERVER_NAME'] . "$2$4", $text);
|
||||
|
||||
$link = '/newsletter/confirm/hash=' . $hash;
|
||||
|
||||
$text = str_replace('[LINK]', $link, $text);
|
||||
|
||||
$send = \S::send_email($email, $lang['potwierdz-zapisanie-sie-do-newslettera'], $text);
|
||||
|
||||
$this->db->insert('pp_newsletter', ['email' => $email, 'hash' => $hash, 'status' => 0]);
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pobranie szablonu po nazwie
|
||||
* @param string $templateName
|
||||
* @return string|bool
|
||||
*/
|
||||
public function getTemplate($templateName)
|
||||
{
|
||||
return $this->db->get('pp_newsletter_templates', 'text', ['name' => $templateName]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wypisanie z newslettera po emailu
|
||||
* @param string $email
|
||||
* @return object|bool
|
||||
*/
|
||||
public function signout($email)
|
||||
{
|
||||
if ($this->db->get('pp_newsletter', 'id', ['email' => $email]))
|
||||
return $this->db->delete('pp_newsletter', ['email' => $email]);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
451
autoload/Domain/Pages/PagesRepository.php
Normal file
451
autoload/Domain/Pages/PagesRepository.php
Normal file
@@ -0,0 +1,451 @@
|
||||
<?php
|
||||
namespace Domain\Pages;
|
||||
|
||||
class PagesRepository
|
||||
{
|
||||
private $db;
|
||||
|
||||
public function __construct( $db )
|
||||
{
|
||||
$this->db = $db;
|
||||
}
|
||||
|
||||
public function saveArticlesOrder( $pageId, $articles ): bool
|
||||
{
|
||||
if ( is_array( $articles ) )
|
||||
{
|
||||
$this->db->update( 'pp_articles_pages', [ 'o' => 0 ], [ 'page_id' => (int) $pageId ] );
|
||||
$x = 0;
|
||||
|
||||
for ( $i = 0; $i < count( $articles ); $i++ )
|
||||
{
|
||||
if ( $articles[$i]['item_id'] )
|
||||
{
|
||||
$x++;
|
||||
$this->db->update( 'pp_articles_pages', [ 'o' => $x ], [
|
||||
'AND' => [ 'page_id' => (int) $pageId, 'article_id' => $articles[$i]['item_id'] ]
|
||||
] );
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public function pageArticles( $pageId )
|
||||
{
|
||||
$results = $this->db->query(
|
||||
'SELECT article_id, o, status FROM pp_articles_pages AS ap INNER JOIN pp_articles AS a ON a.id = ap.article_id WHERE page_id = ' . (int) $pageId . ' AND status != -1 ORDER BY o ASC'
|
||||
)->fetchAll();
|
||||
|
||||
$articles = [];
|
||||
if ( is_array( $results ) )
|
||||
foreach ( $results as $row )
|
||||
{
|
||||
$row['title'] = \admin\factory\Articles::article_title( $row['article_id'] );
|
||||
$articles[] = $row;
|
||||
}
|
||||
|
||||
return $articles;
|
||||
}
|
||||
|
||||
public function menusList()
|
||||
{
|
||||
return $this->db->select( 'pp_menus', '*', [ 'ORDER' => [ 'name' => 'ASC' ] ] );
|
||||
}
|
||||
|
||||
public function savePagesOrder( $menuId, $pages ): bool
|
||||
{
|
||||
if ( is_array( $pages ) )
|
||||
{
|
||||
$this->db->update( 'pp_pages', [ 'o' => 0 ], [ 'menu_id' => (int) $menuId ] );
|
||||
$x = 0;
|
||||
|
||||
for ( $i = 0; $i < count( $pages ); $i++ )
|
||||
{
|
||||
if ( $pages[$i]['item_id'] )
|
||||
{
|
||||
$parentId = $pages[$i]['parent_id'] ? $pages[$i]['parent_id'] : 0;
|
||||
|
||||
if ( $pages[$i]['item_id'] && $pages[$i]['depth'] > 1 )
|
||||
{
|
||||
if ( $pages[$i]['depth'] == 2 )
|
||||
$parentId = null;
|
||||
|
||||
$x++;
|
||||
$this->db->update( 'pp_pages', [ 'o' => $x, 'parent_id' => $parentId ], [ 'id' => (int) $pages[$i]['item_id'] ] );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
\S::delete_cache();
|
||||
return true;
|
||||
}
|
||||
|
||||
public function pageDelete( $pageId ): bool
|
||||
{
|
||||
if ( $this->db->count( 'pp_pages', [ 'parent_id' => (int) $pageId ] ) )
|
||||
return false;
|
||||
|
||||
if ( $this->db->delete( 'pp_pages', [ 'id' => (int) $pageId ] ) )
|
||||
{
|
||||
\S::delete_cache();
|
||||
\S::htacces();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function maxOrder(): int
|
||||
{
|
||||
return (int) $this->db->max( 'pp_pages', 'o' );
|
||||
}
|
||||
|
||||
public function updateSubpagesMenuId( int $parentId, int $menuId ): void
|
||||
{
|
||||
$this->updateSubpagesMenuIdRecursive( $parentId, $menuId );
|
||||
}
|
||||
|
||||
public function generateSeoLink( $title, $pageId, $articleId, $lang, $pid )
|
||||
{
|
||||
$seoLink = \S::seo( $title );
|
||||
$seoLinkCheck = false;
|
||||
$i = 0;
|
||||
|
||||
while ( !$seoLinkCheck )
|
||||
{
|
||||
if ( $this->db->count( 'pp_pages_langs', [ 'AND' => [ 'seo_link' => $seoLink, 'page_id[!]' => (int) $pageId ] ] ) )
|
||||
$seoLink = $seoLink . '-' . ( ++$i );
|
||||
else
|
||||
$seoLinkCheck = true;
|
||||
}
|
||||
|
||||
$seoLinkCheck = false;
|
||||
|
||||
while ( !$seoLinkCheck )
|
||||
{
|
||||
if ( $this->db->count( 'pp_articles_langs', [ 'AND' => [ 'seo_link' => $seoLink, 'article_id[!]' => (int) $articleId ] ] ) )
|
||||
$seoLink = $seoLink . '-' . ( ++$i );
|
||||
else
|
||||
$seoLinkCheck = true;
|
||||
}
|
||||
|
||||
return $seoLink;
|
||||
}
|
||||
|
||||
public function googleUrlPreview( $pageId, $title, $lang, $pid, $id, $seoLink, $languageLink = '' )
|
||||
{
|
||||
$prefix = $languageLink;
|
||||
$status = true;
|
||||
$idPage = $pageId;
|
||||
$seo = '';
|
||||
|
||||
do
|
||||
{
|
||||
if ( $pageId )
|
||||
{
|
||||
$parent = $this->pageDetails( $pageId );
|
||||
$parentId = $parent['parent_id'];
|
||||
}
|
||||
else
|
||||
$parentId = $pid;
|
||||
|
||||
if ( $parentId )
|
||||
{
|
||||
$results = $this->db->query(
|
||||
"SELECT title, seo_link, page_id FROM pp_pages_langs AS ppl, pp_langs AS pl WHERE lang_id = pl.id AND page_id = " . (int) $parentId . " AND ppl.lang_id = '" . $lang . "' "
|
||||
)->fetchAll();
|
||||
if ( $results[0]['seo_link'] )
|
||||
$seo = $results[0]['seo_link'] . '/' . $seo;
|
||||
else
|
||||
$seo = 's-' . $results[0]['page_id'] . '-' . \S::seo( $results[0]['title'] ) . '/' . $seo;
|
||||
$pageId = $results[0]['page_id'];
|
||||
}
|
||||
else
|
||||
$status = false;
|
||||
}
|
||||
while ( $status );
|
||||
|
||||
if ( $id )
|
||||
{
|
||||
if ( !$seoLink )
|
||||
$seo = $seo . 's-' . $id . '-' . \S::seo( $title );
|
||||
else
|
||||
$seo = $seo . $seoLink;
|
||||
}
|
||||
else
|
||||
{
|
||||
if ( !$seoLink )
|
||||
$seo = $seo . 's-' . $idPage . '-' . \S::seo( $title );
|
||||
else
|
||||
$seo = $seo . $seoLink;
|
||||
}
|
||||
|
||||
if ( $prefix )
|
||||
$seo = $prefix . $seo;
|
||||
|
||||
return $seo;
|
||||
}
|
||||
|
||||
public function menuDelete( $menuId )
|
||||
{
|
||||
if ( $this->db->count( 'pp_pages', [ 'menu_id' => (int) $menuId ] ) )
|
||||
return false;
|
||||
|
||||
return $this->db->delete( 'pp_menus', [ 'id' => (int) $menuId ] );
|
||||
}
|
||||
|
||||
public function menuDetails( $menuId )
|
||||
{
|
||||
return $this->db->get( 'pp_menus', '*', [ 'id' => (int) $menuId ] );
|
||||
}
|
||||
|
||||
public function menuSave( $menuId, $name, $status )
|
||||
{
|
||||
$status == 'on' ? $status = 1 : $status = 0;
|
||||
|
||||
if ( !$menuId )
|
||||
{
|
||||
return $this->db->insert( 'pp_menus', [ 'name' => $name, 'status' => $status ] );
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->db->update( 'pp_menus', [ 'name' => $name, 'status' => $status ], [ 'id' => (int) $menuId ] );
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function menuLists()
|
||||
{
|
||||
return $this->db->select( 'pp_menus', '*', [ 'ORDER' => [ 'id' => 'ASC' ] ] );
|
||||
}
|
||||
|
||||
public function pageDetails( $pageId )
|
||||
{
|
||||
$page = $this->db->get( 'pp_pages', '*', [ 'id' => (int) $pageId ] );
|
||||
|
||||
$results = $this->db->select( 'pp_pages_langs', '*', [ 'page_id' => (int) $pageId ] );
|
||||
if ( is_array( $results ) )
|
||||
foreach ( $results as $row )
|
||||
$page['languages'][$row['lang_id']] = $row;
|
||||
|
||||
$page['layout_id'] = $this->db->get( 'pp_layouts_pages', 'layout_id', [ 'page_id' => (int) $pageId ] );
|
||||
|
||||
return $page;
|
||||
}
|
||||
|
||||
public function pageUrl( $pageId )
|
||||
{
|
||||
$results = $this->db->query(
|
||||
"SELECT seo_link, title lang_id FROM pp_pages_langs AS ppl, pp_langs AS pl WHERE lang_id = pl.id AND page_id = " . (int) $pageId . " AND seo_link != '' ORDER BY o ASC LIMIT 1"
|
||||
)->fetchAll();
|
||||
|
||||
if ( !$results[0]['seo_link'] )
|
||||
{
|
||||
$title = $this->pageTitle( $article_id );
|
||||
return 's-' . $pageId . '-' . \S::seo( $title );
|
||||
}
|
||||
else
|
||||
return $results[0]['seo_link'];
|
||||
}
|
||||
|
||||
public function pageTitle( $pageId )
|
||||
{
|
||||
$result = $this->db->select( 'pp_pages_langs', [ '[><]pp_langs' => [ 'lang_id' => 'id' ] ], 'title', [
|
||||
'AND' => [ 'page_id' => (int) $pageId, 'title[!]' => '' ], 'ORDER' => [ 'o' => 'ASC' ], 'LIMIT' => 1
|
||||
] );
|
||||
return $result[0];
|
||||
}
|
||||
|
||||
public function pageLanguages( $pageId )
|
||||
{
|
||||
return $this->db->select( 'pp_pages_langs', '*', [ 'AND' => [ 'page_id' => (int) $pageId, 'title[!]' => null ] ] );
|
||||
}
|
||||
|
||||
public function menuPages( $menuId, $parentId = null )
|
||||
{
|
||||
$results = $this->db->select( 'pp_pages', [ 'id', 'menu_id', 'status', 'parent_id', 'start' ], [
|
||||
'AND' => [ 'menu_id' => $menuId, 'parent_id' => $parentId ], 'ORDER' => [ 'o' => 'ASC' ]
|
||||
] );
|
||||
|
||||
if ( !is_array( $results ) || !count( $results ) )
|
||||
return null;
|
||||
|
||||
foreach ( $results as $row )
|
||||
{
|
||||
$row['title'] = $this->pageTitle( $row['id'] );
|
||||
$row['languages'] = $this->pageLanguages( $row['id'] );
|
||||
$row['subpages'] = $this->menuPages( $menuId, $row['id'] );
|
||||
|
||||
$pages[] = $row;
|
||||
}
|
||||
|
||||
return $pages;
|
||||
}
|
||||
|
||||
public function pageSave(
|
||||
$pageId, $title, $seoLink, $metaTitle, $metaDescription, $metaKeywords, $menuId, $parentId, $pageType, $sortType, $layoutId, $articlesLimit, $showTitle, $status, $link, $noindex, $start,
|
||||
$siteTitle, $blockDirectAccess, $cache, $canonical
|
||||
)
|
||||
{
|
||||
$parentId = $parentId ? $parentId : null;
|
||||
|
||||
if ( !$pageId )
|
||||
return $this->createPage(
|
||||
$title, $seoLink, $metaTitle, $metaDescription, $metaKeywords, $menuId, $parentId, $pageType, $sortType, $layoutId, $articlesLimit, $showTitle, $status, $link, $noindex, $start,
|
||||
$siteTitle, $blockDirectAccess, $cache, $canonical
|
||||
);
|
||||
|
||||
return $this->updatePage(
|
||||
$pageId, $title, $seoLink, $metaTitle, $metaDescription, $metaKeywords, $menuId, $parentId, $pageType, $sortType, $layoutId, $articlesLimit, $showTitle, $status, $link, $noindex, $start,
|
||||
$siteTitle, $blockDirectAccess, $cache, $canonical
|
||||
);
|
||||
}
|
||||
|
||||
private function createPage(
|
||||
$title, $seoLink, $metaTitle, $metaDescription, $metaKeywords, $menuId, $parentId, $pageType, $sortType, $layoutId, $articlesLimit, $showTitle, $status, $link, $noindex, $start, $siteTitle,
|
||||
$blockDirectAccess, $cache, $canonical
|
||||
)
|
||||
{
|
||||
$order = $this->maxOrder() + 1;
|
||||
|
||||
$this->db->insert( 'pp_pages', [
|
||||
'menu_id' => (int) $menuId,
|
||||
'page_type' => $pageType,
|
||||
'sort_type' => $sortType,
|
||||
'articles_limit' => $articlesLimit,
|
||||
'show_title' => $showTitle == 'on' ? 1 : 0,
|
||||
'status' => $status == 'on' ? 1 : 0,
|
||||
'o' => (int) $order,
|
||||
'parent_id' => $parentId,
|
||||
'start' => $start == 'on' ? 1 : 0,
|
||||
'cache' => $cache == 'on' ? 1 : 0,
|
||||
] );
|
||||
|
||||
$id = $this->db->id();
|
||||
if ( !$id )
|
||||
return false;
|
||||
|
||||
if ( $start )
|
||||
$this->db->update( 'pp_pages', [ 'start' => 0 ], [ 'id[!]' => (int) $id ] );
|
||||
|
||||
if ( $layoutId )
|
||||
$this->db->insert( 'pp_layouts_pages', [ 'page_id' => (int) $id, 'layout_id' => (int) $layoutId ] );
|
||||
|
||||
$languages = $this->activeLanguages();
|
||||
$this->savePageLanguages( (int) $id, $languages, $title, $metaDescription, $metaKeywords, $metaTitle, $seoLink, $noindex, $siteTitle, $link, $blockDirectAccess, $canonical );
|
||||
|
||||
\S::htacces();
|
||||
\S::delete_cache();
|
||||
|
||||
return $id;
|
||||
}
|
||||
|
||||
private function updatePage(
|
||||
$pageId, $title, $seoLink, $metaTitle, $metaDescription, $metaKeywords, $menuId, $parentId, $pageType, $sortType, $layoutId, $articlesLimit, $showTitle, $status, $link, $noindex, $start,
|
||||
$siteTitle, $blockDirectAccess, $cache, $canonical
|
||||
)
|
||||
{
|
||||
$this->db->update( 'pp_pages', [
|
||||
'menu_id' => (int) $menuId,
|
||||
'page_type' => $pageType,
|
||||
'sort_type' => $sortType,
|
||||
'articles_limit' => $articlesLimit,
|
||||
'show_title' => $showTitle == 'on' ? 1 : 0,
|
||||
'status' => $status == 'on' ? 1 : 0,
|
||||
'parent_id' => $parentId,
|
||||
'start' => $start == 'on' ? 1 : 0,
|
||||
'cache' => $cache == 'on' ? 1 : 0,
|
||||
], [
|
||||
'id' => (int) $pageId
|
||||
] );
|
||||
|
||||
if ( $layoutId )
|
||||
{
|
||||
$this->db->delete( 'pp_layouts_pages', [ 'page_id' => (int) $pageId ] );
|
||||
$this->db->insert( 'pp_layouts_pages', [ 'layout_id' => (int) $layoutId, 'page_id' => (int) $pageId ] );
|
||||
}
|
||||
|
||||
if ( $start )
|
||||
$this->db->update( 'pp_pages', [ 'start' => 0 ], [ 'id[!]' => (int) $pageId ] );
|
||||
|
||||
$this->db->delete( 'pp_pages_langs', [ 'page_id' => (int) $pageId ] );
|
||||
|
||||
$languages = $this->activeLanguages();
|
||||
$this->savePageLanguages( (int) $pageId, $languages, $title, $metaDescription, $metaKeywords, $metaTitle, $seoLink, $noindex, $siteTitle, $link, $blockDirectAccess, $canonical );
|
||||
|
||||
$this->updateSubpagesMenuIdRecursive( (int) $pageId, (int) $menuId );
|
||||
|
||||
\S::htacces();
|
||||
\S::delete_cache();
|
||||
|
||||
return $pageId;
|
||||
}
|
||||
|
||||
private function activeLanguages(): array
|
||||
{
|
||||
return $this->db->select( 'pp_langs', [ 'id' ], [ 'status' => 1, 'ORDER' => [ 'o' => 'ASC' ] ] ) ?: [];
|
||||
}
|
||||
|
||||
private function savePageLanguages(
|
||||
int $pageId, array $languages, $title, $metaDescription, $metaKeywords, $metaTitle, $seoLink, $noindex, $siteTitle, $link, $blockDirectAccess, $canonical
|
||||
): void
|
||||
{
|
||||
$isMulti = count( $languages ) > 1;
|
||||
|
||||
foreach ( $languages as $i => $row )
|
||||
{
|
||||
$titleValue = $this->languageValue( $title, $i, $isMulti );
|
||||
$metaDescriptionValue = $this->languageValue( $metaDescription, $i, $isMulti );
|
||||
$metaKeywordsValue = $this->languageValue( $metaKeywords, $i, $isMulti );
|
||||
$metaTitleValue = $this->languageValue( $metaTitle, $i, $isMulti );
|
||||
$seoLinkValue = $this->languageValue( $seoLink, $i, $isMulti );
|
||||
$noindexValue = $this->languageValue( $noindex, $i, $isMulti );
|
||||
$siteTitleValue = $this->languageValue( $siteTitle, $i, $isMulti );
|
||||
$linkValue = $this->languageValue( $link, $i, $isMulti );
|
||||
$blockDirectAccessValue = $this->languageValue( $blockDirectAccess, $i, $isMulti );
|
||||
$canonicalValue = $this->languageValue( $canonical, $i, $isMulti );
|
||||
|
||||
$seo = \S::seo( $seoLinkValue );
|
||||
|
||||
$this->db->insert( 'pp_pages_langs', [
|
||||
'page_id' => $pageId,
|
||||
'lang_id' => $row['id'],
|
||||
'title' => $this->nullIfEmpty( $titleValue ),
|
||||
'meta_description' => $this->nullIfEmpty( $metaDescriptionValue ),
|
||||
'meta_keywords' => $this->nullIfEmpty( $metaKeywordsValue ),
|
||||
'meta_title' => $this->nullIfEmpty( $metaTitleValue ),
|
||||
'seo_link' => $seo != '' ? $seo : null,
|
||||
'noindex' => $noindexValue,
|
||||
'site_title' => $this->nullIfEmpty( $siteTitleValue ),
|
||||
'link' => $this->nullIfEmpty( $linkValue ),
|
||||
'block_direct_access' => $blockDirectAccessValue,
|
||||
'canonical' => $this->nullIfEmpty( $canonicalValue )
|
||||
] );
|
||||
}
|
||||
}
|
||||
|
||||
private function languageValue( $value, int $index, bool $isMulti )
|
||||
{
|
||||
if ( $isMulti )
|
||||
return is_array( $value ) ? ( $value[$index] ?? null ) : null;
|
||||
|
||||
return is_array( $value ) ? ( $value[0] ?? null ) : $value;
|
||||
}
|
||||
|
||||
private function nullIfEmpty( $value )
|
||||
{
|
||||
return $value != '' ? $value : null;
|
||||
}
|
||||
|
||||
private function updateSubpagesMenuIdRecursive( int $parentId, int $menuId ): void
|
||||
{
|
||||
$this->db->update( 'pp_pages', [ 'menu_id' => $menuId ], [ 'parent_id' => $parentId ] );
|
||||
|
||||
$results = $this->db->select( 'pp_pages', [ 'id' ], [ 'parent_id' => $parentId ] );
|
||||
if ( is_array( $results ) )
|
||||
foreach ( $results as $row )
|
||||
$this->updateSubpagesMenuIdRecursive( (int) $row['id'], $menuId );
|
||||
}
|
||||
}
|
||||
110
autoload/Domain/Scontainers/ScontainersRepository.php
Normal file
110
autoload/Domain/Scontainers/ScontainersRepository.php
Normal file
@@ -0,0 +1,110 @@
|
||||
<?php
|
||||
namespace Domain\Scontainers;
|
||||
|
||||
class ScontainersRepository
|
||||
{
|
||||
private $db;
|
||||
|
||||
public function __construct( $db )
|
||||
{
|
||||
$this->db = $db;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Odczyt
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
public function containerDetails( $containerId )
|
||||
{
|
||||
$container = $this->db->get( 'pp_scontainers', '*', [ 'id' => $containerId ] );
|
||||
if ( !$container ) return null;
|
||||
|
||||
$langs = $this->db->select( 'pp_scontainers_langs', '*', [ 'container_id' => $containerId ] );
|
||||
$container['languages'] = [];
|
||||
if ( is_array( $langs ) )
|
||||
foreach ( $langs as $lang )
|
||||
$container['languages'][ $lang['lang_id'] ] = $lang;
|
||||
|
||||
return $container;
|
||||
}
|
||||
|
||||
public function scontainerByLang( $scontainerId, $langId )
|
||||
{
|
||||
$cacheKey = "scontainer_details:$scontainerId:$langId";
|
||||
if ( $scontainer = \Shared\Cache\CacheHandler::fetch( $cacheKey ) )
|
||||
return $scontainer;
|
||||
|
||||
$scontainer = $this->db->get( 'pp_scontainers', '*', [ 'id' => $scontainerId ] );
|
||||
if ( !$scontainer ) return null;
|
||||
|
||||
$langData = $this->db->select( 'pp_scontainers_langs', '*', [
|
||||
'AND' => [ 'container_id' => $scontainerId, 'lang_id' => $langId ]
|
||||
] );
|
||||
$scontainer['languages'] = is_array( $langData ) ? $langData : [];
|
||||
|
||||
\Shared\Cache\CacheHandler::store( $cacheKey, $scontainer );
|
||||
return $scontainer;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Zapis / usuwanie
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
public function containerSave( $containerId, $title, $text, $status, $showTitle, $src, $html )
|
||||
{
|
||||
$languages = $this->db->select( 'pp_langs', '*', [ 'status' => 1, 'ORDER' => [ 'o' => 'ASC' ] ] );
|
||||
if ( !is_array( $languages ) ) $languages = [];
|
||||
$langCount = count( $languages );
|
||||
|
||||
if ( !$containerId )
|
||||
{
|
||||
$this->db->insert( 'pp_scontainers', [
|
||||
'status' => $status == 'on' ? 1 : 0,
|
||||
'show_title' => $showTitle == 'on' ? 1 : 0,
|
||||
'src' => $src,
|
||||
] );
|
||||
$containerId = $this->db->id();
|
||||
if ( !$containerId ) return false;
|
||||
|
||||
foreach ( $languages as $i => $lang )
|
||||
{
|
||||
$this->db->insert( 'pp_scontainers_langs', [
|
||||
'container_id' => $containerId,
|
||||
'lang_id' => $lang['id'],
|
||||
'title' => $langCount > 1 ? $title[ $i ] : $title,
|
||||
'text' => $langCount > 1 ? $text[ $i ] : $text,
|
||||
'html' => $langCount > 1 ? $html[ $i ] : $html,
|
||||
] );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->db->update( 'pp_scontainers', [
|
||||
'status' => $status == 'on' ? 1 : 0,
|
||||
'show_title' => $showTitle == 'on' ? 1 : 0,
|
||||
'src' => $src,
|
||||
], [ 'id' => $containerId ] );
|
||||
|
||||
$this->db->delete( 'pp_scontainers_langs', [ 'container_id' => $containerId ] );
|
||||
|
||||
foreach ( $languages as $i => $lang )
|
||||
{
|
||||
$this->db->insert( 'pp_scontainers_langs', [
|
||||
'container_id' => $containerId,
|
||||
'lang_id' => $lang['id'],
|
||||
'title' => $langCount > 1 ? $title[ $i ] : $title,
|
||||
'text' => $langCount > 1 ? $text[ $i ] : $text,
|
||||
'html' => $langCount > 1 ? $html[ $i ] : $html,
|
||||
] );
|
||||
}
|
||||
}
|
||||
|
||||
\S::delete_cache();
|
||||
return $containerId;
|
||||
}
|
||||
|
||||
public function containerDelete( $containerId )
|
||||
{
|
||||
return $this->db->delete( 'pp_scontainers', [ 'id' => $containerId ] );
|
||||
}
|
||||
}
|
||||
73
autoload/Domain/Settings/SettingsRepository.php
Normal file
73
autoload/Domain/Settings/SettingsRepository.php
Normal file
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
namespace Domain\Settings;
|
||||
|
||||
class SettingsRepository
|
||||
{
|
||||
private $db;
|
||||
|
||||
public function __construct($db)
|
||||
{
|
||||
$this->db = $db;
|
||||
}
|
||||
|
||||
/**
|
||||
* Zwraca wszystkie ustawienia jako tablicę asocjacyjną param => value.
|
||||
* Wynik jest cache'owany (TTL 24h).
|
||||
*/
|
||||
public function allSettings(): array
|
||||
{
|
||||
if ( !$settings = \Shared\Cache\CacheHandler::fetch( 'settings_details' ) )
|
||||
{
|
||||
$settings = [];
|
||||
$results = $this->db->select( 'pp_settings', '*' );
|
||||
if ( is_array( $results ) )
|
||||
foreach ( $results as $row )
|
||||
$settings[ $row['param'] ] = $row['value'];
|
||||
|
||||
\Shared\Cache\CacheHandler::store( 'settings_details', $settings );
|
||||
}
|
||||
|
||||
return $settings ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Upsert jednego parametru.
|
||||
*/
|
||||
public function update( string $param, $value ): bool
|
||||
{
|
||||
if ( $this->db->count( 'pp_settings', [ 'param' => $param ] ) )
|
||||
return (bool) $this->db->update( 'pp_settings', [ 'value' => $value ], [ 'param' => $param ] );
|
||||
else
|
||||
return (bool) $this->db->insert( 'pp_settings', [ 'param' => $param, 'value' => $value ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Zapisuje zbiorczo ustawienia (TRUNCATE + INSERT).
|
||||
* Czyści cache i regeneruje .htaccess.
|
||||
*
|
||||
* @param array $data Tablica asocjacyjna [ 'param' => value, ... ]
|
||||
*/
|
||||
public function save( array $data ): bool
|
||||
{
|
||||
$this->db->query( 'TRUNCATE pp_settings' );
|
||||
|
||||
$rows = [];
|
||||
foreach ( $data as $param => $value )
|
||||
$rows[] = [ 'param' => $param, 'value' => $value ];
|
||||
|
||||
$this->db->insert( 'pp_settings', $rows );
|
||||
|
||||
\S::delete_cache();
|
||||
\S::htacces();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Zwraca bieżącą wartość licznika odwiedzin.
|
||||
*/
|
||||
public function visitCounter(): ?string
|
||||
{
|
||||
return $this->db->get( 'pp_settings', 'value', [ 'param' => 'visits' ] ) ?: null;
|
||||
}
|
||||
}
|
||||
235
autoload/Domain/User/UserRepository.php
Normal file
235
autoload/Domain/User/UserRepository.php
Normal file
@@ -0,0 +1,235 @@
|
||||
<?php
|
||||
namespace Domain\User;
|
||||
|
||||
class UserRepository
|
||||
{
|
||||
private $db;
|
||||
|
||||
public function __construct( $db )
|
||||
{
|
||||
$this->db = $db;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Odczyt
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
public function find( int $userId ): ?array
|
||||
{
|
||||
return $this->db->get( 'pp_users', '*', [ 'id' => $userId ] ) ?: null;
|
||||
}
|
||||
|
||||
public function findByLogin( string $login ): ?array
|
||||
{
|
||||
return $this->db->get( 'pp_users', '*', [ 'login' => $login ] ) ?: null;
|
||||
}
|
||||
|
||||
public function all(): array
|
||||
{
|
||||
return $this->db->select( 'pp_users', '*' ) ?: [];
|
||||
}
|
||||
|
||||
public function privileges( int $userId ): array
|
||||
{
|
||||
return $this->db->select( 'pp_users_privileges', '*', [ 'id_user' => $userId ] ) ?: [];
|
||||
}
|
||||
|
||||
public function hasPrivilege( string $name, int $userId ): bool
|
||||
{
|
||||
if ( $userId === 1 )
|
||||
return true;
|
||||
|
||||
if ( !$result = \Shared\Cache\CacheHandler::fetch( "check_privileges:$userId:$name-tmp" ) )
|
||||
{
|
||||
$result = $this->db->count( 'pp_users_privileges', [ 'AND' => [ 'name' => $name, 'id_user' => $userId ] ] );
|
||||
\Shared\Cache\CacheHandler::store( "check_privileges:$userId:$name", $result );
|
||||
}
|
||||
return (bool) $result;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Logowanie
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Weryfikuje login i hasło.
|
||||
* @return int 1 = OK, 0 = złe dane, -1 = konto zablokowane
|
||||
*/
|
||||
public function logon( string $login, string $password ): int
|
||||
{
|
||||
if ( !$this->db->get( 'pp_users', '*', [ 'login' => $login ] ) )
|
||||
return 0;
|
||||
|
||||
if ( !$this->db->get( 'pp_users', '*', [ 'AND' => [ 'login' => $login, 'status' => 1, 'error_logged_count[<]' => 5 ] ] ) )
|
||||
return -1;
|
||||
|
||||
if ( $this->db->get( 'pp_users', '*', [
|
||||
'AND' => [
|
||||
'login' => $login,
|
||||
'status' => 1,
|
||||
'password' => md5( $password ),
|
||||
'OR' => [ 'active_to[>=]' => date( 'Y-m-d' ), 'active_to' => null ]
|
||||
]
|
||||
] ) ) {
|
||||
$this->db->update( 'pp_users', [ 'last_logged' => date( 'Y-m-d H:i:s' ), 'error_logged_count' => 0 ], [ 'login' => $login ] );
|
||||
return 1;
|
||||
}
|
||||
|
||||
$this->db->update( 'pp_users', [ 'last_error_logged' => date( 'Y-m-d H:i:s' ), 'error_logged_count[+]' => 1 ], [ 'login' => $login ] );
|
||||
if ( $this->db->get( 'pp_users', 'error_logged_count', [ 'login' => $login ] ) >= 5 )
|
||||
{
|
||||
$this->db->update( 'pp_users', [ 'status' => 0 ], [ 'login' => $login ] );
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public function isLoginTaken( string $login, int $excludeId = 0 ): bool
|
||||
{
|
||||
return (bool) $this->db->get( 'pp_users', 'login', [ 'AND' => [ 'login' => $login, 'id[!]' => $excludeId ] ] );
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// 2FA
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
public function update( int $userId, array $data ): bool
|
||||
{
|
||||
return (bool) $this->db->update( 'pp_users', $data, [ 'id' => $userId ] );
|
||||
}
|
||||
|
||||
public function sendTwofaCode( int $userId, bool $resend = false ): bool
|
||||
{
|
||||
$user = $this->find( $userId );
|
||||
if ( !$user ) return false;
|
||||
|
||||
if ( (int)$user['twofa_enabled'] !== 1 ) return false;
|
||||
|
||||
$to = $user['twofa_email'] ?: $user['login'];
|
||||
if ( !filter_var( $to, FILTER_VALIDATE_EMAIL ) ) return false;
|
||||
|
||||
if ( $resend && !empty( $user['twofa_sent_at'] ) )
|
||||
{
|
||||
$last = strtotime( $user['twofa_sent_at'] );
|
||||
if ( $last && ( time() - $last ) < 30 ) return false;
|
||||
}
|
||||
|
||||
$code = random_int( 100000, 999999 );
|
||||
$hash = password_hash( (string)$code, PASSWORD_DEFAULT );
|
||||
|
||||
$this->update( $userId, [
|
||||
'twofa_code_hash' => $hash,
|
||||
'twofa_expires_at' => date( 'Y-m-d H:i:s', time() + 10 * 60 ),
|
||||
'twofa_sent_at' => date( 'Y-m-d H:i:s' ),
|
||||
'twofa_failed_attempts' => 0,
|
||||
] );
|
||||
|
||||
$subject = 'Twój kod logowania 2FA';
|
||||
$body = "Twój kod logowania do panelu administratora: {$code}. Kod jest ważny przez 10 minut. Jeśli to nie Ty inicjowałeś logowanie – zignoruj tę wiadomość i poinformuj administratora.";
|
||||
|
||||
$sent = \S::send_email( $to, $subject, $body );
|
||||
if ( !$sent )
|
||||
{
|
||||
$headers = "MIME-Version: 1.0\r\n";
|
||||
$headers .= "Content-type: text/plain; charset=UTF-8\r\n";
|
||||
$headers .= "From: no-reply@" . ( $_SERVER['HTTP_HOST'] ?? 'localhost' ) . "\r\n";
|
||||
$sent = mail( $to, mb_encode_mimeheader( $subject, 'UTF-8' ), $body, $headers );
|
||||
}
|
||||
return (bool) $sent;
|
||||
}
|
||||
|
||||
public function verifyTwofaCode( int $userId, string $code ): bool
|
||||
{
|
||||
$user = $this->find( $userId );
|
||||
if ( !$user ) return false;
|
||||
|
||||
if ( (int)$user['twofa_failed_attempts'] >= 5 ) return false;
|
||||
|
||||
if ( empty( $user['twofa_expires_at'] ) || time() > strtotime( $user['twofa_expires_at'] ) )
|
||||
{
|
||||
$this->update( $userId, [ 'twofa_code_hash' => null, 'twofa_expires_at' => null ] );
|
||||
return false;
|
||||
}
|
||||
|
||||
$ok = !empty( $user['twofa_code_hash'] ) && password_verify( $code, $user['twofa_code_hash'] );
|
||||
if ( $ok )
|
||||
{
|
||||
$this->update( $userId, [
|
||||
'twofa_code_hash' => null,
|
||||
'twofa_expires_at' => null,
|
||||
'twofa_sent_at' => null,
|
||||
'twofa_failed_attempts' => 0,
|
||||
'last_logged' => date( 'Y-m-d H:i:s' ),
|
||||
] );
|
||||
return true;
|
||||
}
|
||||
|
||||
$this->update( $userId, [
|
||||
'twofa_failed_attempts' => (int)$user['twofa_failed_attempts'] + 1,
|
||||
'last_error_logged' => date( 'Y-m-d H:i:s' ),
|
||||
] );
|
||||
return false;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Zapis / usuwanie
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
public function save(
|
||||
$userId, string $login, $status, $activeTo, string $password, string $passwordRe,
|
||||
$admin, $privileges, $twofaEnabled = 0, string $twofaEmail = ''
|
||||
): array {
|
||||
$this->db->delete( 'pp_users_privileges', [ 'id_user' => (int)$userId ] );
|
||||
|
||||
if ( !$userId )
|
||||
{
|
||||
if ( strlen( $password ) < 5 )
|
||||
return [ 'status' => 'error', 'msg' => 'Podane hasło jest zbyt krótkie.' ];
|
||||
if ( $password !== $passwordRe )
|
||||
return [ 'status' => 'error', 'msg' => 'Podane hasła są różne.' ];
|
||||
|
||||
$this->db->insert( 'pp_users', [
|
||||
'login' => $login,
|
||||
'status' => $status == 'on' ? 1 : 0,
|
||||
'active_to' => $activeTo === '' ? null : $activeTo,
|
||||
'admin' => $admin,
|
||||
'password' => md5( $password ),
|
||||
'twofa_enabled' => $twofaEnabled == 'on' ? 1 : 0,
|
||||
'twofa_email' => $twofaEmail,
|
||||
] );
|
||||
$userId = $this->db->get( 'pp_users', 'id', [ 'ORDER' => [ 'id' => 'DESC' ] ] );
|
||||
}
|
||||
else
|
||||
{
|
||||
if ( $password && strlen( $password ) < 5 )
|
||||
return [ 'status' => 'error', 'msg' => 'Podane hasło jest zbyt krótkie.' ];
|
||||
if ( $password && $password !== $passwordRe )
|
||||
return [ 'status' => 'error', 'msg' => 'Podane hasła są różne.' ];
|
||||
|
||||
if ( $password )
|
||||
$this->db->update( 'pp_users', [ 'password' => md5( $password ) ], [ 'id' => (int)$userId ] );
|
||||
|
||||
$this->db->update( 'pp_users', [
|
||||
'login' => $login,
|
||||
'admin' => $admin,
|
||||
'status' => $status == 'on' ? 1 : 0,
|
||||
'active_to' => $activeTo === '' ? null : $activeTo,
|
||||
'error_logged_count' => 0,
|
||||
'twofa_enabled' => $twofaEnabled == 'on' ? 1 : 0,
|
||||
'twofa_email' => $twofaEmail,
|
||||
], [ 'id' => (int)$userId ] );
|
||||
}
|
||||
|
||||
$privileges = (array)$privileges;
|
||||
foreach ( $privileges as $pri )
|
||||
$this->db->insert( 'pp_users_privileges', [ 'name' => $pri, 'id_user' => $userId ] );
|
||||
|
||||
\S::delete_cache();
|
||||
return [ 'status' => 'ok', 'msg' => 'Użytkownik został zapisany.' ];
|
||||
}
|
||||
|
||||
public function delete( int $userId ): bool
|
||||
{
|
||||
return (bool) $this->db->delete( 'pp_users', [ 'id' => $userId ] );
|
||||
}
|
||||
}
|
||||
47
autoload/Shared/Cache/CacheHandler.php
Normal file
47
autoload/Shared/Cache/CacheHandler.php
Normal file
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
namespace Shared\Cache;
|
||||
|
||||
class CacheHandler
|
||||
{
|
||||
public static function store( $key, $data, $ttl = 86400 )
|
||||
{
|
||||
file_put_contents( self::get_file_name( $key ), gzdeflate( serialize( array( time() + $ttl, $data ) ) ) );
|
||||
}
|
||||
|
||||
private static function get_file_name( $key )
|
||||
{
|
||||
$md5 = md5( $key );
|
||||
$dir = 'temp/' . $md5[0] . '/' . $md5[1] . '/';
|
||||
|
||||
if ( !is_dir( $dir ) )
|
||||
mkdir( $dir, 0755, true );
|
||||
|
||||
return $dir . 's_cache_' . $md5;
|
||||
}
|
||||
|
||||
public static function fetch( $key )
|
||||
{
|
||||
$filename = self::get_file_name( $key );
|
||||
|
||||
if ( !file_exists( $filename ) || !is_readable( $filename ) )
|
||||
return false;
|
||||
|
||||
$data = gzinflate( file_get_contents( $filename ) );
|
||||
|
||||
$data = @unserialize( $data );
|
||||
if ( !$data )
|
||||
{
|
||||
unlink( $filename );
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( time() > $data[0] )
|
||||
{
|
||||
if ( file_exists( $filename ) )
|
||||
unlink( $filename );
|
||||
return false;
|
||||
}
|
||||
|
||||
return $data[1];
|
||||
}
|
||||
}
|
||||
94
autoload/Shared/Email/Email.php
Normal file
94
autoload/Shared/Email/Email.php
Normal file
@@ -0,0 +1,94 @@
|
||||
<?php
|
||||
namespace Shared\Email;
|
||||
|
||||
class Email
|
||||
{
|
||||
public $table = 'pp_newsletter_templates';
|
||||
public $text = '';
|
||||
|
||||
public function load_by_name( string $name )
|
||||
{
|
||||
global $mdb;
|
||||
|
||||
$result = $mdb->get( $this->table, '*', [ 'name' => $name ] );
|
||||
if ( is_array( $result ) ) foreach ( $result as $key => $val )
|
||||
$this->$key = $val;
|
||||
}
|
||||
|
||||
public function email_check( $email )
|
||||
{
|
||||
return filter_var( $email, FILTER_VALIDATE_EMAIL );
|
||||
}
|
||||
|
||||
public function send( string $email, string $subject, $replay = '', $file = '' )
|
||||
{
|
||||
global $settings;
|
||||
|
||||
$base = dirname( dirname( dirname( __DIR__ ) ) );
|
||||
|
||||
if ( file_exists( $base . '/libraries/phpmailer/class.phpmailer.php' ) )
|
||||
require_once $base . '/libraries/phpmailer/class.phpmailer.php';
|
||||
if ( file_exists( $base . '/libraries/phpmailer/class.smtp.php' ) )
|
||||
require_once $base . '/libraries/phpmailer/class.smtp.php';
|
||||
|
||||
$text = $this->text;
|
||||
|
||||
$regex = "-(<img[^>]+src\s*=\s*['\"])(((?!'|\"|https?://).)*)(['\"][^>]*>)-i";
|
||||
$text = preg_replace( $regex, "$1https://" . $_SERVER['SERVER_NAME'] . "$2$4", $text );
|
||||
|
||||
$regex = "-(<a[^>]+href\s*=\s*['\"])(((?!'|\"|https?://).)*)(['\"][^>]*>)-i";
|
||||
$text = preg_replace( $regex, "$1https://" . $_SERVER['SERVER_NAME'] . "$2$4", $text );
|
||||
|
||||
if ( $this->email_check( $email ) and $subject )
|
||||
{
|
||||
$mail = new \PHPMailer();
|
||||
$mail->IsSMTP();
|
||||
$mail->SMTPAuth = true;
|
||||
$mail->Host = $settings['email_host'];
|
||||
$mail->Port = $settings['email_port'];
|
||||
$mail->Username = $settings['email_login'];
|
||||
$mail->Password = $settings['email_password'];
|
||||
$mail->CharSet = "UTF-8";
|
||||
$mail->SMTPOptions = array(
|
||||
'ssl' => array(
|
||||
'verify_peer' => false,
|
||||
'verify_peer_name' => false,
|
||||
'allow_self_signed' => true
|
||||
)
|
||||
);
|
||||
|
||||
if ( $this->email_check( $replay ) )
|
||||
{
|
||||
$mail->AddReplyTo( $replay, $replay );
|
||||
$mail->SetFrom( $settings['contact_email'], $settings['contact_email'] );
|
||||
}
|
||||
else
|
||||
{
|
||||
$mail->AddReplyTo( $settings['contact_email'], $settings['firm_name'] );
|
||||
$mail->SetFrom( $settings['contact_email'], $settings['firm_name'] );
|
||||
}
|
||||
|
||||
$mail->AddAddress( $email, '' );
|
||||
$mail->Subject = $subject;
|
||||
$mail->Body = $text;
|
||||
|
||||
if ( is_array( $file ) )
|
||||
{
|
||||
foreach ( $file as $file_tmp )
|
||||
{
|
||||
if ( file_exists( $file_tmp ) )
|
||||
$mail->AddAttachment( $file_tmp );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if ( file_exists( $file ) )
|
||||
$mail->AddAttachment( $file );
|
||||
}
|
||||
|
||||
$mail->IsHTML( true );
|
||||
return $mail->Send();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
1220
autoload/Shared/Helpers/Helpers.php
Normal file
1220
autoload/Shared/Helpers/Helpers.php
Normal file
File diff suppressed because it is too large
Load Diff
93
autoload/Shared/Html/Html.php
Normal file
93
autoload/Shared/Html/Html.php
Normal file
@@ -0,0 +1,93 @@
|
||||
<?php
|
||||
namespace Shared\Html;
|
||||
|
||||
class Html
|
||||
{
|
||||
public static function form_text( array $params = array() )
|
||||
{
|
||||
$tpl = new \Shared\Tpl\Tpl;
|
||||
$tpl->params = $params;
|
||||
return $tpl->render( 'html/form-text' );
|
||||
}
|
||||
|
||||
public static function input_switch( array $params = array() )
|
||||
{
|
||||
$tpl = new \Shared\Tpl\Tpl;
|
||||
$tpl->params = $params;
|
||||
return $tpl->render( 'html/input-switch' );
|
||||
}
|
||||
|
||||
public static function select( array $params = array() )
|
||||
{
|
||||
$tpl = new \Shared\Tpl\Tpl;
|
||||
$tpl->params = $params;
|
||||
return $tpl->render( 'html/select' );
|
||||
}
|
||||
|
||||
public static function textarea( array $params = array() )
|
||||
{
|
||||
$defaults = array(
|
||||
'rows' => 4,
|
||||
);
|
||||
|
||||
$params = array_merge( $defaults, $params );
|
||||
|
||||
$tpl = new \Shared\Tpl\Tpl;
|
||||
$tpl->params = $params;
|
||||
return $tpl->render( 'html/textarea' );
|
||||
}
|
||||
|
||||
public static function input_icon( array $params = array() )
|
||||
{
|
||||
$defaults = array(
|
||||
'type' => 'text',
|
||||
);
|
||||
|
||||
$params = array_merge( $defaults, $params );
|
||||
|
||||
$tpl = new \Shared\Tpl\Tpl;
|
||||
$tpl->params = $params;
|
||||
return $tpl->render( 'html/input-icon' );
|
||||
}
|
||||
|
||||
public static function input( array $params = array() )
|
||||
{
|
||||
$defaults = array(
|
||||
'type' => 'text',
|
||||
);
|
||||
|
||||
$params = array_merge( $defaults, $params );
|
||||
|
||||
$tpl = new \Shared\Tpl\Tpl;
|
||||
$tpl->params = $params;
|
||||
return $tpl->render( 'html/input' );
|
||||
}
|
||||
|
||||
public static function button( array $params = array() )
|
||||
{
|
||||
$defaults = array(
|
||||
'class' => 'btn-sm btn-info',
|
||||
);
|
||||
|
||||
$params = array_merge( $defaults, $params );
|
||||
|
||||
$tpl = new \Shared\Tpl\Tpl;
|
||||
$tpl->params = $params;
|
||||
return $tpl->render( 'html/button' );
|
||||
}
|
||||
|
||||
public static function panel( array $params = array() )
|
||||
{
|
||||
$defaults = array(
|
||||
'title' => 'panel-title',
|
||||
'class' => 'panel-primary',
|
||||
'content' => 'panel-content'
|
||||
);
|
||||
|
||||
$params = array_merge( $defaults, $params );
|
||||
|
||||
$tpl = new \Shared\Tpl\Tpl;
|
||||
$tpl->params = $params;
|
||||
return $tpl->render( 'html/panel' );
|
||||
}
|
||||
}
|
||||
314
autoload/Shared/Image/ImageManipulator.php
Normal file
314
autoload/Shared/Image/ImageManipulator.php
Normal file
@@ -0,0 +1,314 @@
|
||||
<?php
|
||||
namespace Shared\Image;
|
||||
|
||||
class ImageManipulator
|
||||
{
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $width;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $height;
|
||||
|
||||
/**
|
||||
* @var resource
|
||||
*/
|
||||
protected $image;
|
||||
protected $img_src;
|
||||
|
||||
/**
|
||||
* Image manipulator constructor
|
||||
*
|
||||
* @param string $file OPTIONAL Path to image file or image data as string
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($file = null)
|
||||
{
|
||||
if (null !== $file) {
|
||||
if (is_file($file)) {
|
||||
$this->img_src = $file;
|
||||
$this->setImageFile($file);
|
||||
} else {
|
||||
echo 'a'; exit;
|
||||
$this->setImageString($file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set image resource from file
|
||||
*
|
||||
* @param string $file Path to image file
|
||||
* @return ImageManipulator for a fluent interface
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function setImageFile($file)
|
||||
{
|
||||
if (!(is_readable($file) && is_file($file))) {
|
||||
throw new \InvalidArgumentException("Image file $file is not readable");
|
||||
}
|
||||
|
||||
if (is_resource($this->image)) {
|
||||
imagedestroy($this->image);
|
||||
}
|
||||
|
||||
list ($this->width, $this->height, $type) = getimagesize($file);
|
||||
|
||||
switch ($type) {
|
||||
case IMAGETYPE_GIF :
|
||||
$this->image = imagecreatefromgif($file);
|
||||
break;
|
||||
case IMAGETYPE_JPEG :
|
||||
$this->image = imagecreatefromjpeg($file);
|
||||
break;
|
||||
case IMAGETYPE_PNG :
|
||||
$this->image = imagecreatefrompng($file);
|
||||
break;
|
||||
default :
|
||||
throw new \InvalidArgumentException("Image type $type not supported");
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set image resource from string data
|
||||
*
|
||||
* @param string $data
|
||||
* @return ImageManipulator for a fluent interface
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function setImageString($data)
|
||||
{
|
||||
if (is_resource($this->image)) {
|
||||
imagedestroy($this->image);
|
||||
}
|
||||
|
||||
if (!$this->image = imagecreatefromstring($data)) {
|
||||
throw new \RuntimeException('Cannot create image from data string');
|
||||
}
|
||||
$this->width = imagesx($this->image);
|
||||
$this->height = imagesy($this->image);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resamples the current image
|
||||
*
|
||||
* @param int $width New width
|
||||
* @param int $height New height
|
||||
* @param bool $constrainProportions Constrain current image proportions when resizing
|
||||
* @return ImageManipulator for a fluent interface
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function resample( $width, $height, $constrainProportions = true )
|
||||
{
|
||||
if (!is_resource($this->image)) {
|
||||
throw new \RuntimeException('No image set');
|
||||
}
|
||||
if ($constrainProportions) {
|
||||
if ($this->height >= $this->width) {
|
||||
$width = round($height / $this->height * $this->width);
|
||||
} else {
|
||||
$height = round($width / $this->width * $this->height);
|
||||
}
|
||||
}
|
||||
|
||||
$temp = imagecreatetruecolor($width, $height);
|
||||
|
||||
imagecopyresampled($temp, $this->image, 0, 0, 0, 0, $width, $height, $this->width, $this->height);
|
||||
|
||||
if ( function_exists('exif_read_data') )
|
||||
{
|
||||
$exif = exif_read_data( $this->img_src );
|
||||
if ( $exif && isset($exif['Orientation']) )
|
||||
{
|
||||
$orientation = $exif['Orientation'];
|
||||
if ( $orientation != 1 )
|
||||
{
|
||||
$deg = 0;
|
||||
switch ($orientation)
|
||||
{
|
||||
case 3:
|
||||
$deg = 180;
|
||||
break;
|
||||
case 6:
|
||||
$deg = 270;
|
||||
break;
|
||||
case 8:
|
||||
$deg = 90;
|
||||
break;
|
||||
}
|
||||
|
||||
if ( $deg )
|
||||
$temp = imagerotate( $temp, $deg, 0 );
|
||||
}
|
||||
}
|
||||
}
|
||||
return $this->_replace($temp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enlarge canvas
|
||||
*
|
||||
* @param int $width Canvas width
|
||||
* @param int $height Canvas height
|
||||
* @param array $rgb RGB colour values
|
||||
* @param int $xpos X-Position of image in new canvas, null for centre
|
||||
* @param int $ypos Y-Position of image in new canvas, null for centre
|
||||
* @return ImageManipulator for a fluent interface
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function enlargeCanvas($width, $height, array $rgb = array(), $xpos = null, $ypos = null)
|
||||
{
|
||||
if (!is_resource($this->image)) {
|
||||
throw new \RuntimeException('No image set');
|
||||
}
|
||||
|
||||
$width = max($width, $this->width);
|
||||
$height = max($height, $this->height);
|
||||
|
||||
$temp = imagecreatetruecolor($width, $height);
|
||||
if (count($rgb) == 3) {
|
||||
$bg = imagecolorallocate($temp, $rgb[0], $rgb[1], $rgb[2]);
|
||||
imagefill($temp, 0, 0, $bg);
|
||||
}
|
||||
|
||||
if (null === $xpos) {
|
||||
$xpos = round(($width - $this->width) / 2);
|
||||
}
|
||||
if (null === $ypos) {
|
||||
$ypos = round(($height - $this->height) / 2);
|
||||
}
|
||||
|
||||
imagecopy($temp, $this->image, (int) $xpos, (int) $ypos, 0, 0, $this->width, $this->height);
|
||||
return $this->_replace($temp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Crop image
|
||||
*
|
||||
* @param int|array $x1 Top left x-coordinate of crop box or array of coordinates
|
||||
* @param int $y1 Top left y-coordinate of crop box
|
||||
* @param int $x2 Bottom right x-coordinate of crop box
|
||||
* @param int $y2 Bottom right y-coordinate of crop box
|
||||
* @return ImageManipulator for a fluent interface
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function crop($x1, $y1 = 0, $x2 = 0, $y2 = 0)
|
||||
{
|
||||
if (!is_resource($this->image)) {
|
||||
throw new \RuntimeException('No image set');
|
||||
}
|
||||
if (is_array($x1) && 4 == count($x1)) {
|
||||
list($x1, $y1, $x2, $y2) = $x1;
|
||||
}
|
||||
|
||||
$x1 = max($x1, 0);
|
||||
$y1 = max($y1, 0);
|
||||
|
||||
$x2 = min($x2, $this->width);
|
||||
$y2 = min($y2, $this->height);
|
||||
|
||||
$width = $x2 - $x1;
|
||||
$height = $y2 - $y1;
|
||||
|
||||
$temp = imagecreatetruecolor($width, $height);
|
||||
imagecopy($temp, $this->image, 0, 0, $x1, $y1, $width, $height);
|
||||
|
||||
return $this->_replace($temp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace current image resource with a new one
|
||||
*
|
||||
* @param resource $res New image resource
|
||||
* @return ImageManipulator for a fluent interface
|
||||
* @throws \UnexpectedValueException
|
||||
*/
|
||||
protected function _replace($res)
|
||||
{
|
||||
if (!is_resource($res)) {
|
||||
throw new \UnexpectedValueException('Invalid resource');
|
||||
}
|
||||
if (is_resource($this->image)) {
|
||||
imagedestroy($this->image);
|
||||
}
|
||||
$this->image = $res;
|
||||
$this->width = imagesx($res);
|
||||
$this->height = imagesy($res);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save current image to file
|
||||
*
|
||||
* @param string $fileName
|
||||
* @return void
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function save($fileName, $type = IMAGETYPE_JPEG)
|
||||
{
|
||||
$dir = dirname($fileName);
|
||||
if (!is_dir($dir)) {
|
||||
if (!mkdir($dir, 0755, true)) {
|
||||
throw new \RuntimeException('Error creating directory ' . $dir);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
switch ($type) {
|
||||
case IMAGETYPE_GIF :
|
||||
if (!imagegif($this->image, $fileName)) {
|
||||
throw new \RuntimeException;
|
||||
}
|
||||
break;
|
||||
case IMAGETYPE_PNG :
|
||||
if (!imagepng($this->image, $fileName)) {
|
||||
throw new \RuntimeException;
|
||||
}
|
||||
break;
|
||||
case IMAGETYPE_JPEG :
|
||||
default :
|
||||
if (!imagejpeg($this->image, $fileName, 95)) {
|
||||
throw new \RuntimeException;
|
||||
}
|
||||
}
|
||||
} catch (\Exception $ex) {
|
||||
throw new \RuntimeException('Error saving image file to ' . $fileName);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the GD image resource
|
||||
*
|
||||
* @return resource
|
||||
*/
|
||||
public function getResource()
|
||||
{
|
||||
return $this->image;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current image resource width
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getWidth()
|
||||
{
|
||||
return $this->width;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current image height
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getHeight()
|
||||
{
|
||||
return $this->height;
|
||||
}
|
||||
}
|
||||
28
autoload/Shared/Security/CsrfToken.php
Normal file
28
autoload/Shared/Security/CsrfToken.php
Normal file
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
namespace Shared\Security;
|
||||
|
||||
class CsrfToken
|
||||
{
|
||||
const SESSION_KEY = 'csrf_token';
|
||||
|
||||
public static function getToken()
|
||||
{
|
||||
if ( empty( $_SESSION[self::SESSION_KEY] ) )
|
||||
$_SESSION[self::SESSION_KEY] = bin2hex( random_bytes( 32 ) );
|
||||
|
||||
return $_SESSION[self::SESSION_KEY];
|
||||
}
|
||||
|
||||
public static function validate( $token )
|
||||
{
|
||||
if ( empty( $_SESSION[self::SESSION_KEY] ) || empty( $token ) )
|
||||
return false;
|
||||
|
||||
return hash_equals( $_SESSION[self::SESSION_KEY], $token );
|
||||
}
|
||||
|
||||
public static function regenerate()
|
||||
{
|
||||
unset( $_SESSION[self::SESSION_KEY] );
|
||||
}
|
||||
}
|
||||
80
autoload/Shared/Tpl/Tpl.php
Normal file
80
autoload/Shared/Tpl/Tpl.php
Normal file
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
namespace Shared\Tpl;
|
||||
|
||||
class Tpl
|
||||
{
|
||||
protected $dir = 'templates/';
|
||||
protected $vars = array();
|
||||
|
||||
function __construct( $dir = null )
|
||||
{
|
||||
if ( $dir !== null )
|
||||
$this->dir = $dir;
|
||||
}
|
||||
|
||||
public static function view( $file, $values = '' )
|
||||
{
|
||||
$tpl = new self;
|
||||
if ( is_array( $values ) ) foreach ( $values as $key => $val )
|
||||
$tpl->$key = $val;
|
||||
return $tpl->render( $file );
|
||||
}
|
||||
|
||||
public function secureHTML( $val )
|
||||
{
|
||||
$out = stripslashes( $val );
|
||||
$out = str_replace( "'", "'", $out );
|
||||
$out = str_replace( '"', """, $out );
|
||||
$out = str_replace( "<", "<", $out );
|
||||
$out = str_replace( ">", ">", $out );
|
||||
return $out;
|
||||
}
|
||||
|
||||
public function render( $file )
|
||||
{
|
||||
if ( file_exists( 'templates_user/' . $file . '.php' ) )
|
||||
{
|
||||
ob_start();
|
||||
include 'templates_user/' . $file . '.php';
|
||||
$out = ob_get_contents();
|
||||
ob_end_clean();
|
||||
|
||||
return $out;
|
||||
}
|
||||
else if ( file_exists( 'templates/' . $file . '.php' ) )
|
||||
{
|
||||
ob_start();
|
||||
include 'templates/' . $file . '.php';
|
||||
$out = ob_get_contents();
|
||||
ob_end_clean();
|
||||
|
||||
return $out;
|
||||
}
|
||||
else if ( file_exists( $file . '.php' ) )
|
||||
{
|
||||
ob_start();
|
||||
include $file . '.php';
|
||||
$out = ob_get_contents();
|
||||
ob_end_clean();
|
||||
|
||||
return $out;
|
||||
}
|
||||
else
|
||||
return '<div class="alert alert-danger" role="alert">Nie znaleziono pliku widoku: <b>' . $this->dir . $file . '.php</b>';
|
||||
}
|
||||
|
||||
public function __set( $name, $value )
|
||||
{
|
||||
$this->vars[ $name ] = $value;
|
||||
}
|
||||
|
||||
public function __isset( $name )
|
||||
{
|
||||
return isset( $this->vars[ $name ] );
|
||||
}
|
||||
|
||||
public function __get( $name )
|
||||
{
|
||||
return $this->vars[ $name ];
|
||||
}
|
||||
}
|
||||
@@ -37,9 +37,11 @@ class Site
|
||||
|
||||
if (!\admin\factory\Users::send_twofa_code((int)$user['id']))
|
||||
{
|
||||
\S::alert('Nie udało się wysłać kodu 2FA. Spróbuj ponownie.');
|
||||
// E-mail nie dotarł — użytkownik podał poprawne dane, więc przepuszczamy
|
||||
\S::delete_session('twofa_pending');
|
||||
header('Location: /admin/');
|
||||
\S::alert('Nie udało się wysłać kodu 2FA — zalogowano bez weryfikacji e-mail.', 'alert-warning');
|
||||
self::finalize_admin_login($user, $domain, $cookie_name, (bool)\S::get('remember'));
|
||||
header('Location: /admin/articles/view_list/');
|
||||
exit;
|
||||
}
|
||||
|
||||
|
||||
62
autoload/admin/controls/class.Releases.php
Normal file
62
autoload/admin/controls/class.Releases.php
Normal file
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
namespace admin\controls;
|
||||
|
||||
class Releases
|
||||
{
|
||||
public static function main_view(): string
|
||||
{
|
||||
return \admin\view\Releases::main_view();
|
||||
}
|
||||
|
||||
public static function promote(): void
|
||||
{
|
||||
$version = trim(\S::get('version'));
|
||||
if ($version)
|
||||
\admin\factory\Releases::promote($version);
|
||||
header('Location: /admin/releases/main_view/');
|
||||
exit;
|
||||
}
|
||||
|
||||
public static function demote(): void
|
||||
{
|
||||
$version = trim(\S::get('version'));
|
||||
if ($version)
|
||||
\admin\factory\Releases::demote($version);
|
||||
header('Location: /admin/releases/main_view/');
|
||||
exit;
|
||||
}
|
||||
|
||||
public static function discover_versions(): void
|
||||
{
|
||||
$added = \admin\factory\Releases::discover_versions();
|
||||
\S::set_message("Wykryto i dodano {$added} nowych wersji jako stable.");
|
||||
header('Location: /admin/releases/main_view/');
|
||||
exit;
|
||||
}
|
||||
|
||||
public static function save_license(): void
|
||||
{
|
||||
\admin\factory\Releases::save_license($_POST);
|
||||
\S::set_message('Licencja została zapisana.');
|
||||
header('Location: /admin/releases/main_view/#licenses');
|
||||
exit;
|
||||
}
|
||||
|
||||
public static function delete_license(): void
|
||||
{
|
||||
$id = (int)\S::get('id');
|
||||
if ($id)
|
||||
\admin\factory\Releases::delete_license($id);
|
||||
header('Location: /admin/releases/main_view/#licenses');
|
||||
exit;
|
||||
}
|
||||
|
||||
public static function toggle_beta(): void
|
||||
{
|
||||
$id = (int)\S::get('id');
|
||||
if ($id)
|
||||
\admin\factory\Releases::toggle_beta($id);
|
||||
header('Location: /admin/releases/main_view/#licenses');
|
||||
exit;
|
||||
}
|
||||
}
|
||||
@@ -1,714 +1,117 @@
|
||||
<?php
|
||||
namespace admin\factory;
|
||||
|
||||
class Articles
|
||||
{
|
||||
public static function duplicate_article( $article_id )
|
||||
private static function repo(): \Domain\Articles\ArticlesRepository
|
||||
{
|
||||
global $mdb, $user;
|
||||
|
||||
$article = \admin\factory\Articles::article_details( $article_id );
|
||||
|
||||
if ( $article )
|
||||
{
|
||||
$mdb -> insert( 'pp_articles', [
|
||||
'show_title' => $article['show_title'],
|
||||
'show_date_add' => $article['show_date_add'],
|
||||
'show_date_modify' => $article['show_date_modify'],
|
||||
'date_add' => date( 'Y-m-d H:i:s' ),
|
||||
'date_modify' => date( 'Y-m-d H:i:s' ),
|
||||
'modify_by' => $user['id'],
|
||||
'layout_id' => $article['layout_id'],
|
||||
'status' => $article['status'],
|
||||
'repeat_entry' => $article['repeat_entry'],
|
||||
'social_icons' => $article['social_icons'],
|
||||
'date_start' => $article['date_start'],
|
||||
'date_end' => $article['event_date'],
|
||||
'priority' => $article['priority'],
|
||||
'password' => $article['password'],
|
||||
'pixieset' => $article['pixieset']
|
||||
] );
|
||||
|
||||
$article_tmp_id = $mdb -> id();
|
||||
|
||||
if ( $article_tmp_id )
|
||||
{
|
||||
foreach ( $article['languages'] as $key => $val )
|
||||
{
|
||||
$mdb -> insert( 'pp_articles_langs', [
|
||||
'article_id' => $article_tmp_id,
|
||||
'lang_id' => $key,
|
||||
'title' => 'Kopia: ' . $val['title'],
|
||||
'entry' => $val['entry'],
|
||||
'text' => $val['text'],
|
||||
'meta_title' => null,
|
||||
'meta_description' => null,
|
||||
'meta_keywords' => null,
|
||||
'seo_link' => null,
|
||||
'copy_from' => $val['copy_from'],
|
||||
'block_direct_access' => $val['block_direct_access']
|
||||
] );
|
||||
}
|
||||
|
||||
foreach ( $article['params'] as $param )
|
||||
{
|
||||
$mdb -> insert( 'pp_articles_additional_values', [
|
||||
'param_id' => $param['param_id'],
|
||||
'value' => $param['value'],
|
||||
'article_id' => $article_tmp_id,
|
||||
'language_id' => $param['language_id']
|
||||
] );
|
||||
}
|
||||
|
||||
foreach ( $article['pages'] as $page )
|
||||
{
|
||||
$order = self::max_order() + 1;
|
||||
$mdb -> insert( 'pp_articles_pages', [
|
||||
'article_id' => $article_tmp_id,
|
||||
'page_id' => $page,
|
||||
'o' => (int)$order
|
||||
] );
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
global $mdb;
|
||||
return new \Domain\Articles\ArticlesRepository( $mdb );
|
||||
}
|
||||
|
||||
public static function insert_missing_hash() {
|
||||
global $mdb;
|
||||
public static function duplicate_article( $article_id )
|
||||
{
|
||||
global $user;
|
||||
return self::repo()->duplicateArticle( $article_id, (int)$user['id'] );
|
||||
}
|
||||
|
||||
if ( $mdb -> count( 'pp_articles', [ 'hash' => null ] ) ) {
|
||||
$rows = $mdb -> select( 'pp_articles', [ 'id', 'date_add' ], [ 'hash' => null ] );
|
||||
if ( is_array( $rows ) ) foreach ( $rows as $row ) {
|
||||
$mdb -> update( 'pp_articles', [ 'hash' => md5( $row['id'] . $row['date_add'] ) ], [ 'id' => $row['id'] ] );
|
||||
}
|
||||
}
|
||||
return true;
|
||||
public static function insert_missing_hash()
|
||||
{
|
||||
return self::repo()->insertMissingHash();
|
||||
}
|
||||
|
||||
static public function files_order_save( $article_id, $order )
|
||||
{
|
||||
global $mdb;
|
||||
|
||||
$order = explode( ';', $order );
|
||||
if ( is_array( $order ) and !empty( $order ) ) foreach ( $order as $file_id )
|
||||
{
|
||||
$mdb -> update( 'pp_articles_files', [
|
||||
'o' => (int)$i++
|
||||
], [
|
||||
'AND' => [
|
||||
'article_id' => $article_id,
|
||||
'id' => $file_id
|
||||
]
|
||||
] );
|
||||
}
|
||||
self::repo()->filesOrderSave( $article_id, $order );
|
||||
}
|
||||
|
||||
public static function gallery_order_save( $article_id, $order )
|
||||
{
|
||||
global $mdb;
|
||||
|
||||
$order = explode( ';', $order );
|
||||
if ( is_array( $order ) and !empty( $order ) ) foreach ( $order as $image_id )
|
||||
{
|
||||
$mdb -> update( 'pp_articles_images', [
|
||||
'o' => $i++
|
||||
], [
|
||||
'AND' => [
|
||||
'article_id' => $article_id,
|
||||
'id' => $image_id
|
||||
]
|
||||
] );
|
||||
}
|
||||
self::repo()->galleryOrderSave( $article_id, $order );
|
||||
}
|
||||
|
||||
public static function additional_params( $language = 0 )
|
||||
{
|
||||
global $mdb;
|
||||
return $mdb -> select( 'pp_articles_additional_params', '*', [ 'AND' => [ 'status' => 1, 'language' => $language ] ] );
|
||||
return self::repo()->additionalParams( $language );
|
||||
}
|
||||
|
||||
public static function image_alt_change( $image_id, $image_alt )
|
||||
{
|
||||
global $mdb;
|
||||
$result = $mdb -> update( 'pp_articles_images', [
|
||||
'alt' => $image_alt
|
||||
], [
|
||||
'id' => $image_id
|
||||
] );
|
||||
\S::delete_cache();
|
||||
return $result;
|
||||
return self::repo()->imageAltChange( $image_id, $image_alt );
|
||||
}
|
||||
|
||||
public static function articles_by_date_add( $date_start, $date_end )
|
||||
{
|
||||
global $mdb;
|
||||
|
||||
$results = $mdb -> query( 'SELECT '
|
||||
. 'id '
|
||||
. 'FROM '
|
||||
. 'pp_articles '
|
||||
. 'WHERE '
|
||||
. 'status = 1 '
|
||||
. 'AND '
|
||||
. 'date_add BETWEEN \'' . $date_start . '\' AND \'' . $date_end . '\' '
|
||||
. 'ORDER BY '
|
||||
. 'date_add DESC' ) -> fetchAll();
|
||||
if ( is_array( $results ) and !empty( $results ) ) foreach ( $results as $row )
|
||||
$articles[] = \front\factory\Articles::article_details( $row['id'], 'pl' );
|
||||
|
||||
return $articles;
|
||||
return self::repo()->articlesByDateAdd( $date_start, $date_end );
|
||||
}
|
||||
|
||||
public static function article_url( $article_id )
|
||||
{
|
||||
global $mdb;
|
||||
|
||||
$results = $mdb -> query( "SELECT seo_link FROM pp_articles_langs AS pal, pp_langs AS pl WHERE lang_id = pl.id AND article_id = " . (int)$article_id . " AND seo_link != '' ORDER BY o ASC LIMIT 1" ) -> fetchAll();
|
||||
if ( !$results[0]['seo_link'] )
|
||||
{
|
||||
$title = self::article_title( $article_id );
|
||||
return 'a-' . $article_id . '-' . \S::seo( $title );
|
||||
}
|
||||
else
|
||||
return $results[0]['seo_link'];
|
||||
return self::repo()->articleUrl( $article_id );
|
||||
}
|
||||
|
||||
public static function article_pages( $article_id )
|
||||
{
|
||||
global $mdb;
|
||||
|
||||
$results = $mdb -> query( "SELECT page_id FROM pp_articles_pages WHERE article_id = " . (int)$article_id ) -> fetchAll();
|
||||
if ( is_array( $results ) and !empty( $results ) ) foreach ( $results as $row )
|
||||
{
|
||||
if ( $out == '' )
|
||||
$out .= ' - ';
|
||||
|
||||
$out .= \admin\factory\Pages::page_title( $row['page_id'] );
|
||||
|
||||
if ( end( $results ) != $row )
|
||||
$out .= ' / ';
|
||||
}
|
||||
|
||||
return $out;
|
||||
return self::repo()->articlePages( $article_id );
|
||||
}
|
||||
|
||||
public static function article_title( $article_id )
|
||||
{
|
||||
global $mdb;
|
||||
$results = $mdb -> query( "SELECT title FROM pp_articles_langs AS pal, pp_langs AS pl WHERE lang_id = pl.id AND article_id = " . (int)$article_id . " AND title != '' ORDER BY o ASC LIMIT 1" ) -> fetchAll();
|
||||
return $results[0]['title'];
|
||||
return self::repo()->articleTitle( $article_id );
|
||||
}
|
||||
|
||||
public static function articles_set_archive( $article_id )
|
||||
{
|
||||
global $mdb;
|
||||
$result = $mdb -> update( 'pp_articles', [ 'status' => -1 ], [ 'id' => (int)$article_id ] );
|
||||
\S::htacces();
|
||||
\S::delete_cache();
|
||||
return $result;
|
||||
return self::repo()->articlesSetArchive( $article_id );
|
||||
}
|
||||
|
||||
public static function file_name_change( $file_id, $file_name )
|
||||
{
|
||||
global $mdb;
|
||||
$mdb -> update( 'pp_articles_files', [ 'name' => $file_name ], [ 'id' => (int)$file_id ] );
|
||||
return true;
|
||||
return self::repo()->fileNameChange( $file_id, $file_name );
|
||||
}
|
||||
|
||||
public static function delete_file( $file_id )
|
||||
{
|
||||
global $mdb;
|
||||
$mdb -> update( 'pp_articles_files', [ 'to_delete' => 1 ], [ 'id' => (int)$file_id ] );
|
||||
return true;
|
||||
return self::repo()->deleteFile( $file_id );
|
||||
}
|
||||
|
||||
public static function delete_img( $image_id )
|
||||
{
|
||||
global $mdb;
|
||||
$mdb -> update( 'pp_articles_images', [ 'to_delete' => 1 ], [ 'id' => (int)$image_id ] );
|
||||
return true;
|
||||
return self::repo()->deleteImg( $image_id );
|
||||
}
|
||||
|
||||
public static function article_details( $article_id )
|
||||
{
|
||||
global $mdb;
|
||||
|
||||
if ( $article = $mdb -> get( 'pp_articles', '*', [ 'id' => (int)$article_id ] ) )
|
||||
{
|
||||
$results = $mdb -> select( 'pp_articles_langs', '*', [ 'article_id' => (int)$article_id ] );
|
||||
if ( is_array( $results ) ) foreach ( $results as $row )
|
||||
$article['languages'][ $row['lang_id'] ] = $row;
|
||||
|
||||
$article['images'] = $mdb -> select( 'pp_articles_images', '*', [ 'article_id' => (int)$article_id, 'ORDER' => [ 'o' => 'ASC', 'id' => 'ASC' ] ] );
|
||||
$article['files'] = $mdb -> select( 'pp_articles_files', '*', [ 'article_id' => (int)$article_id, 'ORDER' => [ 'o' => 'ASC', 'id' => 'ASC' ] ] );
|
||||
$article['pages'] = $mdb -> select( 'pp_articles_pages', 'page_id', [ 'article_id' => (int)$article_id ] );
|
||||
$article['tags'] = $mdb -> select( 'pp_tags', [ '[><]pp_articles_tags' => [ 'id' => 'tag_id' ] ], 'name', [ 'article_id' => (int)$article_id ] );
|
||||
$article['params'] = $mdb -> select( 'pp_articles_additional_values', [ 'param_id', 'value', 'language_id' ], [ 'article_id' => (int)$article_id ] );
|
||||
}
|
||||
|
||||
return $article;
|
||||
return self::repo()->articleDetails( $article_id );
|
||||
}
|
||||
|
||||
public static function max_order()
|
||||
{
|
||||
global $mdb;
|
||||
return $mdb -> max( 'pp_articles_pages', 'o' );
|
||||
return self::repo()->maxOrder();
|
||||
}
|
||||
|
||||
public static function article_save(
|
||||
$article_id, $title, $main_image, $entry, $text, $table_of_contents, $status, $show_title, $show_table_of_contents, $show_date_add, $date_add, $show_date_modify, $date_modify, $seo_link, $meta_title, $meta_description,
|
||||
$meta_keywords, $layout_id, $pages, $noindex, $repeat_entry, $copy_from, $social_icons, $event_date, $tags, $block_direct_access, $priority,
|
||||
$password, $pixieset, $id_author, $params )
|
||||
$article_id, $title, $main_image, $entry, $text, $table_of_contents, $status, $show_title, $show_table_of_contents, $show_date_add, $date_add, $show_date_modify, $date_modify, $seo_link, $meta_title, $meta_description,
|
||||
$meta_keywords, $layout_id, $pages, $noindex, $repeat_entry, $copy_from, $social_icons, $event_date, $tags, $block_direct_access, $priority,
|
||||
$password, $pixieset, $id_author, $params
|
||||
)
|
||||
{
|
||||
|
||||
global $mdb, $user;
|
||||
|
||||
$event_date = explode( ' - ', $event_date );
|
||||
|
||||
if ( !$article_id )
|
||||
{
|
||||
$mdb -> insert( 'pp_articles', [
|
||||
'show_title' => $show_title == 'on' ? 1 : 0,
|
||||
'show_table_of_contents' => $show_table_of_contents == 'on' ? 1 : 0,
|
||||
'show_date_add' => $show_date_add == 'on' ? 1 : 0,
|
||||
'show_date_modify' => $show_date_modify == 'on' ? 1 : 0,
|
||||
'date_add' => date( 'Y-m-d H:i:s' ),
|
||||
'date_modify' => date( 'Y-m-d H:i:s' ),
|
||||
'modify_by' => $user['id'],
|
||||
'layout_id' => $layout_id ? (int)$layout_id : null,
|
||||
'status' => $status == 'on' ? 1 : 0,
|
||||
'repeat_entry' => $repeat_entry == 'on' ? 1 : 0,
|
||||
'social_icons' => $social_icons == 'on' ? 1 : 0,
|
||||
'date_start' => $event_date[0] ? $event_date[0] : null,
|
||||
'date_end' => $event_date[1] ? $event_date[1] : null,
|
||||
'priority' => $priority == 'on' ? 1 : 0,
|
||||
'password' => $password ? $password : null,
|
||||
'pixieset' => $pixieset,
|
||||
'id_author' => $id_author ? $id_author : null
|
||||
] );
|
||||
|
||||
$id = $mdb -> id();
|
||||
|
||||
if ( $id )
|
||||
{
|
||||
$i = 0;
|
||||
|
||||
/* tłumaczenia */
|
||||
$results = $mdb -> select( 'pp_langs', [ 'id' ], [ 'status' => 1, 'ORDER' => [ 'o' => 'ASC' ] ] );
|
||||
if ( is_array( $results ) and count( $results ) > 1 ) foreach ( $results as $row )
|
||||
{
|
||||
$mdb -> insert( 'pp_articles_langs', [
|
||||
'article_id' => (int)$id,
|
||||
'lang_id' => $row['id'],
|
||||
'title' => $title[ $i ] != '' ? $title[ $i ] : null,
|
||||
'main_image' => $main_image[$i] != '' ? $main_image[$i] : null,
|
||||
'entry' => $entry[ $i ] != '' ? $entry[ $i ] : null,
|
||||
'text' => $text[ $i ] != '' ? $text[ $i ] : null,
|
||||
'table_of_contents' => $table_of_contents[$i] != '' ? $table_of_contents[$i] : null,
|
||||
'meta_title' => $meta_title[ $i ] != '' ? $meta_title[ $i ] : null,
|
||||
'meta_description' => $meta_description[ $i ] != '' ? $meta_description[ $i ] : null,
|
||||
'meta_keywords' => $meta_keywords[ $i ] != '' ? $meta_keywords[ $i ] : null,
|
||||
'seo_link' => \S::seo( $seo_link[ $i ] ) != '' ? \S::seo( $seo_link[ $i ] ) : null,
|
||||
'noindex' => $noindex[ $i ],
|
||||
'copy_from' => $copy_from[ $i ] != '' ? $copy_from[ $i ] : null,
|
||||
'block_direct_access' => $block_direct_access[ $i ]
|
||||
] );
|
||||
$i++;
|
||||
}
|
||||
else if ( is_array( $results ) and count( $results ) == 1 ) foreach ( $results as $row )
|
||||
{
|
||||
$mdb -> insert( 'pp_articles_langs', [
|
||||
'article_id' => (int)$id,
|
||||
'lang_id' => $row['id'],
|
||||
'title' => $title != '' ? $title : null,
|
||||
'main_image' => $main_image != '' ? $main_image : null,
|
||||
'entry' => $entry != '' ? $entry : null,
|
||||
'text' => $text != '' ? $text : null,
|
||||
'table_of_contents' => $table_of_contents != '' ? $table_of_contents : null,
|
||||
'meta_title' => $meta_title != '' ? $meta_title : null,
|
||||
'meta_description' => $meta_description != '' ? $meta_description : null,
|
||||
'meta_keywords' => $meta_keywords != '' ? $meta_keywords : null,
|
||||
'seo_link' => \S::seo( $seo_link ) != '' ? \S::seo( $seo_link ) : null,
|
||||
'noindex' => $noindex,
|
||||
'copy_from' => $copy_from != '' ? $copy_from : null,
|
||||
'block_direct_access' => $block_direct_access
|
||||
] );
|
||||
}
|
||||
|
||||
/* parametry bez wersji językowych */
|
||||
$results = $mdb -> select( 'pp_articles_additional_params', '*', [ 'AND' => [ 'status' => 1, 'language' => 0 ] ] );
|
||||
if ( is_array( $results ) and !empty( $results ) ) foreach ( $results as $row )
|
||||
{
|
||||
$mdb -> insert( 'pp_articles_additional_values', [
|
||||
'param_id' => $row['id'],
|
||||
'value' => $params[ 'ap_' . $row['name'] ],
|
||||
'article_id' => (int)$id,
|
||||
'language_id' => null
|
||||
] );
|
||||
}
|
||||
|
||||
/* strony */
|
||||
if ( is_array( $pages ) ) foreach ( $pages as $page )
|
||||
{
|
||||
$order = self::max_order() + 1;
|
||||
|
||||
$mdb -> insert( 'pp_articles_pages', [
|
||||
'article_id' => (int)$id,
|
||||
'page_id' => (int)$page,
|
||||
'o' => (int)$order
|
||||
] );
|
||||
}
|
||||
else if ( $pages )
|
||||
{
|
||||
$order = self::max_order() + 1;
|
||||
|
||||
$mdb -> insert( 'pp_articles_pages', [
|
||||
'article_id' => (int)$id,
|
||||
'page_id' => (int)$pages,
|
||||
'o' => (int)$order
|
||||
] );
|
||||
}
|
||||
|
||||
/* pliki */
|
||||
$results = $mdb -> select( 'pp_articles_files', '*', [ 'article_id' => null ] );
|
||||
if ( is_array( $results ) ) foreach ( $results as $row )
|
||||
{
|
||||
$dir = '/upload/article_files/article_' . $id;
|
||||
|
||||
$new_file_name = str_replace( '/upload/article_files/tmp', $dir, $row['src'] );
|
||||
|
||||
if ( file_exists( '..' . $row['src'] ) )
|
||||
{
|
||||
if ( !is_dir( '../' . $dir ) and $created !== true )
|
||||
{
|
||||
if ( mkdir( '../' . $dir, 0755, true ) )
|
||||
$created = true;
|
||||
}
|
||||
rename( '..' . $row['src'], '..' . $new_file_name );
|
||||
}
|
||||
|
||||
$mdb -> update( 'pp_articles_files', [ 'src' => $new_file_name, 'article_id' => $id ], [ 'id' => $row['id'] ] );
|
||||
}
|
||||
|
||||
$created = false;
|
||||
|
||||
/* zdjęcia */
|
||||
$results = $mdb -> select( 'pp_articles_images', '*', [ 'article_id' => null ] );
|
||||
if ( is_array( $results ) ) foreach ( $results as $row )
|
||||
{
|
||||
$dir = '/upload/article_images/article_' . $id;
|
||||
|
||||
$new_file_name = str_replace( '/upload/article_images/tmp', $dir, $row['src'] );
|
||||
|
||||
if ( file_exists( '../' . $new_file_name ) )
|
||||
{
|
||||
$ext = strrpos( $new_file_name, '.' );
|
||||
$fileName_a = substr( $new_file_name, 0, $ext );
|
||||
$fileName_b = substr( $new_file_name, $ext );
|
||||
|
||||
$count = 1;
|
||||
|
||||
while ( file_exists( '../' . $fileName_a . '_' . $count . $fileName_b ) )
|
||||
$count++;
|
||||
|
||||
$new_file_name = $fileName_a . '_' . $count . $fileName_b;
|
||||
}
|
||||
|
||||
if ( file_exists( '..' . $row['src'] ) )
|
||||
{
|
||||
if ( !is_dir( '../' . $dir ) and $created !== true )
|
||||
{
|
||||
if ( mkdir( '../' . $dir, 0755, true ) )
|
||||
$created = true;
|
||||
}
|
||||
rename( '..' . $row['src'], '..' . $new_file_name );
|
||||
}
|
||||
|
||||
$mdb -> update( 'pp_articles_images', [ 'src' => $new_file_name, 'article_id' => (int)$id ], [ 'id' => $row['id'] ] );
|
||||
}
|
||||
|
||||
/* tagi */
|
||||
$tags = explode( ',', $tags );
|
||||
if ( is_array( $tags ) ) foreach ( $tags as $tag )
|
||||
{
|
||||
if ( trim( $tag ) != '' )
|
||||
{
|
||||
$tag_id = $mdb -> get( 'pp_tags', 'id', [ 'name' => $tag ] );
|
||||
if ( !$tag_id )
|
||||
{
|
||||
$mdb -> insert( 'pp_tags', [ 'name' => $tag ] );
|
||||
$tag_id = $mdb -> id();
|
||||
}
|
||||
|
||||
$mdb -> insert( 'pp_articles_tags', [ 'article_id' => (int)$id, 'tag_id' => (int)$tag_id ] );
|
||||
}
|
||||
}
|
||||
|
||||
\S::htacces();
|
||||
\S::delete_cache();
|
||||
|
||||
return $id;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$mdb -> update( 'pp_articles', [
|
||||
'show_title' => $show_title == 'on' ? 1 : 0,
|
||||
'show_table_of_contents' => $show_table_of_contents == 'on' ? 1 : 0,
|
||||
'show_date_add' => $show_date_add == 'on' ? 1 : 0,
|
||||
'date_add' => $date_add,
|
||||
'show_date_modify' => $show_date_modify == 'on' ? 1 : 0,
|
||||
'date_modify' => $date_modify ? $date_modify : date( 'Y-m-d H:i:s' ),
|
||||
'modify_by' => $user['id'],
|
||||
'layout_id' => $layout_id ? (int)$layout_id : null,
|
||||
'status' => $status == 'on' ? 1 : 0,
|
||||
'repeat_entry' => $repeat_entry == 'on' ? 1 : 0,
|
||||
'social_icons' => $social_icons == 'on' ? 1 : 0,
|
||||
'date_start' => $event_date[0] ? $event_date[0] : null,
|
||||
'date_end' => $event_date[1] ? $event_date[1] : null,
|
||||
'priority' => $priority == 'on' ? 1 : 0,
|
||||
'password' => $password ? $password : null,
|
||||
'pixieset' => $pixieset,
|
||||
'id_author' => $id_author ? $id_author : null
|
||||
], [
|
||||
'id' => (int)$article_id
|
||||
] );
|
||||
|
||||
if ( $date_add )
|
||||
$mdb -> update( 'pp_articles', [ 'date_add' => $date_add ], [ 'id' => (int)$article_id ] );
|
||||
|
||||
$i = 0;
|
||||
|
||||
/* tłumaczenia */
|
||||
$mdb -> delete( 'pp_articles_langs', [ 'article_id' => (int)$article_id ] );
|
||||
|
||||
$results = $mdb -> select( 'pp_langs', [ 'id' ], [ 'status' => 1, 'ORDER' => [ 'o' => 'ASC' ] ] );
|
||||
if ( is_array( $results ) and count( $results ) > 1 ) foreach ( $results as $row )
|
||||
{
|
||||
$mdb -> insert( 'pp_articles_langs', [
|
||||
'article_id' => (int)$article_id,
|
||||
'lang_id' => $row['id'],
|
||||
'title' => $title[ $i ] != '' ? $title[ $i ] : null,
|
||||
'main_image' => $main_image[$i] != '' ? $main_image[$i] : null,
|
||||
'entry' => $entry[ $i ] != '' ? $entry[ $i ] : null,
|
||||
'text' => $text[ $i ] != '' ? $text[ $i ] : null,
|
||||
'table_of_contents' => $table_of_contents[$i] != '' ? $table_of_contents[$i] : null,
|
||||
'meta_title' => $meta_title[ $i ] != '' ? $meta_title[ $i ] : null,
|
||||
'meta_description' => $meta_description[ $i ] != '' ? $meta_description[ $i ] : null,
|
||||
'meta_keywords' => $meta_keywords[ $i ] != '' ? $meta_keywords[ $i ] : null,
|
||||
'seo_link' => \S::seo( $seo_link[ $i ] ) != '' ? \S::seo( $seo_link[ $i ] ) : null,
|
||||
'noindex' => $noindex[ $i ],
|
||||
'copy_from' => $copy_from[ $i ] != '' ? $copy_from[ $i ] : null,
|
||||
'block_direct_access' => $block_direct_access[ $i ]
|
||||
] );
|
||||
$i++;
|
||||
}
|
||||
else if ( is_array( $results ) and count( $results ) == 1 ) foreach ( $results as $row )
|
||||
{
|
||||
$mdb -> insert( 'pp_articles_langs', [
|
||||
'article_id' => (int)$article_id,
|
||||
'lang_id' => $row['id'],
|
||||
'title' => $title != '' ? $title : null,
|
||||
'main_image' => $main_image != '' ? $main_image : null,
|
||||
'entry' => $entry != '' ? $entry : null,
|
||||
'text' => $text != '' ? $text : null,
|
||||
'table_of_contents' => $table_of_contents != '' ? $table_of_contents : null,
|
||||
'meta_title' => $meta_title != '' ? $meta_title : null,
|
||||
'meta_description' => $meta_description != '' ? $meta_description : null,
|
||||
'meta_keywords' => $meta_keywords != '' ? $meta_keywords : null,
|
||||
'seo_link' => \S::seo( $seo_link ) != '' ? \S::seo( $seo_link ) : null,
|
||||
'noindex' => $noindex,
|
||||
'copy_from' => $copy_from != '' ? $copy_from : null,
|
||||
'block_direct_access' => $block_direct_access
|
||||
] );
|
||||
}
|
||||
|
||||
/* dodatkowe parametry */
|
||||
$mdb -> delete( 'pp_articles_additional_values', [ 'article_id' => (int)$article_id ] );
|
||||
|
||||
/* parametry bez wersji językowych */
|
||||
$results = $mdb -> select( 'pp_articles_additional_params', '*', [ 'AND' => [ 'status' => 1, 'language' => 0 ] ] );
|
||||
if ( is_array( $results ) and !empty( $results ) ) foreach ( $results as $row )
|
||||
{
|
||||
$mdb -> insert( 'pp_articles_additional_values', [
|
||||
'param_id' => $row['id'],
|
||||
'value' => $params[ 'ap_' . $row['name'] ],
|
||||
'article_id' => (int)$article_id,
|
||||
'language_id' => null
|
||||
] );
|
||||
}
|
||||
|
||||
/* parametry z wersjami językowymi */
|
||||
$results = $mdb -> select( 'pp_articles_additional_params', '*', [ 'AND' => [ 'status' => 1, 'language' => 1 ] ] );
|
||||
if ( is_array( $results ) and !empty( $results ) ) foreach ( $results as $row )
|
||||
{
|
||||
$results2 = $mdb -> select( 'pp_langs', [ 'id' ], [ 'status' => 1, 'ORDER' => [ 'o' => 'ASC' ] ] );
|
||||
if ( is_array( $results2 ) ) foreach ( $results2 as $row2 )
|
||||
{
|
||||
$mdb -> insert( 'pp_articles_additional_values', [
|
||||
'param_id' => $row['id'],
|
||||
'value' => $params[ 'ap_' . $row['name'] . '_' . $row2['id'] ],
|
||||
'article_id' => (int)$article_id,
|
||||
'language_id' => $row2['id']
|
||||
] );
|
||||
}
|
||||
}
|
||||
|
||||
/* strony */
|
||||
$not_in = [ 0 ];
|
||||
|
||||
if ( is_array( $pages ) ) foreach ( $pages as $page )
|
||||
$not_in[] = $page;
|
||||
else if ( $pages )
|
||||
$not_in[] = $pages;
|
||||
|
||||
$mdb -> delete( 'pp_articles_pages', [ 'AND' => [ 'article_id' => (int)$article_id, 'page_id[!]' => $not_in ] ] );
|
||||
|
||||
$pages_tmp = $mdb -> select( 'pp_articles_pages', 'page_id', [ 'article_id' => (int)$article_id ] );
|
||||
|
||||
if ( !is_array( $pages ) )
|
||||
$pages = [ $pages ];
|
||||
|
||||
$pages = array_diff( $pages, $pages_tmp );
|
||||
|
||||
if ( is_array( $pages ) ) foreach ( $pages as $page )
|
||||
{
|
||||
$order = self::max_order() + 1;
|
||||
|
||||
$mdb -> insert( 'pp_articles_pages', [
|
||||
'article_id' => (int)$article_id,
|
||||
'page_id' => (int)$page,
|
||||
'o' => (int)$order
|
||||
] );
|
||||
}
|
||||
|
||||
/* pliki */
|
||||
$results = $mdb -> select( 'pp_articles_files', '*', [ 'article_id' => null ] );
|
||||
if ( is_array( $results ) ) foreach ( $results as $row )
|
||||
{
|
||||
$dir = '/upload/article_files/article_' . $article_id;
|
||||
|
||||
$new_file_name = str_replace( '/upload/article_files/tmp', $dir, $row['src'] );
|
||||
|
||||
if ( file_exists( '..' . $row['src'] ) )
|
||||
{
|
||||
if ( !is_dir( '../' . $dir ) and $created !== true )
|
||||
{
|
||||
if ( mkdir( '../' . $dir, 0755, true ) )
|
||||
$created = true;
|
||||
}
|
||||
rename( '..' . $row['src'], '..' . $new_file_name );
|
||||
}
|
||||
|
||||
$mdb -> update( 'pp_articles_files', [ 'src' => $new_file_name, 'article_id' => (int)$article_id ], [ 'id' => $row['id'] ] );
|
||||
}
|
||||
|
||||
$created = false;
|
||||
|
||||
/* zdjęcia */
|
||||
$results = $mdb -> select( 'pp_articles_images', '*', [ 'article_id' => null ] );
|
||||
if ( is_array( $results ) ) foreach ( $results as $row )
|
||||
{
|
||||
$dir = '/upload/article_images/article_' . $article_id;
|
||||
|
||||
$new_file_name = str_replace( '/upload/article_images/tmp', $dir, $row['src'] );
|
||||
|
||||
if ( file_exists( '../' . $new_file_name ) )
|
||||
{
|
||||
$ext = strrpos( $new_file_name, '.' );
|
||||
$fileName_a = substr( $new_file_name, 0, $ext );
|
||||
$fileName_b = substr( $new_file_name, $ext );
|
||||
|
||||
$count = 1;
|
||||
|
||||
while ( file_exists( '../' . $fileName_a . '_' . $count . $fileName_b ) )
|
||||
$count++;
|
||||
|
||||
$new_file_name = $fileName_a . '_' . $count . $fileName_b;
|
||||
}
|
||||
|
||||
if ( file_exists( '..' . $row['src'] ) )
|
||||
{
|
||||
if ( !is_dir( '../' . $dir ) and $created !== true )
|
||||
{
|
||||
if ( mkdir( '../' . $dir, 0755, true ) )
|
||||
$created = true;
|
||||
}
|
||||
rename( '..' . $row['src'], '..' . $new_file_name );
|
||||
}
|
||||
|
||||
$mdb -> update( 'pp_articles_images', [ 'src' => $new_file_name, 'article_id' => (int)$article_id ], [ 'id' => $row['id'] ] );
|
||||
}
|
||||
|
||||
$results = $mdb -> select( 'pp_articles_images', '*', [ 'AND' => [ 'article_id' => (int)$article_id, 'to_delete' => 1 ] ] );
|
||||
if ( is_array( $results ) ) foreach ( $results as $row )
|
||||
{
|
||||
if ( file_exists( '../' . $row['src'] ) )
|
||||
unlink( '../' . $row['src'] );
|
||||
}
|
||||
|
||||
$mdb -> delete( 'pp_articles_images', [ 'AND' => [ 'article_id' => (int)$article_id, 'to_delete' => 1 ] ] );
|
||||
|
||||
$results = $mdb -> select( 'pp_articles_files', '*', [ 'AND' => [ 'article_id' => (int)$article_id, 'to_delete' => 1 ] ] );
|
||||
if ( is_array( $results ) ) foreach ( $results as $row )
|
||||
{
|
||||
if ( file_exists( '../' . $row['src'] ) )
|
||||
unlink( '../' . $row['src'] );
|
||||
}
|
||||
|
||||
$mdb -> delete( 'pp_articles_files', [ 'AND' => [ 'article_id' => (int)$article_id, 'to_delete' => 1 ] ] );
|
||||
|
||||
/* tagi */
|
||||
$mdb -> delete( 'pp_articles_tags', [ 'article_id' => (int)$article_id ] );
|
||||
|
||||
$tags = explode( ',', $tags );
|
||||
if ( is_array( $tags ) ) foreach ( $tags as $tag )
|
||||
{
|
||||
if ( trim( $tag ) != '' )
|
||||
{
|
||||
$tag_id = $mdb -> get( 'pp_tags', 'id', [ 'name' => $tag ] );
|
||||
if ( !$tag_id )
|
||||
{
|
||||
$mdb -> insert( 'pp_tags', [ 'name' => $tag ] );
|
||||
$tag_id = $mdb -> id();
|
||||
}
|
||||
|
||||
$mdb -> insert( 'pp_articles_tags', [ 'article_id' => (int)$article_id, 'tag_id' => (int)$tag_id ] );
|
||||
}
|
||||
}
|
||||
|
||||
\S::htacces();
|
||||
\S::delete_cache();
|
||||
return $article_id;
|
||||
}
|
||||
global $user;
|
||||
return self::repo()->articleSave(
|
||||
$article_id, $title, $main_image, $entry, $text, $table_of_contents, $status, $show_title, $show_table_of_contents, $show_date_add, $date_add, $show_date_modify, $date_modify, $seo_link, $meta_title, $meta_description,
|
||||
$meta_keywords, $layout_id, $pages, $noindex, $repeat_entry, $copy_from, $social_icons, $event_date, $tags, $block_direct_access, $priority,
|
||||
$password, $pixieset, $id_author, $params, (int)$user['id']
|
||||
);
|
||||
}
|
||||
|
||||
public static function delete_nonassigned_files()
|
||||
{
|
||||
global $mdb;
|
||||
|
||||
$results = $mdb -> select( 'pp_articles_files', '*', [ 'article_id' => null ] );
|
||||
if ( is_array( $results ) ) foreach ( $results as $row )
|
||||
{
|
||||
if ( file_exists( '../' . $row['src'] ) )
|
||||
unlink( '../' . $row['src'] );
|
||||
}
|
||||
|
||||
$mdb -> delete( 'pp_articles_files', [ 'article_id' => null ] );
|
||||
self::repo()->deleteNonassignedFiles();
|
||||
}
|
||||
|
||||
public static function delete_nonassigned_images()
|
||||
{
|
||||
global $mdb;
|
||||
|
||||
$results = $mdb -> select( 'pp_articles_images', '*', [ 'article_id' => null ] );
|
||||
if ( is_array( $results ) ) foreach ( $results as $row )
|
||||
{
|
||||
if ( file_exists( '../' . $row['src'] ) )
|
||||
unlink( '../' . $row['src'] );
|
||||
}
|
||||
|
||||
$mdb -> delete( 'pp_articles_images', [ 'article_id' => null ] );
|
||||
self::repo()->deleteNonassignedImages();
|
||||
}
|
||||
}
|
||||
?>
|
||||
?>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<?
|
||||
<?php
|
||||
namespace admin\factory;
|
||||
class Authors
|
||||
{
|
||||
@@ -6,112 +6,31 @@ class Authors
|
||||
static public function get_simple_list()
|
||||
{
|
||||
global $mdb;
|
||||
return $mdb -> select( 'pp_authors', '*', [ 'ORDER' => [ 'author' => 'ASC' ] ] );
|
||||
$repo = new \Domain\Authors\AuthorsRepository($mdb);
|
||||
return $repo->simpleList();
|
||||
}
|
||||
|
||||
// usunięcie autora
|
||||
static public function delete_author( $id_author )
|
||||
{
|
||||
global $mdb;
|
||||
|
||||
$result = $mdb -> delete( 'pp_authors', [ 'id' => (int)$id_author ] );
|
||||
\S::delete_cache();
|
||||
|
||||
return $result;
|
||||
$repo = new \Domain\Authors\AuthorsRepository($mdb);
|
||||
return $repo->authorDelete($id_author);
|
||||
}
|
||||
|
||||
// zapis autora
|
||||
static public function save_author( $id_author, $author, $image, $description )
|
||||
{
|
||||
global $mdb;
|
||||
|
||||
if ( !$id_author )
|
||||
{
|
||||
$mdb -> insert( 'pp_authors', [
|
||||
'author' => $author,
|
||||
'image' => $image
|
||||
] );
|
||||
|
||||
$id = $mdb -> id();
|
||||
|
||||
if ( $id )
|
||||
{
|
||||
$i = 0;
|
||||
|
||||
$results = $mdb -> select( 'pp_langs', [ 'id' ], [ 'status' => 1, 'ORDER' => [ 'o' => 'ASC' ] ] );
|
||||
if ( is_array( $results ) and count( $results ) > 1 ) foreach ( $results as $row )
|
||||
{
|
||||
$mdb -> insert( 'pp_authors_langs', [
|
||||
'id_author' => (int)$id,
|
||||
'id_lang' => $row['id'],
|
||||
'description' => $description[ $i ]
|
||||
] );
|
||||
$i++;
|
||||
}
|
||||
else if ( is_array( $results ) and count( $results ) == 1 ) foreach ( $results as $row )
|
||||
{
|
||||
$mdb -> insert( 'pp_authors_langs', [
|
||||
'id_author' => (int)$id,
|
||||
'id_lang' => $row['id'],
|
||||
'description' => $description
|
||||
] );
|
||||
}
|
||||
|
||||
\S::delete_cache();
|
||||
|
||||
return $id;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$mdb -> update( 'pp_authors', [
|
||||
'author' => $author,
|
||||
'image' => $image
|
||||
], [
|
||||
'id' => (int)$id_author
|
||||
] );
|
||||
|
||||
$mdb -> delete( 'pp_authors_langs', [ 'id_author' => (int)$id_author ] );
|
||||
|
||||
$i = 0;
|
||||
|
||||
$results = $mdb -> select( 'pp_langs', [ 'id' ], [ 'status' => 1, 'ORDER' => [ 'o' => 'ASC' ] ] );
|
||||
if ( is_array( $results ) and count( $results ) > 1 ) foreach ( $results as $row )
|
||||
{
|
||||
$mdb -> insert( 'pp_authors_langs', [
|
||||
'id_author' => (int)$id_author,
|
||||
'id_lang' => $row['id'],
|
||||
'description' => $description[ $i ]
|
||||
] );
|
||||
$i++;
|
||||
}
|
||||
else if ( is_array( $results ) and count( $results ) == 1 ) foreach ( $results as $row )
|
||||
{
|
||||
$mdb -> insert( 'pp_authors_langs', [
|
||||
'id_author' => (int)$id_author,
|
||||
'id_lang' => $row['id'],
|
||||
'description' => $description
|
||||
] );
|
||||
}
|
||||
|
||||
\S::delete_cache();
|
||||
|
||||
return $id_author;
|
||||
}
|
||||
return false;
|
||||
$repo = new \Domain\Authors\AuthorsRepository($mdb);
|
||||
return $repo->authorSave($id_author, $author, $image, $description);
|
||||
}
|
||||
|
||||
// szczególy autora
|
||||
static public function get_single_author( $id_author )
|
||||
{
|
||||
global $mdb;
|
||||
|
||||
$author = $mdb -> get( 'pp_authors', '*', [ 'id' => (int)$id_author ] );
|
||||
|
||||
$results = $mdb -> select( 'pp_authors_langs', '*', [ 'id_author' => (int)$id_author ] );
|
||||
if ( is_array( $results ) ) foreach ( $results as $row )
|
||||
$author['languages'][$row['id_lang']] = $row;
|
||||
|
||||
return $author;
|
||||
$repo = new \Domain\Authors\AuthorsRepository($mdb);
|
||||
return $repo->authorDetails($id_author);
|
||||
}
|
||||
}
|
||||
@@ -7,123 +7,21 @@ class Banners
|
||||
public static function banner_delete( $banner_id )
|
||||
{
|
||||
global $mdb;
|
||||
|
||||
$result = $mdb -> delete( 'pp_banners', [ 'id' => (int) $banner_id ] );
|
||||
\S::delete_cache();
|
||||
|
||||
return $result;
|
||||
$repo = new \Domain\Banners\BannersRepository($mdb);
|
||||
return $repo->bannerDelete($banner_id);
|
||||
}
|
||||
|
||||
public static function banner_save( $banner_id, $name, $status, $date_start, $date_end, $home_page, $src, $url, $html, $text )
|
||||
{
|
||||
global $mdb;
|
||||
|
||||
if ( !$banner_id )
|
||||
{
|
||||
$mdb -> insert( 'pp_banners', [
|
||||
'name' => $name,
|
||||
'status' => $status == 'on' ? 1 : 0,
|
||||
'date_start' => $date_start != '' ? $date_start : null,
|
||||
'date_end' => $date_end != '' ? $date_end : null,
|
||||
'home_page' => $home_page == 'on' ? 1 : 0
|
||||
] );
|
||||
|
||||
$id = $mdb -> id();
|
||||
|
||||
if ( $id )
|
||||
{
|
||||
$i = 0;
|
||||
|
||||
$results = $mdb -> select( 'pp_langs', [ 'id' ], [ 'status' => 1, 'ORDER' => [ 'o' => 'ASC' ] ] );
|
||||
if ( is_array( $results ) and count( $results ) > 1 ) foreach ( $results as $row )
|
||||
{
|
||||
$mdb -> insert( 'pp_banners_langs', [
|
||||
'id_banner' => (int)$id,
|
||||
'id_lang' => $row['id'],
|
||||
'src' => $src[ $i ],
|
||||
'url' => $url[ $i ],
|
||||
'html' => $html[ $i ],
|
||||
'text' => $text[ $i ]
|
||||
] );
|
||||
$i++;
|
||||
}
|
||||
else if ( is_array( $results ) and count( $results ) == 1 ) foreach ( $results as $row )
|
||||
{
|
||||
$mdb -> insert( 'pp_banners_langs', [
|
||||
'id_banner' => (int)$id,
|
||||
'id_lang' => $row['id'],
|
||||
'src' => $src,
|
||||
'url' => $url,
|
||||
'html' => $html,
|
||||
'text' => $text
|
||||
] );
|
||||
}
|
||||
|
||||
\S::delete_cache();
|
||||
|
||||
return $id;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$mdb -> update( 'pp_banners',
|
||||
[
|
||||
'name' => $name,
|
||||
'status' => $status == 'on' ? 1 : 0,
|
||||
'date_start' => $date_start != '' ? $date_start : null,
|
||||
'date_end' => $date_end != '' ? $date_end : null,
|
||||
'home_page' => $home_page == 'on' ? 1 : 0
|
||||
], [
|
||||
'id' => (int) $banner_id
|
||||
] );
|
||||
|
||||
$mdb -> delete( 'pp_banners_langs', [ 'id_banner' => (int)$banner_id ] );
|
||||
|
||||
$i = 0;
|
||||
|
||||
$results = $mdb -> select( 'pp_langs', [ 'id' ], [ 'status' => 1, 'ORDER' => [ 'o' => 'ASC' ] ] );
|
||||
if ( is_array( $results ) and count( $results ) > 1 ) foreach ( $results as $row )
|
||||
{
|
||||
$mdb -> insert( 'pp_banners_langs', [
|
||||
'id_banner' => (int)$banner_id,
|
||||
'id_lang' => $row['id'],
|
||||
'src' => $src[ $i ],
|
||||
'url' => $url[ $i ],
|
||||
'html' => $html[ $i ],
|
||||
'text' => $text[ $i ]
|
||||
] );
|
||||
$i++;
|
||||
}
|
||||
else if ( is_array( $results ) and count( $results ) == 1 ) foreach ( $results as $row )
|
||||
{
|
||||
$mdb -> insert( 'pp_banners_langs', [
|
||||
'id_banner' => (int)$banner_id,
|
||||
'id_lang' => $row['id'],
|
||||
'src' => $src,
|
||||
'url' => $url,
|
||||
'html' => $html,
|
||||
'text' => $text
|
||||
] );
|
||||
}
|
||||
|
||||
\S::delete_cache();
|
||||
return $banner_id;
|
||||
}
|
||||
return false;
|
||||
$repo = new \Domain\Banners\BannersRepository($mdb);
|
||||
return $repo->bannerSave($banner_id, $name, $status, $date_start, $date_end, $home_page, $src, $url, $html, $text);
|
||||
}
|
||||
|
||||
public static function banner_details( $id_banner )
|
||||
{
|
||||
global $mdb;
|
||||
|
||||
$banner = $mdb -> get( 'pp_banners', '*', [ 'id' => (int)$id_banner ] );
|
||||
|
||||
$results = $mdb -> select( 'pp_banners_langs', '*', [ 'id_banner' => (int)$id_banner ] );
|
||||
if ( is_array( $results ) ) foreach ( $results as $row )
|
||||
$banner['languages'][$row['id_lang']] = $row;
|
||||
|
||||
return $banner;
|
||||
$repo = new \Domain\Banners\BannersRepository($mdb);
|
||||
return $repo->bannerDetails($id_banner);
|
||||
}
|
||||
|
||||
}
|
||||
?>
|
||||
}
|
||||
@@ -1,181 +1,64 @@
|
||||
<?
|
||||
<?php
|
||||
namespace admin\factory;
|
||||
|
||||
/**
|
||||
* @deprecated Wrapper — używaj \Domain\Languages\LanguagesRepository przez DI.
|
||||
*/
|
||||
class Languages
|
||||
{
|
||||
public static function available_domains()
|
||||
private static function repo(): \Domain\Languages\LanguagesRepository
|
||||
{
|
||||
global $mdb;
|
||||
return $mdb -> query( 'SELECT domain FROM pp_langs WHERE status = 1 AND domain IS NOT NULL GROUP BY domain' ) -> fetchAll( \PDO::FETCH_ASSOC );
|
||||
return new \Domain\Languages\LanguagesRepository( $mdb );
|
||||
}
|
||||
|
||||
public static function default_domain()
|
||||
|
||||
public static function available_domains(): array
|
||||
{
|
||||
global $mdb;
|
||||
$results = $mdb -> query( 'SELECT domain FROM pp_langs WHERE status = 1 AND domain IS NOT NULL AND main_domain = 1' ) -> fetchAll();
|
||||
return $default_domain = $results[0][0];
|
||||
return self::repo()->availableDomains();
|
||||
}
|
||||
|
||||
public static function translation_delete( $translation_id )
|
||||
|
||||
public static function default_domain(): ?string
|
||||
{
|
||||
global $mdb;
|
||||
return $mdb -> delete( 'pp_langs_translations', [ 'id' => $translation_id ] );
|
||||
return self::repo()->defaultDomain();
|
||||
}
|
||||
|
||||
|
||||
public static function translation_delete( $translation_id ): bool
|
||||
{
|
||||
return self::repo()->translationDelete( (int)$translation_id );
|
||||
}
|
||||
|
||||
public static function translation_save( $translation_id, $text, $languages )
|
||||
{
|
||||
global $mdb;
|
||||
|
||||
if ( $translation_id )
|
||||
{
|
||||
$mdb -> update( 'pp_langs_translations', [ 'text' => $text ], [ 'id' => $translation_id ] );
|
||||
if ( is_array( $languages ) and !empty( $languages ) ): foreach ( $languages as $key => $val ):
|
||||
$mdb -> update( 'pp_langs_translations', [ $key => $val ], [ 'id' => $translation_id ] );
|
||||
endforeach; endif;
|
||||
\S::htacces();
|
||||
\S::delete_cache();
|
||||
return $translation_id;
|
||||
}
|
||||
else
|
||||
{
|
||||
$mdb -> insert( 'pp_langs_translations', [ 'text' => $text ] );
|
||||
if ( $translation_id = $mdb -> id() )
|
||||
{
|
||||
if ( is_array( $languages ) and !empty( $languages ) ): foreach ( $languages as $key => $val ):
|
||||
$mdb -> update( 'pp_langs_translations', [ $key => $val ], [ 'id' => $translation_id ] );
|
||||
endforeach; endif;
|
||||
}
|
||||
\S::htacces();
|
||||
\S::delete_cache();
|
||||
return $translation_id;
|
||||
}
|
||||
}
|
||||
|
||||
public static function translation_details( $translation_id )
|
||||
{
|
||||
global $mdb;
|
||||
return $mdb -> get( 'pp_langs_translations', '*', [ 'id' => $translation_id ] );
|
||||
}
|
||||
|
||||
public static function language_delete( $language_id )
|
||||
{
|
||||
global $mdb;
|
||||
|
||||
if ( $mdb -> count( 'pp_langs' ) > 1 )
|
||||
{
|
||||
if ( $mdb -> query( 'ALTER TABLE pp_langs_translations DROP ' . $language_id )
|
||||
and
|
||||
$mdb -> delete( 'pp_langs', [ 'id' => $language_id ] )
|
||||
)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
return self::repo()->translationSave( $translation_id, (string)$text, (array)$languages );
|
||||
}
|
||||
|
||||
public static function max_order()
|
||||
public static function translation_details( $translation_id ): ?array
|
||||
{
|
||||
global $mdb;
|
||||
return $mdb -> max( 'pp_langs', 'o' );
|
||||
return self::repo()->translationDetails( (int)$translation_id );
|
||||
}
|
||||
|
||||
public static function language_save( $language_id, $name, $status, $start, $o, $domain, $main_domain )
|
||||
public static function language_delete( $language_id ): bool
|
||||
{
|
||||
global $mdb;
|
||||
|
||||
if ( $start == 'on' and $status == 'on' and !\S::get_domain( $domain ) )
|
||||
$mdb -> update( 'pp_langs', [
|
||||
'start' => 0
|
||||
], [
|
||||
'id[!]' => $language_id
|
||||
] );
|
||||
|
||||
if ( $start == 'on' and $status == 'on' and \S::get_domain( $domain ) )
|
||||
$mdb -> update( 'pp_langs', [
|
||||
'start' => 0
|
||||
], [
|
||||
'AND' => [ 'id[!]' => $language_id, 'domain' => \S::get_domain( $domain ) ]
|
||||
] );
|
||||
|
||||
if ( $main_domain == 'on' and $domain and $status == 'on' )
|
||||
$mdb -> update( 'pp_langs', [
|
||||
'main_domain' => 0
|
||||
], [
|
||||
' id[!]' => $language_id
|
||||
] );
|
||||
|
||||
if ( $mdb -> count( 'pp_langs', [ 'id' => $language_id ] ) )
|
||||
{
|
||||
$mdb -> update( 'pp_langs', [
|
||||
'status' => $status == 'on' ? 1 : 0,
|
||||
'start' => $start == 'on' ? 1 : 0,
|
||||
'name' => $name,
|
||||
'o' => $o,
|
||||
'domain' => \S::get_domain( $domain ) ? \S::get_domain( $domain ) : null,
|
||||
'main_domain' => $main_domain == 'on' and \S::get_domain( $domain ) ? 1 : 0,
|
||||
], [
|
||||
'id' => $language_id
|
||||
] );
|
||||
}
|
||||
else
|
||||
{
|
||||
if ( $mdb -> query( 'ALTER TABLE pp_langs_translations ADD ' . strtolower( $language_id ) . ' TEXT NULL DEFAULT NULL' ) )
|
||||
{
|
||||
$mdb -> insert( 'pp_langs', [
|
||||
'id' => strtolower( $language_id ),
|
||||
'name' => $name,
|
||||
'status' => $status == 'on' ? 1 : 0,
|
||||
'start' => $start == 'on' ? 1 : 0,
|
||||
'o' => $o,
|
||||
'domain' => \S::get_domain( $domain ) ? \S::get_domain( $domain ) : null,
|
||||
'main_domain' => $main_domain == 'on' && \S::get_domain( $domain ) ? 1 : 0,
|
||||
] );
|
||||
}
|
||||
}
|
||||
|
||||
if ( !$mdb -> count( 'pp_langs', [ 'AND' => [ 'status' => 1, 'domain[!]' => null ] ] ) )
|
||||
{
|
||||
if ( !$mdb -> count( 'pp_langs', [ 'AND' => [ 'status' => 1, 'start' => 1, 'domain' => null ] ] ) )
|
||||
{
|
||||
if ( $id_tmp = $mdb -> get( 'pp_langs', 'id', [ 'status' => 1, 'ORDER' => [ 'o' => 'ASC' ] ] ) )
|
||||
$mdb -> update( 'pp_langs', [ 'start' => 1 ], [ 'id' => $id_tmp ] );
|
||||
}
|
||||
}
|
||||
|
||||
$domains = $mdb -> select( 'pp_langs', 'domain', [ 'domain[!]' => null, 'GROUP' => 'domain'] );
|
||||
if ( is_array( $domains ) and !empty( $domains ) )
|
||||
{
|
||||
$mdb -> update( 'pp_langs', [ 'start' => 0 ], [ 'domain' => null ] );
|
||||
foreach ( $domains as $domain )
|
||||
{
|
||||
if ( !$mdb -> count( 'pp_langs', [ 'AND' => [ 'status' => 1, 'start' => 1, 'domain' => $domain ] ] ) )
|
||||
{
|
||||
if ( $id_tmp = $mdb -> get( 'pp_langs', 'id', [ 'AND' => [ 'status' => 1, 'domain' => $domain ], 'ORDER' => [ 'o' => 'ASC' ] ] ) )
|
||||
$mdb -> update( 'pp_langs', [ 'start' => 1 ], [ 'id' => $id_tmp ] );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( !$mdb -> count( 'pp_langs', [ 'AND' => [ 'status' => 1, 'main_domain' => 1 ] ] ) )
|
||||
{
|
||||
if ( $id_tmp = $mdb -> get( 'pp_langs', 'id', [ 'AND' => [ 'status' => 1, 'domain[!]' => null ], 'ORDER' => [ 'o' => 'ASC' ] ] ) )
|
||||
$mdb -> update( 'pp_langs', [ 'main_domain' => 1 ], [ 'id' => $id_tmp ] );
|
||||
}
|
||||
|
||||
\S::htacces();
|
||||
\S::delete_cache();
|
||||
return $language_id;
|
||||
return self::repo()->languageDelete( (string)$language_id );
|
||||
}
|
||||
|
||||
public static function language_details( $language_id )
|
||||
public static function max_order(): int
|
||||
{
|
||||
global $mdb;
|
||||
return $mdb -> get( 'pp_langs', '*', [ 'id' => $language_id ] );
|
||||
return self::repo()->maxOrder();
|
||||
}
|
||||
|
||||
public static function languages_list()
|
||||
public static function language_save( $language_id, $name, $status, $start, $o, $domain, $main_domain ): string
|
||||
{
|
||||
global $mdb;
|
||||
return $mdb -> select( 'pp_langs', '*', [ 'ORDER' => [ 'o' => 'ASC' ] ] );
|
||||
return self::repo()->languageSave( (string)$language_id, (string)$name, $status, $start, $o, $domain, $main_domain );
|
||||
}
|
||||
|
||||
public static function language_details( $language_id ): ?array
|
||||
{
|
||||
return self::repo()->languageDetails( (string)$language_id );
|
||||
}
|
||||
|
||||
public static function languages_list(): array
|
||||
{
|
||||
return self::repo()->languagesList();
|
||||
}
|
||||
}
|
||||
?>
|
||||
@@ -3,139 +3,36 @@ namespace admin\factory;
|
||||
|
||||
class Layouts
|
||||
{
|
||||
public static function layout_delete( $layout_id )
|
||||
private static function repo(): \Domain\Layouts\LayoutsRepository
|
||||
{
|
||||
global $mdb;
|
||||
if ( $mdb -> count( 'pp_layouts' ) > 1 )
|
||||
return $mdb -> delete( 'pp_layouts', [ 'id' => (int)$layout_id ] );
|
||||
return false;
|
||||
return new \Domain\Layouts\LayoutsRepository( $mdb );
|
||||
}
|
||||
|
||||
public static function layout_delete( $layout_id )
|
||||
{
|
||||
return self::repo()->layoutDelete( $layout_id );
|
||||
}
|
||||
|
||||
public static function layout_details( $layout_id )
|
||||
{
|
||||
global $mdb;
|
||||
$layout = $mdb -> get( 'pp_layouts', '*', [ 'id' => (int)$layout_id ] );
|
||||
|
||||
$layout['pages'] = $mdb -> select( 'pp_layouts_pages', 'page_id', [ 'layout_id' => (int)$layout_id ] );
|
||||
|
||||
return $layout;
|
||||
|
||||
return self::repo()->layoutDetails( $layout_id );
|
||||
}
|
||||
|
||||
public static function layout_save( $layout_id, $name, $status, $pages, $html, $css, $js, $m_html, $m_css, $m_js )
|
||||
{
|
||||
global $mdb;
|
||||
if ( !$layout_id )
|
||||
{
|
||||
if ( $status == 'on' )
|
||||
$mdb -> update( 'pp_layouts', [ 'status' => 0 ] );
|
||||
|
||||
$mdb -> insert( 'pp_layouts', [
|
||||
'name' => $name,
|
||||
'html' => $html,
|
||||
'css' => $css,
|
||||
'js' => $js,
|
||||
'm_html' => $m_html,
|
||||
'm_css' => $m_css,
|
||||
'm_js' => $m_js,
|
||||
'status' => $status == 'on' ? 1 : 0
|
||||
] );
|
||||
|
||||
$id = $mdb -> id();
|
||||
|
||||
if ( $id )
|
||||
{
|
||||
if ( is_array( $pages ) ) foreach ( $pages as $page )
|
||||
{
|
||||
$mdb -> delete( 'pp_layouts_pages', [ 'page_id' => (int)$page ] );
|
||||
|
||||
$mdb -> insert( 'pp_layouts_pages', [
|
||||
'layout_id' => (int)$id,
|
||||
'page_id' => (int)$page
|
||||
] );
|
||||
}
|
||||
else if ( $pages )
|
||||
{
|
||||
$mdb -> delete( 'pp_layouts_pages', [ 'page_id' => (int)$pages ] );
|
||||
|
||||
$mdb -> insert( 'pp_layouts_pages', [
|
||||
'layout_id' => (int)$id,
|
||||
'page_id' => (int)$pages
|
||||
] );
|
||||
}
|
||||
|
||||
\S::delete_cache();
|
||||
|
||||
return $id;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if ( $status == 'on' )
|
||||
$mdb -> update( 'pp_layouts', [ 'status' => 0 ] );
|
||||
|
||||
$mdb -> update( 'pp_layouts', [
|
||||
'name' => $name,
|
||||
'html' => $html,
|
||||
'css' => $css,
|
||||
'js' => $js,
|
||||
'm_html' => $m_html,
|
||||
'm_css' => $m_css,
|
||||
'm_js' => $m_js,
|
||||
'status' => $status == 'on' ? 1 : 0
|
||||
], [
|
||||
'id' => $layout_id
|
||||
] );
|
||||
|
||||
$mdb -> delete( 'pp_layouts_pages', [ 'layout_id' => (int)$layout_id ] );
|
||||
|
||||
if ( is_array( $pages ) ) foreach ( $pages as $page )
|
||||
{
|
||||
$mdb -> delete( 'pp_layouts_pages', [ 'page_id' => (int)$page ] );
|
||||
|
||||
$mdb -> insert( 'pp_layouts_pages', [
|
||||
'layout_id' => (int)$layout_id,
|
||||
'page_id' => (int)$page
|
||||
] );
|
||||
}
|
||||
else if ( $pages )
|
||||
{
|
||||
$mdb -> delete( 'pp_layouts_pages', [ 'page_id' => (int)$pages ] );
|
||||
|
||||
$mdb -> insert( 'pp_layouts_pages', [
|
||||
'layout_id' => (int)$layout_id,
|
||||
'page_id' => (int)$pages
|
||||
] );
|
||||
}
|
||||
|
||||
\S::delete_cache();
|
||||
|
||||
return $layout_id;
|
||||
}
|
||||
return false;
|
||||
|
||||
{
|
||||
return self::repo()->layoutSave( $layout_id, $name, $status, $pages, $html, $css, $js, $m_html, $m_css, $m_js );
|
||||
}
|
||||
|
||||
public static function menus_list()
|
||||
{
|
||||
global $mdb;
|
||||
$results = $mdb -> select( 'pp_menus', 'id', [ 'ORDER' => [ 'name' => 'ASC' ] ] );
|
||||
if ( is_array( $results ) ) foreach ( $results as $row )
|
||||
{
|
||||
$menu = \admin\factory\Pages::menu_details( $row );
|
||||
$menu['pages'] = \admin\factory\Pages::menu_pages( $row );
|
||||
|
||||
$menus[] = $menu;
|
||||
}
|
||||
return $menus;
|
||||
|
||||
return self::repo()->menusList();
|
||||
}
|
||||
|
||||
public static function layouts_list()
|
||||
{
|
||||
global $mdb;
|
||||
return $mdb -> select( 'pp_layouts', '*', [ 'ORDER' => [ 'name' => 'ASC' ] ] );
|
||||
}
|
||||
return self::repo()->layoutsList();
|
||||
}
|
||||
|
||||
}
|
||||
?>
|
||||
?>
|
||||
|
||||
@@ -6,100 +6,49 @@ class Newsletter
|
||||
public static function emails_import( $emails )
|
||||
{
|
||||
global $mdb;
|
||||
|
||||
$emails = explode( PHP_EOL, $emails );
|
||||
if ( is_array( $emails ) ) foreach ( $emails as $email )
|
||||
{
|
||||
if ( trim( $email ) and !$mdb -> count( 'pp_newsletter', [ 'email' => trim( $email ) ] ) )
|
||||
$mdb -> insert( 'pp_newsletter', [
|
||||
'email' => trim( $email ),
|
||||
'hash' => md5( $email . time() ),
|
||||
'status' => 1
|
||||
] );
|
||||
}
|
||||
return true;
|
||||
$repo = new \Domain\Newsletter\NewsletterRepository($mdb);
|
||||
return $repo->emailsImport($emails);
|
||||
}
|
||||
|
||||
|
||||
public static function is_admin_template( $template_id )
|
||||
{
|
||||
global $mdb;
|
||||
return $mdb -> get( 'pp_newsletter_templates', 'is_admin', [ 'id' => (int)$template_id ] );
|
||||
$repo = new \Domain\Newsletter\NewsletterRepository($mdb);
|
||||
return $repo->isAdminTemplate($template_id);
|
||||
}
|
||||
|
||||
|
||||
public static function newsletter_template_delete( $template_id )
|
||||
{
|
||||
global $mdb;
|
||||
return $mdb -> delete( 'pp_newsletter_templates', [ 'id' => (int)$template_id ] );
|
||||
$repo = new \Domain\Newsletter\NewsletterRepository($mdb);
|
||||
return $repo->templateDelete($template_id);
|
||||
}
|
||||
|
||||
|
||||
public static function send( $dates, $template, $only_once )
|
||||
{
|
||||
global $mdb;
|
||||
|
||||
$results = $mdb -> select( 'pp_newsletter', 'email', [ 'status' => 1 ] );
|
||||
if ( is_array( $results ) and !empty( $results ) ) foreach ( $results as $row )
|
||||
{
|
||||
if ( $template and $only_once )
|
||||
{
|
||||
if ( !$mdb -> count( 'pp_newsletter_send', [ 'AND' => [ 'id_template' => $template, 'email' => $row ] ] ) )
|
||||
$mdb -> insert( 'pp_newsletter_send', [
|
||||
'email' => $row,
|
||||
'dates' => $dates,
|
||||
'id_template' => $template ? $template : null,
|
||||
'only_once' => ( $only_once == 'on' and $template ) ? 1 : 0
|
||||
] );
|
||||
}
|
||||
else
|
||||
$mdb -> insert( 'pp_newsletter_send', [
|
||||
'email' => $row,
|
||||
'dates' => $dates,
|
||||
'id_template' => $template ? $template : null,
|
||||
'only_once' => ( $only_once == 'on' and $template ) ? 1 : 0
|
||||
] );
|
||||
}
|
||||
return true;
|
||||
$repo = new \Domain\Newsletter\NewsletterRepository($mdb);
|
||||
return $repo->send($dates, $template, $only_once);
|
||||
}
|
||||
|
||||
|
||||
public static function email_template_detalis ($id_template)
|
||||
{
|
||||
global $mdb;
|
||||
|
||||
$result = $mdb -> get ('pp_newsletter_templates', '*', [ 'id' => (int)$id_template ] );
|
||||
return $result;
|
||||
$repo = new \Domain\Newsletter\NewsletterRepository($mdb);
|
||||
return $repo->templateDetails($id_template);
|
||||
}
|
||||
|
||||
|
||||
public static function template_save($id, $name, $text)
|
||||
{
|
||||
global $mdb;
|
||||
if ( !$id )
|
||||
{
|
||||
if ( $mdb -> insert( 'pp_newsletter_templates', [
|
||||
'name' => $name,
|
||||
'text' => $text
|
||||
] ) )
|
||||
{
|
||||
\S::delete_cache();
|
||||
return $mdb -> id();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$mdb -> update( 'pp_newsletter_templates', [
|
||||
'name' => $name,
|
||||
'text' => $text
|
||||
$repo = new \Domain\Newsletter\NewsletterRepository($mdb);
|
||||
return $repo->templateSave($id, $name, $text);
|
||||
}
|
||||
|
||||
], [
|
||||
'id' => (int)$id
|
||||
] );
|
||||
|
||||
\S::delete_cache();
|
||||
return $id;
|
||||
}
|
||||
}
|
||||
|
||||
public static function templates_list()
|
||||
{
|
||||
global $mdb;
|
||||
return $mdb -> select( 'pp_newsletter_templates', '*', [ 'is_admin' => 0, 'ORDER' => [ 'name' => 'ASC' ] ] );
|
||||
$repo = new \Domain\Newsletter\NewsletterRepository($mdb);
|
||||
return $repo->templatesList();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,120 +1,53 @@
|
||||
<?
|
||||
|
||||
<?php
|
||||
namespace admin\factory;
|
||||
|
||||
class Pages
|
||||
{
|
||||
|
||||
public static $_page_types = [ 0 => 'pełne artykuły', 1 => 'wprowadzenia', 2 => 'miniaturki', 3 => 'link', 4 => 'kontakt' ];
|
||||
public static $_sort_types = [
|
||||
0 => 'data dodania - najstarsze na początku',
|
||||
1 => 'data dodania - najnowsze na początku',
|
||||
2 => 'data modyfikacji - rosnąco',
|
||||
3 => 'data mofyfikacji - malejąco',
|
||||
4 => 'ręczne',
|
||||
5 => 'alfabetycznie - A - Z',
|
||||
6 => 'alfabetycznie - Z - A'
|
||||
0 => 'data dodania - najstarsze na początku',
|
||||
1 => 'data dodania - najnowsze na początku',
|
||||
2 => 'data modyfikacji - rosnąco',
|
||||
3 => 'data mofyfikacji - malejąco',
|
||||
4 => 'ręczne',
|
||||
5 => 'alfabetycznie - A - Z',
|
||||
6 => 'alfabetycznie - Z - A'
|
||||
];
|
||||
|
||||
private static function repo(): \Domain\Pages\PagesRepository
|
||||
{
|
||||
global $mdb;
|
||||
return new \Domain\Pages\PagesRepository( $mdb );
|
||||
}
|
||||
|
||||
public static function save_articles_order( $page_id, $articles )
|
||||
{
|
||||
global $mdb;
|
||||
if ( is_array( $articles ) )
|
||||
{
|
||||
$mdb -> update( 'pp_articles_pages', [ 'o' => 0 ],
|
||||
[ 'page_id' => (int) $page_id ] );
|
||||
|
||||
for ( $i = 0; $i < count( $articles ); $i++ )
|
||||
{
|
||||
if ( $articles[$i]['item_id'] )
|
||||
{
|
||||
$x++;
|
||||
$mdb -> update( 'pp_articles_pages', [ 'o' => $x ],
|
||||
[ 'AND' => [ 'page_id' => (int) $page_id, 'article_id' => $articles[$i]['item_id'] ] ] );
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
return self::repo()->saveArticlesOrder( $page_id, $articles );
|
||||
}
|
||||
|
||||
public static function page_articles( $page_id )
|
||||
{
|
||||
global $mdb;
|
||||
$results = $mdb -> query( 'SELECT '
|
||||
. 'article_id, o, status '
|
||||
. 'FROM '
|
||||
. 'pp_articles_pages AS ap '
|
||||
. 'INNER JOIN pp_articles AS a ON a.id = ap.article_id '
|
||||
. 'WHERE '
|
||||
. 'page_id = ' . (int) $page_id . ' AND status != -1 '
|
||||
. 'ORDER BY '
|
||||
. 'o ASC' ) -> fetchAll();
|
||||
if ( is_array( $results ) )
|
||||
foreach ( $results as $row )
|
||||
{
|
||||
$row['title'] = \admin\factory\Articles::article_title( $row['article_id'] );
|
||||
$articles[] = $row;
|
||||
}
|
||||
return $articles;
|
||||
return self::repo()->pageArticles( $page_id );
|
||||
}
|
||||
|
||||
public static function menus_list()
|
||||
{
|
||||
global $mdb;
|
||||
return $mdb -> select( 'pp_menus', '*', [ 'ORDER' => [ 'name' => 'ASC' ] ] );
|
||||
return self::repo()->menusList();
|
||||
}
|
||||
|
||||
public static function save_pages_order( $menu_id, $pages )
|
||||
{
|
||||
global $mdb;
|
||||
if ( is_array( $pages ) )
|
||||
{
|
||||
$mdb -> update( 'pp_pages', [ 'o' => 0 ], [ 'menu_id' => (int) $menu_id ] );
|
||||
|
||||
for ( $i = 0; $i < count( $pages ); $i++ )
|
||||
{
|
||||
if ( $pages[$i]['item_id'] )
|
||||
{
|
||||
$pages[$i]['parent_id'] ? $parent_id = $pages[$i]['parent_id'] : $parent_id = 0;
|
||||
|
||||
if ( $pages[$i]['item_id'] && $pages[$i]['depth'] > 1 )
|
||||
{
|
||||
if ( $pages[$i]['depth'] == 2 )
|
||||
$parent_id = null;
|
||||
|
||||
$x++;
|
||||
|
||||
$mdb -> update( 'pp_pages', [ 'o' => $x, 'parent_id' => $parent_id ],
|
||||
[ 'id' => (int) $pages[$i]['item_id'] ] );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
\S::delete_cache();
|
||||
|
||||
return true;
|
||||
return self::repo()->savePagesOrder( $menu_id, $pages );
|
||||
}
|
||||
|
||||
public static function page_delete( $page_id )
|
||||
{
|
||||
global $mdb;
|
||||
if ( $mdb -> count( 'pp_pages', [ 'parent_id' => (int) $page_id ] ) )
|
||||
return false;
|
||||
|
||||
if ( $mdb -> delete( 'pp_pages', [ 'id' => (int) $page_id ] ) )
|
||||
{
|
||||
\S::delete_cache();
|
||||
\S::htacces();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
return self::repo()->pageDelete( $page_id );
|
||||
}
|
||||
|
||||
public static function max_order()
|
||||
{
|
||||
global $mdb;
|
||||
return $mdb -> max( 'pp_pages', 'o' );
|
||||
return self::repo()->maxOrder();
|
||||
}
|
||||
|
||||
public static function page_save(
|
||||
@@ -122,388 +55,70 @@ class Pages
|
||||
$site_title, $block_direct_access, $cache, $canonical
|
||||
)
|
||||
{
|
||||
global $mdb;
|
||||
|
||||
if ( !$parent_id )
|
||||
$parent_id = null;
|
||||
|
||||
if ( !$page_id )
|
||||
{
|
||||
$order = self::max_order() + 1;
|
||||
|
||||
$mdb -> insert( 'pp_pages', [
|
||||
'menu_id' => (int) $menu_id,
|
||||
'page_type' => $page_type,
|
||||
'sort_type' => $sort_type,
|
||||
'articles_limit' => $articles_limit,
|
||||
'show_title' => $show_title == 'on' ? 1 : 0,
|
||||
'status' => $status == 'on' ? 1 : 0,
|
||||
'o' => (int) $order,
|
||||
'parent_id' => $parent_id,
|
||||
'start' => $start == 'on' ? 1 : 0,
|
||||
'cache' => $cache == 'on' ? 1 : 0
|
||||
] );
|
||||
|
||||
$id = $mdb -> id();
|
||||
|
||||
if ( $id )
|
||||
{
|
||||
if ( $start )
|
||||
$mdb -> update( 'pp_pages', [ 'start' => 0 ], [ 'id[!]' => (int)$id ] );
|
||||
|
||||
if ( $layout_id )
|
||||
$mdb -> insert( 'pp_layouts_pages', [ 'page_id' => (int) $id, 'layout_id' => (int)$layout_id ] );
|
||||
|
||||
$i = 0;
|
||||
|
||||
$results = $mdb -> select( 'pp_langs', [ 'id' ], [ 'status' => 1, 'ORDER' => [ 'o' => 'ASC' ] ] );
|
||||
if ( is_array( $results ) and count( $results ) > 1 ) foreach ( $results as $row )
|
||||
{
|
||||
$mdb -> insert( 'pp_pages_langs', [
|
||||
'page_id' => (int) $id,
|
||||
'lang_id' => $row['id'],
|
||||
'title' => $title[$i] != '' ? $title[$i] : null,
|
||||
'meta_description' => $meta_description[$i] != '' ? $meta_description[$i] : null,
|
||||
'meta_keywords' => $meta_keywords[$i] != '' ? $meta_keywords[$i] : null,
|
||||
'meta_title' => $meta_title[$i] != '' ? $meta_title[$i] : null,
|
||||
'seo_link' => \S::seo( $seo_link[$i] ) != '' ? \S::seo( $seo_link[$i] ) : null,
|
||||
'noindex' => $noindex[$i],
|
||||
'site_title' => $site_title[$i] != '' ? $site_title[$i] : null,
|
||||
'link' => $link[$i] != '' ? $link[$i] : null,
|
||||
'block_direct_access' => $block_direct_access[$i],
|
||||
'canonical' => $canonical[$i] != '' ? $canonical[$i] : null
|
||||
] );
|
||||
$i++;
|
||||
}
|
||||
else if ( is_array( $results ) and count( $results ) == 1 )
|
||||
{
|
||||
foreach ( $results as $row )
|
||||
{
|
||||
$mdb -> insert( 'pp_pages_langs', [
|
||||
'page_id' => (int) $id,
|
||||
'lang_id' => $row['id'],
|
||||
'title' => $title != '' ? $title : null,
|
||||
'meta_description' => $meta_description != '' ? $meta_description : null,
|
||||
'meta_keywords' => $meta_keywords != '' ? $meta_keywords : null,
|
||||
'meta_title' => $meta_title != '' ? $meta_title : null,
|
||||
'seo_link' => \S::seo( $seo_link ) != '' ? \S::seo( $seo_link ) : null,
|
||||
'noindex' => $noindex,
|
||||
'site_title' => $site_title != '' ? $site_title : null,
|
||||
'link' => $link != '' ? $link : null,
|
||||
'block_direct_access' => $block_direct_access,
|
||||
'canonical' => $canonical != '' ? $canonical : null
|
||||
] );
|
||||
}
|
||||
}
|
||||
|
||||
\S::htacces();
|
||||
\S::delete_cache();
|
||||
|
||||
return $id;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$mdb -> update( 'pp_pages',
|
||||
[
|
||||
'menu_id' => (int) $menu_id,
|
||||
'page_type' => $page_type,
|
||||
'sort_type' => $sort_type,
|
||||
'articles_limit' => $articles_limit,
|
||||
'show_title' => $show_title == 'on' ? 1 : 0,
|
||||
'status' => $status == 'on' ? 1 : 0,
|
||||
'parent_id' => $parent_id,
|
||||
'start' => $start == 'on' ? 1 : 0,
|
||||
'cache' => $cache == 'on' ? 1 : 0
|
||||
], [
|
||||
'id' => (int) $page_id
|
||||
] );
|
||||
|
||||
if ( $layout_id )
|
||||
{
|
||||
$mdb -> delete( 'pp_layouts_pages', [ 'page_id' => (int) $page_id ] );
|
||||
$mdb -> insert( 'pp_layouts_pages',
|
||||
[ 'layout_id' => (int) $layout_id, 'page_id' => (int) $page_id ] );
|
||||
}
|
||||
|
||||
if ( $start )
|
||||
$mdb -> update( 'pp_pages', [ 'start' => 0 ],
|
||||
[ 'id[!]' => (int) $page_id ] );
|
||||
|
||||
$i = 0;
|
||||
|
||||
$mdb -> delete( 'pp_pages_langs', [ 'page_id' => (int) $page_id ] );
|
||||
|
||||
$results = $mdb -> select( 'pp_langs', [ 'id' ],
|
||||
[ 'status' => 1, 'ORDER' => [ 'o' => 'ASC' ] ] );
|
||||
if ( is_array( $results ) and count( $results ) > 1 )
|
||||
foreach ( $results as $row )
|
||||
{
|
||||
$mdb -> insert( 'pp_pages_langs',
|
||||
[
|
||||
'page_id' => (int) $page_id,
|
||||
'lang_id' => $row['id'],
|
||||
'title' => $title[$i] != '' ? $title[$i] : null,
|
||||
'meta_description' => $meta_description[$i] != '' ? $meta_description[$i] : null,
|
||||
'meta_keywords' => $meta_keywords[$i] != '' ? $meta_keywords[$i] : null,
|
||||
'meta_title' => $meta_title[$i] != '' ? $meta_title[$i] : null,
|
||||
'seo_link' => \S::seo( $seo_link[$i] ) != '' ? \S::seo( $seo_link[$i] ) : null,
|
||||
'noindex' => $noindex[$i],
|
||||
'site_title' => $site_title[$i] != '' ? $site_title[$i] : null,
|
||||
'link' => $link[$i] != '' ? $link[$i] : null,
|
||||
'block_direct_access' => $block_direct_access[$i],
|
||||
'canonical' => $canonical[$i] != '' ? $canonical[$i] : null
|
||||
] );
|
||||
|
||||
$i++;
|
||||
}
|
||||
else if ( is_array( $results ) and count( $results ) == 1 )
|
||||
foreach ( $results as $row )
|
||||
{
|
||||
$mdb -> insert( 'pp_pages_langs',
|
||||
[
|
||||
'page_id' => (int) $page_id,
|
||||
'lang_id' => $row['id'],
|
||||
'title' => $title != '' ? $title : null,
|
||||
'meta_description' => $meta_description != '' ? $meta_description : null,
|
||||
'meta_keywords' => $meta_keywords != '' ? $meta_keywords : null,
|
||||
'meta_title' => $meta_title != '' ? $meta_title : null,
|
||||
'seo_link' => \S::seo( $seo_link ) != '' ? \S::seo( $seo_link ) : null,
|
||||
'noindex' => $noindex,
|
||||
'site_title' => $site_title != '' ? $site_title : null,
|
||||
'link' => $link != '' ? $link : null,
|
||||
'block_direct_access' => $block_direct_access,
|
||||
'canonical' => $canonical != '' ? $canonical : null
|
||||
] );
|
||||
}
|
||||
|
||||
self::update_supages_menu_id( $page_id, $menu_id );
|
||||
|
||||
\S::htacces();
|
||||
\S::delete_cache();
|
||||
|
||||
return $page_id;
|
||||
}
|
||||
return false;
|
||||
return self::repo()->pageSave(
|
||||
$page_id, $title, $seo_link, $meta_title, $meta_description, $meta_keywords, $menu_id, $parent_id, $page_type, $sort_type, $layout_id, $articles_limit, $show_title, $status, $link, $noindex, $start,
|
||||
$site_title, $block_direct_access, $cache, $canonical
|
||||
);
|
||||
}
|
||||
|
||||
public static function update_supages_menu_id( $parent_id, $menu_id )
|
||||
{
|
||||
global $mdb;
|
||||
|
||||
$mdb -> update( 'pp_pages', [ 'menu_id' => (int) $menu_id ],
|
||||
[ 'parent_id' => $parent_id ] );
|
||||
|
||||
$results = $mdb -> select( 'pp_pages', [ 'id' ], [ 'parent_id' => $parent_id ] );
|
||||
if ( is_array( $results ) )
|
||||
foreach ( $results as $row )
|
||||
self::update_supages_menu_id( $row['id'], $menu_id );
|
||||
self::repo()->updateSubpagesMenuId( (int) $parent_id, (int) $menu_id );
|
||||
}
|
||||
|
||||
public static function generate_seo_link( $title, $page_id, $article_id,
|
||||
$lang, $pid )
|
||||
public static function generate_seo_link( $title, $page_id, $article_id, $lang, $pid )
|
||||
{
|
||||
global $mdb;
|
||||
|
||||
$seo_link = \S::seo( $title );
|
||||
|
||||
|
||||
while ( !$seo_link_check )
|
||||
{
|
||||
if ( $mdb -> count( 'pp_pages_langs',
|
||||
[ 'AND' => [ 'seo_link' => $seo_link, 'page_id[!]' => (int) $page_id ] ] ) )
|
||||
$seo_link = $seo_link . '-' . ( ++$i );
|
||||
else
|
||||
$seo_link_check = true;
|
||||
}
|
||||
|
||||
$seo_link_check = false;
|
||||
|
||||
while ( !$seo_link_check )
|
||||
{
|
||||
if ( $mdb -> count( 'pp_articles_langs',
|
||||
[ 'AND' => [ 'seo_link' => $seo_link, 'article_id[!]' => (int) $article_id ] ] ) )
|
||||
$seo_link = $seo_link . '-' . ( ++$i );
|
||||
else
|
||||
$seo_link_check = true;
|
||||
}
|
||||
return $seo_link;
|
||||
return self::repo()->generateSeoLink( $title, $page_id, $article_id, $lang, $pid );
|
||||
}
|
||||
|
||||
public static function google_url_preview( $page_id, $title, $lang, $pid, $id, $seo_link, $language_link = '' )
|
||||
{
|
||||
global $mdb;
|
||||
|
||||
$prefix = $language_link;
|
||||
$status = true;
|
||||
$id_page = $page_id;
|
||||
|
||||
do
|
||||
{
|
||||
if ( $page_id )
|
||||
{
|
||||
$parent = \admin\factory\Pages::page_details( $page_id );
|
||||
$parent_id = $parent['parent_id'];
|
||||
}
|
||||
else
|
||||
$parent_id = $pid;
|
||||
|
||||
if ( $parent_id )
|
||||
{
|
||||
$results = $mdb -> query( "SELECT title, seo_link, page_id FROM pp_pages_langs AS ppl, pp_langs AS pl WHERE lang_id = pl.id AND page_id = " . (int) $parent_id . " AND ppl.lang_id = '" . $lang . "' " ) -> fetchAll();
|
||||
if ( $results[0]['seo_link'] )
|
||||
$seo = $results[0]['seo_link'] . '/' . $seo;
|
||||
else
|
||||
$seo = 's-' . $results[0]['page_id'] . '-' . \S::seo( $results[0]['title'] ) . '/' . $seo;
|
||||
$page_id = $results[0]['page_id'];
|
||||
}
|
||||
else
|
||||
$status = false;
|
||||
}
|
||||
while ( $status );
|
||||
|
||||
if ( $id )
|
||||
{
|
||||
if ( !$seo_link )
|
||||
$seo = $seo . 's-' . $id . '-' . \S::seo( $title );
|
||||
else
|
||||
$seo = $seo . $seo_link;
|
||||
}
|
||||
else
|
||||
{
|
||||
if ( !$seo_link )
|
||||
$seo = $seo . 's-' . $id_page . '-' . \S::seo( $title );
|
||||
else
|
||||
$seo = $seo . $seo_link;
|
||||
}
|
||||
|
||||
if ( $prefix )
|
||||
$seo = $prefix . $seo;
|
||||
|
||||
return $seo;
|
||||
return self::repo()->googleUrlPreview( $page_id, $title, $lang, $pid, $id, $seo_link, $language_link );
|
||||
}
|
||||
|
||||
public static function menu_delete( $menu_id )
|
||||
{
|
||||
global $mdb;
|
||||
|
||||
if ( $mdb -> count( 'pp_pages', [ 'menu_id' => (int) $menu_id ] ) )
|
||||
return false;
|
||||
|
||||
return $mdb -> delete( 'pp_menus', [ 'id' => (int) $menu_id ] );
|
||||
return self::repo()->menuDelete( $menu_id );
|
||||
}
|
||||
|
||||
public static function menu_details( $menu_id )
|
||||
{
|
||||
global $mdb;
|
||||
return $mdb -> get( 'pp_menus', '*', [ 'id' => (int) $menu_id ] );
|
||||
return self::repo()->menuDetails( $menu_id );
|
||||
}
|
||||
|
||||
public static function menu_save( $menu_id, $name, $status )
|
||||
{
|
||||
global $mdb;
|
||||
|
||||
$status == 'on' ? $status = 1 : $status = 0;
|
||||
|
||||
if ( !$menu_id )
|
||||
{
|
||||
return $mdb -> insert( 'pp_menus',
|
||||
[
|
||||
'name' => $name,
|
||||
'status' => $status
|
||||
] );
|
||||
}
|
||||
else
|
||||
{
|
||||
$mdb -> update( 'pp_menus',
|
||||
[
|
||||
'name' => $name,
|
||||
'status' => $status
|
||||
], [
|
||||
'id' => (int) $menu_id
|
||||
] );
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
return self::repo()->menuSave( $menu_id, $name, $status );
|
||||
}
|
||||
|
||||
public static function menu_lists()
|
||||
{
|
||||
global $mdb;
|
||||
return $mdb -> select( 'pp_menus', '*', [ 'ORDER' => [ 'id' => 'ASC' ] ] );
|
||||
return self::repo()->menuLists();
|
||||
}
|
||||
|
||||
public static function page_details( $page_id )
|
||||
{
|
||||
global $mdb;
|
||||
|
||||
$page = $mdb -> get( 'pp_pages', '*', [ 'id' => (int) $page_id ] );
|
||||
|
||||
$results = $mdb -> select( 'pp_pages_langs', '*',
|
||||
[ 'page_id' => (int) $page_id ] );
|
||||
if ( is_array( $results ) )
|
||||
foreach ( $results as $row )
|
||||
$page['languages'][$row['lang_id']] = $row;
|
||||
|
||||
$page['layout_id'] = $mdb -> get( 'pp_layouts_pages', 'layout_id',
|
||||
[ 'page_id' => (int) $page_id ] );
|
||||
|
||||
return $page;
|
||||
return self::repo()->pageDetails( $page_id );
|
||||
}
|
||||
|
||||
public static function page_url( $page_id )
|
||||
{
|
||||
global $mdb;
|
||||
|
||||
$results = $mdb -> query( "SELECT seo_link, title lang_id FROM pp_pages_langs AS ppl, pp_langs AS pl WHERE lang_id = pl.id AND page_id = " . (int) $page_id . " AND seo_link != '' ORDER BY o ASC LIMIT 1" ) -> fetchAll();
|
||||
|
||||
if ( !$results[0]['seo_link'] )
|
||||
{
|
||||
$title = self::page_title( $article_id );
|
||||
return 's-' . $page_id . '-' . \S::seo( $title );
|
||||
}
|
||||
else
|
||||
return $results[0]['seo_link'];
|
||||
return self::repo()->pageUrl( $page_id );
|
||||
}
|
||||
|
||||
public static function page_title( $page_id )
|
||||
{
|
||||
global $mdb;
|
||||
|
||||
$result = $mdb -> select( 'pp_pages_langs',
|
||||
[ '[><]pp_langs' => [ 'lang_id' => 'id' ] ], 'title',
|
||||
[ 'AND' => [ 'page_id' => (int) $page_id, 'title[!]' => '' ], 'ORDER' => [ 'o' => 'ASC' ], 'LIMIT' => 1 ] );
|
||||
return $result[0];
|
||||
return self::repo()->pageTitle( $page_id );
|
||||
}
|
||||
|
||||
public static function page_languages( $page_id )
|
||||
{
|
||||
global $mdb;
|
||||
return $mdb -> select( 'pp_pages_langs', '*',
|
||||
[ 'AND' => [ 'page_id' => (int) $page_id, 'title[!]' => null ] ] );
|
||||
return self::repo()->pageLanguages( $page_id );
|
||||
}
|
||||
|
||||
public static function menu_pages( $menu_id, $parent_id = null )
|
||||
{
|
||||
global $mdb;
|
||||
|
||||
$results = $mdb -> select( 'pp_pages',
|
||||
[ 'id', 'menu_id', 'status', 'parent_id', 'start' ],
|
||||
[ 'AND' => [ 'menu_id' => $menu_id, 'parent_id' => $parent_id ], 'ORDER' => [ 'o' => 'ASC' ] ] );
|
||||
if ( is_array( $results ) )
|
||||
foreach ( $results as $row )
|
||||
{
|
||||
$row['title'] = self::page_title( $row['id'] );
|
||||
$row['languages'] = self::page_languages( $row['id'] );
|
||||
$row['subpages'] = self::menu_pages( $menu_id, $row['id'] );
|
||||
|
||||
$pages[] = $row;
|
||||
}
|
||||
|
||||
return $pages;
|
||||
return self::repo()->menuPages( $menu_id, $parent_id );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
?>
|
||||
?>
|
||||
|
||||
103
autoload/admin/factory/class.Releases.php
Normal file
103
autoload/admin/factory/class.Releases.php
Normal file
@@ -0,0 +1,103 @@
|
||||
<?php
|
||||
namespace admin\factory;
|
||||
|
||||
class Releases
|
||||
{
|
||||
public static function get_versions(): array
|
||||
{
|
||||
global $mdb;
|
||||
$rows = $mdb->select('pp_update_versions', '*', ['ORDER' => ['version' => 'DESC']]);
|
||||
if (!$rows) return [];
|
||||
foreach ($rows as &$row)
|
||||
$row['zip_exists'] = file_exists('../updates/' . self::zip_dir($row['version']) . '/ver_' . $row['version'] . '.zip');
|
||||
return $rows;
|
||||
}
|
||||
|
||||
public static function promote(string $version): void
|
||||
{
|
||||
global $mdb;
|
||||
$mdb->update('pp_update_versions',
|
||||
['channel' => 'stable', 'promoted_at' => date('Y-m-d H:i:s')],
|
||||
['version' => $version]
|
||||
);
|
||||
}
|
||||
|
||||
public static function demote(string $version): void
|
||||
{
|
||||
global $mdb;
|
||||
$mdb->update('pp_update_versions',
|
||||
['channel' => 'beta', 'promoted_at' => null],
|
||||
['version' => $version]
|
||||
);
|
||||
}
|
||||
|
||||
public static function discover_versions(): int
|
||||
{
|
||||
global $mdb;
|
||||
$known = array_flip($mdb->select('pp_update_versions', 'version', []) ?: []);
|
||||
$zips = glob('../updates/*/ver_*.zip') ?: [];
|
||||
$added = 0;
|
||||
foreach ($zips as $path) {
|
||||
preg_match('/ver_([0-9.]+)\.zip$/', $path, $m);
|
||||
if (!$m) continue;
|
||||
$ver = $m[1];
|
||||
if (isset($known[$ver])) continue;
|
||||
$mdb->insert('pp_update_versions', [
|
||||
'version' => $ver,
|
||||
'channel' => 'beta',
|
||||
'created_at' => date('Y-m-d H:i:s'),
|
||||
]);
|
||||
$known[$ver] = true;
|
||||
$added++;
|
||||
}
|
||||
return $added;
|
||||
}
|
||||
|
||||
public static function get_licenses(): array
|
||||
{
|
||||
global $mdb;
|
||||
return $mdb->select('pp_update_licenses', '*', ['ORDER' => ['domain' => 'ASC']]) ?: [];
|
||||
}
|
||||
|
||||
public static function get_license(int $id): array
|
||||
{
|
||||
global $mdb;
|
||||
return $mdb->get('pp_update_licenses', '*', ['id' => $id]) ?: [];
|
||||
}
|
||||
|
||||
public static function save_license(array $data): void
|
||||
{
|
||||
global $mdb;
|
||||
$row = [
|
||||
'key' => trim($data['key'] ?? ''),
|
||||
'domain' => trim($data['domain'] ?? ''),
|
||||
'valid_to_date' => $data['valid_to_date'] ?: null,
|
||||
'valid_to_version' => $data['valid_to_version'] ?: null,
|
||||
'beta' => (int)(bool)($data['beta'] ?? 0),
|
||||
'note' => trim($data['note'] ?? ''),
|
||||
];
|
||||
if (!empty($data['id']))
|
||||
$mdb->update('pp_update_licenses', $row, ['id' => (int)$data['id']]);
|
||||
else
|
||||
$mdb->insert('pp_update_licenses', $row);
|
||||
}
|
||||
|
||||
public static function delete_license(int $id): void
|
||||
{
|
||||
global $mdb;
|
||||
$mdb->delete('pp_update_licenses', ['id' => $id]);
|
||||
}
|
||||
|
||||
public static function toggle_beta(int $id): void
|
||||
{
|
||||
global $mdb;
|
||||
$license = $mdb->get('pp_update_licenses', ['id', 'beta'], ['id' => $id]);
|
||||
if ($license)
|
||||
$mdb->update('pp_update_licenses', ['beta' => $license['beta'] ? 0 : 1], ['id' => $id]);
|
||||
}
|
||||
|
||||
private static function zip_dir(string $version): string
|
||||
{
|
||||
return substr($version, 0, strlen($version) - (strlen($version) == 5 ? 2 : 1)) . '0';
|
||||
}
|
||||
}
|
||||
@@ -7,115 +7,21 @@ class Scontainers
|
||||
public static function container_delete( $container_id )
|
||||
{
|
||||
global $mdb;
|
||||
return $mdb -> delete( 'pp_scontainers', [ 'id' => (int) $container_id ] );
|
||||
$repo = new \Domain\Scontainers\ScontainersRepository($mdb);
|
||||
return $repo->containerDelete($container_id);
|
||||
}
|
||||
|
||||
public static function container_save( $container_id, $title, $text, $status, $show_title, $src, $html )
|
||||
{
|
||||
global $mdb;
|
||||
|
||||
if ( !$container_id )
|
||||
{
|
||||
$mdb -> insert( 'pp_scontainers',
|
||||
[
|
||||
'status' => $status == 'on' ? 1 : 0,
|
||||
'show_title' => $show_title == 'on' ? 1 : 0,
|
||||
'src' => $src
|
||||
] );
|
||||
|
||||
$id = $mdb -> id();
|
||||
|
||||
if ( $id )
|
||||
{
|
||||
$i = 0;
|
||||
|
||||
$results = $mdb -> select( 'pp_langs', [ 'id' ], [ 'status' => 1, 'ORDER' => [ 'o' => 'ASC' ] ] );
|
||||
if ( is_array( $results ) and count( $results ) > 1 ) foreach ( $results as $row )
|
||||
{
|
||||
$mdb -> insert( 'pp_scontainers_langs',
|
||||
[
|
||||
'container_id' => (int) $id,
|
||||
'lang_id' => $row['id'],
|
||||
'title' => $title[$i],
|
||||
'text' => $text[$i],
|
||||
'html' => $html[$i]
|
||||
] );
|
||||
$i++;
|
||||
}
|
||||
else if ( is_array( $results ) and count( $results ) == 1 ) foreach ( $results as $row )
|
||||
{
|
||||
$mdb -> insert( 'pp_scontainers_langs', [
|
||||
'container_id' => (int) $id,
|
||||
'lang_id' => $row['id'],
|
||||
'title' => $title,
|
||||
'text' => $text,
|
||||
'html' => $html
|
||||
] );
|
||||
}
|
||||
|
||||
\S::delete_cache();
|
||||
|
||||
return $id;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$mdb -> update( 'pp_scontainers',
|
||||
[
|
||||
'status' => $status == 'on' ? 1 : 0,
|
||||
'show_title' => $show_title == 'on' ? 1 : 0,
|
||||
'src' => $src
|
||||
],
|
||||
[
|
||||
'id' => (int) $container_id
|
||||
] );
|
||||
|
||||
$mdb -> delete( 'pp_scontainers_langs',
|
||||
[ 'container_id' => (int) $container_id ] );
|
||||
|
||||
$i = 0;
|
||||
|
||||
$results = $mdb -> select( 'pp_langs', [ 'id' ], [ 'status' => 1, 'ORDER' => [ 'o' => 'ASC' ] ] );
|
||||
if ( is_array( $results ) and count( $results ) > 1 ) foreach ( $results as $row )
|
||||
{
|
||||
$mdb -> insert( 'pp_scontainers_langs',
|
||||
[
|
||||
'container_id' => (int) $container_id,
|
||||
'lang_id' => $row['id'],
|
||||
'title' => $title[$i],
|
||||
'text' => $text[$i],
|
||||
'html' => $html[$i]
|
||||
] );
|
||||
$i++;
|
||||
}
|
||||
else if ( is_array( $results ) and count( $results ) == 1 ) foreach ( $results as $row )
|
||||
{
|
||||
$mdb -> insert( 'pp_scontainers_langs',
|
||||
[
|
||||
'container_id' => (int) $container_id,
|
||||
'lang_id' => $row['id'],
|
||||
'title' => $title,
|
||||
'text' => $text,
|
||||
'html' => $html
|
||||
] );
|
||||
}
|
||||
|
||||
\S::delete_cache();
|
||||
|
||||
return $container_id;
|
||||
}
|
||||
$repo = new \Domain\Scontainers\ScontainersRepository($mdb);
|
||||
return $repo->containerSave($container_id, $title, $text, $status, $show_title, $src, $html);
|
||||
}
|
||||
|
||||
public static function container_details( $container_id )
|
||||
{
|
||||
global $mdb;
|
||||
|
||||
$container = $mdb -> get( 'pp_scontainers', '*', [ 'id' => (int) $container_id ] );
|
||||
|
||||
$results = $mdb -> select( 'pp_scontainers_langs', '*', [ 'container_id' => (int) $container_id ] );
|
||||
if ( is_array( $results ) ) foreach ( $results as $row )
|
||||
$container['languages'][$row['lang_id']] = $row;
|
||||
|
||||
return $container;
|
||||
$repo = new \Domain\Scontainers\ScontainersRepository($mdb);
|
||||
return $repo->containerDetails($container_id);
|
||||
}
|
||||
}
|
||||
@@ -1,147 +1,73 @@
|
||||
<?
|
||||
<?php
|
||||
namespace admin\factory;
|
||||
|
||||
/**
|
||||
* @deprecated Wrapper — używaj \Domain\Settings\SettingsRepository przez DI.
|
||||
*/
|
||||
class Settings
|
||||
{
|
||||
public static function settings_update( $param, $value )
|
||||
private static function repo(): \Domain\Settings\SettingsRepository
|
||||
{
|
||||
global $mdb;
|
||||
return new \Domain\Settings\SettingsRepository( $mdb );
|
||||
}
|
||||
|
||||
if ( $mdb -> count( 'pp_settings', [ 'param' => $param ] ) )
|
||||
return $mdb -> update( 'pp_settings', [ 'value' => $value ], [ 'param' => $param ] );
|
||||
else
|
||||
return $mdb -> insert( 'pp_settings', [ 'param' => $param, 'value' => $value ] );
|
||||
public static function settings_details(): array
|
||||
{
|
||||
return self::repo()->allSettings();
|
||||
}
|
||||
|
||||
public static function settings_update( $param, $value )
|
||||
{
|
||||
return self::repo()->update( (string)$param, $value );
|
||||
}
|
||||
|
||||
public static function settings_save(
|
||||
$firm_name, $firm_adress, $additional_info, $contact_form, $contact_email, $email_host, $email_port, $email_login, $email_password, $google_maps,
|
||||
$facebook_link, $statistic_code, $htaccess, $robots, $newsletter_header, $newsletter_footer_1, $newsletter_footer_2, $google_map_key, $google_search_console, $update, $devel,
|
||||
$news_limit, $visit_counter, $calendar, $tags, $ssl, $mysql_debug, $htaccess_cache, $visits, $links_structure, $link_version, $widget_phone, $update_key )
|
||||
{
|
||||
global $mdb;
|
||||
$firm_name, $firm_adress, $additional_info, $contact_form, $contact_email,
|
||||
$email_host, $email_port, $email_login, $email_password, $google_maps,
|
||||
$facebook_link, $statistic_code, $htaccess, $robots,
|
||||
$newsletter_header, $newsletter_footer_1, $newsletter_footer_2,
|
||||
$google_map_key, $google_search_console, $update, $devel,
|
||||
$news_limit, $visit_counter, $calendar, $tags, $ssl, $mysql_debug,
|
||||
$htaccess_cache, $visits, $links_structure, $link_version,
|
||||
$widget_phone, $update_key
|
||||
): bool {
|
||||
$data = [
|
||||
'firm_name' => $firm_name,
|
||||
'firm_adress' => $firm_adress,
|
||||
'additional_info' => $additional_info,
|
||||
'contact_form' => $contact_form,
|
||||
'contact_email' => $contact_email,
|
||||
'email_host' => $email_host,
|
||||
'email_port' => $email_port,
|
||||
'email_login' => $email_login,
|
||||
'email_password' => $email_password,
|
||||
'google_maps' => $google_maps == 'on' ? 1 : 0,
|
||||
'facebook_link' => $facebook_link,
|
||||
'statistic_code' => $statistic_code,
|
||||
'htaccess' => $htaccess,
|
||||
'robots' => $robots,
|
||||
'newsletter_header' => $newsletter_header,
|
||||
'newsletter_footer_1' => $newsletter_footer_1,
|
||||
'newsletter_footer_2' => $newsletter_footer_2,
|
||||
'google_map_key' => $google_map_key,
|
||||
'google_search_console'=> $google_search_console,
|
||||
'update' => $update == 'on' ? 1 : 0,
|
||||
'devel' => $devel == 'on' ? 1 : 0,
|
||||
'news_limit' => $news_limit,
|
||||
'visit_counter' => $visit_counter == 'on' ? 1 : 0,
|
||||
'calendar' => $calendar == 'on' ? 1 : 0,
|
||||
'tags' => $tags == 'on' ? 1 : 0,
|
||||
'ssl' => $ssl == 'on' ? 1 : 0,
|
||||
'mysql_debug' => $mysql_debug == 'on' ? 1 : 0,
|
||||
'htaccess_cache' => $htaccess_cache == 'on' ? 1 : 0,
|
||||
'visits' => $visits,
|
||||
'links_structure' => $links_structure,
|
||||
'link_version' => $link_version,
|
||||
'widget_phone' => $widget_phone == 'on' ? 1 : 0,
|
||||
'update_key' => $update_key,
|
||||
];
|
||||
|
||||
$mdb -> query( 'TRUNCATE pp_settings' );
|
||||
|
||||
$mdb -> insert( 'pp_settings', [
|
||||
[
|
||||
'param' => 'firm_name',
|
||||
'value' => $firm_name,
|
||||
], [
|
||||
'param' => 'firm_adress',
|
||||
'value' => $firm_adress
|
||||
], [
|
||||
'param' => 'additional_info',
|
||||
'value' => $additional_info
|
||||
], [
|
||||
'param' => 'contact_form',
|
||||
'value' => $contact_form
|
||||
], [
|
||||
'param' => 'contact_email',
|
||||
'value' => $contact_email
|
||||
], [
|
||||
'param' => 'email_host',
|
||||
'value' => $email_host
|
||||
], [
|
||||
'param' => 'email_port',
|
||||
'value' => $email_port
|
||||
], [
|
||||
'param' => 'email_login',
|
||||
'value' => $email_login
|
||||
], [
|
||||
'param' => 'email_password',
|
||||
'value' => $email_password
|
||||
], [
|
||||
'param' => 'google_maps',
|
||||
'value' => $google_maps == 'on' ? 1 : 0
|
||||
], [
|
||||
"param" => 'facebook_link',
|
||||
'value' => $facebook_link
|
||||
], [
|
||||
'param' => 'statistic_code',
|
||||
'value' => $statistic_code
|
||||
], [
|
||||
'param' => 'htaccess',
|
||||
'value' => $htaccess
|
||||
], [
|
||||
'param' => 'robots',
|
||||
'value' => $robots
|
||||
], [
|
||||
'param' => 'newsletter_header',
|
||||
'value' => $newsletter_header
|
||||
], [
|
||||
'param' => 'newsletter_footer_1',
|
||||
'value' => $newsletter_footer_1
|
||||
], [
|
||||
'param' => 'newsletter_footer_2',
|
||||
'value' => $newsletter_footer_2
|
||||
], [
|
||||
'param' => 'google_map_key',
|
||||
'value' => $google_map_key
|
||||
], [
|
||||
'param' => 'google_search_console',
|
||||
'value' => $google_search_console
|
||||
], [
|
||||
'param' => 'update',
|
||||
'value' => $update == 'on' ? 1 : 0
|
||||
], [
|
||||
'param' => 'devel',
|
||||
'value' => $devel == 'on' ? 1 : 0
|
||||
], [
|
||||
'param' => 'news_limit',
|
||||
'value' => $news_limit
|
||||
], [
|
||||
'param' => 'visit_counter',
|
||||
'value' => $visit_counter == 'on' ? 1 : 0
|
||||
], [
|
||||
'param' => 'calendar',
|
||||
'value' => $calendar == 'on' ? 1 : 0
|
||||
], [
|
||||
'param' => 'tags',
|
||||
'value' => $tags == 'on' ? 1 : 0
|
||||
], [
|
||||
'param' => 'ssl',
|
||||
'value' => $ssl == 'on' ? 1 : 0
|
||||
], [
|
||||
'param' => 'mysql_debug',
|
||||
'value' => $mysql_debug == 'on' ? 1 : 0
|
||||
], [
|
||||
'param' => 'htaccess_cache',
|
||||
'value' => $htaccess_cache == 'on' ? 1 : 0
|
||||
], [
|
||||
'param' => 'visits',
|
||||
'value' => $visits
|
||||
], [
|
||||
'param' => 'links_structure',
|
||||
'value' => $links_structure
|
||||
], [
|
||||
'param' => 'link_version',
|
||||
'value' => $link_version
|
||||
], [
|
||||
'param' => 'widget_phone',
|
||||
'value' => $widget_phone == 'on' ? 1 : 0
|
||||
], [
|
||||
'param' => 'update_key',
|
||||
'value' => $update_key
|
||||
]
|
||||
]
|
||||
);
|
||||
|
||||
\S::set_message( 'Ustawienia zostały zapisane' );
|
||||
\S::delete_cache();
|
||||
\S::htacces();
|
||||
|
||||
return true;
|
||||
return self::repo()->save( $data );
|
||||
}
|
||||
|
||||
public static function settings_details()
|
||||
{
|
||||
global $mdb;
|
||||
|
||||
$results = $mdb -> select( 'pp_settings', '*', [ 'ORDER' => [ 'id' => 'ASC' ] ] );
|
||||
if ( is_array( $results ) ) foreach ( $results as $row )
|
||||
$settings[$row['param']] = $row['value'];
|
||||
|
||||
return $settings;
|
||||
}
|
||||
|
||||
}
|
||||
?>
|
||||
@@ -1,306 +1,83 @@
|
||||
<?php
|
||||
|
||||
namespace admin\factory;
|
||||
|
||||
/**
|
||||
* @deprecated Wrapper — używaj \Domain\User\UserRepository przez DI.
|
||||
*/
|
||||
class Users
|
||||
{
|
||||
public static function user_delete($user_id)
|
||||
private static function repo(): \Domain\User\UserRepository
|
||||
{
|
||||
global $mdb;
|
||||
|
||||
return $mdb->delete('pp_users', ['id' => (int)$user_id]);
|
||||
return new \Domain\User\UserRepository( $mdb );
|
||||
}
|
||||
|
||||
public static function user_details($user_id)
|
||||
public static function user_delete( $user_id ): bool
|
||||
{
|
||||
global $mdb;
|
||||
return $mdb->get('pp_users', '*', ['id' => (int)$user_id]);
|
||||
return self::repo()->delete( (int)$user_id );
|
||||
}
|
||||
|
||||
public static function user_privileges($user_id)
|
||||
public static function user_details( $user_id ): ?array
|
||||
{
|
||||
global $mdb;
|
||||
return $mdb->select('pp_users_privileges', '*', ['id_user' => (int)$user_id]);
|
||||
return self::repo()->find( (int)$user_id );
|
||||
}
|
||||
|
||||
public static function user_save($user_id, $login, $status, $active_to, $password, $password_re, $admin, $privileges, $twofa_enabled = 0, $twofa_email = '' )
|
||||
public static function user_privileges( $user_id ): array
|
||||
{
|
||||
global $mdb, $lang;
|
||||
|
||||
$mdb->delete('pp_users_privileges', ['id_user' => (int) $user_id]);
|
||||
|
||||
if (!$user_id)
|
||||
{
|
||||
if (strlen($password) < 5)
|
||||
return $response = ['status' => 'error', 'msg' => 'Podane hasło jest zbyt krótkie.'];
|
||||
|
||||
if ($password != $password_re)
|
||||
return $response = ['status' => 'error', 'msg' => 'Podane hasła są różne'];
|
||||
|
||||
if ($mdb->insert(
|
||||
'pp_users',
|
||||
[
|
||||
'login' => $login,
|
||||
'status' => $status == 'on' ? 1 : 0,
|
||||
'active_to' => $active_to == '' ? NULL : $active_to,
|
||||
'admin' => $admin,
|
||||
'password' => md5($password),
|
||||
'twofa_enabled' => $twofa_enabled == 'on' ? 1 : 0,
|
||||
'twofa_email' => $twofa_email
|
||||
]
|
||||
))
|
||||
$id_user = $mdb->get('pp_users', 'id', ['ORDER' => ['id' => 'DESC']]);
|
||||
|
||||
if (is_array($privileges))
|
||||
{
|
||||
foreach ($privileges as $pri)
|
||||
{
|
||||
$mdb->insert(
|
||||
'pp_users_privileges',
|
||||
[
|
||||
'name' => $pri,
|
||||
'id_user' => $id_user
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$mdb->insert(
|
||||
'pp_users_privileges',
|
||||
[
|
||||
'name' => $privileges,
|
||||
'id_user' => $id_user
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
return $response = ['status' => 'ok', 'msg' => 'Użytkownik został zapisany.'];
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
if ($password and strlen($password) < 5)
|
||||
return $response = ['status' => 'error', 'msg' => 'Podane hasło jest zbyt krótkie.'];
|
||||
|
||||
if ($password and $password != $password_re)
|
||||
return $response = ['status' => 'error', 'msg' => 'Podane hasła są różne'];
|
||||
|
||||
if ($password)
|
||||
$mdb->update('pp_users', [
|
||||
'password' => md5($password)
|
||||
], [
|
||||
'id' => (int) $user_id
|
||||
]);
|
||||
|
||||
$mdb->update('pp_users', [
|
||||
'login' => $login,
|
||||
'admin' => $admin,
|
||||
'status' => $status == 'on' ? 1 : 0,
|
||||
'active_to' => $active_to == '' ? NULL : $active_to,
|
||||
'error_logged_count' => 0,
|
||||
'twofa_enabled' => $twofa_enabled == 'on' ? 1 : 0,
|
||||
'twofa_email' => $twofa_email
|
||||
], [
|
||||
'id' => (int) $user_id
|
||||
]);
|
||||
|
||||
if (is_array($privileges))
|
||||
{
|
||||
foreach ($privileges as $pri)
|
||||
{
|
||||
$mdb->insert('pp_users_privileges', [
|
||||
'name' => $pri,
|
||||
'id_user' => $user_id
|
||||
]);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$mdb->insert('pp_users_privileges', [
|
||||
'name' => $privileges,
|
||||
'id_user' => $user_id
|
||||
]);
|
||||
}
|
||||
return $response = ['status' => 'ok', 'msg' => 'Uzytkownik został zapisany.'];
|
||||
}
|
||||
\S::delete_cache();
|
||||
return self::repo()->privileges( (int)$user_id );
|
||||
}
|
||||
|
||||
public static function check_login($login, $user_id)
|
||||
{
|
||||
global $mdb;
|
||||
|
||||
if ($mdb->get('pp_users', 'login', ['AND' => ['login' => $login, 'id[!]' => (int)$user_id]]))
|
||||
return $response = ['status' => 'error', 'msg' => 'Podany login jest już zajęty.'];
|
||||
|
||||
return $response = ['status' => 'ok'];
|
||||
public static function user_save(
|
||||
$user_id, $login, $status, $active_to, $password, $password_re,
|
||||
$admin, $privileges, $twofa_enabled = 0, $twofa_email = ''
|
||||
): array {
|
||||
return self::repo()->save(
|
||||
$user_id, (string)$login, $status, $active_to,
|
||||
(string)$password, (string)$password_re,
|
||||
$admin, $privileges, $twofa_enabled, (string)$twofa_email
|
||||
);
|
||||
}
|
||||
|
||||
public static function logon($login, $password)
|
||||
public static function check_login( $login, $user_id ): array
|
||||
{
|
||||
global $mdb;
|
||||
if ( self::repo()->isLoginTaken( (string)$login, (int)$user_id ) )
|
||||
return [ 'status' => 'error', 'msg' => 'Podany login jest już zajęty.' ];
|
||||
|
||||
if (!$mdb->get('pp_users', '*', ['login' => $login]))
|
||||
return 0;
|
||||
|
||||
if (!$mdb->get('pp_users', '*', ['AND' => ['login' => $login, 'status' => 1, 'error_logged_count[<]' => 5]]))
|
||||
return -1;
|
||||
|
||||
if ($mdb->get('pp_users', '*', [
|
||||
'AND' => [
|
||||
'login' => $login,
|
||||
'status' => 1,
|
||||
'password' => md5($password),
|
||||
'OR' => ['active_to[>=]' => date('Y-m-d'), 'active_to' => null]
|
||||
]
|
||||
]))
|
||||
{
|
||||
$mdb->update('pp_users', ['last_logged' => date('Y-m-d H:i:s'), 'error_logged_count' => 0], ['login' => $login]);
|
||||
return 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
$mdb->update('pp_users', ['last_error_logged' => date('Y-m-d H:i:s'), 'error_logged_count[+]' => 1], ['login' => $login]);
|
||||
if ($mdb->get('pp_users', 'error_logged_count', ['login' => $login]) >= 5)
|
||||
{
|
||||
$mdb->update('pp_users', ['status' => 0], ['login' => $login]);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
return [ 'status' => 'ok' ];
|
||||
}
|
||||
|
||||
public static function details($login)
|
||||
public static function logon( $login, $password ): int
|
||||
{
|
||||
global $mdb;
|
||||
return $mdb->get('pp_users', '*', ['login' => $login]);
|
||||
return self::repo()->logon( (string)$login, (string)$password );
|
||||
}
|
||||
|
||||
public static function check_privileges($name, $user_id)
|
||||
public static function details( $login ): ?array
|
||||
{
|
||||
global $mdb;
|
||||
|
||||
if ($user_id == 1)
|
||||
return true;
|
||||
else
|
||||
{
|
||||
if (!$privilages = \Cache::fetch("check_privileges:$user_id:$name-tmp"))
|
||||
{
|
||||
$privilages = $mdb->count('pp_users_privileges', ['AND' => ['name' => $name, 'id_user' => (int)$user_id]]);
|
||||
\Cache::store("check_privileges:$user_id:$name", $privilages);
|
||||
}
|
||||
return $privilages;
|
||||
}
|
||||
return self::repo()->findByLogin( (string)$login );
|
||||
}
|
||||
|
||||
static public function get_by_id(int $userId): ?array
|
||||
public static function check_privileges( $name, $user_id ): bool
|
||||
{
|
||||
|
||||
global $mdb;
|
||||
return $mdb->get('pp_users', '*', ['id' => $userId]) ?: null;
|
||||
return self::repo()->hasPrivilege( (string)$name, (int)$user_id );
|
||||
}
|
||||
|
||||
static public function send_twofa_code(int $userId, bool $resend = false): bool
|
||||
public static function get_by_id( int $userId ): ?array
|
||||
{
|
||||
|
||||
$user = self::get_by_id($userId);
|
||||
if (!$user)
|
||||
return false;
|
||||
|
||||
if ((int)$user['twofa_enabled'] !== 1)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
$to = $user['twofa_email'] ?: $user['login'];
|
||||
if (!filter_var($to, FILTER_VALIDATE_EMAIL))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($resend && !empty($user['twofa_sent_at']))
|
||||
{
|
||||
$last = strtotime($user['twofa_sent_at']);
|
||||
if ($last && (time() - $last) < 30)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
$code = random_int(100000, 999999);
|
||||
$hash = password_hash((string)$code, PASSWORD_DEFAULT);
|
||||
|
||||
self::update_by_id($userId, [
|
||||
'twofa_code_hash' => $hash,
|
||||
'twofa_expires_at' => date('Y-m-d H:i:s', time() + 10 * 60), // 10 minut
|
||||
'twofa_sent_at' => date('Y-m-d H:i:s'),
|
||||
'twofa_failed_attempts' => 0,
|
||||
]);
|
||||
|
||||
$subject = 'Twój kod logowania 2FA';
|
||||
$body = "Twój kod logowania do panelu administratora: {$code}. Kod jest ważny przez 10 minut. Jeśli to nie Ty inicjowałeś logowanie – zignoruj tę wiadomość i poinformuj administratora.";
|
||||
|
||||
$sent = \S::send_email($to, $subject, $body);
|
||||
|
||||
if (!$sent) {
|
||||
$headers = "MIME-Version: 1.0\r\n";
|
||||
$headers .= "Content-type: text/plain; charset=UTF-8\r\n";
|
||||
$headers .= "From: no-reply@" . ($_SERVER['HTTP_HOST'] ?? 'localhost') . "\r\n";
|
||||
$encodedSubject = mb_encode_mimeheader($subject, 'UTF-8');
|
||||
|
||||
$sent = mail($to, $encodedSubject, $body, $headers);
|
||||
}
|
||||
|
||||
return $sent;
|
||||
return self::repo()->find( $userId );
|
||||
}
|
||||
|
||||
static public function update_by_id(int $userId, array $data): bool
|
||||
public static function send_twofa_code( int $userId, bool $resend = false ): bool
|
||||
{
|
||||
global $mdb;
|
||||
return (bool)$mdb->update('pp_users', $data, ['id' => $userId]);
|
||||
return self::repo()->sendTwofaCode( $userId, $resend );
|
||||
}
|
||||
|
||||
static public function verify_twofa_code(int $userId, string $code): bool
|
||||
public static function update_by_id( int $userId, array $data ): bool
|
||||
{
|
||||
$user = self::get_by_id( $userId );
|
||||
if (!$user) return false;
|
||||
return self::repo()->update( $userId, $data );
|
||||
}
|
||||
|
||||
if ((int)$user['twofa_failed_attempts'] >= 5)
|
||||
{
|
||||
return false; // zbyt wiele prób
|
||||
}
|
||||
|
||||
// sprawdź ważność
|
||||
if (empty($user['twofa_expires_at']) || time() > strtotime($user['twofa_expires_at']))
|
||||
{
|
||||
// wyczyść po wygaśnięciu
|
||||
self::update_by_id($userId, [
|
||||
'twofa_code_hash' => null,
|
||||
'twofa_expires_at' => null,
|
||||
]);
|
||||
return false;
|
||||
}
|
||||
|
||||
$ok = (!empty($user['twofa_code_hash']) && password_verify($code, $user['twofa_code_hash']));
|
||||
if ($ok)
|
||||
{
|
||||
// sukces: czyścimy wszystko
|
||||
self::update_by_id($userId, [
|
||||
'twofa_code_hash' => null,
|
||||
'twofa_expires_at' => null,
|
||||
'twofa_sent_at' => null,
|
||||
'twofa_failed_attempts' => 0,
|
||||
'last_logged' => date('Y-m-d H:i:s'),
|
||||
]);
|
||||
return true;
|
||||
}
|
||||
|
||||
// zła próba — inkrementacja
|
||||
self::update_by_id($userId, [
|
||||
'twofa_failed_attempts' => (int)$user['twofa_failed_attempts'] + 1,
|
||||
'last_error_logged' => date('Y-m-d H:i:s'),
|
||||
]);
|
||||
return false;
|
||||
public static function verify_twofa_code( int $userId, string $code ): bool
|
||||
{
|
||||
return self::repo()->verifyTwofaCode( $userId, $code );
|
||||
}
|
||||
}
|
||||
|
||||
13
autoload/admin/view/class.Releases.php
Normal file
13
autoload/admin/view/class.Releases.php
Normal file
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
namespace admin\view;
|
||||
|
||||
class Releases
|
||||
{
|
||||
public static function main_view(): string
|
||||
{
|
||||
$tpl = new \Tpl;
|
||||
$tpl->versions = \admin\factory\Releases::get_versions();
|
||||
$tpl->licenses = \admin\factory\Releases::get_licenses();
|
||||
return $tpl->render('releases/main-view');
|
||||
}
|
||||
}
|
||||
32
autoload/autoloader.php
Normal file
32
autoload/autoloader.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
/**
|
||||
* Centralny autoloader — hybrydowy (PSR-4 + legacy class.*.php)
|
||||
* Obsługuje namespace'y: Domain\, Shared\, Admin\, Frontend\, admin\, front\
|
||||
*/
|
||||
|
||||
function __autoload_my_classes( $classname )
|
||||
{
|
||||
$base = __DIR__ . '/';
|
||||
|
||||
$q = explode( '\\', $classname );
|
||||
$c = array_pop( $q );
|
||||
|
||||
// Savant3 — special case
|
||||
if ( $c == 'Savant3' )
|
||||
{
|
||||
$f = $base . 'Savant3.php';
|
||||
if ( file_exists( $f ) ) { require_once( $f ); return; }
|
||||
}
|
||||
|
||||
$path = implode( '/', $q );
|
||||
|
||||
// 1. Legacy: class.ClassName.php
|
||||
$f = $base . $path . '/class.' . $c . '.php';
|
||||
if ( file_exists( $f ) ) { require_once( $f ); return; }
|
||||
|
||||
// 2. PSR-4: ClassName.php
|
||||
$f = $base . $path . '/' . $c . '.php';
|
||||
if ( file_exists( $f ) ) require_once( $f );
|
||||
}
|
||||
|
||||
spl_autoload_register( '__autoload_my_classes' );
|
||||
@@ -1,46 +1,8 @@
|
||||
<?php
|
||||
class Cache
|
||||
/**
|
||||
* Wrapper delegujący do \Shared\Cache\CacheHandler.
|
||||
* Zachowany dla wstecznej kompatybilności — stopniowo zastępować \Cache na \Shared\Cache\CacheHandler.
|
||||
*/
|
||||
class Cache extends \Shared\Cache\CacheHandler
|
||||
{
|
||||
public static function store( $key, $data, $ttl = 86400 )
|
||||
{
|
||||
file_put_contents( self::get_file_name( $key ), gzdeflate( serialize( array( time() + $ttl, $data ) ) ) );
|
||||
}
|
||||
|
||||
private static function get_file_name( $key )
|
||||
{
|
||||
$md5 = md5( $key );
|
||||
$dir = 'temp/' . $md5[0] . '/' . $md5[1] . '/';
|
||||
|
||||
if ( !is_dir( $dir ) )
|
||||
mkdir( $dir , 0755 , true );
|
||||
|
||||
return $dir . 's_cache_' . $md5;
|
||||
}
|
||||
|
||||
public static function fetch( $key )
|
||||
{
|
||||
$filename = self::get_file_name( $key );
|
||||
|
||||
if ( !file_exists( $filename ) || !is_readable( $filename ) )
|
||||
return false;
|
||||
|
||||
$data = gzinflate( file_get_contents( $filename ) );
|
||||
|
||||
$data = @unserialize( $data );
|
||||
if ( !$data )
|
||||
{
|
||||
unlink( $filename );
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( time() > $data[0] )
|
||||
{
|
||||
if ( file_exists( $filename ) )
|
||||
unlink( $filename );
|
||||
return false;
|
||||
}
|
||||
|
||||
return $data[1];
|
||||
}
|
||||
}
|
||||
?>
|
||||
@@ -1,91 +1,8 @@
|
||||
<?php
|
||||
class Html
|
||||
/**
|
||||
* Wrapper delegujący do \Shared\Html\Html.
|
||||
* Zachowany dla wstecznej kompatybilności — stopniowo zastępować \Html na \Shared\Html\Html.
|
||||
*/
|
||||
class Html extends \Shared\Html\Html
|
||||
{
|
||||
public static function form_text( array $params = array() )
|
||||
{
|
||||
$tpl = new \Tpl;
|
||||
$tpl -> params = $params;
|
||||
return $tpl -> render( 'html/form-text' );
|
||||
}
|
||||
|
||||
public static function input_switch( array $params = array() )
|
||||
{
|
||||
$tpl = new \Tpl;
|
||||
$tpl -> params = $params;
|
||||
return $tpl -> render( 'html/input-switch' );
|
||||
}
|
||||
|
||||
public static function select( array $params = array() )
|
||||
{
|
||||
$tpl = new \Tpl;
|
||||
$tpl -> params = $params;
|
||||
return $tpl -> render( 'html/select' );
|
||||
}
|
||||
|
||||
public static function textarea( array $params = array() )
|
||||
{
|
||||
$defaults = array(
|
||||
'rows' => 4,
|
||||
);
|
||||
|
||||
$params = array_merge( $defaults, $params );
|
||||
|
||||
$tpl = new \Tpl;
|
||||
$tpl -> params = $params;
|
||||
return $tpl -> render( 'html/textarea' );
|
||||
}
|
||||
|
||||
public static function input_icon( array $params = array() )
|
||||
{
|
||||
$defaults = array(
|
||||
'type' => 'text',
|
||||
);
|
||||
|
||||
$params = array_merge( $defaults, $params );
|
||||
|
||||
$tpl = new \Tpl;
|
||||
$tpl -> params = $params;
|
||||
return $tpl -> render( 'html/input-icon' );
|
||||
}
|
||||
|
||||
public static function input( array $params = array() )
|
||||
{
|
||||
$defaults = array(
|
||||
'type' => 'text',
|
||||
);
|
||||
|
||||
$params = array_merge( $defaults, $params );
|
||||
|
||||
$tpl = new \Tpl;
|
||||
$tpl -> params = $params;
|
||||
return $tpl -> render( 'html/input' );
|
||||
}
|
||||
|
||||
public static function button( array $params = array() )
|
||||
{
|
||||
$defaults = array(
|
||||
'class' => 'btn-sm btn-info',
|
||||
);
|
||||
|
||||
$params = array_merge( $defaults, $params );
|
||||
|
||||
$tpl = new \Tpl;
|
||||
$tpl -> params = $params;
|
||||
return $tpl -> render( 'html/button' );
|
||||
}
|
||||
|
||||
public static function panel( array $params = array() )
|
||||
{
|
||||
$defaults = array(
|
||||
'title' => 'panel-title',
|
||||
'class' => 'panel-primary',
|
||||
'content' => 'panel-content'
|
||||
);
|
||||
|
||||
$params = array_merge( $defaults, $params );
|
||||
|
||||
$tpl = new \Tpl;
|
||||
$tpl -> params = $params;
|
||||
return $tpl -> render( 'html/panel' );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,312 +1,8 @@
|
||||
<?php
|
||||
class ImageManipulator
|
||||
/**
|
||||
* Wrapper delegujący do \Shared\Image\ImageManipulator.
|
||||
* Zachowany dla wstecznej kompatybilności — stopniowo zastępować new ImageManipulator na new \Shared\Image\ImageManipulator.
|
||||
*/
|
||||
class ImageManipulator extends \Shared\Image\ImageManipulator
|
||||
{
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $width;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $height;
|
||||
|
||||
/**
|
||||
* @var resource
|
||||
*/
|
||||
protected $image;
|
||||
protected $img_src;
|
||||
|
||||
/**
|
||||
* Image manipulator constructor
|
||||
*
|
||||
* @param string $file OPTIONAL Path to image file or image data as string
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($file = null)
|
||||
{
|
||||
if (null !== $file) {
|
||||
if (is_file($file)) {
|
||||
$this -> img_src = $file;
|
||||
$this->setImageFile($file);
|
||||
} else {
|
||||
echo 'a'; exit;
|
||||
$this->setImageString($file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set image resource from file
|
||||
*
|
||||
* @param string $file Path to image file
|
||||
* @return ImageManipulator for a fluent interface
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function setImageFile($file)
|
||||
{
|
||||
if (!(is_readable($file) && is_file($file))) {
|
||||
throw new InvalidArgumentException("Image file $file is not readable");
|
||||
}
|
||||
|
||||
if (is_resource($this->image)) {
|
||||
imagedestroy($this->image);
|
||||
}
|
||||
|
||||
list ($this->width, $this->height, $type) = getimagesize($file);
|
||||
|
||||
switch ($type) {
|
||||
case IMAGETYPE_GIF :
|
||||
$this->image = imagecreatefromgif($file);
|
||||
break;
|
||||
case IMAGETYPE_JPEG :
|
||||
$this->image = imagecreatefromjpeg($file);
|
||||
break;
|
||||
case IMAGETYPE_PNG :
|
||||
$this->image = imagecreatefrompng($file);
|
||||
break;
|
||||
default :
|
||||
throw new InvalidArgumentException("Image type $type not supported");
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set image resource from string data
|
||||
*
|
||||
* @param string $data
|
||||
* @return ImageManipulator for a fluent interface
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public function setImageString($data)
|
||||
{
|
||||
if (is_resource($this->image)) {
|
||||
imagedestroy($this->image);
|
||||
}
|
||||
|
||||
if (!$this->image = imagecreatefromstring($data)) {
|
||||
throw new RuntimeException('Cannot create image from data string');
|
||||
}
|
||||
$this->width = imagesx($this->image);
|
||||
$this->height = imagesy($this->image);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resamples the current image
|
||||
*
|
||||
* @param int $width New width
|
||||
* @param int $height New height
|
||||
* @param bool $constrainProportions Constrain current image proportions when resizing
|
||||
* @return ImageManipulator for a fluent interface
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public function resample( $width, $height, $constrainProportions = true )
|
||||
{
|
||||
if (!is_resource($this->image)) {
|
||||
throw new RuntimeException('No image set');
|
||||
}
|
||||
if ($constrainProportions) {
|
||||
if ($this->height >= $this->width) {
|
||||
$width = round($height / $this->height * $this->width);
|
||||
} else {
|
||||
$height = round($width / $this->width * $this->height);
|
||||
}
|
||||
}
|
||||
|
||||
$temp = imagecreatetruecolor($width, $height);
|
||||
|
||||
imagecopyresampled($temp, $this->image, 0, 0, 0, 0, $width, $height, $this->width, $this->height);
|
||||
|
||||
if ( function_exists('exif_read_data') )
|
||||
{
|
||||
$exif = exif_read_data( $this -> img_src );
|
||||
if ( $exif && isset($exif['Orientation']) )
|
||||
{
|
||||
$orientation = $exif['Orientation'];
|
||||
if ( $orientation != 1 )
|
||||
{
|
||||
$deg = 0;
|
||||
switch ($orientation)
|
||||
{
|
||||
case 3:
|
||||
$deg = 180;
|
||||
break;
|
||||
case 6:
|
||||
$deg = 270;
|
||||
break;
|
||||
case 8:
|
||||
$deg = 90;
|
||||
break;
|
||||
}
|
||||
|
||||
if ( $deg )
|
||||
$temp = imagerotate( $temp, $deg, 0 );
|
||||
}
|
||||
}
|
||||
}
|
||||
return $this->_replace($temp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enlarge canvas
|
||||
*
|
||||
* @param int $width Canvas width
|
||||
* @param int $height Canvas height
|
||||
* @param array $rgb RGB colour values
|
||||
* @param int $xpos X-Position of image in new canvas, null for centre
|
||||
* @param int $ypos Y-Position of image in new canvas, null for centre
|
||||
* @return ImageManipulator for a fluent interface
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public function enlargeCanvas($width, $height, array $rgb = array(), $xpos = null, $ypos = null)
|
||||
{
|
||||
if (!is_resource($this->image)) {
|
||||
throw new RuntimeException('No image set');
|
||||
}
|
||||
|
||||
$width = max($width, $this->width);
|
||||
$height = max($height, $this->height);
|
||||
|
||||
$temp = imagecreatetruecolor($width, $height);
|
||||
if (count($rgb) == 3) {
|
||||
$bg = imagecolorallocate($temp, $rgb[0], $rgb[1], $rgb[2]);
|
||||
imagefill($temp, 0, 0, $bg);
|
||||
}
|
||||
|
||||
if (null === $xpos) {
|
||||
$xpos = round(($width - $this->width) / 2);
|
||||
}
|
||||
if (null === $ypos) {
|
||||
$ypos = round(($height - $this->height) / 2);
|
||||
}
|
||||
|
||||
imagecopy($temp, $this->image, (int) $xpos, (int) $ypos, 0, 0, $this->width, $this->height);
|
||||
return $this->_replace($temp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Crop image
|
||||
*
|
||||
* @param int|array $x1 Top left x-coordinate of crop box or array of coordinates
|
||||
* @param int $y1 Top left y-coordinate of crop box
|
||||
* @param int $x2 Bottom right x-coordinate of crop box
|
||||
* @param int $y2 Bottom right y-coordinate of crop box
|
||||
* @return ImageManipulator for a fluent interface
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public function crop($x1, $y1 = 0, $x2 = 0, $y2 = 0)
|
||||
{
|
||||
if (!is_resource($this->image)) {
|
||||
throw new RuntimeException('No image set');
|
||||
}
|
||||
if (is_array($x1) && 4 == count($x1)) {
|
||||
list($x1, $y1, $x2, $y2) = $x1;
|
||||
}
|
||||
|
||||
$x1 = max($x1, 0);
|
||||
$y1 = max($y1, 0);
|
||||
|
||||
$x2 = min($x2, $this->width);
|
||||
$y2 = min($y2, $this->height);
|
||||
|
||||
$width = $x2 - $x1;
|
||||
$height = $y2 - $y1;
|
||||
|
||||
$temp = imagecreatetruecolor($width, $height);
|
||||
imagecopy($temp, $this->image, 0, 0, $x1, $y1, $width, $height);
|
||||
|
||||
return $this->_replace($temp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace current image resource with a new one
|
||||
*
|
||||
* @param resource $res New image resource
|
||||
* @return ImageManipulator for a fluent interface
|
||||
* @throws UnexpectedValueException
|
||||
*/
|
||||
protected function _replace($res)
|
||||
{
|
||||
if (!is_resource($res)) {
|
||||
throw new UnexpectedValueException('Invalid resource');
|
||||
}
|
||||
if (is_resource($this->image)) {
|
||||
imagedestroy($this->image);
|
||||
}
|
||||
$this->image = $res;
|
||||
$this->width = imagesx($res);
|
||||
$this->height = imagesy($res);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save current image to file
|
||||
*
|
||||
* @param string $fileName
|
||||
* @return void
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public function save($fileName, $type = IMAGETYPE_JPEG)
|
||||
{
|
||||
$dir = dirname($fileName);
|
||||
if (!is_dir($dir)) {
|
||||
if (!mkdir($dir, 0755, true)) {
|
||||
throw new RuntimeException('Error creating directory ' . $dir);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
switch ($type) {
|
||||
case IMAGETYPE_GIF :
|
||||
if (!imagegif($this->image, $fileName)) {
|
||||
throw new RuntimeException;
|
||||
}
|
||||
break;
|
||||
case IMAGETYPE_PNG :
|
||||
if (!imagepng($this->image, $fileName)) {
|
||||
throw new RuntimeException;
|
||||
}
|
||||
break;
|
||||
case IMAGETYPE_JPEG :
|
||||
default :
|
||||
if (!imagejpeg($this->image, $fileName, 95)) {
|
||||
throw new RuntimeException;
|
||||
}
|
||||
}
|
||||
} catch (Exception $ex) {
|
||||
throw new RuntimeException('Error saving image file to ' . $fileName);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the GD image resource
|
||||
*
|
||||
* @return resource
|
||||
*/
|
||||
public function getResource()
|
||||
{
|
||||
return $this->image;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current image resource width
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getWidth()
|
||||
{
|
||||
return $this->width;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current image height
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getHeight()
|
||||
{
|
||||
return $this->height;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
1257
autoload/class.S.php
1257
autoload/class.S.php
File diff suppressed because it is too large
Load Diff
@@ -1,73 +1,8 @@
|
||||
<?php
|
||||
class Tpl
|
||||
/**
|
||||
* Wrapper delegujący do \Shared\Tpl\Tpl.
|
||||
* Zachowany dla wstecznej kompatybilności — stopniowo zastępować \Tpl na \Shared\Tpl\Tpl.
|
||||
*/
|
||||
class Tpl extends \Shared\Tpl\Tpl
|
||||
{
|
||||
protected $dir = 'templates/';
|
||||
protected $vars = array();
|
||||
|
||||
function __construct( $dir = null )
|
||||
{
|
||||
if ( $dir !== null )
|
||||
$this -> dir = $dir;
|
||||
}
|
||||
|
||||
public static function view( $file, $values = '' )
|
||||
{
|
||||
$tpl = new \Tpl;
|
||||
if ( is_array( $values ) ) foreach ( $values as $key => $val )
|
||||
$tpl -> $key = $val;
|
||||
return $tpl -> render( $file );
|
||||
}
|
||||
|
||||
public function secureHTML( $val )
|
||||
{
|
||||
$out = stripslashes( $val );
|
||||
$out = str_replace( "'", "'", $out );
|
||||
$out = str_replace( '"', """, $out );
|
||||
$out = str_replace( "<", "<", $out );
|
||||
$out = str_replace( ">", ">", $out );
|
||||
return $out;
|
||||
}
|
||||
|
||||
public function render( $file )
|
||||
{
|
||||
if ( file_exists( 'templates_user/' . $file . '.php' ) )
|
||||
{
|
||||
ob_start();
|
||||
include 'templates_user/' . $file . '.php';
|
||||
$out = ob_get_contents();
|
||||
ob_end_clean();
|
||||
|
||||
return $out;
|
||||
}
|
||||
else if ( file_exists( 'templates/' . $file . '.php' ) )
|
||||
{
|
||||
ob_start();
|
||||
include 'templates/' . $file . '.php';
|
||||
$out = ob_get_contents();
|
||||
ob_end_clean();
|
||||
|
||||
return $out;
|
||||
}
|
||||
else if ( file_exists( $file . '.php' ) )
|
||||
{
|
||||
ob_start();
|
||||
include $file . '.php';
|
||||
$out = ob_get_contents();
|
||||
ob_end_clean();
|
||||
|
||||
return $out;
|
||||
}
|
||||
else
|
||||
return '<div class="alert alert-danger" role="alert">Nie znaleziono pliku widoku: <b>' . $this -> dir . $file . '.php</b>';
|
||||
}
|
||||
|
||||
public function __set( $name, $value )
|
||||
{
|
||||
$this -> vars[ $name ] = $value;
|
||||
}
|
||||
|
||||
public function __get( $name )
|
||||
{
|
||||
return $this -> vars[ $name ];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<?
|
||||
<?php
|
||||
namespace front\factory;
|
||||
class Authors
|
||||
{
|
||||
@@ -6,17 +6,7 @@ class Authors
|
||||
static public function get_single_author( $id_author )
|
||||
{
|
||||
global $mdb;
|
||||
|
||||
if ( !$author = \Cache::fetch( "get_single_author:$id_author" ) )
|
||||
{
|
||||
$author = $mdb -> get( 'pp_authors', '*', [ 'id' => (int)$id_author ] );
|
||||
|
||||
$results = $mdb -> select( 'pp_authors_langs', '*', [ 'id_author' => (int)$id_author ] );
|
||||
if ( is_array( $results ) ) foreach ( $results as $row )
|
||||
$author['languages'][$row['id_lang']] = $row;
|
||||
|
||||
\Cache::store( "get_single_author:$id_author", $author );
|
||||
}
|
||||
return $author;
|
||||
$repo = new \Domain\Authors\AuthorsRepository($mdb);
|
||||
return $repo->authorByLang($id_author);
|
||||
}
|
||||
}
|
||||
@@ -6,58 +6,14 @@ class Banners
|
||||
public static function banners()
|
||||
{
|
||||
global $mdb, $lang;
|
||||
|
||||
if ( !$banners = \Cache::fetch( 'banners' ) )
|
||||
{
|
||||
$results = $mdb -> query( 'SELECT '
|
||||
. 'id, name '
|
||||
. 'FROM '
|
||||
. 'pp_banners '
|
||||
. 'WHERE '
|
||||
. 'status = 1 '
|
||||
. 'AND '
|
||||
. '( date_start <= \'' . date( 'Y-m-d' ) . '\' OR date_start IS NULL ) '
|
||||
. 'AND '
|
||||
. '( date_end >= \'' . date( 'Y-m-d' ) . '\' OR date_end IS NULL ) '
|
||||
. 'AND '
|
||||
. 'home_page = 0' ) -> fetchAll();
|
||||
if ( is_array( $results ) and !empty( $results ) ) foreach ( $results as $row )
|
||||
{
|
||||
$row['languages'] = $mdb -> get( 'pp_banners_langs', '*', [ 'AND' => [ 'id_banner' => (int)$row['id'], 'id_lang' => $lang[0] ] ] );
|
||||
$banners[] = $row;
|
||||
}
|
||||
\Cache::store( 'banners', $banners );
|
||||
}
|
||||
return $banners;
|
||||
$repo = new \Domain\Banners\BannersRepository($mdb);
|
||||
return $repo->activeBanners($lang[0]);
|
||||
}
|
||||
|
||||
|
||||
public static function main_banner()
|
||||
{
|
||||
global $mdb, $lang;
|
||||
|
||||
if ( !$banner = \Cache::fetch( "main_banner:" . $lang[0] ) )
|
||||
{
|
||||
$banner = $mdb -> query( 'SELECT '
|
||||
. '* '
|
||||
. 'FROM '
|
||||
. 'pp_banners '
|
||||
. 'WHERE '
|
||||
. 'status = 1 '
|
||||
. 'AND '
|
||||
. '( date_start <= \'' . date( 'Y-m-d' ) . '\' OR date_start IS NULL ) '
|
||||
. 'AND '
|
||||
. '( date_end >= \'' . date( 'Y-m-d' ) . '\' OR date_end IS NULL ) '
|
||||
. 'AND '
|
||||
. 'home_page = 1 '
|
||||
. 'ORDER BY '
|
||||
. 'date_end ASC '
|
||||
. 'LIMIT 1' ) -> fetchAll();
|
||||
$banner = $banner[0];
|
||||
if ( $banner )
|
||||
$banner['languages'] = $mdb -> get( 'pp_banners_langs', '*', [ 'AND' => [ 'id_banner' => (int)$banner['id'], 'id_lang' => $lang[0] ] ] );
|
||||
|
||||
\Cache::store( "main_banner:" . $lang[0], $banner );
|
||||
}
|
||||
return $banner;
|
||||
$repo = new \Domain\Banners\BannersRepository($mdb);
|
||||
return $repo->mainBanner($lang[0]);
|
||||
}
|
||||
}
|
||||
@@ -1,58 +1,34 @@
|
||||
<?php
|
||||
namespace front\factory;
|
||||
|
||||
/**
|
||||
* @deprecated Wrapper — używaj \Domain\Languages\LanguagesRepository przez DI.
|
||||
*/
|
||||
class Languages
|
||||
{
|
||||
public static function default_domain()
|
||||
private static function repo(): \Domain\Languages\LanguagesRepository
|
||||
{
|
||||
global $mdb;
|
||||
$results = $mdb -> query( 'SELECT domain FROM pp_langs WHERE status = 1 AND domain IS NOT NULL AND main_domain = 1' ) -> fetchAll();
|
||||
return $default_domain = $results[0][0];
|
||||
return new \Domain\Languages\LanguagesRepository( $mdb );
|
||||
}
|
||||
|
||||
public static function default_language( $domain = '' )
|
||||
|
||||
public static function default_domain(): ?string
|
||||
{
|
||||
global $mdb;
|
||||
if ( !$default_language = \Cache::fetch( "default_language:$domain" ) )
|
||||
{
|
||||
if ( $domain )
|
||||
$results = $mdb -> query( 'SELECT id FROM pp_langs WHERE status = 1 AND domain = \'' . $domain . '\' ORDER BY start DESC, o ASC LIMIT 1' ) -> fetchAll();
|
||||
if ( !$domain or !\front\factory\Languages::default_domain() )
|
||||
$results = $mdb -> query( 'SELECT id FROM pp_langs WHERE status = 1 AND domain IS NULL ORDER BY start DESC, o ASC LIMIT 1' ) -> fetchAll();
|
||||
$default_language = $results[0][0];
|
||||
|
||||
\Cache::store( "default_language:$domain", $default_language );
|
||||
}
|
||||
return $default_language;
|
||||
return self::repo()->defaultDomain();
|
||||
}
|
||||
|
||||
public static function active_languages()
|
||||
|
||||
public static function default_language( $domain = '' ): ?string
|
||||
{
|
||||
global $mdb;
|
||||
|
||||
if ( !$active_languages = \Cache::fetch( 'active_languages' ) )
|
||||
{
|
||||
$active_languages = $mdb -> select( 'pp_langs', [ 'id', 'name', 'domain' ], [ 'status' => 1, 'ORDER' => [ 'o' => 'ASC' ] ] );
|
||||
\Cache::store( 'active_languages', $active_languages );
|
||||
}
|
||||
return $active_languages;
|
||||
return self::repo()->defaultLanguage( (string)$domain );
|
||||
}
|
||||
|
||||
public static function lang_translations( $language = 'pl' )
|
||||
|
||||
public static function active_languages(): array
|
||||
{
|
||||
global $mdb;
|
||||
|
||||
if ( !$translations = \Cache::fetch( "lang_translations:$language" ) )
|
||||
{
|
||||
$translations[ '0' ] = $language;
|
||||
|
||||
$results = $mdb -> select( 'pp_langs_translations', [ 'text', $language ] );
|
||||
if ( is_array( $results ) ) foreach ( $results as $row )
|
||||
$translations[ $row['text'] ] = $row[ $language ];
|
||||
|
||||
\Cache::store( "lang_translations:$language", $translations );
|
||||
}
|
||||
|
||||
return $translations;
|
||||
return self::repo()->activeLanguages();
|
||||
}
|
||||
|
||||
public static function lang_translations( $language = 'pl' ): array
|
||||
{
|
||||
return self::repo()->langTranslations( (string)$language );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,113 +6,49 @@ class Newsletter
|
||||
public static function newsletter_unsubscribe( $hash )
|
||||
{
|
||||
global $mdb;
|
||||
return $mdb -> update( 'pp_newsletter', [ 'status' => 0 ], [ 'hash' => $hash ] );
|
||||
$repo = new \Domain\Newsletter\NewsletterRepository($mdb);
|
||||
return $repo->unsubscribe($hash);
|
||||
}
|
||||
|
||||
|
||||
public static function newsletter_confirm( $hash )
|
||||
{
|
||||
global $mdb;
|
||||
if ( !$id = $mdb -> get( 'pp_newsletter', 'id', [ 'AND' => [ 'hash' => $hash, 'status' => 0 ] ] ) )
|
||||
return false;
|
||||
else
|
||||
$mdb -> update( 'pp_newsletter', [ 'status' => 1 ], [ 'id' => $id ] );
|
||||
return true;
|
||||
$repo = new \Domain\Newsletter\NewsletterRepository($mdb);
|
||||
return $repo->confirm($hash);
|
||||
}
|
||||
|
||||
|
||||
public static function newsletter_send( $limit = 5 )
|
||||
{
|
||||
global $mdb, $settings, $lang;
|
||||
|
||||
$results = $mdb -> query( 'SELECT * FROM pp_newsletter_send WHERE mailed = 0 ORDER BY id ASC LIMIT ' . $limit ) -> fetchAll();
|
||||
if ( is_array( $results ) and !empty( $results ) )
|
||||
{
|
||||
foreach ( $results as $row )
|
||||
{
|
||||
$dates = explode( ' - ', $row['dates'] );
|
||||
|
||||
$text = \admin\view\Newsletter::preview(
|
||||
\admin\factory\Articles::articles_by_date_add( $dates[0], $dates[1] ),
|
||||
\admin\factory\Settings::settings_details(),
|
||||
\admin\factory\Newsletter::email_template_detalis($row['id_template'])
|
||||
);
|
||||
|
||||
if ( $settings['ssl'] ) $base = 'https'; else $base = 'http';
|
||||
|
||||
$link = $base . "://" . $_SERVER['SERVER_NAME'] . '/newsletter/unsubscribe/hash=' . \front\factory\Newsletter::get_hash( $row['email'] );
|
||||
$text = str_replace( '[WYPISZ_SIE]', $link, $text );
|
||||
|
||||
$regex = "-(<img[^>]+src\s*=\s*['\"])(((?!'|\"|http(|s)://).)*)(['\"][^>]*>)-i";
|
||||
$text = preg_replace( $regex, "$1" . $base . "://" . $_SERVER['SERVER_NAME'] . "$2$4", $text );
|
||||
|
||||
$regex = "-(<a[^>]+href\s*=\s*['\"])(((?!'|\"|http(|s)://).)*)(['\"][^>]*>)-i";
|
||||
$text = preg_replace( $regex, "$1" . $base . "://" . $_SERVER['SERVER_NAME'] . "$2$4", $text );
|
||||
|
||||
\S::send_email( $row['email'], 'Newsletter ze strony: ' . $_SERVER['SERVER_NAME'], $text );
|
||||
|
||||
if ( $row['only_once'] )
|
||||
$mdb -> update( 'pp_newsletter_send', [ 'mailed' => 1 ], [ 'id' => $row['id'] ] );
|
||||
else
|
||||
$mdb -> delete( 'pp_newsletter_send', [ 'id' => $row['id'] ] );
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
$repo = new \Domain\Newsletter\NewsletterRepository($mdb);
|
||||
return $repo->newsletterSend($limit, $settings, $lang);
|
||||
}
|
||||
|
||||
|
||||
public static function get_hash( $email )
|
||||
{
|
||||
global $mdb;
|
||||
return $mdb -> get( 'pp_newsletter', 'hash', [ 'email' => $email ] );
|
||||
$repo = new \Domain\Newsletter\NewsletterRepository($mdb);
|
||||
return $repo->getHash($email);
|
||||
}
|
||||
|
||||
|
||||
public static function newsletter_signin( $email )
|
||||
{
|
||||
global $mdb, $lang, $settings;
|
||||
|
||||
if ( !\S::email_check( $email ) )
|
||||
return false;
|
||||
|
||||
if ( !$mdb -> get( 'pp_newsletter', 'id', [ 'email' => $email ] ) )
|
||||
{
|
||||
$hash = md5( time() . $email );
|
||||
|
||||
$text = $settings['newsletter_header'];
|
||||
$text .= \front\factory\Newsletter::get_template( '#potwierdzenie-zapisu-do-newslettera' );
|
||||
$text .= $settings['newsletter_footer_1'];
|
||||
|
||||
$settings['ssl'] ? $base = 'https' : $base = 'http';
|
||||
|
||||
$regex = "-(<img[^>]+src\s*=\s*['\"])(((?!'|\"|http://).)*)(['\"][^>]*>)-i";
|
||||
$text = preg_replace( $regex, "$1" . $base . "://" . $_SERVER['SERVER_NAME'] . "$2$4", $text );
|
||||
|
||||
$regex = "-(<a[^>]+href\s*=\s*['\"])(((?!'|\"|http://).)*)(['\"][^>]*>)-i";
|
||||
$text = preg_replace( $regex, "$1" . $base . "://" . $_SERVER['SERVER_NAME'] . "$2$4", $text );
|
||||
|
||||
$link = '/newsletter/confirm/hash=' . $hash;
|
||||
|
||||
$text = str_replace( '[LINK]', $link, $text );
|
||||
|
||||
$send = \S::send_email( $email, $lang['potwierdz-zapisanie-sie-do-newslettera'], $text );
|
||||
|
||||
$mdb -> insert( 'pp_newsletter', [ 'email' => $email, 'hash' => $hash, 'status' => 0 ] );
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
$repo = new \Domain\Newsletter\NewsletterRepository($mdb);
|
||||
return $repo->signin($email, $settings, $lang);
|
||||
}
|
||||
|
||||
|
||||
public static function get_template( $template_name )
|
||||
{
|
||||
global $mdb;
|
||||
return $mdb -> get( 'pp_newsletter_templates', 'text', [ 'name' => $template_name ] );
|
||||
$repo = new \Domain\Newsletter\NewsletterRepository($mdb);
|
||||
return $repo->getTemplate($template_name);
|
||||
}
|
||||
|
||||
|
||||
public static function newsletter_signout( $email )
|
||||
{
|
||||
global $mdb;
|
||||
|
||||
if ( $mdb -> get( 'pp_newsletter', 'id', [ 'email' => $email ] ) )
|
||||
return $mdb -> delete( 'pp_newsletter', [ 'email' => $email ] );
|
||||
return false;
|
||||
$repo = new \Domain\Newsletter\NewsletterRepository($mdb);
|
||||
return $repo->signout($email);
|
||||
}
|
||||
}
|
||||
@@ -6,18 +6,7 @@ class Scontainers
|
||||
public static function scontainer_details( $scontainer_id )
|
||||
{
|
||||
global $mdb, $lang;
|
||||
|
||||
if ( !$scontainer = \Cache::fetch( "scontainer_details:$scontainer_id:" . $lang[0] ) )
|
||||
{
|
||||
$scontainer = $mdb -> get( 'pp_scontainers', '*', [ 'id' => (int)$scontainer_id ] );
|
||||
|
||||
$results = $mdb -> select( 'pp_scontainers_langs', '*', [ 'AND' => [ 'container_id' => (int)$scontainer_id, 'lang_id' => $lang[0] ] ] );
|
||||
if ( is_array( $results ) ) foreach ( $results as $row )
|
||||
$scontainer['languages'] = $row;
|
||||
|
||||
\Cache::store( "scontainer_details:$scontainer_id:" . $lang[0], $scontainer );
|
||||
}
|
||||
|
||||
return $scontainer;
|
||||
$repo = new \Domain\Scontainers\ScontainersRepository($mdb);
|
||||
return $repo->scontainerByLang($scontainer_id, $lang[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,27 +1,24 @@
|
||||
<?php
|
||||
namespace front\factory;
|
||||
|
||||
/**
|
||||
* @deprecated Wrapper — używaj \Domain\Settings\SettingsRepository przez DI.
|
||||
*/
|
||||
class Settings
|
||||
{
|
||||
public static function settings_details()
|
||||
private static function repo(): \Domain\Settings\SettingsRepository
|
||||
{
|
||||
global $mdb;
|
||||
|
||||
if ( !$settings = \Cache::fetch( 'settings_details' ) )
|
||||
{
|
||||
$results = $mdb -> select( 'pp_settings', '*' );
|
||||
if ( is_array( $results ) ) foreach ( $results as $row )
|
||||
$settings[ $row['param'] ] = $row['value'];
|
||||
|
||||
\Cache::store( 'settings_details', $settings );
|
||||
}
|
||||
|
||||
return $settings;
|
||||
return new \Domain\Settings\SettingsRepository( $mdb );
|
||||
}
|
||||
|
||||
public static function visit_counter()
|
||||
public static function settings_details(): array
|
||||
{
|
||||
global $mdb;
|
||||
return $mdb -> get( 'pp_settings', 'value', [ 'param' => 'visits'] );
|
||||
return self::repo()->allSettings();
|
||||
}
|
||||
|
||||
public static function visit_counter(): ?string
|
||||
{
|
||||
return self::repo()->visitCounter();
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
File diff suppressed because one or more lines are too long
18
composer.json
Normal file
18
composer.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^10.5"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Domain\\": "autoload/Domain/",
|
||||
"Shared\\": "autoload/Shared/",
|
||||
"Admin\\": "autoload/Admin/",
|
||||
"Frontend\\": "autoload/Frontend/"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"Tests\\": "tests/"
|
||||
}
|
||||
}
|
||||
}
|
||||
1688
composer.lock
generated
Normal file
1688
composer.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
BIN
composer.phar
Normal file
BIN
composer.phar
Normal file
Binary file not shown.
@@ -1,5 +1,6 @@
|
||||
<?php
|
||||
$database['host'] = 'localhost';
|
||||
$database['host_remote'] = 'host117523.hostido.net.pl';
|
||||
$database['user'] = 'host117523_cmspro';
|
||||
$database['password'] = '3sJADeqKHLqHddfavDeR';
|
||||
$database['name'] = 'host117523_cmspro';
|
||||
|
||||
11
cron.php
11
cron.php
@@ -1,15 +1,6 @@
|
||||
<?php
|
||||
error_reporting( E_ALL ^ E_NOTICE ^ E_STRICT ^ E_WARNING );
|
||||
function __autoload_my_classes( $classname )
|
||||
{
|
||||
$q = explode( '\\' , $classname );
|
||||
$c = array_pop( $q );
|
||||
$f = 'autoload/' . implode( '/' , $q ) . '/class.' . $c . '.php';
|
||||
|
||||
if ( file_exists( $f ) )
|
||||
require_once( $f );
|
||||
}
|
||||
spl_autoload_register( '__autoload_my_classes' );
|
||||
require_once __DIR__ . '/autoload/autoloader.php';
|
||||
date_default_timezone_set( 'Europe/Warsaw' );
|
||||
|
||||
require_once 'config.php';
|
||||
|
||||
178
docs/FORM_EDIT_SYSTEM.md
Normal file
178
docs/FORM_EDIT_SYSTEM.md
Normal file
@@ -0,0 +1,178 @@
|
||||
# Form Edit System - Dokumentacja użycia
|
||||
|
||||
## Architektura
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Controller │
|
||||
│ ┌─────────────────┐ ┌─────────────────┐ │
|
||||
│ │ edit() │ │ save() │ │
|
||||
│ │ - buduje VM │ │ - walidacja │ │
|
||||
│ │ - renderuje │ │ - zapis │ │
|
||||
│ └────────┬────────┘ └─────────────────┘ │
|
||||
└───────────┼─────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ FormEditViewModel │
|
||||
│ - title, formId, data, fields, tabs, actions │
|
||||
│ - validationErrors, persist, languages │
|
||||
└───────────┬─────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ components/form-edit.php (szablon) │
|
||||
│ ┌─────────────────────────────────────────────────────┐ │
|
||||
│ │ FormFieldRenderer - renderuje każde pole │ │
|
||||
│ │ ├─ input, select, textarea, switch │ │
|
||||
│ │ ├─ date, datetime, editor, image │ │
|
||||
│ │ └─ lang_section (zagnieżdżone pola) │ │
|
||||
│ └─────────────────────────────────────────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Pliki systemu
|
||||
|
||||
| Plik | Opis |
|
||||
|------|------|
|
||||
| `autoload/admin/ViewModels/Forms/FormFieldType.php` | Stale typow pol |
|
||||
| `autoload/admin/ViewModels/Forms/FormField.php` | Factory methods per typ |
|
||||
| `autoload/admin/ViewModels/Forms/FormTab.php` | Zakladki |
|
||||
| `autoload/admin/ViewModels/Forms/FormAction.php` | Akcje (zapisz, anuluj) |
|
||||
| `autoload/admin/ViewModels/Forms/FormEditViewModel.php` | ViewModel formularza |
|
||||
| `autoload/admin/Support/Forms/FormValidator.php` | Walidacja pol |
|
||||
| `autoload/admin/Support/Forms/FormRequestHandler.php` | Obsluga POST + persist |
|
||||
| `autoload/admin/Support/Forms/FormFieldRenderer.php` | Renderowanie HTML |
|
||||
| `admin/templates/components/form-edit.php` | Uniwersalny szablon |
|
||||
|
||||
## Przykład użycia w kontrolerze
|
||||
|
||||
```php
|
||||
use admin\ViewModels\Forms\FormEditViewModel;
|
||||
use admin\ViewModels\Forms\FormField;
|
||||
use admin\ViewModels\Forms\FormTab;
|
||||
use admin\ViewModels\Forms\FormAction;
|
||||
use admin\Support\Forms\FormRequestHandler;
|
||||
|
||||
class BannerController
|
||||
{
|
||||
public function edit(): string
|
||||
{
|
||||
$banner = $this->repository->find($id);
|
||||
$languages = \admin\factory\Languages::languages_list();
|
||||
|
||||
$viewModel = new FormEditViewModel(
|
||||
formId: 'banner-edit',
|
||||
title: 'Edycja banera',
|
||||
data: $banner,
|
||||
tabs: [
|
||||
new FormTab('settings', 'Ustawienia', 'fa-wrench'),
|
||||
new FormTab('content', 'Zawartość', 'fa-file'),
|
||||
],
|
||||
fields: [
|
||||
// Zakładka Ustawienia
|
||||
FormField::text('name', [
|
||||
'label' => 'Nazwa',
|
||||
'tab' => 'settings',
|
||||
'required' => true,
|
||||
]),
|
||||
FormField::switch('status', [
|
||||
'label' => 'Aktywny',
|
||||
'tab' => 'settings',
|
||||
]),
|
||||
FormField::date('date_start', [
|
||||
'label' => 'Data rozpoczęcia',
|
||||
'tab' => 'settings',
|
||||
]),
|
||||
|
||||
// Sekcja językowa w zakładce Zawartość
|
||||
FormField::langSection('translations', 'content', [
|
||||
FormField::image('src', ['label' => 'Obraz']),
|
||||
FormField::text('url', ['label' => 'Url']),
|
||||
FormField::editor('text', ['label' => 'Treść']),
|
||||
]),
|
||||
],
|
||||
actions: [
|
||||
FormAction::save('/admin/banners/save', '/admin/banners'),
|
||||
FormAction::cancel('/admin/banners'),
|
||||
],
|
||||
languages: $languages,
|
||||
persist: true,
|
||||
);
|
||||
|
||||
return \Tpl::view('components/form-edit', ['form' => $viewModel]);
|
||||
}
|
||||
|
||||
public function save(): void
|
||||
{
|
||||
$formHandler = new FormRequestHandler();
|
||||
$viewModel = $this->buildFormViewModel(); // jak w edit()
|
||||
|
||||
$result = $formHandler->handleSubmit($viewModel, $_POST);
|
||||
|
||||
if (!$result['success']) {
|
||||
// Błędy walidacji - zapisane automatycznie do sesji
|
||||
echo json_encode(['success' => false, 'errors' => $result['errors']]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Sukces - persist wyczyszczony automatycznie
|
||||
$this->repository->save($result['data']);
|
||||
echo json_encode(['success' => true]);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Dostępne typy pól
|
||||
|
||||
| Typ | Metoda | Opcje |
|
||||
|-----|--------|-------|
|
||||
| `text` | `FormField::text(name, ['label' => '...', 'required' => true])` | placeholder, help |
|
||||
| `number` | `FormField::number(name, [...])` | - |
|
||||
| `email` | `FormField::email(name, [...])` | walidacja formatu |
|
||||
| `password` | `FormField::password(name, [...])` | - |
|
||||
| `date` | `FormField::date(name, [...])` | datetimepicker |
|
||||
| `datetime` | `FormField::datetime(name, [...])` | datetimepicker z czasem |
|
||||
| `switch` | `FormField::switch(name, [...])` | checked (bool) |
|
||||
| `select` | `FormField::select(name, ['options' => [...]])` | options: [key => label] |
|
||||
| `textarea` | `FormField::textarea(name, ['rows' => 4])` | rows |
|
||||
| `editor` | `FormField::editor(name, ['toolbar' => 'MyTool'])` | CKEditor |
|
||||
| `image` | `FormField::image(name, ['filemanager' => true])` | filemanager URL |
|
||||
| `file` | `FormField::file(name, [...])` | filemanager |
|
||||
| `hidden` | `FormField::hidden(name, value)` | - |
|
||||
| `color` | `FormField::color(name, ['label' => '...'])` | HTML5 color picker + text input |
|
||||
| `lang_section` | `FormField::langSection(name, 'tab', [fields])` | pola per język |
|
||||
|
||||
## Walidacja
|
||||
|
||||
Walidacja jest automatyczna na podstawie właściwości pól:
|
||||
- `required` - pole wymagane
|
||||
- `type` = `email` - walidacja formatu e-mail
|
||||
- `type` = `number` - walidacja liczby
|
||||
- `type` = `date` - walidacja formatu YYYY-MM-DD
|
||||
|
||||
Dla sekcji językowych walidacja jest powtarzana dla każdego aktywnego języka.
|
||||
|
||||
## Persist (zapamiętywanie danych)
|
||||
|
||||
Gdy `persist = true`:
|
||||
1. Przy błędzie walidacji dane są zapisywane w `$_SESSION['form_persist'][$formId]`
|
||||
2. Formularz automatycznie przywraca dane z sesji przy ponownym wyświetleniu
|
||||
3. Po udanym zapisie sesja jest czyszczona automatycznie przez `FormRequestHandler`
|
||||
|
||||
## Przerabianie istniejących formularzy
|
||||
|
||||
1. **Kontroler** - zamień `view\Xxx::edit()` na `FormEditViewModel`
|
||||
2. **Repository** - dostosuj `save()` do formatu z `FormRequestHandler` (lub dodaj wsparcie dla obu formatów)
|
||||
3. **Szablon** - usuń stary szablon lub zostaw jako fallback
|
||||
4. **Testy** - zaktualizuj testy jeśli zmienił się format danych
|
||||
|
||||
## Aktualizacja 2026-02-15 (ver. 0.275)
|
||||
|
||||
- Modul `ShopCategory` zostal zmigrowany do warstwy Domain + DI, ale formularz kategorii nadal korzysta z legacy `gridEdit`.
|
||||
- W ramach migracji wydzielono skrypty UI do osobnych partiali `*-custom-script.php` (lista, browse, edycja, produkty), co upraszcza dalsze przepiecie formularza na `components/form-edit`.
|
||||
- Po migracji `ShopCategory` kolejnym kandydatem do pelnej migracji formularza na Form Edit System pozostaje modul `Order` (zgodnie z `REFACTORING_PLAN.md`).
|
||||
|
||||
---
|
||||
*Dokument aktualizowany: 2026-02-15*
|
||||
24
docs/MEMORY.md
Normal file
24
docs/MEMORY.md
Normal file
@@ -0,0 +1,24 @@
|
||||
# Pamięć projektu cnsPRO
|
||||
|
||||
Notatki i wnioski zebrane podczas pracy z kodem. Aktualizowane na bieżąco.
|
||||
|
||||
---
|
||||
|
||||
## Serwer produkcyjny
|
||||
|
||||
- PHP < 8.0 — unikać `match`, named arguments, union types, `str_contains()` itp.
|
||||
- Zamiast `match` używać operatorów trójargumentowych (ternary) lub `if/else`
|
||||
|
||||
## Redis cache — konwencje
|
||||
|
||||
- TTL domyślnie 86400 (24h)
|
||||
- Klucze produktów: `shop\product:{id}:{lang}:{permutation_hash}`
|
||||
- Wzorzec czyszczenia: `CacheHandler::deletePattern("shop\\product:{$id}:*")`
|
||||
- Dane w cache są serializowane — wymagają `unserialize()` po `get()`
|
||||
|
||||
## Aktualizacje klienckie
|
||||
|
||||
- Pliki `*.md` NIGDY nie trafiają do ZIP aktualizacji
|
||||
- `updates/changelog.php` to plik serwisowy repozytorium, nie runtime klienta
|
||||
- Główny `.htaccess` wdrażany osobno, poza ZIP aktualizacji
|
||||
- W archiwum ZIP NIE powinno być folderu z nazwą wersji — struktura zaczyna się od katalogów projektu
|
||||
126
docs/PROJECT_STRUCTURE.md
Normal file
126
docs/PROJECT_STRUCTURE.md
Normal file
@@ -0,0 +1,126 @@
|
||||
# Struktura projektu cmsPRO
|
||||
|
||||
## Punkty wejścia
|
||||
|
||||
| Plik | Opis |
|
||||
|------|------|
|
||||
| `index.php` | Router frontendu |
|
||||
| `admin/index.php` | Router panelu admina |
|
||||
| `ajax.php` | AJAX frontend |
|
||||
| `admin/ajax.php` | AJAX admin |
|
||||
| `api.php` | Publiczne API |
|
||||
| `cron.php` | Zadania cykliczne (newsletter) |
|
||||
| `download.php` | Chronione pobieranie plików |
|
||||
|
||||
Każdy punkt wejścia ładuje centralny autoloader (hybrydowy PSR-4 + legacy):
|
||||
```php
|
||||
require_once __DIR__ . '/autoload/autoloader.php';
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Wzorzec architektoniczny — Static Factory (MVCish)
|
||||
|
||||
```
|
||||
autoload/{admin|front}/
|
||||
├── controls/class.{Module}.php ← obsługa requestów
|
||||
├── factory/class.{Module}.php ← logika biznesowa + DB
|
||||
└── view/class.{Module}.php ← generowanie HTML
|
||||
```
|
||||
|
||||
Przestrzenie nazw: `\admin\controls`, `\admin\factory`, `\admin\view`,
|
||||
`\front\controls`, `\front\factory`, `\front\view`
|
||||
|
||||
---
|
||||
|
||||
## Refaktoryzacja DDD — stan aktualny
|
||||
|
||||
Projekt migruje stopniowo do architektury DDD. Stare klasy stają się
|
||||
cienkimi wrapperami delegującymi do nowych klas w `Shared\` i `Domain\`.
|
||||
|
||||
### Faza 0 ✓ — Autoloader PSR-4
|
||||
Centralny autoloader w `autoload/autoloader.php` (hybrydowy: PSR-4 + legacy class.*.php).
|
||||
Wszystkie 7 punktów wejścia używają jednego pliku. composer.json z PSR-4 mapowaniem:
|
||||
Domain\, Shared\, Admin\, Frontend\ → autoload/.
|
||||
|
||||
### Faza 1 ✓ — Shared utilities (`autoload/Shared/`)
|
||||
|
||||
```
|
||||
autoload/Shared/
|
||||
├── Cache/CacheHandler.php ← \Shared\Cache\CacheHandler
|
||||
├── Email/Email.php ← \Shared\Email\Email
|
||||
├── Helpers/Helpers.php ← \Shared\Helpers\Helpers
|
||||
├── Html/Html.php ← \Shared\Html\Html
|
||||
├── Image/ImageManipulator.php ← \Shared\Image\ImageManipulator
|
||||
├── Security/CsrfToken.php ← \Shared\Security\CsrfToken
|
||||
└── Tpl/Tpl.php ← \Shared\Tpl\Tpl
|
||||
```
|
||||
|
||||
Stare klasy (`class.S.php`, `class.Cache.php`, itd.) są teraz cienkimi
|
||||
wrapperami — zachowana pełna kompatybilność wsteczna.
|
||||
Helpers::send_email() → Email, Helpers::get_token()/is_token_valid() → CsrfToken.
|
||||
|
||||
### Faza 2 (w toku) - Domain Repositories (`autoload/Domain/`)
|
||||
|
||||
```
|
||||
autoload/Domain/
|
||||
├── Languages/LanguagesRepository.php ← \Domain\Languages\LanguagesRepository ✓
|
||||
├── Settings/SettingsRepository.php ← \Domain\Settings\SettingsRepository ✓
|
||||
├── User/UserRepository.php ← \Domain\User\UserRepository ✓
|
||||
├── Pages/PagesRepository.php ← \Domain\Pages\PagesRepository ✓
|
||||
├── Layouts/LayoutsRepository.php ← \Domain\Layouts\LayoutsRepository ✓
|
||||
└── Articles/ArticlesRepository.php ← \Domain\Articles\ArticlesRepository ✓
|
||||
```
|
||||
|
||||
Następne: `Domain\Scontainers`, `Domain\Banners`, `Domain\Authors`, `Domain\Newsletter`, ...
|
||||
---
|
||||
|
||||
## Katalogi
|
||||
|
||||
| Katalog | Zawartość |
|
||||
|---------|-----------|
|
||||
| `autoload/` | Klasy PHP (modele, kontrolery, fabryki, widoki, Shared, Domain) |
|
||||
| `admin/templates/` | Szablony panelu admina (17 modułów) |
|
||||
| `templates/` | Szablony frontendu (systemowe, tylko do odczytu) |
|
||||
| `templates_user/` | Szablony frontendu (nadpisywalne przez użytkownika) |
|
||||
| `layout/` | SCSS → CSS (style.scss → style.css) |
|
||||
| `upload/` | Pliki użytkownika (article_images/, article_files/, filemanager/) |
|
||||
| `libraries/` | Zewnętrzne biblioteki (Medoo, CKEditor, Bootstrap, jQuery…) |
|
||||
| `plugins/` | Hooki (special-actions.php, -middle.php, -end.php) |
|
||||
| `migrations/` | Pliki SQL per wersja (np. `0.304.sql`) |
|
||||
| `updates/` | Paczki ZIP aktualizacji |
|
||||
| `temp/` | Cache plikowy (gzip, 24h, generowany automatycznie) |
|
||||
| `docs/` | Dokumentacja techniczna |
|
||||
|
||||
---
|
||||
|
||||
## Kluczowe klasy
|
||||
|
||||
| Klasa | Opis |
|
||||
|-------|------|
|
||||
| `\Shared\Helpers\Helpers` (`class.S.php`) | Megautylita: sesja, cookie, email, SEO, detekcja botów |
|
||||
| `\Shared\Tpl\Tpl` (`class.Tpl.php`) | Silnik szablonów |
|
||||
| `\Shared\Cache\CacheHandler` (`class.Cache.php`) | Cache plikowy |
|
||||
| `\Shared\Html\Html` (`class.Html.php`) | Builder komponentów formularzy |
|
||||
| `\Shared\Image\ImageManipulator` (`class.Image.php`) | Manipulacja obrazami + WebP |
|
||||
| `class.Article.php` | Model artykułu (ArrayAccess, lazy multilang) |
|
||||
|
||||
---
|
||||
|
||||
## Baza danych
|
||||
|
||||
Prefiks tabel: `pp_`. ORM: Medoo (globalny `$mdb`). Konfiguracja: `config.php`.
|
||||
|
||||
Główne tabele: `pp_users`, `pp_articles`, `pp_articles_langs`, `pp_pages`,
|
||||
`pp_pages_langs`, `pp_languages`, `pp_settings`, `pp_newsletter`,
|
||||
`pp_newsletter_users`, `pp_tags`, `pp_banners`, `pp_layouts`, `pp_backups`.
|
||||
|
||||
---
|
||||
|
||||
## System wielojęzyczny
|
||||
|
||||
- Sesja: `$_SESSION['current-lang']`
|
||||
- Tabela: `pp_languages`
|
||||
- Składnia w treści: `[LANG:klucz]`
|
||||
- Cache tłumaczeń: `$_SESSION['lang-{lang_id}']`
|
||||
|
||||
66
docs/TESTING.md
Normal file
66
docs/TESTING.md
Normal file
@@ -0,0 +1,66 @@
|
||||
# Testowanie cmsPRO
|
||||
|
||||
## Szybki start
|
||||
|
||||
```bash
|
||||
# Instalacja PHPUnit (jednorazowo)
|
||||
composer install
|
||||
|
||||
# Uruchomienie testów
|
||||
./vendor/bin/phpunit
|
||||
|
||||
# Konkretny plik
|
||||
./vendor/bin/phpunit tests/Unit/Domain/Settings/SettingsRepositoryTest.php
|
||||
|
||||
# Konkretny test
|
||||
./vendor/bin/phpunit --filter testAllSettingsReturnsMappedArray
|
||||
```
|
||||
|
||||
## Aktualny stan
|
||||
|
||||
```text
|
||||
Testy jednostkowe dla Domain\ (Faza 2 DDD)
|
||||
```
|
||||
|
||||
## Konfiguracja
|
||||
|
||||
- **PHPUnit 10** via `composer`
|
||||
- **Bootstrap:** `tests/bootstrap.php`
|
||||
- **Config:** `phpunit.xml`
|
||||
|
||||
## Struktura testów
|
||||
|
||||
```
|
||||
tests/
|
||||
├── bootstrap.php ← autoloader + stuby (CacheHandler, S)
|
||||
└── Unit/
|
||||
└── Domain/
|
||||
├── Languages/LanguagesRepositoryTest.php
|
||||
├── Settings/SettingsRepositoryTest.php
|
||||
└── User/UserRepositoryTest.php
|
||||
```
|
||||
|
||||
## Stuby (bootstrap.php)
|
||||
|
||||
- `\Shared\Cache\CacheHandler` — in-memory stub z `fetch()`/`store()`/`delete()`/`reset()`
|
||||
- `\S` — stub z `delete_cache()`, `htacces()`, `get_domain()`, `send_email()`
|
||||
- `medoo` — mockowany przez PHPUnit (`$this->createMock(\medoo::class)`)
|
||||
|
||||
## Dodawanie nowych testów
|
||||
|
||||
1. Plik w `tests/Unit/Domain/<Modul>/<Klasa>Test.php`.
|
||||
2. Rozszerz `PHPUnit\Framework\TestCase`.
|
||||
3. Nazwy metod zaczynaj od `test`.
|
||||
4. Wzorzec AAA: Arrange, Act, Assert.
|
||||
|
||||
## Mockowanie Medoo
|
||||
|
||||
```php
|
||||
$db = $this->createMock(\medoo::class);
|
||||
$db->method('get')->willReturn(['id' => 1]);
|
||||
|
||||
$repo = new SettingsRepository($db);
|
||||
$value = $repo->visitCounter();
|
||||
|
||||
$this->assertSame('1', $value);
|
||||
```
|
||||
73
docs/UPDATE_INSTRUCTIONS.md
Normal file
73
docs/UPDATE_INSTRUCTIONS.md
Normal file
@@ -0,0 +1,73 @@
|
||||
# Instrukcja tworzenia aktualizacji shopPRO
|
||||
|
||||
## Nowy sposób (od v0.301) — automatyczny build script
|
||||
|
||||
### Wymagania
|
||||
- Git z tagami wersji (np. `v0.299`, `v0.300`)
|
||||
- PowerShell
|
||||
|
||||
### Workflow
|
||||
|
||||
```
|
||||
1. Pracuj normalnie: commit, push, commit, push...
|
||||
2. Gdy wersja gotowa:
|
||||
→ git tag v0.XXX
|
||||
→ ./build-update.ps1 -ToTag v0.XXX -ChangelogEntry "NEW - opis"
|
||||
3. Upload plików z updates/0.XX/ na serwer aktualizacji
|
||||
```
|
||||
|
||||
### Użycie build-update.ps1
|
||||
|
||||
```powershell
|
||||
# Podgląd zmian (bez tworzenia plików)
|
||||
./build-update.ps1 -ToTag v0.301 -DryRun
|
||||
|
||||
# Budowanie paczki (auto-detect poprzedniego tagu)
|
||||
./build-update.ps1 -ToTag v0.301 -ChangelogEntry "NEW - opis zmiany"
|
||||
|
||||
# Z jawnym tagiem źródłowym
|
||||
./build-update.ps1 -FromTag v0.300 -ToTag v0.301 -ChangelogEntry "NEW - opis"
|
||||
```
|
||||
|
||||
### Co robi skrypt automatycznie
|
||||
1. `git diff --name-status` między tagami → listy dodanych/zmodyfikowanych/usuniętych plików
|
||||
2. Filtrowanie przez `.updateignore` (pliki deweloperskie, konfiguracyjne itp.)
|
||||
3. Kopiowanie plików do temp, tworzenie ZIP
|
||||
4. SHA256 checksum ZIP-a
|
||||
5. Generowanie `ver_X.XXX_manifest.json`
|
||||
6. Generowanie legacy `_sql.txt` i `_files.txt` (okres przejściowy)
|
||||
7. Aktualizacja `versions.php` i `changelog.php`
|
||||
8. Cleanup
|
||||
|
||||
### Pliki wynikowe
|
||||
- `updates/0.XX/ver_X.XXX.zip` — paczka z plikami
|
||||
- `updates/0.XX/ver_X.XXX_manifest.json` — manifest z checksumem, listą zmian, SQL
|
||||
- `updates/0.XX/ver_X.XXX_sql.txt` — legacy SQL (okres przejściowy)
|
||||
- `updates/0.XX/ver_X.XXX_files.txt` — legacy lista plików do usunięcia (okres przejściowy)
|
||||
|
||||
### Migracje SQL
|
||||
Pliki SQL umieszczaj w `migrations/{version}.sql` (np. `migrations/0.301.sql`).
|
||||
Build script automatycznie je wczyta i umieści w manifeście + legacy `_sql.txt`.
|
||||
|
||||
### Format manifestu
|
||||
```json
|
||||
{
|
||||
"version": "0.301",
|
||||
"date": "2026-02-22",
|
||||
"checksum_zip": "sha256:abc123...",
|
||||
"files": {
|
||||
"modified": ["autoload/Domain/Order/OrderRepository.php"],
|
||||
"added": ["autoload/Domain/Order/NewHelper.php"],
|
||||
"deleted": ["autoload/shop/OldClass.php"]
|
||||
},
|
||||
"directories_deleted": [],
|
||||
"sql": ["ALTER TABLE pp_x ADD COLUMN y INT DEFAULT 0"],
|
||||
"changelog": "NEW - opis zmiany"
|
||||
}
|
||||
```
|
||||
|
||||
### .updateignore
|
||||
Plik w katalogu głównym projektu, wzorce plików wykluczonych z paczek (jak `.gitignore`).
|
||||
|
||||
### INFO
|
||||
pamiętaj że push czasem zwraca błąd autoryzacji, wtedy spróbuj ponownie
|
||||
113
docs/plans/2026-02-28-update-channels-design.md
Normal file
113
docs/plans/2026-02-28-update-channels-design.md
Normal file
@@ -0,0 +1,113 @@
|
||||
# Design: Dwukanałowy system aktualizacji + zarządzanie licencjami
|
||||
|
||||
**Data:** 2026-02-28
|
||||
**Status:** Zatwierdzony
|
||||
|
||||
## Kontekst
|
||||
|
||||
cmsPRO jest używany przez wielu klientów. Celem jest wprowadzenie dwustopniowej dystrybucji aktualizacji:
|
||||
- Kanał **beta** — tylko wybrane testowe instalacje (1–2 strony dewelopera)
|
||||
- Kanał **stable** — wszyscy pozostali klienci
|
||||
|
||||
Zarządzanie odbywa się z panelu admina na `cmspro.project-dc.pl` (ten sam serwer i DB co serwer aktualizacji).
|
||||
|
||||
**Kluczowe ograniczenie:** nowy moduł admina oraz nowe tabele DB nigdy nie trafiają do paczek aktualizacyjnych wysyłanych klientom.
|
||||
|
||||
---
|
||||
|
||||
## Sekcja 1: Baza danych
|
||||
|
||||
Dwie nowe tabele tworzone **ręcznie tylko na serwerze dewelopera**. Nie mogą pojawić się w żadnym SQL migracyjnym dla klientów.
|
||||
|
||||
```sql
|
||||
CREATE TABLE pp_update_licenses (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
`key` VARCHAR(64) NOT NULL UNIQUE,
|
||||
domain VARCHAR(255) NOT NULL,
|
||||
valid_to_date DATE NULL,
|
||||
valid_to_version VARCHAR(10) NULL,
|
||||
beta TINYINT(1) NOT NULL DEFAULT 0,
|
||||
note TEXT NULL,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE pp_update_versions (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
version VARCHAR(10) NOT NULL UNIQUE,
|
||||
channel ENUM('beta','stable') NOT NULL DEFAULT 'beta',
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
promoted_at DATETIME NULL
|
||||
);
|
||||
```
|
||||
|
||||
Dane z `updates/versions.php` (tablica `$license`) migrujemy jednorazowo do `pp_update_licenses`.
|
||||
|
||||
---
|
||||
|
||||
## Sekcja 2: Plik `updates/versions.php`
|
||||
|
||||
Plik zostaje w tym samym miejscu, zmienia się tylko logika (hardkodowane tablice zastępowane odczytem z DB).
|
||||
|
||||
### Nowy przepływ:
|
||||
|
||||
1. `require_once '../config.php'` → dostęp do `$mdb` (Medoo)
|
||||
2. Skan filesystem — lista istniejących ZIPów (jak dotychczas)
|
||||
3. **Auto-discovery:** każdy ZIP nieobecny w `pp_update_versions` → `INSERT IGNORE` z `channel='beta'`. Wrzucenie nowego ZIPa na serwer automatycznie rejestruje go jako beta — bez zmian w `build-update.ps1`.
|
||||
4. Walidacja klucza z `pp_update_licenses` (zamiast `$license[...]`)
|
||||
5. Filtrowanie wersji według kanału:
|
||||
- klient z `beta=1` → wersje gdzie `channel IN ('beta','stable')`
|
||||
- klient z `beta=0` → tylko `channel='stable'`
|
||||
6. Dalsze filtrowanie po `valid_to_date` i `valid_to_version` (bez zmian)
|
||||
7. Wypisanie listy wersji (jak dotychczas)
|
||||
|
||||
---
|
||||
|
||||
## Sekcja 3: Nowy moduł admina `Releases`
|
||||
|
||||
Nowa nazwa `Releases` (odróżnienie od istniejącego modułu `Update` używanego przez klientów do aktualizacji własnej instalacji).
|
||||
|
||||
### Zakładka "Wersje"
|
||||
|
||||
- Tabela z `pp_update_versions` + status istnienia ZIPa na dysku
|
||||
- Kolumny: wersja, kanał, data dodania, data promocji
|
||||
- Akcje: **Promuj do stable** / **Cofnij do beta**
|
||||
|
||||
### Zakładka "Licencje"
|
||||
|
||||
- Tabela z `pp_update_licenses`
|
||||
- Kolumny: domena, klucz (skrócony hash), ważna do daty, ważna do wersji, beta (toggle), notatka
|
||||
- Pełny CRUD: dodaj / edytuj / usuń
|
||||
- Szybki toggle flagi `beta` bezpośrednio z listy
|
||||
|
||||
### Pliki modułu
|
||||
|
||||
Wszystkie wykluczone z paczek ZIP dla klientów:
|
||||
|
||||
```
|
||||
autoload/admin/controls/class.Releases.php
|
||||
autoload/admin/factory/class.Releases.php
|
||||
autoload/admin/view/class.Releases.php
|
||||
admin/templates/releases/main-view.php
|
||||
admin/templates/releases/licenses-view.php
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Sekcja 4: Wykluczenie z aktualizacji klientów
|
||||
|
||||
### Pliki PHP
|
||||
W `build-update.ps1` — dodanie powyższych ścieżek do listy wykluczeń.
|
||||
|
||||
### Tabele DB
|
||||
`pp_update_licenses` i `pp_update_versions` **nigdy** nie pojawiają się w:
|
||||
- plikach `_sql.txt` dołączanych do paczek
|
||||
- sekcji `sql` w plikach `_manifest.json`
|
||||
|
||||
Tabele tworzone jednorazowo ręcznie na serwerze dewelopera.
|
||||
|
||||
---
|
||||
|
||||
## Migracja danych
|
||||
|
||||
Jednorazowy skrypt PHP (uruchamiany ręcznie na serwerze) przenosi dane z `$license[...]` z `versions.php` do `pp_update_licenses`. Po migracji tablica `$license` w `versions.php` jest usuwana.
|
||||
766
docs/plans/2026-02-28-update-channels.md
Normal file
766
docs/plans/2026-02-28-update-channels.md
Normal file
@@ -0,0 +1,766 @@
|
||||
# Releases Module Implementation Plan
|
||||
|
||||
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
||||
|
||||
**Goal:** Wdrożyć dwukanałowy system aktualizacji (beta/stable) z zarządzaniem licencjami w panelu admina.
|
||||
|
||||
**Architecture:** Dwie nowe tabele MySQL (`pp_update_licenses`, `pp_update_versions`) dostępne tylko na serwerze dewelopera. `updates/versions.php` czyta z DB przez Medoo. Nowy moduł `Releases` w panelu admina zarządza wersjami i licencjami. Całość wykluczona z paczek klientów przez `.updateignore`.
|
||||
|
||||
**Tech Stack:** PHP 8.x, Medoo 1.x (`$mdb` global), jQuery UI (dialog), Bootstrap 3 (klasy CSS), `\Tpl` (admin templates z `admin/templates/`), `\S::get()` (POST→GET params).
|
||||
|
||||
---
|
||||
|
||||
### Task 1: Dodaj wykluczenia do `.updateignore`
|
||||
|
||||
**Files:**
|
||||
- Modify: `.updateignore`
|
||||
|
||||
**Step 1: Dopisz nowe wykluczenia**
|
||||
|
||||
Otwórz `.updateignore` i dopisz na końcu:
|
||||
|
||||
```
|
||||
# Moduł zarządzania releaseami (tylko serwer dewelopera)
|
||||
autoload/admin/controls/class.Releases.php
|
||||
autoload/admin/factory/class.Releases.php
|
||||
autoload/admin/view/class.Releases.php
|
||||
admin/templates/releases/
|
||||
|
||||
# Menu dewelopera
|
||||
templates/additional-menu.php
|
||||
```
|
||||
|
||||
**Step 2: Zweryfikuj manualnie**
|
||||
|
||||
Uruchom build w trybie dry-run i sprawdź, że powyższe pliki NIE pojawiają się na liście:
|
||||
```powershell
|
||||
./build-update.ps1 -ToTag v9.999 -DryRun
|
||||
```
|
||||
|
||||
**Step 3: Commit**
|
||||
|
||||
```bash
|
||||
git add .updateignore
|
||||
git commit -m "chore: wyklucz modul Releases i menu dewelopera z paczek klientow"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 2: Utwórz tabele DB (jednorazowo na serwerze)
|
||||
|
||||
**Files:**
|
||||
- Create: `_db_releases_setup.sql` (uruchom raz w phpMyAdmin, nie commituj)
|
||||
|
||||
**Step 1: Utwórz plik SQL**
|
||||
|
||||
```sql
|
||||
CREATE TABLE pp_update_licenses (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
`key` VARCHAR(64) NOT NULL UNIQUE,
|
||||
domain VARCHAR(255) NOT NULL,
|
||||
valid_to_date DATE NULL,
|
||||
valid_to_version VARCHAR(10) NULL,
|
||||
beta TINYINT(1) NOT NULL DEFAULT 0,
|
||||
note TEXT NULL,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
|
||||
CREATE TABLE pp_update_versions (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
version VARCHAR(10) NOT NULL UNIQUE,
|
||||
channel ENUM('beta','stable') NOT NULL DEFAULT 'beta',
|
||||
created_at DATETIME NULL,
|
||||
promoted_at DATETIME NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
```
|
||||
|
||||
**Step 2: Uruchom w phpMyAdmin na serwerze `cmspro.project-dc.pl`**
|
||||
|
||||
Wklej SQL do phpMyAdmin → Execute. Obie tabele muszą być widoczne.
|
||||
|
||||
**Step 3: Nie commituj** — ten plik jest tylko pomocniczy, możesz go usunąć po wykonaniu.
|
||||
|
||||
---
|
||||
|
||||
### Task 3: Utwórz `admin\factory\Releases`
|
||||
|
||||
**Files:**
|
||||
- Create: `autoload/admin/factory/class.Releases.php`
|
||||
|
||||
**Step 1: Napisz klasę**
|
||||
|
||||
```php
|
||||
<?php
|
||||
namespace admin\factory;
|
||||
|
||||
class Releases
|
||||
{
|
||||
public static function get_versions(): array
|
||||
{
|
||||
global $mdb;
|
||||
$rows = $mdb->select('pp_update_versions', '*', ['ORDER' => ['version' => 'DESC']]);
|
||||
if (!$rows) return [];
|
||||
foreach ($rows as &$row)
|
||||
$row['zip_exists'] = file_exists('../updates/' . self::zip_dir($row['version']) . '/ver_' . $row['version'] . '.zip');
|
||||
return $rows;
|
||||
}
|
||||
|
||||
public static function promote(string $version): void
|
||||
{
|
||||
global $mdb;
|
||||
$mdb->update('pp_update_versions',
|
||||
['channel' => 'stable', 'promoted_at' => date('Y-m-d H:i:s')],
|
||||
['version' => $version]
|
||||
);
|
||||
}
|
||||
|
||||
public static function demote(string $version): void
|
||||
{
|
||||
global $mdb;
|
||||
$mdb->update('pp_update_versions',
|
||||
['channel' => 'beta', 'promoted_at' => null],
|
||||
['version' => $version]
|
||||
);
|
||||
}
|
||||
|
||||
public static function get_licenses(): array
|
||||
{
|
||||
global $mdb;
|
||||
return $mdb->select('pp_update_licenses', '*', ['ORDER' => ['domain' => 'ASC']]) ?: [];
|
||||
}
|
||||
|
||||
public static function get_license(int $id): array
|
||||
{
|
||||
global $mdb;
|
||||
return $mdb->get('pp_update_licenses', '*', ['id' => $id]) ?: [];
|
||||
}
|
||||
|
||||
public static function save_license(array $data): void
|
||||
{
|
||||
global $mdb;
|
||||
$row = [
|
||||
'key' => trim($data['key'] ?? ''),
|
||||
'domain' => trim($data['domain'] ?? ''),
|
||||
'valid_to_date' => $data['valid_to_date'] ?: null,
|
||||
'valid_to_version' => $data['valid_to_version'] ?: null,
|
||||
'beta' => (int)(bool)($data['beta'] ?? 0),
|
||||
'note' => trim($data['note'] ?? ''),
|
||||
];
|
||||
if (!empty($data['id']))
|
||||
$mdb->update('pp_update_licenses', $row, ['id' => (int)$data['id']]);
|
||||
else
|
||||
$mdb->insert('pp_update_licenses', $row);
|
||||
}
|
||||
|
||||
public static function delete_license(int $id): void
|
||||
{
|
||||
global $mdb;
|
||||
$mdb->delete('pp_update_licenses', ['id' => $id]);
|
||||
}
|
||||
|
||||
public static function toggle_beta(int $id): void
|
||||
{
|
||||
global $mdb;
|
||||
$license = $mdb->get('pp_update_licenses', ['id', 'beta'], ['id' => $id]);
|
||||
if ($license)
|
||||
$mdb->update('pp_update_licenses', ['beta' => $license['beta'] ? 0 : 1], ['id' => $id]);
|
||||
}
|
||||
|
||||
private static function zip_dir(string $version): string
|
||||
{
|
||||
return substr($version, 0, strlen($version) - (strlen($version) == 5 ? 2 : 1)) . '0';
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Step 2: Weryfikacja składni**
|
||||
|
||||
```bash
|
||||
php -l autoload/admin/factory/class.Releases.php
|
||||
```
|
||||
Oczekiwane: `No syntax errors detected`
|
||||
|
||||
---
|
||||
|
||||
### Task 4: Utwórz `admin\controls\Releases`
|
||||
|
||||
**Files:**
|
||||
- Create: `autoload/admin/controls/class.Releases.php`
|
||||
|
||||
**Step 1: Napisz klasę**
|
||||
|
||||
```php
|
||||
<?php
|
||||
namespace admin\controls;
|
||||
|
||||
class Releases
|
||||
{
|
||||
public static function main_view(): string
|
||||
{
|
||||
return \admin\view\Releases::main_view();
|
||||
}
|
||||
|
||||
public static function promote(): void
|
||||
{
|
||||
$version = trim(\S::get('version'));
|
||||
if ($version)
|
||||
\admin\factory\Releases::promote($version);
|
||||
header('Location: /admin/releases/main_view/');
|
||||
exit;
|
||||
}
|
||||
|
||||
public static function demote(): void
|
||||
{
|
||||
$version = trim(\S::get('version'));
|
||||
if ($version)
|
||||
\admin\factory\Releases::demote($version);
|
||||
header('Location: /admin/releases/main_view/');
|
||||
exit;
|
||||
}
|
||||
|
||||
public static function save_license(): void
|
||||
{
|
||||
\admin\factory\Releases::save_license($_POST);
|
||||
\S::set_message('Licencja została zapisana.');
|
||||
header('Location: /admin/releases/main_view/');
|
||||
exit;
|
||||
}
|
||||
|
||||
public static function delete_license(): void
|
||||
{
|
||||
$id = (int)\S::get('id');
|
||||
if ($id)
|
||||
\admin\factory\Releases::delete_license($id);
|
||||
header('Location: /admin/releases/main_view/');
|
||||
exit;
|
||||
}
|
||||
|
||||
public static function toggle_beta(): void
|
||||
{
|
||||
$id = (int)\S::get('id');
|
||||
if ($id)
|
||||
\admin\factory\Releases::toggle_beta($id);
|
||||
header('Location: /admin/releases/main_view/');
|
||||
exit;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Step 2: Weryfikacja składni**
|
||||
|
||||
```bash
|
||||
php -l autoload/admin/controls/class.Releases.php
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 5: Utwórz `admin\view\Releases`
|
||||
|
||||
**Files:**
|
||||
- Create: `autoload/admin/view/class.Releases.php`
|
||||
|
||||
**Step 1: Napisz klasę**
|
||||
|
||||
```php
|
||||
<?php
|
||||
namespace admin\view;
|
||||
|
||||
class Releases
|
||||
{
|
||||
public static function main_view(): string
|
||||
{
|
||||
$tpl = new \Tpl;
|
||||
$tpl->versions = \admin\factory\Releases::get_versions();
|
||||
$tpl->licenses = \admin\factory\Releases::get_licenses();
|
||||
return $tpl->render('releases/main-view');
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Step 2: Weryfikacja składni**
|
||||
|
||||
```bash
|
||||
php -l autoload/admin/view/class.Releases.php
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 6: Utwórz szablon `admin/templates/releases/main-view.php`
|
||||
|
||||
**Files:**
|
||||
- Create: `admin/templates/releases/main-view.php`
|
||||
|
||||
**Step 1: Utwórz katalog i plik**
|
||||
|
||||
```php
|
||||
<?php global $user; ?>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
|
||||
<!-- TABS NAV -->
|
||||
<ul class="nav nav-tabs releases-tabs" style="margin-bottom:20px">
|
||||
<li class="active"><a href="#" data-tab="versions">Wersje</a></li>
|
||||
<li><a href="#" data-tab="licenses">Licencje</a></li>
|
||||
</ul>
|
||||
|
||||
<!-- TAB: WERSJE -->
|
||||
<div id="tab-versions">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading"><h3 class="panel-title">Wersje</h3></div>
|
||||
<div class="panel-body">
|
||||
<table class="table table-striped table-condensed">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Wersja</th><th>Kanał</th><th>Dodana</th>
|
||||
<th>Promocja do stable</th><th>ZIP</th><th>Akcje</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<? foreach ($this->versions as $v): ?>
|
||||
<tr>
|
||||
<td><strong><?= htmlspecialchars($v['version']) ?></strong></td>
|
||||
<td>
|
||||
<? if ($v['channel'] == 'stable'): ?>
|
||||
<span class="label label-success">stable</span>
|
||||
<? else: ?>
|
||||
<span class="label label-warning">beta</span>
|
||||
<? endif; ?>
|
||||
</td>
|
||||
<td><?= $v['created_at'] ? substr($v['created_at'], 0, 10) : '-' ?></td>
|
||||
<td><?= $v['promoted_at'] ? substr($v['promoted_at'], 0, 10) : '-' ?></td>
|
||||
<td><?= $v['zip_exists'] ? '<span class="text-success">✓</span>' : '<span class="text-danger">✗</span>' ?></td>
|
||||
<td>
|
||||
<? if ($v['channel'] == 'beta'): ?>
|
||||
<a href="/admin/releases/promote/?version=<?= urlencode($v['version']) ?>"
|
||||
class="btn btn-xs btn-success"
|
||||
onclick="return confirm('Promować <?= $v['version'] ?> do stable?')">
|
||||
Promuj →stable
|
||||
</a>
|
||||
<? else: ?>
|
||||
<a href="/admin/releases/demote/?version=<?= urlencode($v['version']) ?>"
|
||||
class="btn btn-xs btn-default"
|
||||
onclick="return confirm('Cofnąć <?= $v['version'] ?> do beta?')">
|
||||
Cofnij →beta
|
||||
</a>
|
||||
<? endif; ?>
|
||||
</td>
|
||||
</tr>
|
||||
<? endforeach; ?>
|
||||
<? if (!$this->versions): ?>
|
||||
<tr><td colspan="6" class="text-center text-muted">Brak wersji w bazie. Wersje będą rejestrowane automatycznie przy pierwszym odpytaniu versions.php.</td></tr>
|
||||
<? endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div><!-- /tab-versions -->
|
||||
|
||||
<!-- TAB: LICENCJE -->
|
||||
<div id="tab-licenses" style="display:none">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading" style="display:flex;justify-content:space-between;align-items:center">
|
||||
<h3 class="panel-title">Licencje</h3>
|
||||
<button class="btn btn-sm btn-system" id="btn-add-license">+ Dodaj licencję</button>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
|
||||
<!-- Formularz dodawania/edycji (ukryty) -->
|
||||
<div id="license-form-box" style="display:none;border:1px solid #ddd;padding:15px;margin-bottom:20px;background:#f9f9f9">
|
||||
<h4 id="license-form-title">Nowa licencja</h4>
|
||||
<form method="post" action="/admin/releases/save_license/">
|
||||
<input type="hidden" name="id" id="f-id" value="">
|
||||
<div class="row">
|
||||
<div class="form-group col-lg-3">
|
||||
<label>Klucz (hash)</label>
|
||||
<input type="text" name="key" id="f-key" class="form-control" placeholder="md5/hash lub pusty dla domyślnego">
|
||||
</div>
|
||||
<div class="form-group col-lg-3">
|
||||
<label>Domena</label>
|
||||
<input type="text" name="domain" id="f-domain" class="form-control" required>
|
||||
</div>
|
||||
<div class="form-group col-lg-2">
|
||||
<label>Ważna do daty</label>
|
||||
<input type="date" name="valid_to_date" id="f-valid-date" class="form-control">
|
||||
</div>
|
||||
<div class="form-group col-lg-2">
|
||||
<label>Ważna do wersji</label>
|
||||
<input type="text" name="valid_to_version" id="f-valid-ver" class="form-control" placeholder="np. 1.618">
|
||||
</div>
|
||||
<div class="form-group col-lg-1">
|
||||
<label>Beta</label><br>
|
||||
<input type="checkbox" name="beta" id="f-beta" value="1">
|
||||
</div>
|
||||
<div class="form-group col-lg-2">
|
||||
<label>Notatka</label>
|
||||
<input type="text" name="note" id="f-note" class="form-control">
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-system btn-sm">Zapisz</button>
|
||||
<button type="button" class="btn btn-default btn-sm" id="btn-cancel-license">Anuluj</button>
|
||||
</form>
|
||||
</div><!-- /license-form-box -->
|
||||
|
||||
<table class="table table-striped table-condensed">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Domena</th><th>Klucz</th><th>Do daty</th>
|
||||
<th>Do wersji</th><th>Beta</th><th>Notatka</th><th>Akcje</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<? foreach ($this->licenses as $lic): ?>
|
||||
<tr>
|
||||
<td><?= htmlspecialchars($lic['domain']) ?></td>
|
||||
<td><code title="<?= htmlspecialchars($lic['key']) ?>">
|
||||
<?= $lic['key'] === '' ? '<em>(domyślny)</em>' : htmlspecialchars(substr($lic['key'], 0, 8)) . '…' ?>
|
||||
</code></td>
|
||||
<td><?= $lic['valid_to_date'] ?: '<em>∞</em>' ?></td>
|
||||
<td><?= $lic['valid_to_version'] ?: '<em>∞</em>' ?></td>
|
||||
<td>
|
||||
<a href="/admin/releases/toggle_beta/?id=<?= $lic['id'] ?>"
|
||||
class="label <?= $lic['beta'] ? 'label-info' : 'label-default' ?>"
|
||||
title="Kliknij aby przełączyć">
|
||||
<?= $lic['beta'] ? 'beta' : 'stable' ?>
|
||||
</a>
|
||||
</td>
|
||||
<td><?= htmlspecialchars($lic['note'] ?? '') ?></td>
|
||||
<td>
|
||||
<button class="btn btn-xs btn-default btn-edit-license"
|
||||
data-id="<?= $lic['id'] ?>"
|
||||
data-key="<?= htmlspecialchars($lic['key']) ?>"
|
||||
data-domain="<?= htmlspecialchars($lic['domain']) ?>"
|
||||
data-valid-date="<?= $lic['valid_to_date'] ?? '' ?>"
|
||||
data-valid-ver="<?= $lic['valid_to_version'] ?? '' ?>"
|
||||
data-beta="<?= $lic['beta'] ?>"
|
||||
data-note="<?= htmlspecialchars($lic['note'] ?? '') ?>">Edytuj</button>
|
||||
<a href="/admin/releases/delete_license/?id=<?= $lic['id'] ?>"
|
||||
class="btn btn-xs btn-danger"
|
||||
onclick="return confirm('Usunąć licencję <?= htmlspecialchars($lic['domain']) ?>?')">Usuń</a>
|
||||
</td>
|
||||
</tr>
|
||||
<? endforeach; ?>
|
||||
<? if (!$this->licenses): ?>
|
||||
<tr><td colspan="7" class="text-center text-muted">Brak licencji. Dodaj pierwszą lub uruchom skrypt migracji.</td></tr>
|
||||
<? endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div><!-- /tab-licenses -->
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Przełączanie tabów
|
||||
$('.releases-tabs a').on('click', function(e) {
|
||||
e.preventDefault();
|
||||
$('.releases-tabs li').removeClass('active');
|
||||
$(this).parent().addClass('active');
|
||||
$('[id^="tab-"]').hide();
|
||||
$('#tab-' + $(this).data('tab')).show();
|
||||
});
|
||||
|
||||
// Formularz: Dodaj nową licencję
|
||||
$('#btn-add-license').on('click', function() {
|
||||
$('#license-form-title').text('Nowa licencja');
|
||||
$('#f-id').val('');
|
||||
$('#f-key, #f-domain, #f-valid-date, #f-valid-ver, #f-note').val('');
|
||||
$('#f-beta').prop('checked', false);
|
||||
$('#license-form-box').slideDown();
|
||||
});
|
||||
|
||||
// Formularz: Edytuj istniejącą licencję
|
||||
$('.btn-edit-license').on('click', function() {
|
||||
var d = $(this).data();
|
||||
$('#license-form-title').text('Edytuj licencję: ' + d.domain);
|
||||
$('#f-id').val(d.id);
|
||||
$('#f-key').val(d.key);
|
||||
$('#f-domain').val(d.domain);
|
||||
$('#f-valid-date').val(d.validDate);
|
||||
$('#f-valid-ver').val(d.validVer);
|
||||
$('#f-beta').prop('checked', d.beta == 1);
|
||||
$('#f-note').val(d.note);
|
||||
$('#license-form-box').slideDown();
|
||||
$('html, body').animate({ scrollTop: $('#license-form-box').offset().top - 20 }, 300);
|
||||
});
|
||||
|
||||
// Anuluj formularz
|
||||
$('#btn-cancel-license').on('click', function() {
|
||||
$('#license-form-box').slideUp();
|
||||
});
|
||||
</script>
|
||||
```
|
||||
|
||||
**Step 2: Zweryfikuj, że szablon jest poprawny**
|
||||
|
||||
Otwórz w przeglądarce: `https://cmspro.project-dc.pl/admin/releases/main_view/`
|
||||
|
||||
Oczekiwane: strona ładuje się bez błędów PHP, widoczne dwa taby, tabela wersji pusta lub z danymi.
|
||||
|
||||
---
|
||||
|
||||
### Task 7: Dodaj pozycję menu dla dewelopera
|
||||
|
||||
**Files:**
|
||||
- Create: `templates/additional-menu.php` (ten plik nie trafia do klientów — dodany do `.updateignore` w Task 1)
|
||||
|
||||
**Step 1: Utwórz plik**
|
||||
|
||||
```php
|
||||
<?php
|
||||
// Menu tylko na serwerze dewelopera — wykluczone z .updateignore
|
||||
?>
|
||||
<div class="title">Developer</div>
|
||||
<ul>
|
||||
<li>
|
||||
<a href="/admin/releases/main_view/">
|
||||
<img src="/admin/css/icons/settings-20-filled.svg">Releases & Licencje
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
```
|
||||
|
||||
**Step 2: Sprawdź w przeglądarce**
|
||||
|
||||
Po odświeżeniu panelu admina powinna pojawić się sekcja "Developer" z linkiem "Releases & Licencje" w menu bocznym.
|
||||
|
||||
---
|
||||
|
||||
### Task 8: Migracja danych licencji do DB
|
||||
|
||||
Licencje z hardkodowanej tablicy `$license` w `updates/versions.php` muszą trafić do `pp_update_licenses` **przed** przejściem na nową wersję `versions.php`.
|
||||
|
||||
**Files:**
|
||||
- Create: `_migrate_licenses.php` (tymczasowy, uruchom raz przez przeglądarkę, potem usuń)
|
||||
|
||||
**Step 1: Utwórz skrypt migracji**
|
||||
|
||||
```php
|
||||
<?php
|
||||
// UWAGA: Uruchom JEDEN raz przez przeglądarkę, potem natychmiast usuń!
|
||||
// URL: https://cmspro.project-dc.pl/_migrate_licenses.php
|
||||
|
||||
require_once 'config.php';
|
||||
require_once 'libraries/medoo/medoo.php';
|
||||
|
||||
$mdb = new medoo([
|
||||
'database_type' => 'mysql',
|
||||
'database_name' => $database['name'],
|
||||
'server' => $database['host'],
|
||||
'username' => $database['user'],
|
||||
'password' => $database['password'],
|
||||
'charset' => 'utf8'
|
||||
]);
|
||||
|
||||
// Wyodrębnij $license z pliku versions.php bez uruchamiania die()
|
||||
$source = file_get_contents('updates/versions.php');
|
||||
|
||||
// Wyciągnij wszystkie wpisy $license['key']['domain'] itd.
|
||||
preg_match_all('/\$license\[\'([^\']*)\'\]\[\'domain\'\]\s*=\s*\'([^\']*)\';/', $source, $m_domain);
|
||||
preg_match_all('/\$license\[\'([^\']*)\'\]\[\'valid_to_date\'\]\s*=\s*\'([^\']*)\';/', $source, $m_date);
|
||||
preg_match_all('/\$license\[\'([^\']*)\'\]\[\'valid_to_version\'\]\s*=\s*\'([^\']*)\';/', $source, $m_ver);
|
||||
|
||||
// Zbuduj mapę po kluczu
|
||||
$dates = array_combine($m_date[1], $m_date[2]);
|
||||
$vers = array_combine($m_ver[1], $m_ver[2]);
|
||||
|
||||
$count = 0;
|
||||
foreach ($m_domain[1] as $i => $key) {
|
||||
$domain = $m_domain[2][$i];
|
||||
$row = [
|
||||
'key' => $key,
|
||||
'domain' => $domain,
|
||||
'valid_to_date' => ($dates[$key] ?? '') ?: null,
|
||||
'valid_to_version' => ($vers[$key] ?? '') ?: null,
|
||||
'beta' => 0,
|
||||
];
|
||||
// Pomiń jeśli już istnieje
|
||||
if ($mdb->has('pp_update_licenses', ['key' => $key])) {
|
||||
echo "SKIP (już istnieje): $domain ($key)<br>";
|
||||
continue;
|
||||
}
|
||||
$mdb->insert('pp_update_licenses', $row);
|
||||
echo "OK: $domain ($key)<br>";
|
||||
$count++;
|
||||
}
|
||||
echo "<hr><strong>Zmigrowano $count licencji.</strong>";
|
||||
echo "<br><strong style='color:red'>USUŃ ten plik z serwera!</strong>";
|
||||
```
|
||||
|
||||
**Step 2: Wgraj na serwer i uruchom**
|
||||
|
||||
```
|
||||
https://cmspro.project-dc.pl/_migrate_licenses.php
|
||||
```
|
||||
|
||||
Oczekiwane: lista "OK: domena (klucz)" dla każdej licencji, na końcu podsumowanie.
|
||||
|
||||
**Step 3: Usuń skrypt z serwera**
|
||||
|
||||
Skasuj `_migrate_licenses.php` — nie commituj go do repozytorium.
|
||||
|
||||
**Step 4: Ustaw flagę beta dla swoich testowych stron**
|
||||
|
||||
W panelu admina `/admin/releases/main_view/` → zakładka Licencje → przy swoich testowych domenach kliknij "stable" aby przełączyć na "beta".
|
||||
|
||||
---
|
||||
|
||||
### Task 9: Przebuduj `updates/versions.php`
|
||||
|
||||
**Files:**
|
||||
- Modify: `updates/versions.php`
|
||||
|
||||
> **WAŻNE:** Wykonaj ten krok dopiero po Task 8 (migracja danych do DB).
|
||||
|
||||
**Step 1: Zastąp całą zawartość pliku**
|
||||
|
||||
```php
|
||||
<?
|
||||
require_once '../config.php';
|
||||
require_once '../libraries/medoo/medoo.php';
|
||||
|
||||
$mdb = new medoo( [
|
||||
'database_type' => 'mysql',
|
||||
'database_name' => $database['name'],
|
||||
'server' => $database['host'],
|
||||
'username' => $database['user'],
|
||||
'password' => $database['password'],
|
||||
'charset' => 'utf8'
|
||||
] );
|
||||
|
||||
$current_ver = 1691; // aktualizowane automatycznie przez build-update.ps1
|
||||
|
||||
// 1. Skan filesystem — lista istniejących ZIPów
|
||||
$versions = [];
|
||||
for ( $i = 1; $i <= $current_ver; $i++ )
|
||||
{
|
||||
$dir = substr( number_format( $i / 1000, 3 ), 0, strlen( number_format( $i / 1000, 3 ) ) - 2 ) . '0';
|
||||
$version_old = number_format( $i / 1000, 2 );
|
||||
$version_new = number_format( $i / 1000, 3 );
|
||||
|
||||
if ( file_exists( '../updates/' . $dir . '/ver_' . $version_old . '.zip' ) )
|
||||
$versions[] = $version_old;
|
||||
|
||||
if ( file_exists( '../updates/' . $dir . '/ver_' . $version_new . '.zip' ) )
|
||||
$versions[] = $version_new;
|
||||
}
|
||||
$versions = array_unique( $versions );
|
||||
|
||||
// 2. Walidacja klucza licencji
|
||||
$license = $mdb->get( 'pp_update_licenses', '*', [ 'key' => ( $_GET['key'] ?? '' ) ] );
|
||||
if ( !$license )
|
||||
die();
|
||||
|
||||
// 3. Sprawdź ważność daty
|
||||
if ( $license['valid_to_date'] && $license['valid_to_date'] < date( 'Y-m-d' ) )
|
||||
die();
|
||||
|
||||
// 4. Auto-discovery: rejestruj nowe ZIPy jako beta
|
||||
$known = array_flip( $mdb->select( 'pp_update_versions', 'version', [] ) ?: [] );
|
||||
foreach ( $versions as $ver )
|
||||
{
|
||||
if ( !isset( $known[$ver] ) )
|
||||
{
|
||||
@$mdb->insert( 'pp_update_versions', [
|
||||
'version' => $ver,
|
||||
'channel' => 'beta',
|
||||
'created_at' => date( 'Y-m-d H:i:s' )
|
||||
] );
|
||||
$known[$ver] = true;
|
||||
}
|
||||
}
|
||||
|
||||
// 5. Filtruj wersje wg kanału (beta widzi beta+stable, reszta tylko stable)
|
||||
$channels = $license['beta'] ? [ 'beta', 'stable' ] : [ 'stable' ];
|
||||
$allowed = array_flip( $mdb->select( 'pp_update_versions', 'version', [ 'channel' => $channels ] ) ?: [] );
|
||||
|
||||
// 6. Wypisz dostępne wersje
|
||||
$valid_to_version = $license['valid_to_version'];
|
||||
foreach ( $versions as $ver )
|
||||
{
|
||||
if ( !isset( $allowed[$ver] ) )
|
||||
continue;
|
||||
|
||||
if ( $valid_to_version && $ver > $valid_to_version )
|
||||
continue;
|
||||
|
||||
echo $ver . PHP_EOL;
|
||||
}
|
||||
```
|
||||
|
||||
**Step 2: Weryfikacja składni**
|
||||
|
||||
```bash
|
||||
php -l updates/versions.php
|
||||
```
|
||||
|
||||
**Step 3: Test ręczny**
|
||||
|
||||
Otwórz w przeglądarce (podaj klucz jednej z migrowanych licencji):
|
||||
```
|
||||
https://cmspro.project-dc.pl/updates/versions.php?key=TWOJ_KLUCZ
|
||||
```
|
||||
|
||||
Oczekiwane:
|
||||
- Dla klucza z `beta=0`: zwraca TYLKO wersje `channel='stable'` (pusta lista jeśli żadna jeszcze niepromowana)
|
||||
- Dla klucza z `beta=1`: zwraca wersje `channel='beta'` i `'stable'`
|
||||
- Dla nieprawidłowego klucza: brak odpowiedzi (die())
|
||||
|
||||
**Step 4: Sprawdź panel aktualizacji u klienta**
|
||||
|
||||
Na swojej testowej stronie (beta=1) otwórz `/admin/update/main_view/` — powinien widzieć dostępne wersje.
|
||||
|
||||
---
|
||||
|
||||
### Task 10: Commit końcowy
|
||||
|
||||
**Step 1: Sprawdź status**
|
||||
|
||||
```bash
|
||||
git status
|
||||
git diff updates/versions.php
|
||||
```
|
||||
|
||||
**Step 2: Commit**
|
||||
|
||||
```bash
|
||||
git add \
|
||||
.updateignore \
|
||||
autoload/admin/factory/class.Releases.php \
|
||||
autoload/admin/controls/class.Releases.php \
|
||||
autoload/admin/view/class.Releases.php \
|
||||
admin/templates/releases/main-view.php \
|
||||
templates/additional-menu.php \
|
||||
updates/versions.php
|
||||
|
||||
git commit -m "$(cat <<'EOF'
|
||||
feat: dwukanalowy system aktualizacji (beta/stable) + zarzadzanie licencjami
|
||||
|
||||
- Nowy modul admin\Releases: lista wersji z promocja beta→stable,
|
||||
CRUD licencji z flaga beta
|
||||
- versions.php czyta z DB (pp_update_licenses, pp_update_versions)
|
||||
zamiast hardkodowanej tablicy $license
|
||||
- Auto-discovery: nowe ZIPy automatycznie rejestrowane jako 'beta'
|
||||
- Calosc wykluczona z paczek klientow przez .updateignore
|
||||
|
||||
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
||||
EOF
|
||||
)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Weryfikacja end-to-end
|
||||
|
||||
Po wdrożeniu sprawdź ręcznie:
|
||||
|
||||
1. **Nowy ZIP (beta):** Wrzuć nowy ZIP na serwer → odpytaj `versions.php` kluczem beta → pojawia się nowa wersja → w panelu admina widoczna jako `beta`
|
||||
2. **Promocja:** Kliknij "Promuj →stable" → odpytaj kluczem `beta=0` → wersja pojawia się na liście
|
||||
3. **Cofnięcie:** Kliknij "Cofnij →beta" → klient `beta=0` nie widzi wersji
|
||||
4. **Licencja:** Dodaj nową licencję przez formularz → weryfikuj w phpMyAdmin
|
||||
5. **Toggle beta:** Kliknij "stable" przy licencji → zmienia się na "beta"
|
||||
6. **Zabezpieczenie:** Wgraj nową wersję CMS do klienta → sprawdź czy `admin/templates/releases/` i `autoload/admin/*/class.Releases.php` NIE znalazły się w ZIPie (build dry-run)
|
||||
11
download.php
11
download.php
@@ -1,15 +1,6 @@
|
||||
<?php
|
||||
error_reporting( E_ALL & ~E_NOTICE & ~E_WARNING );
|
||||
function __autoload_my_classes( $classname )
|
||||
{
|
||||
$q = explode( '\\' , $classname );
|
||||
$c = array_pop( $q );
|
||||
$f = 'autoload/' . implode( '/' , $q ) . '/class.' . $c . '.php';
|
||||
|
||||
if ( file_exists( $f ) )
|
||||
require_once( $f );
|
||||
}
|
||||
spl_autoload_register( '__autoload_my_classes' );
|
||||
require_once __DIR__ . '/autoload/autoloader.php';
|
||||
date_default_timezone_set( 'Europe/Warsaw' );
|
||||
|
||||
require_once 'config.php';
|
||||
|
||||
11
index.php
11
index.php
@@ -1,15 +1,6 @@
|
||||
<?php
|
||||
error_reporting( E_ALL ^ E_NOTICE ^ E_STRICT ^ E_WARNING ^ E_DEPRECATED );
|
||||
function __autoload_my_classes( $classname )
|
||||
{
|
||||
$q = explode( '\\', $classname );
|
||||
$c = array_pop( $q );
|
||||
$f = 'autoload/' . implode( '/', $q ) . '/class.' . $c . '.php';
|
||||
|
||||
if ( file_exists( $f ) )
|
||||
require_once( $f );
|
||||
}
|
||||
spl_autoload_register( '__autoload_my_classes' );
|
||||
require_once __DIR__ . '/autoload/autoloader.php';
|
||||
date_default_timezone_set( 'Europe/Warsaw' );
|
||||
|
||||
require_once 'config.php';
|
||||
|
||||
96
log.txt
96
log.txt
@@ -1,96 +0,0 @@
|
||||
Array
|
||||
(
|
||||
)
|
||||
Array
|
||||
(
|
||||
[title] => "Udar mózgu: Kluczowe znaczenie wczesnej diagnostyki i interwencji medycznej"
|
||||
[entry] => Udar mózgu stanowi jedno z najpoważniejszych zagrożeń dla zdrowia, dotykając każdego roku wiele osób w Polsce. Wczesne rozpoznanie jego objawów oraz niezwłoczna interwencja medyczna mogą uratować życie i zminimalizować ryzyko trwałych uszkodzeń neurologicznych.
|
||||
[text] => <p><span style="font-weight:bold;">Udar mózgu</span> to jedno z najgroźniejszych zagrożeń dla zdrowia człowieka. Każdego roku dotyka on tysiące Polaków, a prawidłowa i szybka reakcja może uratować życie oraz zapobiec trwałym uszkodzeniom neurologicznym. Niestety, wiele osób nie rozpoznaje wczesnych objawów, które wysyła nasz organizm przed udarem. <span style="font-style:italic;">Neurologowie</span> ostrzegają, że pierwsze symptomy mogą być bardzo subtelne, lecz ich zignorowanie niesie poważne konsekwencje.</p>
|
||||
|
||||
<p><span style="font-weight:bold;">Czym jest udar mózgu?</span> Udar mózgu to nagłe zaburzenie krążenia krwi w obrębie mózgu. Może on mieć charakter niedokrwienny (najczęstszy, spowodowany zatkaniem naczynia krwionośnego) lub krwotoczny (wynikający z pęknięcia naczynia i krwawienia do mózgu). Obie formy prowadzą do niedotlenienia obszaru mózgu i szybkiego obumierania komórek nerwowych.</p>
|
||||
|
||||
<p><span style="font-weight:bold;">Najczęściej występujące czynniki ryzyka udaru:</span></p>
|
||||
<ul>
|
||||
<li>nadciśnienie tętnicze</li>
|
||||
<li>cukrzyca</li>
|
||||
<li>choroby serca, w szczególności migotanie przedsionków</li>
|
||||
<li>palenie papierosów</li>
|
||||
<li>otyłość</li>
|
||||
<li>niezdrowa dieta i brak aktywności fizycznej</li>
|
||||
<li>przewlekły stres</li>
|
||||
<li>nadużywanie alkoholu</li>
|
||||
<li>podeszły wiek</li>
|
||||
<li>czynniki genetyczne</li>
|
||||
</ul>
|
||||
|
||||
<p><span style="font-weight:bold;">Organizm ostrzega przed udarem – na jakie symptomy uważać?</span> Wiele osób sądzi, że udar pojawia się nagle i bez zapowiedzi. Tymczasem ciało często wysyła sygnały ostrzegawcze. Wczesne rozpoznanie pozwala szybko zareagować i zapobiec powikłaniom.</p>
|
||||
|
||||
<p><span style="text-decoration:underline;">Najczęściej występujące symptomy zwiastujące udar:</span></p>
|
||||
<ol>
|
||||
<li>
|
||||
<span style="font-weight:bold;">Nagła, jednostronna słabość lub drętwienie:</span>
|
||||
<span>Dochodzi do niej najczęściej w obrębie twarzy, ramienia bądź nogi. Uczucie osłabienia lub brak czucia po jednej stronie ciała to klasyczny znak ostrzegawczy. Zdarza się też opadanie kącika ust.</span>
|
||||
</li>
|
||||
<li>
|
||||
<span style="font-weight:bold;">Problemy z mową i rozumieniem:</span>
|
||||
<span>Chory ma trudności z wypowiadaniem słów, zrozumieniem tego, co się do niego mówi, lub doświadcza bełkotliwej mowy. Może mieć również trudności ze znalezieniem właściwych słów.</span>
|
||||
</li>
|
||||
<li>
|
||||
<span style="font-weight:bold;">Nagłe zaburzenia widzenia:</span>
|
||||
<span>Pojawia się pogorszenie ostrości wzroku, podwójne widzenie lub częściowa utrata wzroku – najczęściej w jednym oku lub po jednej stronie pola widzenia.</span>
|
||||
</li>
|
||||
<li>
|
||||
<span style="font-weight:bold;">Silny, nagły ból głowy:</span>
|
||||
<span>Zwłaszcza jeśli towarzyszą mu nudności, wymioty, zaburzenia świadomości lub sztywność karku. Takie bóle głowy mogą wskazywać na udar krwotoczny.</span>
|
||||
</li>
|
||||
<li>
|
||||
<span style="font-weight:bold;">Zaburzenia równowagi, zawroty głowy, trudności w chodzeniu:</span>
|
||||
<span>Osoba może mieć uczucie wirowania, trudności z koordynacją, traci równowagę oraz przewraca się bez wyraźnej przyczyny.</span>
|
||||
</li>
|
||||
<li>
|
||||
<span style="font-weight:bold;">Nagła utrata przytomności lub splątanie:</span>
|
||||
<span>Osoba staje się zdezorientowana, trudna do nawiązania kontaktu, a nawet może zemdleć.</span>
|
||||
</li>
|
||||
</ol>
|
||||
|
||||
<p>Nie należy lekceważyć nawet przejściowych objawów, które same ustępują. Mogą one oznaczać tzw. przejściowy atak niedokrwienny (TIA), będący <span style="font-weight:bold;">ostrzegawczym sygnałem</span> przed pełnoobjawowym udarem.</p>
|
||||
|
||||
<p><span style="font-weight:bold;">Jak zachować się, gdy pojawią się objawy udaru?</span></p>
|
||||
<ul>
|
||||
<li>Niezwłocznie wezwij pogotowie ratunkowe (numer 112 lub 999).</li>
|
||||
<li>Nie próbuj samodzielnie dojechać do szpitala – leczenie musi rozpocząć się jak najszybciej, najlepiej w ambulansie, gdzie podejmowane są pierwsze działania ratujące życie.</li>
|
||||
<li>Osoba z podejrzeniem udaru powinna być ułożona w bezpiecznej pozycji, nie podawać jej jedzenia ani picia.</li>
|
||||
<li>Poinformuj ratowników o wszystkich zaobserwowanych objawach i ich czasie wystąpienia.</li>
|
||||
</ul>
|
||||
|
||||
<p><span style="font-weight:bold;">Leczenie i rehabilitacja</span></p>
|
||||
<p>Najważniejsze jest rozpoczęcie terapii jak najszybciej po wystąpieniu objawów – istnieje tzw. „<span style="font-style:italic;">złota godzina</span>”, w której wdrożenie leczenia trombolitycznego (rozpuszczającego zakrzepy) istotnie poprawia rokowanie pacjenta. Rehabilitacja neurologiczna powinna rozpocząć się jak najszybciej, co sprzyja powrotowi sprawności i minimalizuje skutki udaru.</p>
|
||||
|
||||
<p><span style="font-weight:bold;">Profilaktyka udaru – co możemy zrobić?</span></p>
|
||||
<ul>
|
||||
<li>Kontroluj ciśnienie tętnicze krwi i regularnie przyjmuj leki przepisane przez lekarza.</li>
|
||||
<li>Wyeliminuj czynniki ryzyka: rzuć palenie, unikaj nadmiernego spożycia alkoholu, dbaj o prawidłową masę ciała.</li>
|
||||
<li>Zadbaj o zbilansowaną dietę bogatą w warzywa, owoce, pełnoziarniste produkty i zdrowe tłuszcze.</li>
|
||||
<li>Systematycznie uprawiaj aktywność fizyczną.</li>
|
||||
<li>Regularnie badaj poziom cholesterolu i cukru we krwi.</li>
|
||||
<li>Lecz choroby przewlekłe takie jak cukrzyca i schorzenia serca.</li>
|
||||
<li>Unikaj przewlekłego stresu i zadbaj o higienę snu.</li>
|
||||
</ul>
|
||||
|
||||
<p><span style="font-weight:bold;">Podsumowanie</span></p>
|
||||
<p>Udar mózgu może dotknąć każdego, szczególnie osoby z grup ryzyka. Wczesne rozpoznanie objawów i szybkie udzielenie pomocy daje największą szansę na przeżycie i powrót do sprawności. Nie lekceważ nawet krótkotrwałych symptomów – każdy z nich może być ostrzeżeniem wysyłanym przez Twój organizm. Edukacja i profilaktyka to najskuteczniejsze narzędzia w walce ze skutkami udaru.</p>
|
||||
[page_id] => 41
|
||||
[action] => add_article
|
||||
)
|
||||
Array
|
||||
(
|
||||
[main_image] => Array
|
||||
(
|
||||
[name] => FLUX.1-dev
|
||||
[type] => image/jpeg
|
||||
[tmp_name] => /tmp/phpafhT1D
|
||||
[error] => 0
|
||||
[size] => 115301
|
||||
)
|
||||
|
||||
)
|
||||
14
phpunit.xml
Normal file
14
phpunit.xml
Normal file
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.5/phpunit.xsd" bootstrap="tests/bootstrap.php" colors="true">
|
||||
<testsuites>
|
||||
<testsuite name="Unit">
|
||||
<directory>tests/Unit</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
<source>
|
||||
<include>
|
||||
<directory>autoload/Domain</directory>
|
||||
<directory>autoload/Shared</directory>
|
||||
</include>
|
||||
</source>
|
||||
</phpunit>
|
||||
@@ -1,15 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
||||
<url>
|
||||
<loc>https://cmsen.project-dc.pl</loc>
|
||||
<lastmod>2021-12-15</lastmod>
|
||||
<changefreq>daily</changefreq>
|
||||
<priority>1</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://cmsen.project-dc.pl/home</loc>
|
||||
<lastmod>2021-12-15</lastmod>
|
||||
<changefreq>daily</changefreq>
|
||||
<priority>1</priority>
|
||||
</url>
|
||||
</urlset>
|
||||
@@ -1,45 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
||||
<url>
|
||||
<loc>https://cmspro.project-dc.pl</loc>
|
||||
<lastmod>2021-12-15</lastmod>
|
||||
<changefreq>daily</changefreq>
|
||||
<priority>1</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://cmspro.project-dc.pl/home</loc>
|
||||
<lastmod>2021-12-15</lastmod>
|
||||
<changefreq>daily</changefreq>
|
||||
<priority>1</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://cmspro.project-dc.pl/s-49-test</loc>
|
||||
<lastmod>2021-12-15</lastmod>
|
||||
<changefreq>daily</changefreq>
|
||||
<priority>1</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://cmspro.project-dc.pl/strona-testowa</loc>
|
||||
<lastmod>2021-12-15</lastmod>
|
||||
<changefreq>daily</changefreq>
|
||||
<priority>1</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://cmspro.project-dc.pl/s-42-strona-druga-2</loc>
|
||||
<lastmod>2021-12-15</lastmod>
|
||||
<changefreq>daily</changefreq>
|
||||
<priority>1</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://cmspro.project-dc.pl/s-48-prowadzenie-spraw-przed-prezesem-urzedu-ochrony-danych-osobowych-prezesem-urzedu-konkurencji-i-konsumentow-oraz-krajowa-izba-odwolawcza</loc>
|
||||
<lastmod>2021-12-15</lastmod>
|
||||
<changefreq>daily</changefreq>
|
||||
<priority>1</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://cmspro.project-dc.pl/a-11-test</loc>
|
||||
<lastmod>2021-12-15</lastmod>
|
||||
<changefreq>daily</changefreq>
|
||||
<priority>0.6</priority>
|
||||
</url>
|
||||
</urlset>
|
||||
@@ -1,4 +0,0 @@
|
||||
]ŽÑ
|
||||
Â0Eÿ%_°ÎIÝíoø.QË,tE–„±7Õ*Ó<<3C>œ$·e´X`¬5öз½-<2D>c˜÷„±Ç"ºGáJNTSK®L>,è@‰G_ñè%Ó+dÓ
|
||||
zPä4Ì<x!<21>µ?±
|
||||
¦+`‹9•ͪï±^«>sJ~¢ÍÓ;<3B>L—B
|
||||
Binary file not shown.
@@ -1 +0,0 @@
|
||||
Kエ2イェホエ2ーホエ2477エ07166qャ<71>ャk
|
||||
BIN
temp/2moto.jpg
BIN
temp/2moto.jpg
Binary file not shown.
|
Before Width: | Height: | Size: 3.8 KiB |
BIN
temp/2moto.png
BIN
temp/2moto.png
Binary file not shown.
|
Before Width: | Height: | Size: 16 KiB |
Binary file not shown.
@@ -1,4 +0,0 @@
|
||||
ŤTËŽÓJÝ#ń–¬ c;ďžÁ!BH<42>tĹEČŞt—ížŘݦ»3ĆFlŘđ
|
||||
<EFBFBD>_ąKćż(;ńŤÍ‰HQuęqR}ęt‹ŘgÉ‚KÉÂĺ2\®ÖŃzŮţ¸6]łĎ–™źHSÄ
|
||||
|
||||
ô/-<0B>ßš0<ůA´v™3„<>Njy,U˘[w´bţ¦ÜľőôősÖ=˛ÎhUŘ\”Űű÷şŇ<C59F>ů\+Üʼn6Eçc~ŘŮé9†ČĽť1żŞŞ'Ľ°ĄŃú^#wŹź”]<¤žşä8ÓÖuÔ]{Ăĺ<šNÚŁz˘ĐÝ))µ9–0?š÷w>†rťJőW
|
||||
@@ -1 +0,0 @@
|
||||
K<EFBFBD>2<EFBFBD><EFBFBD>δ2<EFBFBD>δ2477<EFBFBD>07166q<><71><EFBFBD><0C><><0C><>k
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 13 KiB |
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user