diff --git a/CHANGELOG.md b/CHANGELOG.md index 0144fd7..aace413 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,12 +9,20 @@ Earlier releases (`v0.1.0-alpha.1` to `v0.1.7-alpha.1`) were experimental and do ## [v0.1.7-alpha.2] - 2025-07-15 +### Added +- Storage helper for file uploads, saving to `public/uploads` and generating URLs. +- Modern file access in controllers: `$request->file('avatar')`, `$request->hasFile('avatar')`. +- Unified request data: merges `$_GET`, `$_POST`, and JSON body. +- `mimes` and `image` validation rules for secure file uploads. +- HTMX-powered file upload with progress bar in the main form (no JS required). +- Generic error-clearing script for all form fields. + ### Changed +- File uploads are now web-accessible by default. +- Improved documentation for file upload, validation, and request handling. -- **Adopted proper SemVer pre-release flow:** All future releases will follow `alpha.n → beta.n → rc.n → stable`. -- **Stability commitment:** This release marks the beginning of structured, progressive versioning and a focus on stability. -- **Changelog and versioning policy:** Added `CHANGELOG.md` and `VERSIONING.md` to document release history and policy. -- **(List any new features, bug fixes, or improvements here)** +### Fixed +- No more duplicate `/uploads/uploads/...` in file URLs. --- diff --git a/DOCS.md b/DOCS.md index 39cf2b9..31f45db 100644 --- a/DOCS.md +++ b/DOCS.md @@ -503,3 +503,110 @@ This will run the production build process (minifies, strips dev code, precompil - The old `build` command is now replaced by `bloom` for clarity and branding. - Use this command before deploying your app to production. + +## File Upload & Storage + +SproutPHP now includes a Storage helper for easy file uploads and URL generation, saving files in `public/uploads` for web accessibility. + +### Usage Example + +```php +use Core\Support\Storage; + +// In your controller +if ($request->hasFile('avatar')) { + $path = Storage::put($request->file('avatar'), 'avatars'); + $url = Storage::url($path); // /uploads/avatars/filename.jpg +} +``` + +- Files are saved in `public/uploads/{subdir}`. +- URLs are generated as `/uploads/{subdir}/{filename}`. + +--- + +## Modern Request File Access + +You can now access uploaded files in controllers using: + +```php +$request->file('avatar'); // Returns the file array or null +$request->hasFile('avatar'); // Returns true if a file was uploaded +``` + +Request data merges `$_GET`, `$_POST`, and JSON body for unified access. + +--- + +## File Validation: mimes & image + +You can validate file uploads with new rules: + +- `mimes:jpg,png,gif` — File must have one of the allowed extensions +- `image` — File must be a valid image (checked by MIME type) + +**Example:** + +```php +$validator = new Validator($request->data, [ + 'avatar' => 'required|image|mimes:jpg,jpeg,png,gif' +]); +``` + +--- + +## HTMX File Upload with + +You can use your main form for file upload: + +```twig +
+ +
+ + + {% if errors.avatar %} +
{{ errors.avatar }}
+ {% endif %} +
+ + +
+``` + +- On success, your server can return a fragment with the uploaded avatar URL and preview. + +--- + +## Error Clearing Script + +A generic script clears error messages for any field when focused: + +```html + +``` + +- Works for all fields with `.error[for="fieldname"]`. + +--- + +See the rest of this documentation for more on validation, request handling, and UI best practices. diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 247f78c..c084745 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,42 +1,32 @@ -# SproutPHP v0.1.7-alpha.1 Release Notes +# SproutPHP v0.1.7-alpha.2 Release Notes ## 🎉 New Features & Improvements -### Validation System - -- **Expanded validation rules**: Now supports numeric, integer, string, boolean, array, in, not_in, same, different, confirmed, regex, url, ip, date, before, after, nullable, present, digits, digits_between, size, starts_with, ends_with, uuid, and more. -- **Improved documentation**: DOCS.md now lists all available rules and usage examples. -- **Better error clearing**: Validation errors are cleared on input focus for a smoother UX. - -### Dark/Light Mode Support - -- **PicoCSS dark/light mode toggle**: Optional sun/moon icon button in the navbar for instant theme switching. -- **Theme preference**: Saved in localStorage and applied instantly. -- **Post-install option**: Users can choose to auto-include the toggle during installation. - -### Other Improvements - -- **Post-install script**: Now prompts for dark/light mode toggle inclusion. -- **Code cleanup and bug fixes**: Heredoc indentation, improved scripts, and UI polish. -- **Documentation updates**: More examples and clearer instructions for new features. +- **File Upload & Storage:** New Storage helper for easy file uploads and URL generation, saving files in `public/uploads`. +- **Modern Request API:** Access uploaded files via `$request->file('avatar')` and `$request->hasFile('avatar')`. +- **Unified Input:** Request data now merges `$_GET`, `$_POST`, and JSON body for easier access. +- **Validation:** Added `mimes` and `image` rules for secure file validation. +- **HTMX File Upload:** File upload with progress bar using only HTMX, no custom JS required. +- **Error Handling:** Generic script to clear errors on focus for all fields. +- **Docs:** Updated with new usage examples and best practices. ## 🛠️ Upgrade Guide -- Use the new validation rules in your controllers and forms. -- To add the dark/light mode toggle, re-run the post-install script or add the button and script as shown in DOCS.md. -- See DOCS.md for updated usage examples. +- Use the new Storage helper and request methods for file uploads. +- Update your forms to use the new validation rules and error-clearing script. +- See DOCS.md for updated usage and examples. ## 📅 Release Date -July 2024 +2025-07-15 ## 📦 Framework Version -v0.1.7-alpha.1 +v0.1.7-alpha.2 --- -**Release Date**: July 2024 -**Framework Version**: v0.1.7-alpha.1 +**Release Date**: 2025-07-15 +**Framework Version**: v0.1.7-alpha.2 **PHP Version**: 8.1+ **Composer**: 2.0+ diff --git a/app/Controllers/ValidationTestController.php b/app/Controllers/ValidationTestController.php index 725b64b..4d06b1f 100644 --- a/app/Controllers/ValidationTestController.php +++ b/app/Controllers/ValidationTestController.php @@ -2,6 +2,8 @@ namespace App\Controllers; +use Core\Http\Request; +use Core\Support\Storage; use Core\Support\Validator; class ValidationTestController @@ -21,13 +23,39 @@ public function index() public function handleForm() { - $data = $_POST; + $request = Request::capture(); + + $data = [ + 'name' => $request->input('name'), + 'email' => $request->input('email'), + 'avatar' => $request->input('avatar'), + ]; + + // Or You can get all input data simply using + // $data = $request->data; + $validator = new Validator($data, [ 'email' => 'required|email', 'name' => 'required|min:3', + 'avatar' => 'image|mimes:jpg,jpeg,png,gif', ]); - if ($validator->fails()) { + // File Upload Validation + $avatarError = null; + if ($request->hasFile('avatar')) { + $file = $request->file('avatar'); + $path = Storage::put($file, 'avatars'); + if (!$path) { + $avatarError = 'File upload failed.'; + } + } else { + $avatarError = "Please upload an avatar."; + } + + if ($validator->fails() || $avatarError) { + $errors = $validator->errors(); + if ($avatarError) + $errors['avatar'] = $avatarError; // Return only the form fragment with errors for HTMX return view('partials/validation-form', [ 'errors' => $validator->errors(), @@ -39,6 +67,7 @@ public function handleForm() return view('partials/validation-success', [ 'name' => $data['name'], 'email' => $data['email'], + 'avatar_url' => isset($path) ? Storage::url($path) : null, ]); } } diff --git a/app/Views/partials/validation-form.twig b/app/Views/partials/validation-form.twig index 82b155a..91875bd 100644 --- a/app/Views/partials/validation-form.twig +++ b/app/Views/partials/validation-form.twig @@ -6,6 +6,7 @@ hx-indicator="#spinner" method="POST" autocomplete="off" + enctype="multipart/form-data" > {# CSRF TOKEN #} {{ csrf_field()|raw }} @@ -23,17 +24,20 @@
{{ errors.email }}
{% endif %} +
+ + + {% if errors.avatar %} +
{{ errors.avatar }}
+ {% endif %} +