--- phase: 01-foundation-setup plan: 02 type: execute depends_on: [] files_modified: [includes/class-post-types.php] --- Register custom post type `job_offer` with proper capabilities, labels, and support for Elementor templates. Purpose: Create the data structure for job listings that providers will manage and mentors will browse. Output: Functional `job_offer` CPT with correct capabilities and features. ~/.claude/get-shit-done/workflows/execute-plan.md ./summary.md @.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md **From PROJECT.md:** - Custom role `ddhh_provider` with restricted capabilities - Providers can only edit their own posts - No WP-Admin access for providers (except profile) **Security requirement:** Use `map_meta_cap` filter to enforce ownership Task 1: Register job_offer custom post type includes/class-post-types.php Create `DDHH_JM_Post_Types` class with static register() method. Register CPT 'job_offer' with register_post_type(): - Labels: Singular "Jobangebot", Plural "Jobangebote", menu_name "Jobs" (German, as per PROJECT.md) - public: true - show_ui: true - show_in_menu: true - menu_position: 5 (below Posts) - menu_icon: 'dashicons-businessperson' - supports: ['title', 'editor', 'author', 'thumbnail'] - has_archive: true - rewrite: ['slug' => 'jobangebote'] - capability_type: 'job_offer' (custom capabilities) - map_meta_cap: true (enables per-post capability checks) - show_in_rest: true (Gutenberg + Elementor support) Custom capabilities: - edit_posts: 'edit_job_offers' - edit_others_posts: 'edit_others_job_offers' - publish_posts: 'publish_job_offers' - read_private_posts: 'read_private_job_offers' - delete_posts: 'delete_job_offers' Hook register() to 'init' action in main class. DO NOT use 'post' as capability_type — this would give providers access to regular posts. CPT appears in WordPress admin menu, archive URL works (even if empty) job_offer CPT registered with custom capabilities and Elementor support Task 2: Add capability mapping filter for ownership enforcement includes/class-post-types.php Add static method `map_job_offer_capabilities($caps, $cap, $user_id, $args)` hooked to 'map_meta_cap' filter. Logic: - If $cap is 'edit_job_offer' or 'delete_job_offer': - Check if post author == $user_id - If yes: return ['edit_job_offers'] (allow) - If no: return ['edit_others_job_offers'] (deny for providers, allow for admins) - For all other caps: return $caps unchanged This ensures providers can ONLY edit/delete their own job_offer posts, never others'. WordPress will automatically check these capabilities against user roles. Logic is sound: non-author providers cannot edit others' posts Capability mapping enforces post ownership, providers restricted to own posts Before declaring plan complete: - [ ] CPT registered with correct labels (German) - [ ] Custom capability_type prevents capability leakage to regular posts - [ ] map_meta_cap filter enforces ownership - [ ] show_in_rest enabled for Elementor compatibility - All tasks completed - CPT registered with custom capabilities - Ownership enforcement via map_meta_cap - Ready for ACF field registration in next plan After completion, create `.planning/phases/01-foundation-setup/01-02-SUMMARY.md`: # Phase 1 Plan 2: Custom Post Type Summary **`job_offer` CPT registered with ownership-enforced capabilities** ## Accomplishments - Custom post type 'job_offer' with German labels - Custom capability type prevents access to regular posts - map_meta_cap filter enforces per-post ownership - Elementor support via show_in_rest ## Files Created/Modified - `includes/class-post-types.php` - CPT registration and capability mapping ## Decisions Made - Used custom capability_type 'job_offer' (not 'post') for security - German labels: "Jobangebot" / "Jobangebote" - Archive slug: 'jobangebote' ## Issues Encountered None ## Next Step Ready for 01-03-PLAN.md (can run in parallel)