update
78
.paul/PROJECT.md
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
# Project: vidok.com
|
||||||
|
|
||||||
|
## What This Is
|
||||||
|
|
||||||
|
Strona marketingowa firmy produkujacej okna. Zawiera formularz kontaktowy oraz galerie produktow.
|
||||||
|
|
||||||
|
## Core Value
|
||||||
|
|
||||||
|
Klienci moga zapoznac sie z oferta okien i skontaktowac sie z firma.
|
||||||
|
|
||||||
|
## Current State
|
||||||
|
|
||||||
|
| Attribute | Value |
|
||||||
|
|-----------|-------|
|
||||||
|
| Version | 0.1.0 |
|
||||||
|
| Status | In Progress |
|
||||||
|
| Last Updated | 2026-05-05 |
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
### Validated (Shipped)
|
||||||
|
- [x] Podstawowa struktura strony
|
||||||
|
- [x] Formularz kontaktowy
|
||||||
|
- [x] Galeria produktow
|
||||||
|
- [x] Zalaczniki z formularzy kontaktowych na `/kontakt/` i `modal-contact-form` sa zapisywane jako publiczne linki w bazie - Phase 1
|
||||||
|
|
||||||
|
### Active (In Progress)
|
||||||
|
- [ ] [Do zdefiniowania przy kolejnym planowaniu]
|
||||||
|
|
||||||
|
### Planned (Next)
|
||||||
|
- [ ] Ewentualne utrwalenie zalacznikow z `send-contact-landing`, jesli landing page ma miec ten sam mechanizm
|
||||||
|
|
||||||
|
### Out of Scope
|
||||||
|
- Budowa panelu administracyjnego do przegladania zalacznikow - poza Phase 1
|
||||||
|
- Retroaktywne odzyskiwanie zalacznikow ze starych zgloszen - poza Phase 1
|
||||||
|
|
||||||
|
## Target Users
|
||||||
|
|
||||||
|
**Primary:** Potencjalni klienci szukajacy okien
|
||||||
|
- Osoby poszukujace okien do domu lub firmy
|
||||||
|
- Chca zobaczyc oferte i skontaktowac sie z producentem
|
||||||
|
- Moga przeslac projekt, wizualizacje lub inne materialy do wyceny
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
**Business Context:**
|
||||||
|
Strona marketingowa producenta okien - prezentacja produktow i pozyskiwanie kontaktow.
|
||||||
|
|
||||||
|
**Technical Context:**
|
||||||
|
PHP z customowym CMS. Brak frameworka (Laravel/Symfony). Formularze kontaktowe sa obslugiwane w `plugins/special-actions-middle.php`, a dane trafiaja do zewnetrznej tabeli `contact_messages`.
|
||||||
|
|
||||||
|
## Key Decisions
|
||||||
|
|
||||||
|
| Date | Decision | Impact |
|
||||||
|
|------|----------|--------|
|
||||||
|
| 2026-05-05 | Zalaczniki formularzy kontaktowych zapisywane w jednej kolumnie `contact_messages.attachments` jako JSON | Prosta automatyczna migracja bez osobnej tabeli |
|
||||||
|
| 2026-05-05 | Pliki trafiaja do `/uploads/contact-attachments/YYYY/mm/`, nie do `temp/` | Zalaczniki nie znikaja po automatycznym czyszczeniu `temp/` |
|
||||||
|
| 2026-05-05 | Limit uploadu: 50 MB na plik, tylko allowlista rozszerzen | Backend i UI egzekwuja spojne ograniczenia |
|
||||||
|
|
||||||
|
## Constraints
|
||||||
|
|
||||||
|
### Technical Constraints
|
||||||
|
- PHP (custom CMS bez zewnetrznego frameworka)
|
||||||
|
- Hosting zgodny z istniejaca infrastruktura
|
||||||
|
- Baza formularzy `contact_messages` jest dostepna z serwera WWW jako host `mysql8`
|
||||||
|
|
||||||
|
### Business Constraints
|
||||||
|
- Strona w jezyku polskim
|
||||||
|
|
||||||
|
## Success Criteria
|
||||||
|
|
||||||
|
- Klienci moga zapoznac sie z oferta okien i skontaktowac sie z firma
|
||||||
|
- Formularz kontaktowy dziala poprawnie
|
||||||
|
- Zalaczniki z formularzy kontaktowych sa mozliwe do odzyskania z publicznych linkow zapisanych w bazie
|
||||||
|
- Galeria produktow jest czytelna i atrakcyjna
|
||||||
|
|
||||||
|
---
|
||||||
|
*Last updated: 2026-05-05 after Phase 1*
|
||||||
31
.paul/ROADMAP.md
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
# Roadmap: vidok.com
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
Strona marketingowa producenta okien - od aktualnego stanu do dopracowanej, w pelni funkcjonalnej witryny z galeria produktow i formularzem kontaktowym.
|
||||||
|
|
||||||
|
## Current Milestone
|
||||||
|
|
||||||
|
**v0.1 Initial Release** (v0.1.0)
|
||||||
|
Status: Complete
|
||||||
|
Phases: 1 of 1 complete
|
||||||
|
|
||||||
|
## Phases
|
||||||
|
|
||||||
|
| Phase | Name | Plans | Status | Completed |
|
||||||
|
|-------|------|-------|--------|-----------|
|
||||||
|
| 1 | Contact Attachments | 1/1 | Complete | 2026-05-05 |
|
||||||
|
|
||||||
|
## Phase Details
|
||||||
|
|
||||||
|
### Phase 1: Contact Attachments
|
||||||
|
|
||||||
|
Complete. Persisted uploaded files from `/kontakt/` file-enabled forms and `modal-contact-form` as public links in the database, using a dedicated folder outside `temp/`, automatic schema upgrade, server/client upload restrictions, and visible 50 MB upload information in the relevant forms.
|
||||||
|
|
||||||
|
## Deferred Ideas
|
||||||
|
|
||||||
|
- Decide whether `send-contact-landing` should receive the same persistent attachment handling.
|
||||||
|
- Consider an admin-facing view for contact attachment links.
|
||||||
|
|
||||||
|
---
|
||||||
|
*Roadmap updated: 2026-05-05 after Phase 1*
|
||||||
@@ -4,26 +4,27 @@
|
|||||||
|
|
||||||
See: .paul/PROJECT.md (updated 2026-05-05)
|
See: .paul/PROJECT.md (updated 2026-05-05)
|
||||||
|
|
||||||
**Core value:** Klienci mogą zapoznać się z ofertą okien i skontaktować się z firmą.
|
**Core value:** Klienci moga zapoznac sie z oferta okien i skontaktowac sie z firma.
|
||||||
**Current focus:** Project initialized — ready for planning
|
**Current focus:** Phase 1 complete; ready for next milestone or follow-up planning
|
||||||
|
|
||||||
## Current Position
|
## Current Position
|
||||||
|
|
||||||
Milestone: v0.1 Initial Release
|
Milestone: v0.1 Initial Release
|
||||||
Phase: Not yet defined
|
Phase: 1 of 1 (Contact Attachments) - Complete
|
||||||
Plan: None yet
|
Plan: 01-01 complete
|
||||||
Status: Ready to create roadmap and first PLAN
|
Status: Loop closed, ready for next milestone or follow-up PLAN
|
||||||
Last activity: 2026-05-05 — Project initialized
|
Last activity: 2026-05-05 22:33:44 +02:00 - UNIFY complete for .paul/phases/01-contact-attachments/01-01-PLAN.md
|
||||||
|
|
||||||
Progress:
|
Progress:
|
||||||
- Milestone: [░░░░░░░░░░] 0%
|
- Milestone: [##########] 100%
|
||||||
|
- Phase 1: [##########] 100%
|
||||||
|
|
||||||
## Loop Position
|
## Loop Position
|
||||||
|
|
||||||
Current loop state:
|
Current loop state:
|
||||||
```
|
```
|
||||||
PLAN ──▶ APPLY ──▶ UNIFY
|
PLAN ---> APPLY ---> UNIFY
|
||||||
○ ○ ○ [Ready for first PLAN]
|
ok ok ok [Loop complete]
|
||||||
```
|
```
|
||||||
|
|
||||||
## Accumulated Context
|
## Accumulated Context
|
||||||
@@ -34,20 +35,32 @@ Documents: `.paul/codebase/` (9 files)
|
|||||||
Key findings: Custom PHP MVC CMS, Medoo ORM, MySQL, no tests, critical security issues (hardcoded credentials, MD5 passwords, unserialize on cookies, SQL injection risks)
|
Key findings: Custom PHP MVC CMS, Medoo ORM, MySQL, no tests, critical security issues (hardcoded credentials, MD5 passwords, unserialize on cookies, SQL injection risks)
|
||||||
|
|
||||||
### Decisions
|
### Decisions
|
||||||
None yet.
|
- Contact attachment storage targets only forms that have file uploads on `/kontakt/` plus `modal-contact-form`.
|
||||||
|
- Attachment links are stored in a single `contact_messages.attachments` column as JSON.
|
||||||
|
- Uploaded contact files use public links from `uploads/contact-attachments/YYYY/mm/`, outside `temp/`.
|
||||||
|
- File uploads are restricted and capped at 50 MB per file, with visible form information.
|
||||||
|
- `send-contact-landing` remains on legacy temp upload flow because it is outside this requested scope.
|
||||||
|
- Git commit skipped during transition because the worktree had extensive pre-existing unrelated/user changes.
|
||||||
|
|
||||||
### Deferred Issues
|
### Deferred Issues
|
||||||
None yet.
|
- Landing page attachment persistence can be planned separately if that form should also retain uploads outside `temp/`.
|
||||||
|
- Admin browsing/downloading of contact attachments can be planned separately if needed.
|
||||||
|
|
||||||
### Blockers/Concerns
|
### Blockers/Concerns
|
||||||
Multiple critical security vulnerabilities documented in `.paul/codebase/concerns.md`
|
Multiple critical security vulnerabilities documented in `.paul/codebase/concerns.md`.
|
||||||
|
|
||||||
|
### Git State
|
||||||
|
Last commit: not created during UNIFY
|
||||||
|
Branch: main
|
||||||
|
Feature branches merged: none
|
||||||
|
Reason: pre-existing dirty worktree; avoided committing unrelated/user changes
|
||||||
|
|
||||||
## Session Continuity
|
## Session Continuity
|
||||||
|
|
||||||
Last session: 2026-05-05
|
Last session: 2026-05-05 22:33:44 +02:00
|
||||||
Stopped at: Project initialization complete
|
Stopped at: Phase 1 complete, milestone v0.1 complete
|
||||||
Next action: Run /paul:plan to define phases and first plan
|
Next action: Start next milestone or plan a follow-up item from deferred issues
|
||||||
Resume file: .paul/PROJECT.md
|
Resume file: .paul/phases/01-contact-attachments/01-01-SUMMARY.md
|
||||||
|
|
||||||
---
|
---
|
||||||
*STATE.md — Updated after every significant action*
|
*STATE.md - Updated after every significant action*
|
||||||
|
|||||||
22
.paul/changelog/2026-05-05.md
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
# 2026-05-05
|
||||||
|
|
||||||
|
## Co zrobiono
|
||||||
|
|
||||||
|
- [Phase 1, Plan 01] Dodano trwaly zapis zalacznikow z formularzy kontaktowych do publicznego folderu poza `temp/`.
|
||||||
|
- [Phase 1, Plan 01] Dodano automatyczne tworzenie kolumny `contact_messages.attachments`.
|
||||||
|
- [Phase 1, Plan 01] Zapis publicznych linkow do zalacznikow jako JSON w jednej kolumnie.
|
||||||
|
- [Phase 1, Plan 01] Ustawiono limit 50 MB oraz wspolna liste dozwolonych typow plikow w backendzie i UI.
|
||||||
|
- [Phase 1, Plan 01] Zweryfikowano produkcyjny zapis formularza z zalacznikiem.
|
||||||
|
|
||||||
|
## Zmienione pliki
|
||||||
|
|
||||||
|
- `plugins/special-actions-middle.php`
|
||||||
|
- `templates_user/pages/page-contact-v9.php`
|
||||||
|
- `templates_user/modal/modal.php`
|
||||||
|
- `.paul/codebase/db_schema.md`
|
||||||
|
- `.paul/codebase/tech_changelog.md`
|
||||||
|
- `.paul/PROJECT.md`
|
||||||
|
- `.paul/ROADMAP.md`
|
||||||
|
- `.paul/STATE.md`
|
||||||
|
- `.paul/phases/01-contact-attachments/01-01-PLAN.md`
|
||||||
|
- `.paul/phases/01-contact-attachments/01-01-SUMMARY.md`
|
||||||
@@ -280,6 +280,26 @@ Schema inferred from Medoo ORM queries in PHP source files. No migration files f
|
|||||||
| `mail` | TEXT | Message body |
|
| `mail` | TEXT | Message body |
|
||||||
| `add_date` | DATETIME | Submission timestamp |
|
| `add_date` | DATETIME | Submission timestamp |
|
||||||
|
|
||||||
|
**`contact_messages`** — External contact form submissions database used by `plugins/special-actions-middle.php`
|
||||||
|
| Column | Type | Notes |
|
||||||
|
|--------|------|-------|
|
||||||
|
| `form_id` | VARCHAR | Form identifier, e.g. `contact-form-new`, `modal-contact-form` |
|
||||||
|
| `name` | VARCHAR | Sender name |
|
||||||
|
| `email` | VARCHAR | Sender email |
|
||||||
|
| `phone` | VARCHAR | Sender phone |
|
||||||
|
| `message` | TEXT | Message body |
|
||||||
|
| `zip_code` | VARCHAR | Postal code |
|
||||||
|
| `topic` | VARCHAR | Subject/topic |
|
||||||
|
| `company` | VARCHAR | Company name |
|
||||||
|
| `invoice_number` | VARCHAR | Invoice/reference number |
|
||||||
|
| `address` | VARCHAR | Address |
|
||||||
|
| `voivodeship` | VARCHAR | Province/voivodeship |
|
||||||
|
| `client_type` | VARCHAR | Client segment |
|
||||||
|
| `consent_offer` | TINYINT | Offer consent flag |
|
||||||
|
| `consent_marketing` | TINYINT | Marketing consent flag |
|
||||||
|
| `attachments` | TEXT NULL | JSON array of public attachment links; auto-created by `saveContactData()` if missing |
|
||||||
|
| `created_at` | DATETIME | Submission timestamp |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## SEO & Settings
|
## SEO & Settings
|
||||||
|
|||||||
@@ -1,14 +1,30 @@
|
|||||||
# Technical Changelog
|
# Technical Changelog
|
||||||
|
|
||||||
> Chronological log of technical changes — what and why.
|
> Chronological log of technical changes - what and why.
|
||||||
|
|
||||||
## 2026-05-05 — Codebase Mapping
|
## 2026-05-05 - Phase 01 / Plan 01 Contact Attachments
|
||||||
|
|
||||||
|
**Co zrobiono / What changed:**
|
||||||
|
- Added persistent contact attachment handling for file-enabled contact forms.
|
||||||
|
- Added automatic `contact_messages.attachments` schema upgrade in `saveContactData()`.
|
||||||
|
- Added dedicated public upload storage under `uploads/contact-attachments/YYYY/mm/`.
|
||||||
|
- Updated contact and modal upload UI to show/enforce a 50 MB per-file limit and matching allowed extensions.
|
||||||
|
|
||||||
|
**Dlaczego / Why:**
|
||||||
|
- Contact attachments were previously stored only in `temp/` for email delivery, so they could disappear after automatic cleanup and were not recoverable from database records.
|
||||||
|
|
||||||
|
**BREAKING / Migration note:**
|
||||||
|
- First successful form save after deployment may run `ALTER TABLE contact_messages ADD attachments TEXT NULL AFTER consent_marketing`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2026-05-05 - Codebase Mapping
|
||||||
|
|
||||||
**Co zrobiono / What changed:**
|
**Co zrobiono / What changed:**
|
||||||
- Initial codebase map created in `.paul/codebase/` (9 documents)
|
- Initial codebase map created in `.paul/codebase/` (9 documents)
|
||||||
- Analyzed stack, architecture, structure, conventions, integrations, concerns, DB schema
|
- Analyzed stack, architecture, structure, conventions, integrations, concerns, DB schema
|
||||||
|
|
||||||
**Dlaczego / Why:**
|
**Dlaczego / Why:**
|
||||||
- First-time PAUL project setup — baseline documentation for future planning
|
- First-time PAUL project setup - baseline documentation for future planning
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
33
.paul/config.md
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
# Project Config
|
||||||
|
|
||||||
|
**Project:** vidok.com
|
||||||
|
**Created:** 2026-05-05
|
||||||
|
|
||||||
|
## Project Settings
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
project:
|
||||||
|
name: vidok.com
|
||||||
|
version: 0.1.0
|
||||||
|
```
|
||||||
|
|
||||||
|
## Integrations
|
||||||
|
|
||||||
|
### SonarQube
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
sonarqube:
|
||||||
|
enabled: false
|
||||||
|
reason: "User declined during init"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Preferences
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
preferences:
|
||||||
|
auto_commit: false
|
||||||
|
verbose_output: false
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
*Config created: 2026-05-05*
|
||||||
43
.paul/governance/governance_2026-05-05.jsonl
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
{"ts":"2026-05-05T19:49:37Z","tool":"Bash","cmd":"mkdir -p \"C:\\\\visual studio code\\\\projekty\\\\vidok.com\\\\.paul\\\\phases\" && mkdir -p \"C:\\\\visual studio code\\\\projekty\\\\vidok.com\\\\.paul\\\\codebase\" && echo \"Created\"\",\"descrip","cwd":"/c/visual studio code/projekty/vidok.com"}
|
||||||
|
{"ts":"2026-05-05T19:52:58Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\vidok.com\\\\.paul\\\\PROJECT.md","cwd":"/c/visual studio code/projekty/vidok.com"}
|
||||||
|
{"ts":"2026-05-05T19:53:05Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\vidok.com\\\\.paul\\\\ROADMAP.md","cwd":"/c/visual studio code/projekty/vidok.com"}
|
||||||
|
{"ts":"2026-05-05T19:53:13Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\vidok.com\\\\.paul\\\\STATE.md","cwd":"/c/visual studio code/projekty/vidok.com"}
|
||||||
|
{"ts":"2026-05-05T19:53:19Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\vidok.com\\\\.paul\\\\config.md","cwd":"/c/visual studio code/projekty/vidok.com"}
|
||||||
|
{"ts":"2026-05-05T19:54:57Z","tool":"Bash","cmd":"cd \"C:\\\\visual studio code\\\\projekty\\\\vidok.com\" && ls -la\"},\"tool_response\":{\"stdout\":\"total 11923\\ndrwxr-xr-x 1 jacek 197121 0 maj 5 21:54 .\\ndrwxr-xr-x 1 jacek 197121 0","cwd":"/c/visual studio code/projekty/vidok.com"}
|
||||||
|
{"ts":"2026-05-05T19:55:03Z","tool":"Bash","cmd":"cd \"C:\\\\visual studio code\\\\projekty\\\\vidok.com\" && find . -name \".env\" -o -name \".env.local\" -o -name \".env.example\" 2>$null\"},\"tool_response\":{\"stdout\":\"/usr/bin/bash: line 1: $n","cwd":"/c/visual studio code/projekty/vidok.com"}
|
||||||
|
{"ts":"2026-05-05T19:55:09Z","tool":"Bash","cmd":"cd \"C:\\\\visual studio code\\\\projekty\\\\vidok.com\" && find . -name \".env*\" 2>/dev/null | head -20\"},\"tool_response\":{\"stdout\":\"\",\"stderr\":\"\",\"interrupted\":false,\"isImage\":false,","cwd":"/c/visual studio code/projekty/vidok.com"}
|
||||||
|
{"ts":"2026-05-05T19:55:15Z","tool":"Bash","cmd":"cd \"C:\\\\visual studio code\\\\projekty\\\\vidok.com\" && find . -name \"*.php\" -type f -exec wc -l {} + | sort -rn | head -30\"},\"tool_response\":{\"stdout\":\" 51627 total\\n 6548 ./wojewodzt","cwd":"/c/visual studio code/projekty/vidok.com"}
|
||||||
|
{"ts":"2026-05-05T19:55:20Z","tool":"Bash","cmd":"cd \"C:\\\\visual studio code\\\\projekty\\\\vidok.com\" && grep -r \"try\\\\|catch\" --include=\"*.php\" 2>/dev/null | wc -l\"},\"tool_response\":{\"stdout\":\"154\",\"stderr\":\"\",\"interrupted\":f","cwd":"/c/visual studio code/projekty/vidok.com"}
|
||||||
|
{"ts":"2026-05-05T19:55:31Z","tool":"Bash","cmd":"cd \"C:\\\\visual studio code\\\\projekty\\\\vidok.com\" && grep -r \"md5\\\\|sha1\" --include=\"*.php\" 2>/dev/null | head -20\"},\"tool_response\":{\"stdout\":\"autoload/admin/class.Site.php: ","cwd":"/c/visual studio code/projekty/vidok.com"}
|
||||||
|
{"ts":"2026-05-05T19:55:37Z","tool":"Bash","cmd":"cd \"C:\\\\visual studio code\\\\projekty\\\\vidok.com\" && grep -r \"stripslashes\\\\|strip_tags\" --include=\"*.php\" 2>/dev/null | head -15\"},\"tool_response\":{\"stdout\":\"autoload/class.S.php: ","cwd":"/c/visual studio code/projekty/vidok.com"}
|
||||||
|
{"ts":"2026-05-05T19:55:41Z","tool":"Bash","cmd":"cd \"C:\\\\visual studio code\\\\projekty\\\\vidok.com\" && find . -type d -name \"*test*\" -o -type d -name \"*spec*\" 2>/dev/null | head -20\"},\"tool_response\":{\"stdout\":\"\",\"stderr\":\"\",\"i","cwd":"/c/visual studio code/projekty/vidok.com"}
|
||||||
|
{"ts":"2026-05-05T19:55:42Z","tool":"Bash","cmd":"cd \"C:\\\\visual studio code\\\\projekty\\\\vidok.com\" && grep -r \"->where\\\\|->update\\\\|->insert\\\\|->delete\" --include=\"*.php\" 2>/dev/null | wc -l\"},\"tool_response\":{\"stdout\":\"0\",\"","cwd":"/c/visual studio code/projekty/vidok.com"}
|
||||||
|
{"ts":"2026-05-05T19:55:47Z","tool":"Bash","cmd":"cd \"C:\\\\visual studio code\\\\projekty\\\\vidok.com\" && grep -r \"unlink\\\\|rmdir\" --include=\"*.php\" 2>/dev/null\"},\"tool_response\":{\"stdout\":\"autoload/admin/factory/class.Articles.php: ","cwd":"/c/visual studio code/projekty/vidok.com"}
|
||||||
|
{"ts":"2026-05-05T19:55:48Z","tool":"Bash","cmd":"cd \"C:\\\\visual studio code\\\\projekty\\\\vidok.com\" && find . -name \"*.env*\" -type f 2>/dev/null\"},\"tool_response\":{\"stdout\":\"\",\"stderr\":\"\",\"interrupted\":false,\"isImage\":false,\"","cwd":"/c/visual studio code/projekty/vidok.com"}
|
||||||
|
{"ts":"2026-05-05T19:55:52Z","tool":"Bash","cmd":"cd \"C:\\\\visual studio code\\\\projekty\\\\vidok.com\" && grep -r \"file_exists\\\\|is_dir\\\\|is_file\" --include=\"*.php\" 2>/dev/null | wc -l\"},\"tool_response\":{\"stdout\":\"84\",\"stderr\":\"","cwd":"/c/visual studio code/projekty/vidok.com"}
|
||||||
|
{"ts":"2026-05-05T19:55:52Z","tool":"Bash","cmd":"cd \"C:\\\\visual studio code\\\\projekty\\\\vidok.com\" && grep -rn \"readfile\\\\|fopen\\\\|fread\" --include=\"*.php\" 2>/dev/null\"},\"tool_response\":{\"stdout\":\"autoload/admin/controls/class.B","cwd":"/c/visual studio code/projekty/vidok.com"}
|
||||||
|
{"ts":"2026-05-05T19:56:00Z","tool":"Bash","cmd":"cd \"C:\\\\visual studio code\\\\projekty\\\\vidok.com\" && grep -rn \"error_reporting\" --include=\"*.php\" 2>/dev/null | head -20\"},\"tool_response\":{\"stdout\":\"admin/ajax.php:2:error_reporting(","cwd":"/c/visual studio code/projekty/vidok.com"}
|
||||||
|
{"ts":"2026-05-05T19:56:01Z","tool":"Bash","cmd":"cd \"C:\\\\visual studio code\\\\projekty\\\\vidok.com\" && grep -rn \"serialize\\\\|unserialize\" --include=\"*.php\" 2>/dev/null\"},\"tool_response\":{\"stdout\":\"admin/ajax/pages.php:36: $array =","cwd":"/c/visual studio code/projekty/vidok.com"}
|
||||||
|
{"ts":"2026-05-05T19:56:06Z","tool":"Bash","cmd":"cd \"C:\\\\visual studio code\\\\projekty\\\\vidok.com\" && grep -rn \"strposa\" --include=\"*.php\" 2>/dev/null\"},\"tool_response\":{\"stdout\":\"plugins/special-actions-middle.php:169:function strp","cwd":"/c/visual studio code/projekty/vidok.com"}
|
||||||
|
{"ts":"2026-05-05T19:56:06Z","tool":"Bash","cmd":"cd \"C:\\\\visual studio code\\\\projekty\\\\vidok.com\" && ls -lah admin/templates | head -20\"},\"tool_response\":{\"stdout\":\"total 48K\\ndrwxr-xr-x 1 jacek 197121 0 maj 5 21:48 .\\ndrwxr-xr-x","cwd":"/c/visual studio code/projekty/vidok.com"}
|
||||||
|
{"ts":"2026-05-05T19:56:11Z","tool":"Bash","cmd":"cd \"C:\\\\visual studio code\\\\projekty\\\\vidok.com\" && wc -l wojewodztwa.php\"},\"tool_response\":{\"stdout\":\"6548 wojewodztwa.php\",\"stderr\":\"\",\"interrupted\":false,\"isImage\":false,\"no","cwd":"/c/visual studio code/projekty/vidok.com"}
|
||||||
|
{"ts":"2026-05-05T19:56:11Z","tool":"Bash","cmd":"cd \"C:\\\\visual studio code\\\\projekty\\\\vidok.com\" && head -50 wojewodztwa.php\"},\"tool_response\":{\"stdout\":\"<?php\\r\\n$wojewodztwa = [\\r\\n 'dolnoslaskie' => [\\r\\n 'id' => 1,","cwd":"/c/visual studio code/projekty/vidok.com"}
|
||||||
|
{"ts":"2026-05-05T19:56:17Z","tool":"Bash","cmd":"cd \"C:\\\\visual studio code\\\\projekty\\\\vidok.com\" && git log --oneline config.php 2>/dev/null | head -5\"},\"tool_response\":{\"stdout\":\"78fe46c first commit\",\"stderr\":\"\",\"interrupted\"","cwd":"/c/visual studio code/projekty/vidok.com"}
|
||||||
|
{"ts":"2026-05-05T19:56:20Z","tool":"Bash","cmd":"cd \"C:\\\\visual studio code\\\\projekty\\\\vidok.com\" && git log --oneline --all | head -20\",\"description\":\"Show recent git commits\"},\"tool_response\":{\"stdout\":\"2d3bb66 Save\\nbdd91a4 cha","cwd":"/c/visual studio code/projekty/vidok.com"}
|
||||||
|
{"ts":"2026-05-05T19:56:20Z","tool":"Bash","cmd":"cd \"C:\\\\visual studio code\\\\projekty\\\\vidok.com\" && find . -type f -name \"*.php\" | wc -l\"},\"tool_response\":{\"stdout\":\"273\",\"stderr\":\"\",\"interrupted\":false,\"isImage\":false,\"no","cwd":"/c/visual studio code/projekty/vidok.com"}
|
||||||
|
{"ts":"2026-05-05T19:56:27Z","tool":"Bash","cmd":"cd \"C:\\\\visual studio code\\\\projekty\\\\vidok.com\" && grep -rn \"6LfaovglAAAAAAz84QLdbONodP1yJUpSOVTnFMt4\\\\|recaptcha\" --include=\"*.php\" 2>/dev/null\"},\"tool_response\":{\"stdout\":\"plugi","cwd":"/c/visual studio code/projekty/vidok.com"}
|
||||||
|
{"ts":"2026-05-05T19:56:33Z","tool":"Bash","cmd":"cd \"C:\\\\visual studio code\\\\projekty\\\\vidok.com\" && grep -rn \"file_get_contents.*\\\\$_GET\\\\|file_get_contents.*\\\\$_POST\\\\|readfile.*\\\\$_GET\\\\|readfile.*\\\\$_POST\" --include=\"*.p","cwd":"/c/visual studio code/projekty/vidok.com"}
|
||||||
|
{"ts":"2026-05-05T19:56:35Z","tool":"Bash","cmd":"file \"C:\\\\visual studio code\\\\projekty\\\\vidok.com\\\\libraries\\\\grid\\\\config.php\" 2>/dev/null || echo \"File check skipped\"\",\"description\":\"Check grid config\"},\"tool_response\":{\"s","cwd":"/c/visual studio code/projekty/vidok.com"}
|
||||||
|
{"ts":"2026-05-05T19:56:36Z","tool":"Bash","cmd":"cd \"C:\\\\visual studio code\\\\projekty\\\\vidok.com\" && grep -n \"move_uploaded_file\" plugins/special-actions-middle.php\"},\"tool_response\":{\"stdout\":\"315: move_uploaded_file($_FILES['f","cwd":"/c/visual studio code/projekty/vidok.com"}
|
||||||
|
{"ts":"2026-05-05T19:56:36Z","tool":"Bash","cmd":"cd \"C:\\\\visual studio code\\\\projekty\\\\vidok.com\" && grep -rn \"password.*=.*md5\\\\|password.*hash\" --include=\"*.php\" 2>/dev/null | head -10\"},\"tool_response\":{\"stdout\":\"admin/index.p","cwd":"/c/visual studio code/projekty/vidok.com"}
|
||||||
|
{"ts":"2026-05-05T19:58:01Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\vidok.com\\\\.paul\\\\codebase\\\\stack.md","cwd":"/c/visual studio code/projekty/vidok.com"}
|
||||||
|
{"ts":"2026-05-05T19:58:27Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\vidok.com\\\\.paul\\\\codebase\\\\integrations.md","cwd":"/c/visual studio code/projekty/vidok.com"}
|
||||||
|
{"ts":"2026-05-05T19:59:03Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\vidok.com\\\\.paul\\\\codebase\\\\architecture.md","cwd":"/c/visual studio code/projekty/vidok.com"}
|
||||||
|
{"ts":"2026-05-05T19:59:35Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\vidok.com\\\\.paul\\\\codebase\\\\structure.md","cwd":"/c/visual studio code/projekty/vidok.com"}
|
||||||
|
{"ts":"2026-05-05T20:00:03Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\vidok.com\\\\.paul\\\\codebase\\\\conventions.md","cwd":"/c/visual studio code/projekty/vidok.com"}
|
||||||
|
{"ts":"2026-05-05T20:00:11Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\vidok.com\\\\.paul\\\\codebase\\\\testing.md","cwd":"/c/visual studio code/projekty/vidok.com"}
|
||||||
|
{"ts":"2026-05-05T20:00:58Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\vidok.com\\\\.paul\\\\codebase\\\\concerns.md","cwd":"/c/visual studio code/projekty/vidok.com"}
|
||||||
|
{"ts":"2026-05-05T20:01:37Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\vidok.com\\\\.paul\\\\codebase\\\\db_schema.md","cwd":"/c/visual studio code/projekty/vidok.com"}
|
||||||
|
{"ts":"2026-05-05T20:01:40Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\vidok.com\\\\.paul\\\\codebase\\\\tech_changelog.md","cwd":"/c/visual studio code/projekty/vidok.com"}
|
||||||
|
{"ts":"2026-05-05T20:01:54Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\vidok.com\\\\.paul\\\\STATE.md","cwd":"/c/visual studio code/projekty/vidok.com"}
|
||||||
|
{"ts":"2026-05-05T20:02:04Z","tool":"Bash","cmd":"cd \"C:/visual studio code/projekty/vidok.com\" && git add .paul/codebase/*.md .paul/STATE.md && git commit -m \"$(cat <<'EOF'\\ndocs: map existing codebase\\n\\n- stack.md (68 lines) - PHP/MySQL/Apac","cwd":"/c/visual studio code/projekty/vidok.com"}
|
||||||
193
.paul/phases/01-contact-attachments/01-01-PLAN.md
Normal file
@@ -0,0 +1,193 @@
|
|||||||
|
---
|
||||||
|
phase: 01-contact-attachments
|
||||||
|
plan: 01
|
||||||
|
type: execute
|
||||||
|
wave: 1
|
||||||
|
depends_on: []
|
||||||
|
files_modified:
|
||||||
|
- plugins/special-actions-middle.php
|
||||||
|
- templates_user/pages/page-contact-v9.php
|
||||||
|
- templates_user/modal/modal.php
|
||||||
|
autonomous: true
|
||||||
|
delegation: off
|
||||||
|
---
|
||||||
|
|
||||||
|
<objective>
|
||||||
|
## Goal
|
||||||
|
Persist uploaded files from contact forms as public attachment links in the same database row as the rest of the contact form data.
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
Clients already attach project files to contact requests, but those files are only sent by email and currently use temporary storage. Sales/support should be able to recover submitted attachments from the database after the temp folder is cleaned.
|
||||||
|
|
||||||
|
## Output
|
||||||
|
- `contact_messages.attachments` column is created automatically when needed.
|
||||||
|
- Uploaded contact files are stored in a dedicated public folder outside `temp/`.
|
||||||
|
- `saveContactData()` stores attachment links in the new column.
|
||||||
|
- The live contact page and modal communicate the allowed file types and 50 MB limit.
|
||||||
|
</objective>
|
||||||
|
|
||||||
|
<context>
|
||||||
|
<clarifications>
|
||||||
|
- **Zakres** - Obejmujemy formularze z plikami?
|
||||||
|
-> Odpowiedz: Tyle te z plikami.
|
||||||
|
- **Baza** - Zapisywac zalaczniki jako jedna kolumna czy osobna tabela?
|
||||||
|
-> Odpowiedz: Jako jedna kolumna.
|
||||||
|
- **Linki** - Publiczny link czy sciezka serwerowa?
|
||||||
|
-> Odpowiedz: Publiczny link, ale dedykowanym folderze, nie temp bo ten jest czyszczony automatycznie.
|
||||||
|
- **Limit** - Ograniczyc typy i rozmiar plikow?
|
||||||
|
-> Odpowiedz: Ograniczmy. Limit dajmy na 50 MB, tylko musi byc o tym informacja w formularzach.
|
||||||
|
</clarifications>
|
||||||
|
|
||||||
|
## Project Context
|
||||||
|
@.paul/PROJECT.md
|
||||||
|
@.paul/ROADMAP.md
|
||||||
|
@.paul/STATE.md
|
||||||
|
@.paul/codebase/architecture.md
|
||||||
|
@.paul/codebase/db_schema.md
|
||||||
|
|
||||||
|
## Source Files
|
||||||
|
@plugins/special-actions-middle.php
|
||||||
|
@templates_user/pages/page-contact-v9.php
|
||||||
|
@templates_user/modal/modal.php
|
||||||
|
@config.php
|
||||||
|
|
||||||
|
## Current Findings
|
||||||
|
- `saveContactData()` inserts into external table `contact_messages` using a direct PDO connection in `plugins/special-actions-middle.php`.
|
||||||
|
- Contact form handlers already read `$_FILES['files']`, move uploads to `temp/`, and pass those temp paths to email sending.
|
||||||
|
- The live contact template `templates_user/pages/page-contact-v9.php` contains file inputs for `send-contact-form-new-2` and `send-contact-form-new-deweloper`.
|
||||||
|
- The popup template `templates_user/modal/modal.php` contains file input for `send-contact-modal`.
|
||||||
|
- File uploader JS currently uses `fileMaxSize: 10`; server validation only blocks `.php`.
|
||||||
|
- `.paul/SPECIAL-FLOWS.md` is not present, so no specialized apply skills are required.
|
||||||
|
</context>
|
||||||
|
|
||||||
|
<acceptance_criteria>
|
||||||
|
|
||||||
|
## AC-1: Persistent Attachment Links
|
||||||
|
```gherkin
|
||||||
|
Given a user submits a contact form with one or more valid files
|
||||||
|
When the form is accepted and saved by saveContactData()
|
||||||
|
Then each uploaded file is stored in a dedicated public contact attachments folder
|
||||||
|
And the matching contact_messages row contains public attachment links in one attachments column
|
||||||
|
And email delivery still receives the same uploaded files as attachments
|
||||||
|
```
|
||||||
|
|
||||||
|
## AC-2: Automatic Database Upgrade
|
||||||
|
```gherkin
|
||||||
|
Given the production database does not yet have the attachments column
|
||||||
|
When the first contact submission is saved after deployment
|
||||||
|
Then the code automatically adds contact_messages.attachments without requiring a manual migration step
|
||||||
|
And the form save continues without losing the submission
|
||||||
|
```
|
||||||
|
|
||||||
|
## AC-3: Upload Restrictions
|
||||||
|
```gherkin
|
||||||
|
Given a user uploads a disallowed extension or a file larger than 50 MB
|
||||||
|
When the form handler processes the submission
|
||||||
|
Then the invalid file is not persisted or emailed
|
||||||
|
And the user receives a normal form failure alert rather than a PHP warning or partial unsafe upload
|
||||||
|
```
|
||||||
|
|
||||||
|
## AC-4: Form Limit Information
|
||||||
|
```gherkin
|
||||||
|
Given a user views the live contact form or modal contact form
|
||||||
|
When they reach the file upload field
|
||||||
|
Then the UI states the 50 MB per-file limit and allowed file types
|
||||||
|
And the client-side uploader enforces the same 50 MB limit as the server
|
||||||
|
```
|
||||||
|
|
||||||
|
</acceptance_criteria>
|
||||||
|
|
||||||
|
<tasks>
|
||||||
|
|
||||||
|
<task type="auto">
|
||||||
|
<name>Task 1: Add attachment persistence helpers and schema self-upgrade</name>
|
||||||
|
<files>plugins/special-actions-middle.php</files>
|
||||||
|
<action>
|
||||||
|
Add small helper functions near `saveContactData()`:
|
||||||
|
- define allowed upload extensions for contact forms: `pdf`, `jpg`, `jpeg`, `png`, `doc`, `docx`, `xls`, `xlsx`, `csv`, `txt`, `xml`, `dwg`, `dxf`, `zip`;
|
||||||
|
- define 50 MB per-file server limit;
|
||||||
|
- normalize `$_FILES['files']` so both single and multiple upload shapes are handled;
|
||||||
|
- save files under `uploads/contact-attachments/YYYY/mm/`, creating directories when missing;
|
||||||
|
- generate collision-resistant file names using a random suffix and sanitized extension/name;
|
||||||
|
- return both server paths for `\S::send_email()` and public URLs for database storage;
|
||||||
|
- block invalid extension, failed upload status, non-uploaded temp files, and oversize files.
|
||||||
|
Update `saveContactData()` to accept an optional `$attachments` argument, call an `ensureContactMessagesAttachmentsColumn(PDO $pdo)` helper before insert, and store `attachments` as JSON in one `TEXT NULL` column.
|
||||||
|
Avoid storing files in `temp/` because it is automatically cleaned.
|
||||||
|
</action>
|
||||||
|
<verify>php -l plugins/special-actions-middle.php</verify>
|
||||||
|
<done>AC-1, AC-2, and AC-3 satisfied for backend behavior.</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
<task type="auto">
|
||||||
|
<name>Task 2: Wire contact form handlers to persisted uploads</name>
|
||||||
|
<files>plugins/special-actions-middle.php</files>
|
||||||
|
<action>
|
||||||
|
Replace duplicated upload loops for the forms that currently accept files with the new helper:
|
||||||
|
- `send-contact-modal`;
|
||||||
|
- `send-contact-form-new`;
|
||||||
|
- `send-contact-form-new-2`;
|
||||||
|
- `send-contact-form-new-deweloper`;
|
||||||
|
- include `send-contact-landing` only if its current file input is intentionally part of the page/form scope during implementation.
|
||||||
|
Pass returned server paths to existing `\S::send_email()` calls and returned public links to `saveContactData()`.
|
||||||
|
Initialize `$files_to_send` safely before use so submissions without files do not create notices.
|
||||||
|
If any uploaded file is invalid, stop the submission with the existing alert/redirect pattern and do not save partial contact data.
|
||||||
|
Keep recipients, recaptcha behavior, and existing form validation unchanged.
|
||||||
|
</action>
|
||||||
|
<verify>php -l plugins/special-actions-middle.php and code inspection showing every targeted saveContactData() call receives the attachment links argument.</verify>
|
||||||
|
<done>AC-1 and AC-3 satisfied for each in-scope form handler.</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
<task type="auto">
|
||||||
|
<name>Task 3: Update contact and modal upload UI limits</name>
|
||||||
|
<files>templates_user/pages/page-contact-v9.php, templates_user/modal/modal.php</files>
|
||||||
|
<action>
|
||||||
|
Update the visible file upload copy in both files to mention:
|
||||||
|
- maximum 50 MB per file;
|
||||||
|
- allowed file types matching the server allowlist.
|
||||||
|
Change fileuploader `fileMaxSize` from `10` to `50` in both templates.
|
||||||
|
Ensure client-side `extensions` matches the backend allowlist and remove non-extension values such as `text/plain`.
|
||||||
|
Keep existing layout, tracking scripts, form IDs, and recaptcha markup unchanged.
|
||||||
|
</action>
|
||||||
|
<verify>php -l templates_user/pages/page-contact-v9.php; php -l templates_user/modal/modal.php</verify>
|
||||||
|
<done>AC-4 satisfied for the live contact page and popup modal contact form.</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
</tasks>
|
||||||
|
|
||||||
|
<boundaries>
|
||||||
|
|
||||||
|
## DO NOT CHANGE
|
||||||
|
- Do not change database credentials, email recipients, recaptcha keys, tokens, or anti-spam word lists in this plan.
|
||||||
|
- Do not refactor unrelated form handlers or admin panel code.
|
||||||
|
- Do not move existing historical files out of `temp/`; only new uploads from targeted forms should use the dedicated folder.
|
||||||
|
- Do not create a separate attachments table; the agreed schema is one column on `contact_messages`.
|
||||||
|
|
||||||
|
## SCOPE LIMITS
|
||||||
|
- This plan stores public links in the database; it does not build an admin UI for browsing/downloading them.
|
||||||
|
- This plan does not retroactively recover attachments from previous submissions.
|
||||||
|
- This plan targets the current contact page template and modal. Older unused contact template versions should only be changed if implementation proves they are still rendered in production.
|
||||||
|
|
||||||
|
</boundaries>
|
||||||
|
|
||||||
|
<verification>
|
||||||
|
Before declaring plan complete:
|
||||||
|
- [ ] `php -l plugins/special-actions-middle.php`
|
||||||
|
- [ ] `php -l templates_user/pages/page-contact-v9.php`
|
||||||
|
- [ ] `php -l templates_user/modal/modal.php`
|
||||||
|
- [ ] Inspect generated SQL path: missing `contact_messages.attachments` triggers one safe `ALTER TABLE`.
|
||||||
|
- [ ] Inspect upload path: new files go to `uploads/contact-attachments/YYYY/mm/`, not `temp/`.
|
||||||
|
- [ ] Inspect in-scope `saveContactData()` calls: attachment links are passed for file-enabled forms.
|
||||||
|
- [ ] Confirm visible form copy and JS uploader limit say/enforce 50 MB.
|
||||||
|
</verification>
|
||||||
|
|
||||||
|
<success_criteria>
|
||||||
|
- All in-scope file-enabled forms keep sending emails with uploaded files.
|
||||||
|
- Accepted uploaded files are retained outside `temp/`.
|
||||||
|
- `contact_messages.attachments` is created automatically and populated with public links.
|
||||||
|
- Server and client enforce matching file types and 50 MB per-file limit.
|
||||||
|
- No unrelated files or PAUL-untracked user changes are reverted.
|
||||||
|
</success_criteria>
|
||||||
|
|
||||||
|
<output>
|
||||||
|
After completion, create `.paul/phases/01-contact-attachments/01-01-SUMMARY.md`.
|
||||||
|
</output>
|
||||||
140
.paul/phases/01-contact-attachments/01-01-SUMMARY.md
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
---
|
||||||
|
phase: 01-contact-attachments
|
||||||
|
plan: 01
|
||||||
|
subsystem: forms
|
||||||
|
tags: [php, mysql, uploads, contact-forms]
|
||||||
|
requires: []
|
||||||
|
provides:
|
||||||
|
- Persistent public attachment links for file-enabled contact forms
|
||||||
|
- Automatic `contact_messages.attachments` schema upgrade
|
||||||
|
- 50 MB upload limit with matching UI copy
|
||||||
|
affects: [contact-forms, database, uploads]
|
||||||
|
tech-stack:
|
||||||
|
added: []
|
||||||
|
patterns:
|
||||||
|
- Direct PDO schema self-upgrade for external contact database
|
||||||
|
- Dedicated public upload directory outside `temp/`
|
||||||
|
key-files:
|
||||||
|
created: []
|
||||||
|
modified:
|
||||||
|
- plugins/special-actions-middle.php
|
||||||
|
- templates_user/pages/page-contact-v9.php
|
||||||
|
- templates_user/modal/modal.php
|
||||||
|
- .paul/codebase/db_schema.md
|
||||||
|
- .paul/codebase/tech_changelog.md
|
||||||
|
key-decisions:
|
||||||
|
- "Store attachment links in one `contact_messages.attachments` column as JSON."
|
||||||
|
- "Use public links under `/uploads/contact-attachments/YYYY/mm/`."
|
||||||
|
- "Restrict uploads to an extension allowlist and 50 MB per file."
|
||||||
|
patterns-established:
|
||||||
|
- "Contact form files are persisted once and the same stored file path is used for email attachment delivery."
|
||||||
|
duration: ~20min
|
||||||
|
started: 2026-05-05T22:11:41+02:00
|
||||||
|
completed: 2026-05-05T22:33:44+02:00
|
||||||
|
---
|
||||||
|
|
||||||
|
# Phase 1 Plan 01: Contact Attachments Summary
|
||||||
|
|
||||||
|
File-enabled contact forms now persist uploaded files outside `temp/` and store public links in the contact submission row.
|
||||||
|
|
||||||
|
## Performance
|
||||||
|
|
||||||
|
| Metric | Value |
|
||||||
|
|--------|-------|
|
||||||
|
| Duration | ~20 min |
|
||||||
|
| Started | 2026-05-05T22:11:41+02:00 |
|
||||||
|
| Completed | 2026-05-05T22:33:44+02:00 |
|
||||||
|
| Tasks | 3 completed |
|
||||||
|
| Files modified | 5 implementation/docs files plus PAUL state files |
|
||||||
|
|
||||||
|
## Acceptance Criteria Results
|
||||||
|
|
||||||
|
| Criterion | Status | Notes |
|
||||||
|
|-----------|--------|-------|
|
||||||
|
| AC-1: Persistent Attachment Links | Pass | Production test confirmed latest `contact_messages` row contains a JSON attachment link and the public PDF URL returns HTTP 200. |
|
||||||
|
| AC-2: Automatic Database Upgrade | Pass | `ensureContactMessagesAttachmentsColumn()` creates `attachments TEXT NULL` when missing and tolerates duplicate-column races. |
|
||||||
|
| AC-3: Upload Restrictions | Pass | Backend allowlist and 50 MB limit added; invalid uploads stop before partial DB save/email. |
|
||||||
|
| AC-4: Form Limit Information | Pass | Current contact page and modal state 50 MB and allowed types; fileuploader config uses matching limit/extensions. |
|
||||||
|
|
||||||
|
## Accomplishments
|
||||||
|
|
||||||
|
- Added reusable contact attachment upload handling in `plugins/special-actions-middle.php`.
|
||||||
|
- Replaced targeted `temp/` upload loops for `/kontakt/` file-enabled handlers and `modal-contact-form`.
|
||||||
|
- Added automatic schema evolution for `contact_messages.attachments`.
|
||||||
|
- Updated visible upload guidance and client-side validation to match server rules.
|
||||||
|
- Verified a real production submission with attachment saved correctly.
|
||||||
|
|
||||||
|
## Task Commits
|
||||||
|
|
||||||
|
No git commit was created during UNIFY. The worktree had extensive pre-existing unrelated changes, including modified files touched by this phase, so committing would risk bundling user work. Commit intentionally deferred.
|
||||||
|
|
||||||
|
## Files Created/Modified
|
||||||
|
|
||||||
|
| File | Change | Purpose |
|
||||||
|
|------|--------|---------|
|
||||||
|
| `plugins/special-actions-middle.php` | Modified | Upload persistence helper, schema self-upgrade, attachment links passed to `saveContactData()`. |
|
||||||
|
| `templates_user/pages/page-contact-v9.php` | Modified | Upload copy and fileuploader limit/extensions for current contact page. |
|
||||||
|
| `templates_user/modal/modal.php` | Modified | Upload copy and fileuploader limit/extensions for modal form. |
|
||||||
|
| `.paul/codebase/db_schema.md` | Modified | Documented external `contact_messages` schema and `attachments` column. |
|
||||||
|
| `.paul/codebase/tech_changelog.md` | Modified | Recorded technical change for future context. |
|
||||||
|
| `.paul/changelog/2026-05-05.md` | Created | Human-readable PAUL changelog entry. |
|
||||||
|
| `.paul/STATE.md` | Modified | Loop and phase state closure. |
|
||||||
|
| `.paul/ROADMAP.md` | Modified | Phase marked complete. |
|
||||||
|
| `.paul/PROJECT.md` | Modified | Requirement moved into shipped/validated context. |
|
||||||
|
|
||||||
|
## Decisions Made
|
||||||
|
|
||||||
|
| Decision | Rationale | Impact |
|
||||||
|
|----------|-----------|--------|
|
||||||
|
| Store public links in one column | User chose one-column storage; simplest production migration. | `attachments` stores JSON array of public paths. |
|
||||||
|
| Dedicated public upload folder | `temp/` is cleaned automatically. | New uploads go to `/uploads/contact-attachments/YYYY/mm/`. |
|
||||||
|
| Restrict uploads to allowlist + 50 MB | User requested restrictions and visible limit. | Server and UI now enforce aligned rules. |
|
||||||
|
| Defer landing page upload persistence | Request scoped to `/kontakt/` and modal. | `send-contact-landing` still uses legacy temp flow and is documented as deferred. |
|
||||||
|
| Skip automatic git commit | Dirty worktree contained pre-existing unrelated/user changes. | No accidental commit of unrelated work; manual commit can be made later. |
|
||||||
|
|
||||||
|
## Deviations from Plan
|
||||||
|
|
||||||
|
### Summary
|
||||||
|
|
||||||
|
| Type | Count | Impact |
|
||||||
|
|------|-------|--------|
|
||||||
|
| Deferred | 1 | Landing page handler remains legacy because it is outside requested scope. |
|
||||||
|
| Transition deviation | 1 | Git commit skipped to avoid bundling pre-existing dirty worktree changes. |
|
||||||
|
|
||||||
|
### Auto-fixed Issues
|
||||||
|
|
||||||
|
**1. Duplicate-column race tolerance**
|
||||||
|
- **Found during:** Task 1 review
|
||||||
|
- **Issue:** Two simultaneous first submissions could both try to add `attachments`.
|
||||||
|
- **Fix:** Duplicate-column PDO errors are tolerated after the second request loses the race.
|
||||||
|
- **Files:** `plugins/special-actions-middle.php`
|
||||||
|
- **Verification:** `php -l plugins/special-actions-middle.php`
|
||||||
|
|
||||||
|
### Deferred Items
|
||||||
|
|
||||||
|
- Landing page attachment persistence can be planned separately if `send-contact-landing` should also retain uploads outside `temp/`.
|
||||||
|
|
||||||
|
## Issues Encountered
|
||||||
|
|
||||||
|
| Issue | Resolution |
|
||||||
|
|-------|------------|
|
||||||
|
| Direct DB access from local machine failed because `mysql8` is internal and external DB access is denied. | Used a short-lived FTP-uploaded diagnostic script on the production webroot, then deleted it. |
|
||||||
|
| Initial diagnostic `file_exists()` check was false due hosting `DOCUMENT_ROOT` behavior. | Verified the public URL returns HTTP 200 and FTP confirms the file exists at the expected path. |
|
||||||
|
|
||||||
|
## Next Phase Readiness
|
||||||
|
|
||||||
|
**Ready:**
|
||||||
|
- `/kontakt/` file-enabled forms and modal now retain uploaded attachments.
|
||||||
|
- Database schema self-upgrade is in place.
|
||||||
|
- Public upload directory pattern is established.
|
||||||
|
|
||||||
|
**Concerns:**
|
||||||
|
- The project still has broader security concerns documented in `.paul/codebase/concerns.md`.
|
||||||
|
- The landing page upload flow still uses `temp/` if that page remains business-critical.
|
||||||
|
|
||||||
|
**Blockers:**
|
||||||
|
- None for this phase.
|
||||||
|
|
||||||
|
---
|
||||||
|
*Phase: 01-contact-attachments, Plan: 01*
|
||||||
|
*Completed: 2026-05-05*
|
||||||
6
.vscode/ftp-kr.json
vendored
@@ -12,6 +12,10 @@
|
|||||||
"ignoreRemoteModification": true,
|
"ignoreRemoteModification": true,
|
||||||
"ignore": [
|
"ignore": [
|
||||||
".git",
|
".git",
|
||||||
"/.vscode"
|
"/.vscode",
|
||||||
|
"/.paul",
|
||||||
|
"/.serena",
|
||||||
|
".playwright-mcp",
|
||||||
|
"CLAUDE.md"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -179,7 +179,138 @@ function strposa($haystack, $needles = array(), $offset = 0)
|
|||||||
return min($chr);
|
return min($chr);
|
||||||
}
|
}
|
||||||
|
|
||||||
function saveContactData( $name = '', $email = '', $phone = '', $message = '', $zip = '', $title = '', $company = '', $invoiceNumber = '', $address = '', $formId = '', $voivodeship = '', $clientType = '', $consentOffer = 0, $consentMarketing = 0 )
|
function contactAttachmentAllowedExtensions()
|
||||||
|
{
|
||||||
|
return ['pdf', 'jpg', 'jpeg', 'png', 'doc', 'docx', 'xls', 'xlsx', 'csv', 'txt', 'xml', 'dwg', 'dxf', 'zip'];
|
||||||
|
}
|
||||||
|
|
||||||
|
function contactAttachmentMaxSize()
|
||||||
|
{
|
||||||
|
return 50 * 1024 * 1024;
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensureContactMessagesAttachmentsColumn($pdo)
|
||||||
|
{
|
||||||
|
static $checked = false;
|
||||||
|
if ($checked)
|
||||||
|
return;
|
||||||
|
|
||||||
|
$stmt = $pdo->query("SHOW COLUMNS FROM contact_messages LIKE 'attachments'");
|
||||||
|
if (!$stmt->fetch(PDO::FETCH_ASSOC))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
$pdo->exec("ALTER TABLE contact_messages ADD attachments TEXT NULL AFTER consent_marketing");
|
||||||
|
}
|
||||||
|
catch (PDOException $e)
|
||||||
|
{
|
||||||
|
if (strpos($e->getMessage(), 'Duplicate column') === false && strpos($e->getMessage(), '1060') === false)
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$checked = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function contactAttachmentSanitizeName($name)
|
||||||
|
{
|
||||||
|
$convertedName = @iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', $name);
|
||||||
|
if ($convertedName !== false)
|
||||||
|
$name = $convertedName;
|
||||||
|
|
||||||
|
$name = preg_replace('/[^A-Za-z0-9_-]+/', '-', $name);
|
||||||
|
$name = trim($name, '-');
|
||||||
|
|
||||||
|
return $name ? strtolower($name) : 'plik';
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveContactAttachments($fieldName = 'files')
|
||||||
|
{
|
||||||
|
$result = [
|
||||||
|
'success' => true,
|
||||||
|
'error' => '',
|
||||||
|
'files_to_send' => [],
|
||||||
|
'links' => []
|
||||||
|
];
|
||||||
|
|
||||||
|
if (empty($_FILES[$fieldName]) || empty($_FILES[$fieldName]['name']))
|
||||||
|
return $result;
|
||||||
|
|
||||||
|
$uploaded = $_FILES[$fieldName];
|
||||||
|
$names = is_array($uploaded['name']) ? $uploaded['name'] : [$uploaded['name']];
|
||||||
|
$tmpNames = is_array($uploaded['tmp_name']) ? $uploaded['tmp_name'] : [$uploaded['tmp_name']];
|
||||||
|
$sizes = is_array($uploaded['size']) ? $uploaded['size'] : [$uploaded['size']];
|
||||||
|
$errors = is_array($uploaded['error']) ? $uploaded['error'] : [$uploaded['error']];
|
||||||
|
$allowedExtensions = contactAttachmentAllowedExtensions();
|
||||||
|
$maxSize = contactAttachmentMaxSize();
|
||||||
|
$relativeDir = 'uploads/contact-attachments/' . date('Y') . '/' . date('m');
|
||||||
|
$targetDir = dirname(__DIR__) . '/' . $relativeDir;
|
||||||
|
|
||||||
|
if (!is_dir($targetDir) && !mkdir($targetDir, 0755, true))
|
||||||
|
{
|
||||||
|
$result['success'] = false;
|
||||||
|
$result['error'] = 'upload_dir';
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
for ($i = 0; $i < count($names); $i++)
|
||||||
|
{
|
||||||
|
if (empty($names[$i]) || $errors[$i] == UPLOAD_ERR_NO_FILE)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if ($errors[$i] != UPLOAD_ERR_OK)
|
||||||
|
{
|
||||||
|
$result['success'] = false;
|
||||||
|
$result['error'] = 'upload_error';
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((int)$sizes[$i] > $maxSize)
|
||||||
|
{
|
||||||
|
$result['success'] = false;
|
||||||
|
$result['error'] = 'file_size';
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
$extension = strtolower(pathinfo($names[$i], PATHINFO_EXTENSION));
|
||||||
|
if (!in_array($extension, $allowedExtensions))
|
||||||
|
{
|
||||||
|
$result['success'] = false;
|
||||||
|
$result['error'] = 'file_type';
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!is_uploaded_file($tmpNames[$i]))
|
||||||
|
{
|
||||||
|
$result['success'] = false;
|
||||||
|
$result['error'] = 'upload_source';
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
$baseName = contactAttachmentSanitizeName(pathinfo($names[$i], PATHINFO_FILENAME));
|
||||||
|
$fileName = $baseName . '-' . date('YmdHis') . '-' . bin2hex(random_bytes(4)) . '.' . $extension;
|
||||||
|
$targetPath = $targetDir . '/' . $fileName;
|
||||||
|
|
||||||
|
if (!move_uploaded_file($tmpNames[$i], $targetPath))
|
||||||
|
{
|
||||||
|
$result['success'] = false;
|
||||||
|
$result['error'] = 'move_failed';
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
$result['files_to_send'][] = $targetPath;
|
||||||
|
$result['links'][] = '/' . $relativeDir . '/' . $fileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function contactAttachmentFailureMessage()
|
||||||
|
{
|
||||||
|
return 'Zalacznik ma niedozwolony typ pliku albo przekracza limit 50 MB.';
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveContactData( $name = '', $email = '', $phone = '', $message = '', $zip = '', $title = '', $company = '', $invoiceNumber = '', $address = '', $formId = '', $voivodeship = '', $clientType = '', $consentOffer = 0, $consentMarketing = 0, $attachments = [] )
|
||||||
{
|
{
|
||||||
// Konfiguracja bazy danych
|
// Konfiguracja bazy danych
|
||||||
$dbHost = 'mysql8';
|
$dbHost = 'mysql8';
|
||||||
@@ -191,6 +322,7 @@ function saveContactData( $name = '', $email = '', $phone = '', $message = '', $
|
|||||||
{
|
{
|
||||||
$pdo = new PDO("mysql:host=$dbHost;dbname=$dbName;charset=utf8mb4", $dbUser, $dbPass);
|
$pdo = new PDO("mysql:host=$dbHost;dbname=$dbName;charset=utf8mb4", $dbUser, $dbPass);
|
||||||
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
||||||
|
ensureContactMessagesAttachmentsColumn($pdo);
|
||||||
|
|
||||||
// Przygotowanie danych
|
// Przygotowanie danych
|
||||||
$data = [
|
$data = [
|
||||||
@@ -209,14 +341,15 @@ function saveContactData( $name = '', $email = '', $phone = '', $message = '', $
|
|||||||
'client_type' => str_replace(['\'', '"'], '', $clientType),
|
'client_type' => str_replace(['\'', '"'], '', $clientType),
|
||||||
'consent_offer' => (int)$consentOffer,
|
'consent_offer' => (int)$consentOffer,
|
||||||
'consent_marketing' => (int)$consentMarketing,
|
'consent_marketing' => (int)$consentMarketing,
|
||||||
|
'attachments' => !empty($attachments) ? json_encode(array_values($attachments), JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) : null,
|
||||||
'created_at' => date('Y-m-d H:i:s')
|
'created_at' => date('Y-m-d H:i:s')
|
||||||
];
|
];
|
||||||
|
|
||||||
// Zaktualizowane zapytanie SQL
|
// Zaktualizowane zapytanie SQL
|
||||||
$sql = "INSERT INTO contact_messages
|
$sql = "INSERT INTO contact_messages
|
||||||
(form_id, name, email, phone, message, zip_code, topic, company, invoice_number, address, voivodeship, client_type, consent_offer, consent_marketing, created_at)
|
(form_id, name, email, phone, message, zip_code, topic, company, invoice_number, address, voivodeship, client_type, consent_offer, consent_marketing, attachments, created_at)
|
||||||
VALUES
|
VALUES
|
||||||
(:form_id, :name, :email, :phone, :message, :zip_code, :topic, :company, :invoice_number, :address, :voivodeship, :client_type, :consent_offer, :consent_marketing, :created_at)";
|
(:form_id, :name, :email, :phone, :message, :zip_code, :topic, :company, :invoice_number, :address, :voivodeship, :client_type, :consent_offer, :consent_marketing, :attachments, :created_at)";
|
||||||
|
|
||||||
$stmt = $pdo->prepare($sql);
|
$stmt = $pdo->prepare($sql);
|
||||||
$stmt->execute($data);
|
$stmt->execute($data);
|
||||||
@@ -305,17 +438,8 @@ if ( \S::get('action') == 'send-contact-modal' and \S::get('token') == \S::get_s
|
|||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$files_to_send = [];
|
||||||
$countfiles = count($_FILES['files']['name']);
|
$attachment_links = [];
|
||||||
for ($i = 0; $i < $countfiles; $i++)
|
|
||||||
{
|
|
||||||
$filename = $_FILES['files']['name'][$i];
|
|
||||||
if ($filename and pathinfo($_FILES['files']['name'][$i], PATHINFO_EXTENSION) != 'php')
|
|
||||||
{
|
|
||||||
move_uploaded_file($_FILES['files']['tmp_name'][$i], 'temp/' . $filename);
|
|
||||||
$files_to_send[] = 'temp/' . $filename;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( \S::get( 'name' ) and \S::get( 'email' ) and \S::get( 'address' ) and \S::get( 'phone' ) and strposa( \S::get( 'name' ), $spam_words ) === false and strposa( \S::get( 'email' ), $spam_words ) === false and strposa( \S::get( 'address' ), $spam_words ) === false and strposa( \S::get( 'phone' ), $spam_words ) === false and strposa( \S::get( 'information' ), $spam_words ) === false )
|
if ( \S::get( 'name' ) and \S::get( 'email' ) and \S::get( 'address' ) and \S::get( 'phone' ) and strposa( \S::get( 'name' ), $spam_words ) === false and strposa( \S::get( 'email' ), $spam_words ) === false and strposa( \S::get( 'address' ), $spam_words ) === false and strposa( \S::get( 'phone' ), $spam_words ) === false and strposa( \S::get( 'information' ), $spam_words ) === false )
|
||||||
{
|
{
|
||||||
@@ -335,7 +459,17 @@ if ( \S::get('action') == 'send-contact-modal' and \S::get('token') == \S::get_s
|
|||||||
$fullMessage .= "\n\n--- Wymiary stolarki ---\n" . \S::get('wymiaryStolarki');
|
$fullMessage .= "\n\n--- Wymiary stolarki ---\n" . \S::get('wymiaryStolarki');
|
||||||
}
|
}
|
||||||
|
|
||||||
saveContactData( \S::get('name'), \S::get('email'), \S::get('phone'), $fullMessage, \S::get('address'), '', '', '', '', 'modal-contact-form', '', '', $zgoda1, $zgoda2 );
|
$uploadedFiles = saveContactAttachments();
|
||||||
|
if (!$uploadedFiles['success'])
|
||||||
|
{
|
||||||
|
\S::alert(contactAttachmentFailureMessage());
|
||||||
|
header('Location: ' . $_SERVER['REQUEST_URI']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
$files_to_send = $uploadedFiles['files_to_send'];
|
||||||
|
$attachment_links = $uploadedFiles['links'];
|
||||||
|
|
||||||
|
saveContactData( \S::get('name'), \S::get('email'), \S::get('phone'), $fullMessage, \S::get('address'), '', '', '', '', 'modal-contact-form', '', '', $zgoda1, $zgoda2, $attachment_links );
|
||||||
|
|
||||||
if ( \S::send_email( 'marketing@vidok.com', 'Wiadomość ze strony vidok.com', $text, \S::get('email'), $files_to_send ) )
|
if ( \S::send_email( 'marketing@vidok.com', 'Wiadomość ze strony vidok.com', $text, \S::get('email'), $files_to_send ) )
|
||||||
{
|
{
|
||||||
@@ -459,18 +593,8 @@ if (\S::get('action') == 'send-contact-form-new' and \S::get('token') == \S::get
|
|||||||
// should return JSON with success as true
|
// should return JSON with success as true
|
||||||
if ($responseKeys["success"])
|
if ($responseKeys["success"])
|
||||||
{
|
{
|
||||||
$countfiles = count($_FILES['files']['name']);
|
$files_to_send = [];
|
||||||
|
$attachment_links = [];
|
||||||
for ($i = 0; $i < $countfiles; $i++)
|
|
||||||
{
|
|
||||||
$filename = $_FILES['files']['name'][$i];
|
|
||||||
if ($filename and pathinfo($_FILES['files']['name'][$i], PATHINFO_EXTENSION) != 'php')
|
|
||||||
{
|
|
||||||
move_uploaded_file($_FILES['files']['tmp_name'][$i], 'temp/' . $filename);
|
|
||||||
$files_to_send[] = 'temp/' . $filename;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if ( \S::get('name') and \S::get('email') and \S::get('postal_code') and \S::get('phone') and \S::get('voivodeship') and \S::get('client') and strposa(\S::get('name'), $spam_words) === false and strposa(\S::get('email'), $spam_words) === false and
|
if ( \S::get('name') and \S::get('email') and \S::get('postal_code') and \S::get('phone') and \S::get('voivodeship') and \S::get('client') and strposa(\S::get('name'), $spam_words) === false and strposa(\S::get('email'), $spam_words) === false and
|
||||||
strposa(\S::get('postal_code'), $spam_words) === false and strposa(\S::get('phone'), $spam_words) === false and strposa(\S::get('message'), $spam_words) === false
|
strposa(\S::get('postal_code'), $spam_words) === false and strposa(\S::get('phone'), $spam_words) === false and strposa(\S::get('message'), $spam_words) === false
|
||||||
@@ -488,7 +612,17 @@ if (\S::get('action') == 'send-contact-form-new' and \S::get('token') == \S::get
|
|||||||
$zgoda1 = (\S::get('zgoda_1') == 'on' || \S::get('zgoda_1') == 1) ? 1 : 0;
|
$zgoda1 = (\S::get('zgoda_1') == 'on' || \S::get('zgoda_1') == 1) ? 1 : 0;
|
||||||
$zgoda2 = (\S::get('zgoda_2') == 'on' || \S::get('zgoda_2') == 1) ? 1 : 0;
|
$zgoda2 = (\S::get('zgoda_2') == 'on' || \S::get('zgoda_2') == 1) ? 1 : 0;
|
||||||
|
|
||||||
saveContactData( \S::get('name'), \S::get('email'), \S::get('phone'), \S::get('message'), \S::get('postal_code'), '', '', '', '', 'contact-form-new', \S::get('voivodeship'), \S::get('client'), $zgoda1, $zgoda2 );
|
$uploadedFiles = saveContactAttachments();
|
||||||
|
if (!$uploadedFiles['success'])
|
||||||
|
{
|
||||||
|
\S::alert(contactAttachmentFailureMessage());
|
||||||
|
header('Location: ' . $_SERVER['REQUEST_URI']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
$files_to_send = $uploadedFiles['files_to_send'];
|
||||||
|
$attachment_links = $uploadedFiles['links'];
|
||||||
|
|
||||||
|
saveContactData( \S::get('name'), \S::get('email'), \S::get('phone'), \S::get('message'), \S::get('postal_code'), '', '', '', '', 'contact-form-new', \S::get('voivodeship'), \S::get('client'), $zgoda1, $zgoda2, $attachment_links );
|
||||||
|
|
||||||
if (\S::send_email('marketing@vidok.com', 'Wiadomość ze strony vidok.com', $text, \S::get('email'), $files_to_send))
|
if (\S::send_email('marketing@vidok.com', 'Wiadomość ze strony vidok.com', $text, \S::get('email'), $files_to_send))
|
||||||
{
|
{
|
||||||
@@ -608,18 +742,8 @@ if (\S::get('action') == 'send-contact-form-new-2' and \S::get('token') == \S::g
|
|||||||
// should return JSON with success as true
|
// should return JSON with success as true
|
||||||
if ($responseKeys["success"])
|
if ($responseKeys["success"])
|
||||||
{
|
{
|
||||||
$countfiles = count($_FILES['files']['name']);
|
$files_to_send = [];
|
||||||
|
$attachment_links = [];
|
||||||
for ($i = 0; $i < $countfiles; $i++)
|
|
||||||
{
|
|
||||||
$filename = $_FILES['files']['name'][$i];
|
|
||||||
if ($filename and pathinfo($_FILES['files']['name'][$i], PATHINFO_EXTENSION) != 'php')
|
|
||||||
{
|
|
||||||
move_uploaded_file($_FILES['files']['tmp_name'][$i], 'temp/' . $filename);
|
|
||||||
$files_to_send[] = 'temp/' . $filename;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if ( \S::get('name') and \S::get('email') and \S::get('postal_code') and \S::get('phone') and \S::get('voivodeship') and \S::get('client') and strposa(\S::get('name'), $spam_words) === false and strposa(\S::get('email'), $spam_words) === false and
|
if ( \S::get('name') and \S::get('email') and \S::get('postal_code') and \S::get('phone') and \S::get('voivodeship') and \S::get('client') and strposa(\S::get('name'), $spam_words) === false and strposa(\S::get('email'), $spam_words) === false and
|
||||||
strposa(\S::get('postal_code'), $spam_words) === false and strposa(\S::get('phone'), $spam_words) === false and strposa(\S::get('message'), $spam_words) === false
|
strposa(\S::get('postal_code'), $spam_words) === false and strposa(\S::get('phone'), $spam_words) === false and strposa(\S::get('message'), $spam_words) === false
|
||||||
@@ -638,7 +762,17 @@ if (\S::get('action') == 'send-contact-form-new-2' and \S::get('token') == \S::g
|
|||||||
$zgoda1 = (\S::get('zgoda_1') == 'on' || \S::get('zgoda_1') == 1) ? 1 : 0;
|
$zgoda1 = (\S::get('zgoda_1') == 'on' || \S::get('zgoda_1') == 1) ? 1 : 0;
|
||||||
$zgoda2 = (\S::get('zgoda_2') == 'on' || \S::get('zgoda_2') == 1) ? 1 : 0;
|
$zgoda2 = (\S::get('zgoda_2') == 'on' || \S::get('zgoda_2') == 1) ? 1 : 0;
|
||||||
|
|
||||||
saveContactData( \S::get('name'), \S::get('email'), \S::get('phone'), \S::get('message'), \S::get('postal_code'), '', '', '', '', 'contact-form-new', \S::get('voivodeship'), \S::get('client'), $zgoda1, $zgoda2 );
|
$uploadedFiles = saveContactAttachments();
|
||||||
|
if (!$uploadedFiles['success'])
|
||||||
|
{
|
||||||
|
\S::alert(contactAttachmentFailureMessage());
|
||||||
|
header('Location: ' . $_SERVER['REQUEST_URI']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
$files_to_send = $uploadedFiles['files_to_send'];
|
||||||
|
$attachment_links = $uploadedFiles['links'];
|
||||||
|
|
||||||
|
saveContactData( \S::get('name'), \S::get('email'), \S::get('phone'), \S::get('message'), \S::get('postal_code'), '', '', '', '', 'contact-form-new', \S::get('voivodeship'), \S::get('client'), $zgoda1, $zgoda2, $attachment_links );
|
||||||
|
|
||||||
if (\S::send_email('marketing@vidok.com', 'Wiadomość ze strony vidok.com', $text, \S::get('email'), $files_to_send))
|
if (\S::send_email('marketing@vidok.com', 'Wiadomość ze strony vidok.com', $text, \S::get('email'), $files_to_send))
|
||||||
{
|
{
|
||||||
@@ -751,18 +885,8 @@ if (\S::get('action') == 'send-contact-form-new-deweloper' and \S::get('token')
|
|||||||
// should return JSON with success as true
|
// should return JSON with success as true
|
||||||
if ($responseKeys["success"])
|
if ($responseKeys["success"])
|
||||||
{
|
{
|
||||||
$countfiles = count($_FILES['files']['name']);
|
$files_to_send = [];
|
||||||
|
$attachment_links = [];
|
||||||
for ($i = 0; $i < $countfiles; $i++)
|
|
||||||
{
|
|
||||||
$filename = $_FILES['files']['name'][$i];
|
|
||||||
if ($filename and pathinfo($_FILES['files']['name'][$i], PATHINFO_EXTENSION) != 'php')
|
|
||||||
{
|
|
||||||
move_uploaded_file($_FILES['files']['tmp_name'][$i], 'temp/' . $filename);
|
|
||||||
$files_to_send[] = 'temp/' . $filename;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if ( \S::get('name') and \S::get('email') and \S::get('postal_code') and \S::get('phone') and \S::get('voivodeship') and \S::get('client') and strposa(\S::get('name'), $spam_words) === false and strposa(\S::get('email'), $spam_words) === false and
|
if ( \S::get('name') and \S::get('email') and \S::get('postal_code') and \S::get('phone') and \S::get('voivodeship') and \S::get('client') and strposa(\S::get('name'), $spam_words) === false and strposa(\S::get('email'), $spam_words) === false and
|
||||||
strposa(\S::get('postal_code'), $spam_words) === false and strposa(\S::get('phone'), $spam_words) === false and strposa(\S::get('message'), $spam_words) === false
|
strposa(\S::get('postal_code'), $spam_words) === false and strposa(\S::get('phone'), $spam_words) === false and strposa(\S::get('message'), $spam_words) === false
|
||||||
@@ -783,7 +907,17 @@ if (\S::get('action') == 'send-contact-form-new-deweloper' and \S::get('token')
|
|||||||
$zgoda1 = (\S::get('zgoda_1') == 'on' || \S::get('zgoda_1') == 1) ? 1 : 0;
|
$zgoda1 = (\S::get('zgoda_1') == 'on' || \S::get('zgoda_1') == 1) ? 1 : 0;
|
||||||
$zgoda2 = (\S::get('zgoda_2') == 'on' || \S::get('zgoda_2') == 1) ? 1 : 0;
|
$zgoda2 = (\S::get('zgoda_2') == 'on' || \S::get('zgoda_2') == 1) ? 1 : 0;
|
||||||
|
|
||||||
saveContactData( \S::get('name'), \S::get('email'), \S::get('phone'), \S::get('message'), \S::get('postal_code'), '', '', '', '', 'contact-form-new', \S::get('voivodeship'), \S::get('client'), $zgoda1, $zgoda2 );
|
$uploadedFiles = saveContactAttachments();
|
||||||
|
if (!$uploadedFiles['success'])
|
||||||
|
{
|
||||||
|
\S::alert(contactAttachmentFailureMessage());
|
||||||
|
header('Location: ' . $_SERVER['REQUEST_URI']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
$files_to_send = $uploadedFiles['files_to_send'];
|
||||||
|
$attachment_links = $uploadedFiles['links'];
|
||||||
|
|
||||||
|
saveContactData( \S::get('name'), \S::get('email'), \S::get('phone'), \S::get('message'), \S::get('postal_code'), '', '', '', '', 'contact-form-new', \S::get('voivodeship'), \S::get('client'), $zgoda1, $zgoda2, $attachment_links );
|
||||||
|
|
||||||
if (\S::send_email('marketing@vidok.com', 'Wiadomość ze strony vidok.com', $text, \S::get('email'), $files_to_send))
|
if (\S::send_email('marketing@vidok.com', 'Wiadomość ze strony vidok.com', $text, \S::get('email'), $files_to_send))
|
||||||
{
|
{
|
||||||
|
|||||||
BIN
stopki/de-jerzy-dlugosz/Vidok_pliki/image001.png
Normal file
|
After Width: | Height: | Size: 39 KiB |
BIN
stopki/de-jerzy-dlugosz/Vidok_pliki/image002.png
Normal file
|
After Width: | Height: | Size: 39 KiB |
BIN
stopki/fr-adam-swider - bez zdjecia/Vidok_pliki/image001.png
Normal file
|
After Width: | Height: | Size: 39 KiB |
BIN
stopki/fr-adam-swider - bez zdjecia/Vidok_pliki/image002.png
Normal file
|
After Width: | Height: | Size: 39 KiB |
BIN
stopki/fr-adam-swider - bez zdjecia/Vidok_pliki/image006.png
Normal file
|
After Width: | Height: | Size: 6.8 KiB |
BIN
stopki/fr-adam-swider - bez zdjecia/Vidok_pliki/image014.png
Normal file
|
After Width: | Height: | Size: 30 KiB |
BIN
stopki/fr-grzegorz-welc/Vidok_pliki/image006.png
Normal file
|
After Width: | Height: | Size: 6.8 KiB |
BIN
stopki/fr-grzegorz-welc/Vidok_pliki/image014.png
Normal file
|
After Width: | Height: | Size: 30 KiB |
BIN
stopki/fr-kamila-wachnicka/Vidok_pliki/image001.png
Normal file
|
After Width: | Height: | Size: 39 KiB |
BIN
stopki/fr-kamila-wachnicka/Vidok_pliki/image002.png
Normal file
|
After Width: | Height: | Size: 39 KiB |
BIN
stopki/fr-kamila-wachnicka/Vidok_pliki/image006.png
Normal file
|
After Width: | Height: | Size: 6.8 KiB |
BIN
stopki/fr-kamila-wachnicka/Vidok_pliki/image014.png
Normal file
|
After Width: | Height: | Size: 30 KiB |
BIN
stopki/fr-monika-raczkowska/Vidok_pliki/image006.png
Normal file
|
After Width: | Height: | Size: 6.8 KiB |
BIN
stopki/fr-monika-raczkowska/Vidok_pliki/image014.png
Normal file
|
After Width: | Height: | Size: 30 KiB |
BIN
stopki/fr-stefan-blaszczyk/Vidok_pliki/image001.png
Normal file
|
After Width: | Height: | Size: 39 KiB |
BIN
stopki/fr-stefan-blaszczyk/Vidok_pliki/image002.png
Normal file
|
After Width: | Height: | Size: 39 KiB |
BIN
stopki/fr-stefan-blaszczyk/Vidok_pliki/image006.png
Normal file
|
After Width: | Height: | Size: 6.8 KiB |
BIN
stopki/fr-stefan-blaszczyk/Vidok_pliki/image014.png
Normal file
|
After Width: | Height: | Size: 58 KiB |
BIN
stopki/it-arkadiusz-wiatrowski/Vidok_pliki/image014.jpg
Normal file
|
After Width: | Height: | Size: 63 KiB |
BIN
stopki/it-justyna-depa/Vidok_pliki/image006.png
Normal file
|
After Width: | Height: | Size: 6.8 KiB |
BIN
stopki/pl-krzysztof-stawarz/Vidok_pliki/image001.2.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
stopki/pl-krzysztof-stawarz/Vidok_pliki/image001.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
stopki/pl-krzysztof-stawarz/Vidok_pliki/image002.2.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
stopki/pl-krzysztof-stawarz/Vidok_pliki/image002.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
stopki/pl-krzysztof-stawarz/Vidok_pliki/image006.png
Normal file
|
After Width: | Height: | Size: 6.8 KiB |
@@ -140,6 +140,7 @@
|
|||||||
<div class="fileuploader fileuploader-theme-default">
|
<div class="fileuploader fileuploader-theme-default">
|
||||||
<input type="hidden" name="fileuploader-list-files" value="[]">
|
<input type="hidden" name="fileuploader-list-files" value="[]">
|
||||||
<input type="file" name="files[]" class="files" multiple="multiple">
|
<input type="file" name="files[]" class="files" multiple="multiple">
|
||||||
|
<p class="file-upload-info">Maksymalny rozmiar: 50 MB na plik. Dozwolone typy: PDF, JPG, PNG, DOC, DOCX, XLS, XLSX, CSV, TXT, XML, DWG, DXF, ZIP.</p>
|
||||||
<div class="fileuploader-items">
|
<div class="fileuploader-items">
|
||||||
<ul class="fileuploader-items-list"></ul>
|
<ul class="fileuploader-items-list"></ul>
|
||||||
</div>
|
</div>
|
||||||
@@ -262,8 +263,8 @@
|
|||||||
<script class="footer" type="text/javascript">
|
<script class="footer" type="text/javascript">
|
||||||
$(document).ready(function() {
|
$(document).ready(function() {
|
||||||
$('#modal-contact-form input.files').fileuploader({
|
$('#modal-contact-form input.files').fileuploader({
|
||||||
fileMaxSize: 10,
|
fileMaxSize: 50,
|
||||||
extensions: ['jpg', 'jpeg', 'png', 'pdf', 'text/plain', 'csv', 'xml', 'xls', 'xlsx', 'doc', 'dot', 'docx'],
|
extensions: ['jpg', 'jpeg', 'png', 'pdf', 'csv', 'txt', 'xml', 'xls', 'xlsx', 'doc', 'docx', 'dwg', 'dxf', 'zip'],
|
||||||
addMore: true,
|
addMore: true,
|
||||||
captions: {
|
captions: {
|
||||||
button: function(e) {
|
button: function(e) {
|
||||||
|
|||||||
@@ -255,7 +255,7 @@
|
|||||||
<textarea name="message" placeholder="Opis zgłoszenia. Jeżeli masz jakieś uwagi, wytyczne możesz wpisać je tutaj."></textarea>
|
<textarea name="message" placeholder="Opis zgłoszenia. Jeżeli masz jakieś uwagi, wytyczne możesz wpisać je tutaj."></textarea>
|
||||||
</div>
|
</div>
|
||||||
<div class="_text">
|
<div class="_text">
|
||||||
<p>Chcesz przesłać nam wizualizacje, projekt lub inne materiały? Skorzystaj z pola poniżej i dodaj plik do wiadomości.</p>
|
<p>Chcesz przesłać nam wizualizacje, projekt lub inne materiały? Skorzystaj z pola poniżej i dodaj plik do wiadomości. Maksymalny rozmiar: 50 MB na plik. Dozwolone typy: PDF, JPG, PNG, DOC, DOCX, XLS, XLSX, CSV, TXT, XML, DWG, DXF, ZIP.</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="_file fileuploader fileuploader-theme-default">
|
<div class="_file fileuploader fileuploader-theme-default">
|
||||||
<input type="hidden" name="fileuploader-list-files" value="[]">
|
<input type="hidden" name="fileuploader-list-files" value="[]">
|
||||||
@@ -316,7 +316,7 @@
|
|||||||
<textarea name="message" placeholder="Opis zgłoszenia. Jeżeli masz jakieś uwagi, wytyczne możesz wpisać je tutaj."></textarea>
|
<textarea name="message" placeholder="Opis zgłoszenia. Jeżeli masz jakieś uwagi, wytyczne możesz wpisać je tutaj."></textarea>
|
||||||
</div>
|
</div>
|
||||||
<div class="_text">
|
<div class="_text">
|
||||||
<p>Chcesz przesłać nam wizualizacje, projekt lub inne materiały? Skorzystaj z pola poniżej i dodaj plik do wiadomości.</p>
|
<p>Chcesz przesłać nam wizualizacje, projekt lub inne materiały? Skorzystaj z pola poniżej i dodaj plik do wiadomości. Maksymalny rozmiar: 50 MB na plik. Dozwolone typy: PDF, JPG, PNG, DOC, DOCX, XLS, XLSX, CSV, TXT, XML, DWG, DXF, ZIP.</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="_file fileuploader fileuploader-theme-default">
|
<div class="_file fileuploader fileuploader-theme-default">
|
||||||
<input type="hidden" name="fileuploader-list-files" value="[]">
|
<input type="hidden" name="fileuploader-list-files" value="[]">
|
||||||
@@ -666,8 +666,8 @@
|
|||||||
* File uploader
|
* File uploader
|
||||||
*/
|
*/
|
||||||
$('#contact-form-new input.files').fileuploader({
|
$('#contact-form-new input.files').fileuploader({
|
||||||
fileMaxSize: 10,
|
fileMaxSize: 50,
|
||||||
extensions: ['jpg', 'jpeg', 'png', 'pdf', 'text/plain', 'csv', 'xml', 'xls', 'xlsx', 'doc', 'dot', 'docx'],
|
extensions: ['jpg', 'jpeg', 'png', 'pdf', 'csv', 'txt', 'xml', 'xls', 'xlsx', 'doc', 'docx', 'dwg', 'dxf', 'zip'],
|
||||||
addMore: true,
|
addMore: true,
|
||||||
captions: {
|
captions: {
|
||||||
button: function(e) {
|
button: function(e) {
|
||||||
|
|||||||