feat(media-folder-pro): add virtual folder system for WordPress media library

Custom WordPress plugin that replaces the default flat media library with
a structured folder view. Features: hierarchical folders via custom taxonomy,
sidebar folder tree, drag & drop, modal integration with Elementor/builders,
bulk assign, upload auto-assign, toast notifications.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-28 14:08:49 +01:00
parent 4ad3303b18
commit 5014b9108f
19 changed files with 3692 additions and 0 deletions

View File

@@ -0,0 +1,258 @@
---
phase: 01-media-folders-plugin
plan: 01
type: execute
wave: 1
depends_on: []
files_modified:
- wp-content/plugins/media-folder-pro/media-folder-pro.php
- wp-content/plugins/media-folder-pro/includes/class-taxonomy.php
- wp-content/plugins/media-folder-pro/includes/class-ajax-handler.php
- wp-content/plugins/media-folder-pro/assets/css/admin.css
- wp-content/plugins/media-folder-pro/assets/js/folder-tree.js
autonomous: true
---
<objective>
## Goal
Stworzyc fundament pluginu WordPress "RM2 Media Folders" — rejestracja custom taxonomy `media_folder` dla attachment post type, CRUD folderow przez AJAX, oraz bazowy UI drzewka folderow w panelu admina.
## Purpose
Bez tego fundamentu nie mozna budowac integracji z biblioteka mediow. Taxonomy to core mechanizm organizacji.
## Output
Dzialajacy plugin z:
- Custom taxonomy `media_folder` (hierarchiczna, niewidoczna publicznie)
- AJAX endpoints: tworzenie, zmiana nazwy, usuwanie, przenoszenie folderow
- Bazowe drzewko folderow renderowane w JS
</objective>
<context>
## Project Context
@.paul/PROJECT.md
@.paul/ROADMAP.md
## Source Files
Nowy plugin — brak istniejacych plikow. Tworzymy od zera.
</context>
<acceptance_criteria>
## AC-1: Plugin aktywuje sie bez bledow
```gherkin
Given WordPress 6.x z PHP 8.0+
When uzytkownik aktywuje plugin "RM2 Media Folders"
Then plugin aktywuje sie bez bledow i warnings
And w bazie danych pojawia sie taxonomy "media_folder"
```
## AC-2: CRUD folderow dziala przez AJAX
```gherkin
Given plugin jest aktywny
When uzytkownik tworzy folder "Zdjecia produktow" przez UI
Then folder zostaje zapisany jako term w taxonomy media_folder
And odpowiedz AJAX zawiera id i nazwe nowego folderu
Given istniejacy folder "Zdjecia produktow"
When uzytkownik zmienia nazwe na "Produkty 2026"
Then term name i slug zostaja zaktualizowane
Given istniejacy pusty folder
When uzytkownik usuwa folder
Then term zostaje usuniety z bazy
Given istniejacy folder
When uzytkownik przenosi go jako subfolder innego folderu
Then term parent zostaje zaktualizowany
```
## AC-3: Drzewko folderow renderuje sie poprawnie
```gherkin
Given plugin jest aktywny i istnieja foldery z podfolderami
When uzytkownik otwiera strone Media w panelu admina
Then widoczne jest drzewko folderow z hierarchia
And foldery mozna rozwijac/zwijac
And klikniecie folderu filtruje liste mediow
```
</acceptance_criteria>
<tasks>
<task type="auto">
<name>Task 1: Core plugin file + taxonomy registration</name>
<files>wp-content/plugins/rm2-media-folders/rm2-media-folders.php, wp-content/plugins/rm2-media-folders/includes/class-taxonomy.php</files>
<action>
1. Utworzyc glowny plik pluginu rm2-media-folders.php:
- Plugin header (Name: RM2 Media Folders, Version: 0.1.0)
- Autoload includes/
- Hook init: rejestracja taxonomy
- Hook admin_enqueue_scripts: ladowanie CSS/JS na stronach media
- Hook wp_ajax_*: rejestracja AJAX handlers
2. Utworzyc includes/class-taxonomy.php:
- Klasa RM2_Media_Folders_Taxonomy
- Metoda register(): register_taxonomy('media_folder', 'attachment', args)
- hierarchical: true
- public: false
- show_ui: false (wlasny UI)
- show_in_rest: true (dla block editora)
- Metoda get_folder_tree(): zwraca hierarchiczne drzewo folderow
- Uzywa get_terms() z 'hide_empty' => false
- Buduje drzewo parent-child
Avoid: Nie uzywac show_ui: true — bedziemy miec wlasny interfejs.
Avoid: Nie rejestrowac taxonomy dla innych post types niz attachment.
</action>
<verify>
- Plugin pojawia sie na liscie pluginow WP
- Po aktywacji: wp term list media_folder --format=json (WP-CLI) nie zwraca bledu
- Taxonomy istnieje: taxonomy_exists('media_folder') === true
</verify>
<done>AC-1 satisfied: Plugin aktywuje sie, taxonomy zarejestrowana</done>
</task>
<task type="auto">
<name>Task 2: AJAX handler for folder CRUD</name>
<files>wp-content/plugins/rm2-media-folders/includes/class-ajax-handler.php</files>
<action>
Utworzyc includes/class-ajax-handler.php:
- Klasa RM2_Media_Folders_Ajax
Endpointy AJAX (kazdy z nonce verification + capability check 'upload_files'):
1. rm2_mf_create_folder:
- Parametry: name (string), parent_id (int, 0 = root)
- wp_insert_term($name, 'media_folder', ['parent' => $parent_id])
- Zwraca: {success: true, folder: {id, name, slug, parent}}
2. rm2_mf_rename_folder:
- Parametry: folder_id (int), name (string)
- wp_update_term($folder_id, 'media_folder', ['name' => $name])
- Zwraca: {success: true, folder: {id, name, slug}}
3. rm2_mf_delete_folder:
- Parametry: folder_id (int)
- Sprawdz czy folder jest pusty (brak attachmentow i subfolderow)
- Jesli nie pusty: zwroc blad
- wp_delete_term($folder_id, 'media_folder')
- Zwraca: {success: true}
4. rm2_mf_move_folder:
- Parametry: folder_id (int), new_parent_id (int, 0 = root)
- Walidacja: nie mozna przeniesc folderu do samego siebie lub swojego dziecka
- wp_update_term($folder_id, 'media_folder', ['parent' => $new_parent_id])
- Zwraca: {success: true, folder: {id, name, parent}}
5. rm2_mf_get_folders:
- Brak parametrow
- Zwraca pelne drzewo folderow z countami attachmentow
Kazdy endpoint:
- check_ajax_referer('rm2_media_folders_nonce', 'nonce')
- current_user_can('upload_files')
- wp_send_json_success() / wp_send_json_error()
Avoid: Nie uzywac $_GET — wszystko przez $_POST.
Avoid: Nie usuwac folderow z dziecmi — wymuszaj pusty folder.
</action>
<verify>
- AJAX call do rm2_mf_create_folder zwraca 200 z JSON {success: true}
- Utworzony term widoczny w get_terms('media_folder')
- Proba bez nonce zwraca 403
- Proba bez uprawnien zwraca blad
</verify>
<done>AC-2 satisfied: CRUD folderow dziala przez AJAX z walidacja</done>
</task>
<task type="auto">
<name>Task 3: Admin assets + folder tree UI</name>
<files>wp-content/plugins/rm2-media-folders/assets/css/admin.css, wp-content/plugins/rm2-media-folders/assets/js/folder-tree.js</files>
<action>
1. admin.css:
- Styl dla sidebar drzewka folderow (lewy panel, 250px szerokosci)
- Style dla elementow drzewka: folder icon, nazwa, expand/collapse arrow
- Styl aktywnego folderu (podswietlenie)
- Style dla context menu (prawy klik: rename, delete, new subfolder)
- Style dla inline edit (zmiana nazwy folderu)
- Responsywnosc: na mniejszych ekranach sidebar chowa sie
2. folder-tree.js (vanilla JS, bez jQuery dependency):
- Klasa RM2FolderTree
- init(): fetch folderow z AJAX (rm2_mf_get_folders), renderuj drzewo
- renderTree(folders, container): rekurencyjny render z <ul><li>
- toggleFolder(folderId): expand/collapse children
- selectFolder(folderId): podswietl + wyslij event 'rm2-folder-selected'
- createFolder(parentId): prompt nazwa → AJAX create
- renameFolder(folderId): inline edit → AJAX rename
- deleteFolder(folderId): confirm → AJAX delete
- moveFolder(folderId, newParentId): AJAX move (drag & drop w nastepnej fazie)
Toolbar nad drzewkiem:
- Przycisk "+" (nowy folder w root)
- Przycisk "All Media" (reset filtra)
Context menu (prawy klik na folder):
- New subfolder
- Rename
- Delete
Localization: uzyj wp_localize_script z rm2MediaFolders object:
- ajaxUrl, nonce, i18n strings
3. W glownym pliku pluginu dodac:
- admin_enqueue_scripts hook ladujacy CSS i JS tylko na upload.php i media-new.php
- wp_localize_script z nonce i ajax URL
- Hook na admin_footer (upload.php) wstawiajacy container div dla drzewka
Avoid: Nie ladowac assetow na stronach innych niz media.
Avoid: Nie uzywac jQuery — czysty vanilla JS.
</action>
<verify>
- Na stronie Media > Library widoczny sidebar z drzewkiem
- Klikniecie "+" otwiera prompt, po wpisaniu nazwy folder sie pojawia
- Prawy klik na folder pokazuje context menu
- Klikniecie folderu podswietla go
- CSS nie koliduje z domyslnymi stylami WP admin
</verify>
<done>AC-3 satisfied: Drzewko folderow renderuje sie z CRUD operacjami</done>
</task>
</tasks>
<boundaries>
## DO NOT CHANGE
- wp-content/plugins/elementor-pro/* (nie modyfikujemy Elementora)
- Zadne pliki core WordPress
## SCOPE LIMITS
- Brak drag & drop mediow do folderow (Phase 2)
- Brak integracji z media modal (Phase 3)
- Brak bulk operations (Phase 4)
- Brak filtrowania listy mediow po kliknieciu folderu (bedzie placeholder, pelna implementacja w Phase 2)
</boundaries>
<verification>
Before declaring plan complete:
- [ ] Plugin aktywuje sie bez bledow PHP
- [ ] Taxonomy media_folder zarejestrowana i hierarchiczna
- [ ] Wszystkie 5 AJAX endpointow odpowiada poprawnie
- [ ] Nonce verification dziala (odrzuca requesty bez nonce)
- [ ] Drzewko folderow wyswietla sie na stronie Media
- [ ] Tworzenie/usuwanie/zmiana nazwy folderu dziala z UI
- [ ] Assets ladowane tylko na stronach media (nie na innych)
</verification>
<success_criteria>
- Wszystkie taski ukonczone
- Wszystkie verification checks przeszly
- Brak bledow PHP (error_log czysty)
- Brak JS errors w konsoli przegladarki
- Plugin gotowy jako baza do Phase 2
</success_criteria>
<output>
After completion, create `.paul/phases/01-media-folders-plugin/01-01-SUMMARY.md`
</output>

View File

@@ -0,0 +1,126 @@
---
phase: 01-media-folders-plugin
plan: 01
subsystem: media
tags: [wordpress, plugin, taxonomy, ajax, vanilla-js]
requires: []
provides:
- Custom taxonomy media_folder (hierarchical)
- AJAX CRUD endpoints for folder management
- Folder tree sidebar UI (vanilla JS)
affects: [02-media-library-grid, 03-media-modal]
tech-stack:
added: []
patterns: [custom taxonomy for virtual folders, MFP_ class prefix, vanilla JS UI]
key-files:
created:
- wp-content/plugins/media-folder-pro/media-folder-pro.php
- wp-content/plugins/media-folder-pro/includes/class-taxonomy.php
- wp-content/plugins/media-folder-pro/includes/class-ajax-handler.php
- wp-content/plugins/media-folder-pro/assets/css/admin.css
- wp-content/plugins/media-folder-pro/assets/js/folder-tree.js
modified: []
key-decisions:
- "Plugin renamed to media-folder-pro (user request)"
- "Author set to Project Pro (https://www.project-pro.pl)"
- "Vanilla JS instead of jQuery for tree UI"
- "MFP_ prefix for all classes and constants"
patterns-established:
- "MFP_Taxonomy centralizes all taxonomy operations"
- "MFP_Ajax_Handler with nonce + capability check on every endpoint"
- "Assets loaded only on upload.php and media-new.php"
- "CustomEvent 'mfp-folder-selected' for cross-component communication"
duration: ~15min
started: 2026-03-28
completed: 2026-03-28
---
# Phase 1 Plan 01: Plugin Foundation + Taxonomy Summary
**WordPress plugin "Media Folder Pro" with hierarchical media_folder taxonomy, 5 AJAX CRUD endpoints, and vanilla JS folder tree sidebar UI.**
## Performance
| Metric | Value |
|--------|-------|
| Duration | ~15min |
| Tasks | 3 completed |
| Files created | 5 |
## Acceptance Criteria Results
| Criterion | Status | Notes |
|-----------|--------|-------|
| AC-1: Plugin aktywuje sie bez bledow | Pass | Plugin header, taxonomy registration, autoload — all correct |
| AC-2: CRUD folderow dziala przez AJAX | Pass | 5 endpoints with nonce verification + capability check |
| AC-3: Drzewko folderow renderuje sie poprawnie | Pass | Sidebar with tree, context menu, inline rename, expand/collapse |
## Accomplishments
- Custom taxonomy `media_folder` registered for `attachment` post type (hierarchical, hidden UI, REST enabled)
- 5 AJAX endpoints: create, rename, delete, move, get_folders — all with security checks
- Folder tree sidebar with context menu (right-click), inline rename, expand/collapse, active state
## Files Created/Modified
| File | Change | Purpose |
|------|--------|---------|
| `wp-content/plugins/media-folder-pro/media-folder-pro.php` | Created | Main plugin file — singleton, hooks, asset loading |
| `wp-content/plugins/media-folder-pro/includes/class-taxonomy.php` | Created | MFP_Taxonomy — register, get_folder_tree, folder_has_children, would_create_cycle |
| `wp-content/plugins/media-folder-pro/includes/class-ajax-handler.php` | Created | MFP_Ajax_Handler — 5 AJAX endpoints with validation |
| `wp-content/plugins/media-folder-pro/assets/css/admin.css` | Created | Sidebar layout, tree styles, context menu, responsive |
| `wp-content/plugins/media-folder-pro/assets/js/folder-tree.js` | Created | Vanilla JS folder tree — CRUD, context menu, inline edit |
## Decisions Made
| Decision | Rationale | Impact |
|----------|-----------|--------|
| Plugin renamed to media-folder-pro | User request | All references use MFP_ prefix |
| Author: Project Pro | User request | Plugin header and URI set |
| Vanilla JS (no jQuery) | Modern, no dependency | Simpler, lighter bundle |
| Empty-folder-only delete | Safety — prevent accidental data loss | Users must move content before deleting |
## Deviations from Plan
### Summary
| Type | Count | Impact |
|------|-------|--------|
| Scope changes | 1 | Plugin name change — no functional impact |
**Total impact:** Naming only, no functional deviation.
### Details
1. **Plugin renamed** from `rm2-media-folders` to `media-folder-pro` per user request during APPLY. All class prefixes changed from `RM2_Media_Folders_` to `MFP_`.
## Issues Encountered
None
## Next Phase Readiness
**Ready:**
- Taxonomy registered and operational
- AJAX infrastructure in place (all endpoints working)
- Folder tree UI renders with full CRUD
- `mfp-folder-selected` CustomEvent dispatched on folder click (Phase 2 hooks here)
- CSS sidebar pushes main content via `margin-left`
**Concerns:**
- Folder filtering not yet connected to WP media query (Phase 2 scope)
- Drag & drop not implemented (Phase 2 scope)
- No i18n .pot file generated yet (Phase 4)
**Blockers:**
- None
---
*Phase: 01-media-folders-plugin, Plan: 01*
*Completed: 2026-03-28*