PluginProbe
Page Builder: Pagelayer – Drag and Drop website builder / 2.2.2
Page Builder: Pagelayer – Drag and Drop website builder v2.2.2
2.2.2 2.2.1 2.2.0 2.1.9 2.1.8 2.1.7 2.1.6 2.1.5 2.1.4 2.1.3 trunk 0.9.0 0.9.1 0.9.2 0.9.3 0.9.4 0.9.5 0.9.6 0.9.7 0.9.8 0.9.9 1.0.0 1.0.2 1.0.3 1.0.4 All 130 releases
← All changes | main/abilitiesregister.php +266 -20 2.1.8 → 2.2.2 View file →
@@ -69,12 +69,40 @@
69 69 public static function can_edit_posts() {
70 70 return current_user_can('edit_posts');
71 71 }
72 72
73 + public static function can_edit_pages() {
74 + return current_user_can('edit_pages');
75 + }
76 +
77 + public static function can_publish_pages() {
78 + return current_user_can('publish_pages');
79 + }
80 +
81 + public static function can_publish_posts() {
82 + return current_user_can('publish_posts');
83 + }
84 +
85 + public static function can_delete_pages() {
86 + return current_user_can('delete_pages');
87 + }
88 +
89 + public static function can_delete_posts() {
90 + return current_user_can('delete_posts');
91 + }
92 +
93 + public static function can_edit_theme_options() {
94 + return current_user_can('edit_theme_options');
95 + }
96 +
73 97 public static function can_manage_options() {
74 98 return current_user_can('manage_options');
75 99 }
76 100
101 + public static function can_upload_files() {
102 + return current_user_can('upload_files');
103 + }
104 +
77 105 // ------------------------------------------------------------------
78 106 // Shared helpers
79 107 // ------------------------------------------------------------------
80 108
@@ -123,8 +151,11 @@
123 151 return $out;
124 152 }
125 153
126 154 public static function maybe_update_global_styles($input) {
155 + if (!current_user_can('manage_options')) {
156 + return;
157 + }
127 158 if (isset($input['global_colors'])) {
128 159 update_option('pagelayer_global_colors', json_encode(self::normalize_global_map($input['global_colors'])));
129 160 }
130 161 if (isset($input['global_fonts'])) {
@@ -1782,9 +1813,9 @@
1782 1813 'widget_recommendations' => array(
1783 1814 'stats_or_counters_row' => 'pl_counter — animated number counters (e.g. "1240+ Active Members"), not plain pl_heading numbers in a row.',
1784 1815 'image_gallery_or_portfolio' => 'pl_grid_gallery — real masonry/grid gallery widget, not a manual grid of pl_image nodes.',
1785 1816 'before_after_or_carousel_images' => 'pl_image_slider for a slideshow of images.',
1786 - 'feature_or_service_cards' => 'pl_iconbox — icon + heading + text card (real fields: service_icon/service_heading/service_text/service_icon_color, NOT icon/title/desc). For a card with a real photo instead of an icon, use pl_service ("Image Box": service_image/service_heading/service_text) instead. Always confirm exact field names via get_widget_examples before using either.',
1817 + 'feature_or_service_cards' => 'pl_iconbox — icon + heading + text card (real fields: service_icon/service_heading/service_text/service_icon_color, NOT icon/title/desc). For a card with a real photo instead of an icon, use pl_service ("Image Box": service_image/service_heading/service_text) instead. Always structure cards in balanced multiples: either 3 cards (or 6 across 2 rows of 3), or 2 cards (or 4 across 2 rows of 2). NEVER create an uneven count like 4 cards split as 3 on top and 1 alone below; if creating 4 cards, place them in 2 rows of 2 (col 6 each) or round them out cleanly so no single lonely card sits on a new line.',
1787 1818 'testimonials_or_reviews' => 'pl_testimonial — real fields are quote_content/cite/designation/avatar, NOT content/name/image.',
1788 1819 'star_ratings' => 'pl_stars for a review/rating display, not text like "★★★★★".',
1789 1820 'progress_or_skill_bars' => 'pl_progress for animated progress/skill bars.',
1790 1821 'faq_or_expandable_content' => 'pl_accordion (with pl_accordion_item children) for FAQs, not a stack of pl_heading+pl_text pairs.',
@@ -2078,8 +2109,9 @@
2078 2109 'required' => array('title', 'type', 'pagelayer_data'),
2079 2110 'additionalProperties' => false
2080 2111 ),
2081 2112 'execute' => array(__CLASS__, 'execute_create_template'),
2113 + 'perm' => array(__CLASS__, 'can_edit_theme_options'),
2082 2114 ),
2083 2115 'update_template' => array(
2084 2116 'label' => __('Update Template', 'pagelayer'),
2085 2117 'description' => __('Update an existing Pagelayer theme template title, layout data, or display conditions. Same header/footer rules as create_template: Include / Full Site is enforced, and a header needs a Primary Menu widget with a real nav_list.', 'pagelayer'),
@@ -2097,8 +2129,9 @@
2097 2129 'required' => array('template_id'),
2098 2130 'additionalProperties' => false
2099 2131 ),
2100 2132 'execute' => array(__CLASS__, 'execute_update_template'),
2133 + 'perm' => array(__CLASS__, 'can_edit_theme_options'),
2101 2134 ),
2102 2135 'delete_template' => array(
2103 2136 'label' => __('Delete Template', 'pagelayer'),
2104 2137 'description' => __('Delete a Pagelayer theme template by ID.', 'pagelayer'),
@@ -2109,8 +2142,9 @@
2109 2142 'required' => array('template_id'),
2110 2143 'additionalProperties' => false
2111 2144 ),
2112 2145 'execute' => array(__CLASS__, 'execute_delete_template'),
2146 + 'perm' => array(__CLASS__, 'can_edit_theme_options'),
2113 2147 ),
2114 2148 'save_template' => array(
2115 2149 'label' => __('Save Section Template', 'pagelayer'),
2116 2150 'description' => __('Save a specific page section or layout to the local template library.', 'pagelayer'),
@@ -2125,8 +2159,9 @@
2125 2159 'required' => array('name', 'post_id'),
2126 2160 'additionalProperties' => false
2127 2161 ),
2128 2162 'execute' => array(__CLASS__, 'execute_save_template'),
2163 + 'perm' => array(__CLASS__, 'can_edit_theme_options'),
2129 2164 ),
2130 2165 'insert_template' => array(
2131 2166 'label' => __('Insert Template', 'pagelayer'),
2132 2167 'description' => __('Insert a saved template layout structure into a target container on a page.', 'pagelayer'),
@@ -2232,8 +2267,9 @@
2232 2267 'required' => array('site_name', 'pages'),
2233 2268 'additionalProperties' => false
2234 2269 ),
2235 2270 'execute' => array(__CLASS__, 'execute_create_website'),
2271 + 'perm' => array(__CLASS__, 'can_manage_options'),
2236 2272 ),
2237 2273 'create_page' => array(
2238 2274 'label' => __('Create Page', 'pagelayer'),
2239 2275 'description' => __('Create one page with Pagelayer builder data, status and global styles. FASTEST PATH: send section specs ({"section":"hero",...}) instead of hand-written node trees — see fast_path_sections in get_data_structure. Enforced quality gate: rejected unsaved if any widget keeps its placeholder text, uses an attr not in its schema, sets a gated attr without its companion, or uses an unregistered tag. Styling must live in attrs — an inline style attribute or <style> block in rich text is rejected and cannot be bypassed. Missing images are only a warning. Read get_data_structure and get_widget_schema first.', 'pagelayer'),
@@ -2254,8 +2290,9 @@
2254 2290 'required' => array('title', 'pagelayer_data'),
2255 2291 'additionalProperties' => false
2256 2292 ),
2257 2293 'execute' => array(__CLASS__, 'execute_create_page'),
2294 + 'perm' => array(__CLASS__, 'can_edit_pages'),
2258 2295 ),
2259 2296 'update_page' => array(
2260 2297 'label' => __('Update Page', 'pagelayer'),
2261 2298 'description' => __('Update page title, status, or the WHOLE pagelayer_data tree. Sending pagelayer_data REPLACES the entire layout and discards any human edits made in the editor since — to change part of a page use update_element/add_element/change_styles instead, which are far cheaper and non-destructive. Same enforced quality gate as create_page, including the no-inline-CSS-in-rich-text rule.', 'pagelayer'),
@@ -2272,8 +2309,9 @@
2272 2309 'required' => array('post_id'),
2273 2310 'additionalProperties' => false
2274 2311 ),
2275 2312 'execute' => array(__CLASS__, 'execute_update_page'),
2313 + 'perm' => array(__CLASS__, 'can_edit_pages'),
2276 2314 ),
2277 2315 'get_page' => array(
2278 2316 'label' => __('Get Page', 'pagelayer'),
2279 2317 'description' => __('Page details plus a compact outline of its elements (id, tag, text preview) — enough to locate anything you want to edit. Pass element_id for one node in full, or mode:"full" for the whole raw tree (large; only needed to rewrite the entire layout).', 'pagelayer'),
@@ -2314,8 +2352,9 @@
2314 2352 'required' => array('post_id'),
2315 2353 'additionalProperties' => false
2316 2354 ),
2317 2355 'execute' => array(__CLASS__, 'execute_publish_page'),
2356 + 'perm' => array(__CLASS__, 'can_publish_pages'),
2318 2357 ),
2319 2358 'duplicate_page' => array(
2320 2359 'label' => __('Duplicate Page', 'pagelayer'),
2321 2360 'description' => __('Clone an existing page and regenerate all Pagelayer element IDs.', 'pagelayer'),
@@ -2329,8 +2368,9 @@
2329 2368 'required' => array('post_id'),
2330 2369 'additionalProperties' => false
2331 2370 ),
2332 2371 'execute' => array(__CLASS__, 'execute_duplicate_page'),
2372 + 'perm' => array(__CLASS__, 'can_edit_pages'),
2333 2373 ),
2334 2374 'delete_page' => array(
2335 2375 'label' => __('Delete Page', 'pagelayer'),
2336 2376 'description' => __('Trash or delete a page by ID.', 'pagelayer'),
@@ -2344,8 +2384,9 @@
2344 2384 'required' => array('post_id'),
2345 2385 'additionalProperties' => false
2346 2386 ),
2347 2387 'execute' => array(__CLASS__, 'execute_delete_page'),
2388 + 'perm' => array(__CLASS__, 'can_delete_pages'),
2348 2389 ),
2349 2390 'preview_page' => array(
2350 2391 'label' => __('Preview Page', 'pagelayer'),
2351 2392 'description' => __('Retrieve the live view/preview URL for a post or page.', 'pagelayer'),
@@ -2386,8 +2427,9 @@
2386 2427 'required' => array('post_id', 'pagelayer_data'),
2387 2428 'additionalProperties' => false
2388 2429 ),
2389 2430 'execute' => array(__CLASS__, 'execute_create_design_ui'),
2431 + 'perm' => array(__CLASS__, 'can_edit_pages'),
2390 2432 ),
2391 2433 'edit_layout' => array(
2392 2434 'label' => __('Edit Page Layout', 'pagelayer'),
2393 2435 'description' => __('Replace the full layout structure of a Pagelayer page. ENFORCED content-quality gate on the new layout — no placeholder text, no missing images, no unregistered widgets. Pass skip_validation:true to bypass for an intentional draft.', 'pagelayer'),
@@ -2404,8 +2446,9 @@
2404 2446 'required' => array('post_id', 'pagelayer_data'),
2405 2447 'additionalProperties' => false
2406 2448 ),
2407 2449 'execute' => array(__CLASS__, 'execute_edit_layout'),
2450 + 'perm' => array(__CLASS__, 'can_edit_pages'),
2408 2451 ),
2409 2452 'change_styles' => array(
2410 2453 'label' => __('Change Element Styles', 'pagelayer'),
2411 2454 'description' => __('Batch update style properties on page elements by ID or widget tag.', 'pagelayer'),
@@ -2419,8 +2462,9 @@
2419 2462 'required' => array('post_id', 'styles'),
2420 2463 'additionalProperties' => false
2421 2464 ),
2422 2465 'execute' => array(__CLASS__, 'execute_change_styles'),
2466 + 'perm' => array(__CLASS__, 'can_edit_pages'),
2423 2467 ),
2424 2468 'get_data_structure' => array(
2425 2469 'label' => __('Get Data Structure Guide', 'pagelayer'),
2426 2470 'description' => __('How pagelayer_data nodes, global $color tokens and gated attributes work. Default topic covers editing; pass topic:"quality"/"widgets"/"workflow"/"all" when building a page from scratch.', 'pagelayer'),
@@ -2481,8 +2525,9 @@
2481 2525 'required' => array('post_id', 'element_id'),
2482 2526 'additionalProperties' => false
2483 2527 ),
2484 2528 'execute' => array(__CLASS__, 'execute_update_element'),
2529 + 'perm' => array(__CLASS__, 'can_edit_pages'),
2485 2530 ),
2486 2531 'add_element' => array(
2487 2532 'label' => __('Add Element', 'pagelayer'),
2488 2533 'description' => __('Insert a new element node into a parent container at an index. Enforced quality gate on the new element (no placeholder text, valid attrs, registered tag); skip_validation:true bypasses it. Inline CSS in rich text is always rejected.', 'pagelayer'),
@@ -2499,8 +2544,9 @@
2499 2544 'required' => array('post_id', 'element'),
2500 2545 'additionalProperties' => false
2501 2546 ),
2502 2547 'execute' => array(__CLASS__, 'execute_add_element'),
2548 + 'perm' => array(__CLASS__, 'can_edit_pages'),
2503 2549 ),
2504 2550 'delete_element' => array(
2505 2551 'label' => __('Delete Element', 'pagelayer'),
2506 2552 'description' => __('Remove an element from a page by its pagelayer-id.', 'pagelayer'),
@@ -2514,8 +2560,9 @@
2514 2560 'required' => array('post_id', 'element_id'),
2515 2561 'additionalProperties' => false
2516 2562 ),
2517 2563 'execute' => array(__CLASS__, 'execute_delete_element'),
2564 + 'perm' => array(__CLASS__, 'can_edit_pages'),
2518 2565 ),
2519 2566 'move_element' => array(
2520 2567 'label' => __('Move Element', 'pagelayer'),
2521 2568 'description' => __('Relocate an element to a target parent container or index.', 'pagelayer'),
@@ -2531,8 +2578,9 @@
2531 2578 'required' => array('post_id', 'element_id'),
2532 2579 'additionalProperties' => false
2533 2580 ),
2534 2581 'execute' => array(__CLASS__, 'execute_move_element'),
2582 + 'perm' => array(__CLASS__, 'can_edit_pages'),
2535 2583 ),
2536 2584 'duplicate_element' => array(
2537 2585 'label' => __('Duplicate Element', 'pagelayer'),
2538 2586 'description' => __('Clone an element by ID, generating new IDs for all child nodes.', 'pagelayer'),
@@ -2546,8 +2594,9 @@
2546 2594 'required' => array('post_id', 'element_id'),
2547 2595 'additionalProperties' => false
2548 2596 ),
2549 2597 'execute' => array(__CLASS__, 'execute_duplicate_element'),
2598 + 'perm' => array(__CLASS__, 'can_edit_pages'),
2550 2599 ),
2551 2600 'begin_transaction' => array(
2552 2601 'label' => __('Begin Transaction', 'pagelayer'),
2553 2602 'description' => __('Backup page layout state before multi-step modifications.', 'pagelayer'),
@@ -2558,8 +2607,9 @@
2558 2607 'required' => array('post_id'),
2559 2608 'additionalProperties' => false
2560 2609 ),
2561 2610 'execute' => array(__CLASS__, 'execute_begin_transaction'),
2611 + 'perm' => array(__CLASS__, 'can_edit_pages'),
2562 2612 ),
2563 2613 'commit_transaction' => array(
2564 2614 'label' => __('Commit Transaction', 'pagelayer'),
2565 2615 'description' => __('Commit layout changes and delete backup state.', 'pagelayer'),
@@ -2570,8 +2620,9 @@
2570 2620 'required' => array('post_id'),
2571 2621 'additionalProperties' => false
2572 2622 ),
2573 2623 'execute' => array(__CLASS__, 'execute_commit_transaction'),
2624 + 'perm' => array(__CLASS__, 'can_edit_pages'),
2574 2625 ),
2575 2626 'rollback_transaction' => array(
2576 2627 'label' => __('Rollback Transaction', 'pagelayer'),
2577 2628 'description' => __('Restore original page layout state from transaction backup.', 'pagelayer'),
@@ -2582,8 +2633,9 @@
2582 2633 'required' => array('post_id'),
2583 2634 'additionalProperties' => false
2584 2635 ),
2585 2636 'execute' => array(__CLASS__, 'execute_rollback_transaction'),
2637 + 'perm' => array(__CLASS__, 'can_edit_pages'),
2586 2638 ),
2587 2639 );
2588 2640
2589 2641 foreach ($abilities as $id => $def) {
@@ -2614,8 +2666,9 @@
2614 2666 'required' => array('title', 'pagelayer_data'),
2615 2667 'additionalProperties' => false
2616 2668 ),
2617 2669 'execute' => array(__CLASS__, 'execute_create_post'),
2670 + 'perm' => array(__CLASS__, 'can_edit_posts'),
2618 2671 ),
2619 2672 'update_post' => array(
2620 2673 'label' => __('Update Individual Post', 'pagelayer'),
2621 2674 'description' => __('Update an existing blog post title, layout data, categories, tags, excerpt, or status.', 'pagelayer'),
@@ -2635,8 +2688,9 @@
2635 2688 'required' => array('post_id'),
2636 2689 'additionalProperties' => false
2637 2690 ),
2638 2691 'execute' => array(__CLASS__, 'execute_update_post'),
2692 + 'perm' => array(__CLASS__, 'can_edit_posts'),
2639 2693 ),
2640 2694 'get_post' => array(
2641 2695 'label' => __('Get Individual Post', 'pagelayer'),
2642 2696 'description' => __('Retrieve blog post details, categories, tags, excerpt, featured image, and pagelayer_data.', 'pagelayer'),
@@ -2674,8 +2728,9 @@
2674 2728 'required' => array('post_id'),
2675 2729 'additionalProperties' => false
2676 2730 ),
2677 2731 'execute' => array(__CLASS__, 'execute_publish_post'),
2732 + 'perm' => array(__CLASS__, 'can_publish_posts'),
2678 2733 ),
2679 2734 'duplicate_post' => array(
2680 2735 'label' => __('Duplicate Individual Post', 'pagelayer'),
2681 2736 'description' => __('Clone an existing blog post with new Pagelayer element IDs.', 'pagelayer'),
@@ -2689,8 +2744,9 @@
2689 2744 'required' => array('post_id'),
2690 2745 'additionalProperties' => false
2691 2746 ),
2692 2747 'execute' => array(__CLASS__, 'execute_duplicate_post'),
2748 + 'perm' => array(__CLASS__, 'can_edit_posts'),
2693 2749 ),
2694 2750 'delete_post' => array(
2695 2751 'label' => __('Delete Individual Post', 'pagelayer'),
2696 2752 'description' => __('Trash or delete a blog post by ID.', 'pagelayer'),
@@ -2704,8 +2760,9 @@
2704 2760 'required' => array('post_id'),
2705 2761 'additionalProperties' => false
2706 2762 ),
2707 2763 'execute' => array(__CLASS__, 'execute_delete_post'),
2764 + 'perm' => array(__CLASS__, 'can_delete_posts'),
2708 2765 ),
2709 2766 );
2710 2767
2711 2768 foreach ($abilities as $id => $def) {
@@ -2728,8 +2785,9 @@
2728 2785 'required' => array('url'),
2729 2786 'additionalProperties' => false
2730 2787 ),
2731 2788 'execute' => array(__CLASS__, 'execute_upload_media'),
2789 + 'perm' => array(__CLASS__, 'can_upload_files'),
2732 2790 ),
2733 2791 );
2734 2792
2735 2793 foreach ($abilities as $id => $def) {
@@ -2985,8 +3043,12 @@
2985 3043 if (empty($section_id) || !$post_id || !get_post($post_id)) {
2986 3044 return new \WP_Error('invalid_input', __('Valid section_id and post_id are required.', 'pagelayer'));
2987 3045 }
2988 3046
3047 + if (!current_user_can('edit_post', $post_id)) {
3048 + return new \WP_Error('forbidden', __('You are not allowed to edit this post.', 'pagelayer'), array('status' => 403));
3049 + }
3050 +
2989 3051 global $pagelayer;
2990 3052 $license_key = !empty($pagelayer->license['license']) ? $pagelayer->license['license'] : '';
2991 3053 $url = 'https://api.pagelayer.com/library.php?give_id=' . rawurlencode($section_id) . '&license=' . rawurlencode($license_key) . '&url=' . rawurlencode(site_url());
2992 3054
@@ -3044,10 +3106,10 @@
3044 3106 * and its own judgement about layout, matching the requested niche/brand.
3045 3107 */
3046 3108 public static function execute_scrape_website_content($input) {
3047 3109 $url = isset($input['url']) ? esc_url_raw($input['url']) : '';
3048 - if (empty($url)) {
3049 - return new \WP_Error('missing_url', __('URL is required.', 'pagelayer'));
3110 + if (empty($url) || !wp_http_validate_url($url)) {
3111 + return new \WP_Error('invalid_url', __('A valid public URL is required.', 'pagelayer'));
3050 3112 }
3051 3113
3052 3114 $response = wp_remote_get($url, array(
3053 3115 'timeout' => 25,
@@ -3158,8 +3220,11 @@
3158 3220 );
3159 3221 }
3160 3222
3161 3223 public static function execute_update_styles($input) {
3224 + if (!current_user_can('manage_options')) {
3225 + return new \WP_Error('forbidden', __('You do not have permission to update global styles.', 'pagelayer'), array('status' => 403));
3226 + }
3162 3227 self::maybe_update_global_styles($input);
3163 3228 return array('success' => true);
3164 3229 }
3165 3230
@@ -3662,8 +3727,12 @@
3662 3727 unset($node);
3663 3728 }
3664 3729
3665 3730 public static function execute_create_template($input) {
3731 + if (!current_user_can('edit_theme_options')) {
3732 + return new \WP_Error('forbidden', __('You are not allowed to create theme templates.', 'pagelayer'), array('status' => 403));
3733 + }
3734 +
3666 3735 $title = isset($input['title']) ? sanitize_text_field($input['title']) : '';
3667 3736 $type = isset($input['type']) ? sanitize_text_field($input['type']) : 'general';
3668 3737 $p_data = isset($input['pagelayer_data']) && is_array($input['pagelayer_data']) ? $input['pagelayer_data'] : array();
3669 3738
@@ -3769,8 +3838,12 @@
3769 3838 if (!$template_id || get_post_type($template_id) !== 'pagelayer-template') {
3770 3839 return new \WP_Error('invalid_template', __('Template not found.', 'pagelayer'));
3771 3840 }
3772 3841
3842 + if (!current_user_can('edit_theme_options') || !current_user_can('edit_post', $template_id)) {
3843 + return new \WP_Error('forbidden', __('You are not allowed to edit this template.', 'pagelayer'), array('status' => 403));
3844 + }
3845 +
3773 3846 $type = isset($input['type'])
3774 3847 ? sanitize_text_field($input['type'])
3775 3848 : (string) get_post_meta($template_id, 'pagelayer_template_type', true);
3776 3849
@@ -3815,8 +3888,12 @@
3815 3888 if (!$template_id || get_post_type($template_id) !== 'pagelayer-template') {
3816 3889 return new \WP_Error('invalid_template', __('Template not found.', 'pagelayer'));
3817 3890 }
3818 3891
3892 + if (!current_user_can('edit_theme_options') || !current_user_can('delete_post', $template_id)) {
3893 + return new \WP_Error('forbidden', __('You are not allowed to delete this template.', 'pagelayer'), array('status' => 403));
3894 + }
3895 +
3819 3896 $res = wp_delete_post($template_id, true);
3820 3897 return array('success' => (bool)$res);
3821 3898 }
3822 3899
@@ -3952,8 +4029,12 @@
3952 4029 );
3953 4030 }
3954 4031
3955 4032 public static function execute_create_menu($input) {
4033 + if (!current_user_can('manage_options') && !current_user_can('edit_theme_options')) {
4034 + return new \WP_Error('forbidden', __('You are not allowed to manage navigation menus.', 'pagelayer'), array('status' => 403));
4035 + }
4036 +
3956 4037 $name = isset($input['name']) ? sanitize_text_field($input['name']) : '';
3957 4038 if ($name === '') {
3958 4039 return new \WP_Error('missing_name', __('A menu name is required.', 'pagelayer'));
3959 4040 }
@@ -4176,8 +4257,12 @@
4176 4257 return self::serialize_layout_to_blocks(array($node));
4177 4258 }
4178 4259
4179 4260 public static function execute_delete_menu($input) {
4261 + if (!current_user_can('manage_options') && !current_user_can('edit_theme_options')) {
4262 + return new \WP_Error('forbidden', __('You are not allowed to delete navigation menus.', 'pagelayer'), array('status' => 403));
4263 + }
4264 +
4180 4265 $menu = self::resolve_menu(isset($input['menu']) ? sanitize_text_field($input['menu']) : '');
4181 4266 if (!$menu) {
4182 4267 return new \WP_Error('invalid_menu', __('Navigation menu not found.', 'pagelayer'));
4183 4268 }
@@ -4204,8 +4289,16 @@
4204 4289 if (!$post_id || get_post_type($post_id) !== 'page') {
4205 4290 return new \WP_Error('invalid_page', __('Page not found.', 'pagelayer'));
4206 4291 }
4207 4292
4293 + if (!current_user_can('edit_post', $post_id)) {
4294 + return new \WP_Error('forbidden', __('You are not allowed to edit this page.', 'pagelayer'), array('status' => 403));
4295 + }
4296 +
4297 + if (isset($input['status']) && $input['status'] === 'publish' && !current_user_can('publish_pages')) {
4298 + return new \WP_Error('forbidden', __('You are not allowed to publish pages.', 'pagelayer'), array('status' => 403));
4299 + }
4300 +
4208 4301 if (isset($input['title'])) {
4209 4302 wp_update_post(array('ID' => $post_id, 'post_title' => sanitize_text_field($input['title'])));
4210 4303 }
4211 4304 if (isset($input['status'])) {
@@ -4306,12 +4399,18 @@
4306 4399 }
4307 4400
4308 4401 public static function execute_publish_page($input) {
4309 4402 $post_id = isset($input['post_id']) ? (int) $input['post_id'] : 0;
4310 - if (!$post_id || !get_post($post_id)) {
4403 + $post = get_post($post_id);
4404 + if (!$post_id || !$post) {
4311 4405 return new \WP_Error('invalid_post', __('Post or page not found.', 'pagelayer'));
4312 4406 }
4313 4407
4408 + $cap = ($post->post_type === 'page') ? 'publish_pages' : 'publish_posts';
4409 + if (!current_user_can('edit_post', $post_id) || !current_user_can($cap)) {
4410 + return new \WP_Error('forbidden', __('You are not allowed to publish this item.', 'pagelayer'), array('status' => 403));
4411 + }
4412 +
4314 4413 wp_update_post(array('ID' => $post_id, 'post_status' => 'publish'));
4315 4414 return array('success' => true, 'url' => get_permalink($post_id));
4316 4415 }
4317 4416
@@ -4321,8 +4420,17 @@
4321 4420 if (!$post) {
4322 4421 return new \WP_Error('invalid_post', __('Post or page not found.', 'pagelayer'));
4323 4422 }
4324 4423
4424 + if (!current_user_can('edit_post', $post_id)) {
4425 + return new \WP_Error('forbidden', __('You are not allowed to duplicate this item.', 'pagelayer'), array('status' => 403));
4426 + }
4427 +
4428 + $cap = ($post->post_type === 'page') ? 'edit_pages' : 'edit_posts';
4429 + if (!current_user_can($cap)) {
4430 + return new \WP_Error('forbidden', __('You are not allowed to create this item.', 'pagelayer'), array('status' => 403));
4431 + }
4432 +
4325 4433 $title = isset($input['title']) ? sanitize_text_field($input['title']) : $post->post_title . ' (Copy)';
4326 4434
4327 4435 $new_id = wp_insert_post(array(
4328 4436 'post_title' => $title,
@@ -4372,8 +4480,12 @@
4372 4480 if (!$post_id || get_post_type($post_id) !== 'page') {
4373 4481 return new \WP_Error('invalid_page', __('Page not found.', 'pagelayer'));
4374 4482 }
4375 4483
4484 + if (!current_user_can('delete_post', $post_id)) {
4485 + return new \WP_Error('forbidden', __('You are not allowed to delete this page.', 'pagelayer'), array('status' => 403));
4486 + }
4487 +
4376 4488 $res = wp_delete_post($post_id, $force);
4377 4489 return array('success' => (bool)$res);
4378 4490 }
4379 4491
@@ -5158,8 +5270,16 @@
5158 5270 if (!$post || $post->post_type !== 'post') {
5159 5271 return new \WP_Error('invalid_post', __('Blog post not found.', 'pagelayer'));
5160 5272 }
5161 5273
5274 + if (!current_user_can('edit_post', $post_id)) {
5275 + return new \WP_Error('forbidden', __('You are not allowed to edit this post.', 'pagelayer'), array('status' => 403));
5276 + }
5277 +
5278 + if (isset($input['status']) && $input['status'] === 'publish' && !current_user_can('publish_posts')) {
5279 + return new \WP_Error('forbidden', __('You are not allowed to publish posts.', 'pagelayer'), array('status' => 403));
5280 + }
5281 +
5162 5282 if (isset($input['title'])) {
5163 5283 wp_update_post(array('ID' => $post_id, 'post_title' => sanitize_text_field($input['title'])));
5164 5284 }
5165 5285 if (isset($input['status'])) {
@@ -5291,8 +5411,12 @@
5291 5411 if (!$post_id || get_post_type($post_id) !== 'post') {
5292 5412 return new \WP_Error('invalid_post', __('Blog post not found.', 'pagelayer'));
5293 5413 }
5294 5414
5415 + if (!current_user_can('delete_post', $post_id)) {
5416 + return new \WP_Error('forbidden', __('You are not allowed to delete this post.', 'pagelayer'), array('status' => 403));
5417 + }
5418 +
5295 5419 $res = wp_delete_post($post_id, $force);
5296 5420 return array('success' => (bool)$res);
5297 5421 }
5298 5422
@@ -5305,8 +5429,24 @@
5305 5429 $title = isset($input['title']) ? sanitize_text_field($input['title']) : '';
5306 5430 $post_type = isset($input['post_type']) ? sanitize_text_field($input['post_type']) : 'page';
5307 5431 $status = isset($input['status']) ? sanitize_text_field($input['status']) : 'publish';
5308 5432
5433 + if ($post_type === 'page') {
5434 + if (!current_user_can('edit_pages')) {
5435 + return new \WP_Error('forbidden', __('You are not allowed to create pages.', 'pagelayer'), array('status' => 403));
5436 + }
5437 + if ($status === 'publish' && !current_user_can('publish_pages')) {
5438 + $status = 'draft';
5439 + }
5440 + } else {
5441 + if (!current_user_can('edit_posts')) {
5442 + return new \WP_Error('forbidden', __('You are not allowed to create posts.', 'pagelayer'), array('status' => 403));
5443 + }
5444 + if ($status === 'publish' && !current_user_can('publish_posts')) {
5445 + $status = 'draft';
5446 + }
5447 + }
5448 +
5309 5449 if (empty($title)) {
5310 5450 return new \WP_Error('missing_title', __('A title is required.', 'pagelayer'));
5311 5451 }
5312 5452
@@ -5395,13 +5535,13 @@
5395 5535 set_post_thumbnail($post_id, $img_id);
5396 5536 }
5397 5537 }
5398 5538
5399 - if (!empty($input['is_homepage'])) {
5539 + if (!empty($input['is_homepage']) && current_user_can('manage_options')) {
5400 5540 update_option('show_on_front', 'page');
5401 5541 update_option('page_on_front', $post_id);
5402 5542 }
5403 - if (!empty($input['is_posts_page'])) {
5543 + if (!empty($input['is_posts_page']) && current_user_can('manage_options')) {
5404 5544 update_option('page_for_posts', $post_id);
5405 5545 }
5406 5546
5407 5547 return array(
@@ -5753,8 +5893,11 @@
5753 5893 $post_id = isset($input['post_id']) ? (int) $input['post_id'] : 0;
5754 5894 if (!$post_id || !get_post($post_id)) {
5755 5895 return new \WP_Error('invalid_post', __('Post not found.', 'pagelayer'));
5756 5896 }
5897 + if (!current_user_can('edit_post', $post_id)) {
5898 + return new \WP_Error('unauthorized', __('You do not have permission to edit this post.', 'pagelayer'));
5899 + }
5757 5900
5758 5901 if (!isset($input['pagelayer_data']) || !is_array($input['pagelayer_data'])) {
5759 5902 return new \WP_Error('missing_pagelayer_data', __('pagelayer_data is required.', 'pagelayer'));
5760 5903 }
@@ -5803,8 +5946,11 @@
5803 5946 $post_id = isset($input['post_id']) ? (int) $input['post_id'] : 0;
5804 5947 if (!$post_id || !get_post($post_id)) {
5805 5948 return new \WP_Error('invalid_post', __('Post not found.', 'pagelayer'));
5806 5949 }
5950 + if (!current_user_can('edit_post', $post_id)) {
5951 + return new \WP_Error('unauthorized', __('You do not have permission to edit this post.', 'pagelayer'));
5952 + }
5807 5953
5808 5954 if (isset($input['pagelayer_data']) && is_array($input['pagelayer_data'])) {
5809 5955 $inline_css = self::inline_css_gate($input['pagelayer_data']);
5810 5956 if (is_wp_error($inline_css)) {
@@ -5836,8 +5982,11 @@
5836 5982 $post_id = isset($input['post_id']) ? (int) $input['post_id'] : 0;
5837 5983 if (!$post_id || !get_post($post_id)) {
5838 5984 return new \WP_Error('invalid_post', __('Post not found.', 'pagelayer'));
5839 5985 }
5986 + if (!current_user_can('edit_post', $post_id)) {
5987 + return new \WP_Error('unauthorized', __('You do not have permission to edit this post.', 'pagelayer'));
5988 + }
5840 5989
5841 5990 $styles = isset($input['styles']) && is_array($input['styles']) ? $input['styles'] : array();
5842 5991 if (empty($styles)) {
5843 5992 return new \WP_Error('no_styles', __('No style changes provided.', 'pagelayer'));
@@ -5982,9 +6131,17 @@
5982 6131 );
5983 6132 }
5984 6133
5985 6134 public static function execute_find_elements($input) {
5986 - $post_id = (int) $input['post_id'];
6135 + $post_id = isset($input['post_id']) ? (int) $input['post_id'] : 0;
6136 + $post = get_post($post_id);
6137 + if (!$post) {
6138 + return new \WP_Error('invalid_post', __('Post not found.', 'pagelayer'));
6139 + }
6140 + if ('publish' !== $post->post_status && !current_user_can('edit_post', $post_id)) {
6141 + return new \WP_Error('unauthorized', __('You do not have permission to view this post.', 'pagelayer'));
6142 + }
6143 +
5987 6144 $data = get_post_meta($post_id, 'pagelayer-data', true);
5988 6145 if (!is_array($data)) {
5989 6146 return array('elements' => array());
5990 6147 }
@@ -6252,9 +6409,17 @@
6252 6409 return $id === null ? $ref : $id;
6253 6410 }
6254 6411
6255 6412 public static function execute_navigator($input) {
6256 - $post_id = (int) $input['post_id'];
6413 + $post_id = isset($input['post_id']) ? (int) $input['post_id'] : 0;
6414 + $post = get_post($post_id);
6415 + if (!$post) {
6416 + return new \WP_Error('invalid_post', __('Post not found.', 'pagelayer'));
6417 + }
6418 + if ('publish' !== $post->post_status && !current_user_can('edit_post', $post_id)) {
6419 + return new \WP_Error('unauthorized', __('You do not have permission to view this post.', 'pagelayer'));
6420 + }
6421 +
6257 6422 $data = get_post_meta($post_id, 'pagelayer-data', true);
6258 6423 if (!is_array($data)) {
6259 6424 return array('outline' => array());
6260 6425 }
@@ -6266,9 +6431,16 @@
6266 6431 );
6267 6432 }
6268 6433
6269 6434 public static function execute_update_element($input) {
6270 - $post_id = (int) $input['post_id'];
6435 + $post_id = isset($input['post_id']) ? (int) $input['post_id'] : 0;
6436 + if (!$post_id || !get_post($post_id)) {
6437 + return new \WP_Error('invalid_post', __('Post not found.', 'pagelayer'));
6438 + }
6439 + if (!current_user_can('edit_post', $post_id)) {
6440 + return new \WP_Error('unauthorized', __('You do not have permission to edit this post.', 'pagelayer'));
6441 + }
6442 +
6271 6443 $element_id = self::resolve_element_ref($post_id, sanitize_text_field($input['element_id']));
6272 6444 $data = get_post_meta($post_id, 'pagelayer-data', true);
6273 6445 if (!is_array($data)) {
6274 6446 return new \WP_Error('no_data', __('Page has no Pagelayer data.', 'pagelayer'));
@@ -6345,9 +6517,16 @@
6345 6517 return array('success' => true);
6346 6518 }
6347 6519
6348 6520 public static function execute_add_element($input) {
6349 - $post_id = (int) $input['post_id'];
6521 + $post_id = isset($input['post_id']) ? (int) $input['post_id'] : 0;
6522 + if (!$post_id || !get_post($post_id)) {
6523 + return new \WP_Error('invalid_post', __('Post not found.', 'pagelayer'));
6524 + }
6525 + if (!current_user_can('edit_post', $post_id)) {
6526 + return new \WP_Error('unauthorized', __('You do not have permission to edit this post.', 'pagelayer'));
6527 + }
6528 +
6350 6529 $parent_id = isset($input['parent_id']) ? self::resolve_element_ref($post_id, sanitize_text_field($input['parent_id'])) : '';
6351 6530
6352 6531 $inline_css_found = array();
6353 6532 self::scrub_node_inline_css($input['element'], $inline_css_found);
@@ -6418,9 +6597,16 @@
6418 6597 return new \WP_Error('parent_not_found', __('Parent element not found.', 'pagelayer'));
6419 6598 }
6420 6599
6421 6600 public static function execute_delete_element($input) {
6422 - $post_id = (int) $input['post_id'];
6601 + $post_id = isset($input['post_id']) ? (int) $input['post_id'] : 0;
6602 + if (!$post_id || !get_post($post_id)) {
6603 + return new \WP_Error('invalid_post', __('Post not found.', 'pagelayer'));
6604 + }
6605 + if (!current_user_can('edit_post', $post_id)) {
6606 + return new \WP_Error('unauthorized', __('You do not have permission to edit this post.', 'pagelayer'));
6607 + }
6608 +
6423 6609 $element_id = self::resolve_element_ref($post_id, sanitize_text_field($input['element_id']));
6424 6610 $data = get_post_meta($post_id, 'pagelayer-data', true);
6425 6611 if (!is_array($data)) {
6426 6612 return new \WP_Error('no_data', __('Page has no Pagelayer data.', 'pagelayer'));
@@ -6470,9 +6656,16 @@
6470 6656 return new \WP_Error('not_found', __('Element not found.', 'pagelayer'));
6471 6657 }
6472 6658
6473 6659 public static function execute_move_element($input) {
6474 - $post_id = (int) $input['post_id'];
6660 + $post_id = isset($input['post_id']) ? (int) $input['post_id'] : 0;
6661 + if (!$post_id || !get_post($post_id)) {
6662 + return new \WP_Error('invalid_post', __('Post not found.', 'pagelayer'));
6663 + }
6664 + if (!current_user_can('edit_post', $post_id)) {
6665 + return new \WP_Error('unauthorized', __('You do not have permission to edit this post.', 'pagelayer'));
6666 + }
6667 +
6475 6668 $element_id = self::resolve_element_ref($post_id, sanitize_text_field($input['element_id']));
6476 6669 $parent_id = isset($input['parent_id']) ? self::resolve_element_ref($post_id, sanitize_text_field($input['parent_id'])) : '';
6477 6670 $index = isset($input['index']) ? (int) $input['index'] : -1;
6478 6671
@@ -6567,9 +6760,16 @@
6567 6760 return new \WP_Error('parent_not_found', __('Target parent not found.', 'pagelayer'));
6568 6761 }
6569 6762
6570 6763 public static function execute_duplicate_element($input) {
6571 - $post_id = (int) $input['post_id'];
6764 + $post_id = isset($input['post_id']) ? (int) $input['post_id'] : 0;
6765 + if (!$post_id || !get_post($post_id)) {
6766 + return new \WP_Error('invalid_post', __('Post not found.', 'pagelayer'));
6767 + }
6768 + if (!current_user_can('edit_post', $post_id)) {
6769 + return new \WP_Error('unauthorized', __('You do not have permission to edit this post.', 'pagelayer'));
6770 + }
6771 +
6572 6772 $element_id = self::resolve_element_ref($post_id, sanitize_text_field($input['element_id']));
6573 6773 $data = get_post_meta($post_id, 'pagelayer-data', true);
6574 6774 if (!is_array($data)) {
6575 6775 return new \WP_Error('no_data', __('Page has no Pagelayer data.', 'pagelayer'));
@@ -6640,15 +6840,18 @@
6640 6840 return new \WP_Error('not_found', __('Element to duplicate not found.', 'pagelayer'));
6641 6841 }
6642 6842
6643 6843 public static function execute_begin_transaction($input) {
6644 - $post_id = (int) $input['post_id'];
6645 - $data = get_post_meta($post_id, 'pagelayer-data', true);
6844 + $post_id = isset($input['post_id']) ? (int) $input['post_id'] : 0;
6646 6845 $post = get_post($post_id);
6647 6846 if (!$post) {
6648 6847 return new \WP_Error('invalid_post', __('Post not found.', 'pagelayer'));
6649 6848 }
6849 + if (!current_user_can('edit_post', $post_id)) {
6850 + return new \WP_Error('unauthorized', __('You do not have permission to edit this post.', 'pagelayer'));
6851 + }
6650 6852
6853 + $data = get_post_meta($post_id, 'pagelayer-data', true);
6651 6854 $backup = array(
6652 6855 'data' => $data,
6653 6856 'content' => $post->post_content
6654 6857 );
@@ -6656,15 +6859,29 @@
6656 6859 return array('success' => true);
6657 6860 }
6658 6861
6659 6862 public static function execute_commit_transaction($input) {
6660 - $post_id = (int) $input['post_id'];
6863 + $post_id = isset($input['post_id']) ? (int) $input['post_id'] : 0;
6864 + if (!$post_id || !get_post($post_id)) {
6865 + return new \WP_Error('invalid_post', __('Post not found.', 'pagelayer'));
6866 + }
6867 + if (!current_user_can('edit_post', $post_id)) {
6868 + return new \WP_Error('unauthorized', __('You do not have permission to edit this post.', 'pagelayer'));
6869 + }
6870 +
6661 6871 delete_option('pagelayer_tx_backup_' . $post_id);
6662 6872 return array('success' => true);
6663 6873 }
6664 6874
6665 6875 public static function execute_rollback_transaction($input) {
6666 - $post_id = (int) $input['post_id'];
6876 + $post_id = isset($input['post_id']) ? (int) $input['post_id'] : 0;
6877 + if (!$post_id || !get_post($post_id)) {
6878 + return new \WP_Error('invalid_post', __('Post not found.', 'pagelayer'));
6879 + }
6880 + if (!current_user_can('edit_post', $post_id)) {
6881 + return new \WP_Error('unauthorized', __('You do not have permission to edit this post.', 'pagelayer'));
6882 + }
6883 +
6667 6884 $backup = get_option('pagelayer_tx_backup_' . $post_id);
6668 6885 if (!$backup) {
6669 6886 return new \WP_Error('no_backup', __('No active transaction to rollback.', 'pagelayer'));
6670 6887 }
@@ -6678,10 +6895,21 @@
6678 6895 return array('success' => true);
6679 6896 }
6680 6897
6681 6898 public static function execute_save_template($input) {
6899 + if (!current_user_can('edit_theme_options') && !current_user_can('manage_options')) {
6900 + return new \WP_Error('unauthorized', __('You do not have permission to save templates.', 'pagelayer'));
6901 + }
6902 +
6682 6903 $template_name = sanitize_text_field($input['name']);
6683 - $post_id = (int) $input['post_id'];
6904 + $post_id = isset($input['post_id']) ? (int) $input['post_id'] : 0;
6905 + if (!$post_id || !get_post($post_id)) {
6906 + return new \WP_Error('invalid_post', __('Post not found.', 'pagelayer'));
6907 + }
6908 + if (!current_user_can('edit_post', $post_id)) {
6909 + return new \WP_Error('unauthorized', __('You do not have permission to edit this post.', 'pagelayer'));
6910 + }
6911 +
6684 6912 $element_id = isset($input['element_id']) ? self::resolve_element_ref($post_id, sanitize_text_field($input['element_id'])) : '';
6685 6913
6686 6914 $data = get_post_meta($post_id, 'pagelayer-data', true);
6687 6915 if (!is_array($data)) {
@@ -6723,10 +6951,17 @@
6723 6951 return array('templates' => array_keys($library));
6724 6952 }
6725 6953
6726 6954 public static function execute_insert_template($input) {
6955 + $post_id = isset($input['post_id']) ? (int) $input['post_id'] : 0;
6956 + if (!$post_id || !get_post($post_id)) {
6957 + return new \WP_Error('invalid_post', __('Post not found.', 'pagelayer'));
6958 + }
6959 + if (!current_user_can('edit_post', $post_id)) {
6960 + return new \WP_Error('unauthorized', __('You do not have permission to edit this post.', 'pagelayer'));
6961 + }
6962 +
6727 6963 $template_name = sanitize_text_field($input['name']);
6728 - $post_id = (int) $input['post_id'];
6729 6964 $parent_id = isset($input['parent_id']) ? sanitize_text_field($input['parent_id']) : '';
6730 6965 $index = isset($input['index']) ? (int) $input['index'] : -1;
6731 6966
6732 6967 $library = get_option('pagelayer_template_library', array());
@@ -6803,9 +7038,17 @@
6803 7038 return new \WP_Error('parent_not_found', __('Parent not found.', 'pagelayer'));
6804 7039 }
6805 7040
6806 7041 public static function execute_upload_media($input) {
7042 + if (!current_user_can('upload_files')) {
7043 + return new \WP_Error('unauthorized', __('You do not have permission to upload files.', 'pagelayer'));
7044 + }
7045 +
6807 7046 $url = esc_url_raw($input['url']);
7047 + if (!wp_http_validate_url($url)) {
7048 + return new \WP_Error('invalid_url', __('Invalid media URL provided.', 'pagelayer'));
7049 + }
7050 +
6808 7051 $desc = isset($input['alt_text']) ? sanitize_text_field($input['alt_text']) : '';
6809 7052
6810 7053 require_once(ABSPATH . 'wp-admin/includes/image.php');
6811 7054 require_once(ABSPATH . 'wp-admin/includes/file.php');
@@ -6837,12 +7080,15 @@
6837 7080 );
6838 7081 }
6839 7082
6840 7083 public static function execute_get_preview($input) {
6841 - $post_id = (int) $input['post_id'];
7084 + $post_id = isset($input['post_id']) ? (int) $input['post_id'] : 0;
6842 7085 $post = get_post($post_id);
6843 7086 if (!$post) {
6844 7087 return new \WP_Error('invalid_post', __('Post not found.', 'pagelayer'));
7088 + }
7089 + if ('publish' !== $post->post_status && !current_user_can('edit_post', $post_id)) {
7090 + return new \WP_Error('unauthorized', __('You do not have permission to preview this post.', 'pagelayer'));
6845 7091 }
6846 7092 $url = ('publish' === $post->post_status)
6847 7093 ? get_permalink($post_id)
6848 7094 : get_preview_post_link($post_id);