Plugin Directory

Changeset 3472097


Ignore:
Timestamp:
03/01/2026 02:23:59 PM (8 days ago)
Author:
vzisis
Message:

bump to 0.3.5, tighten phpcs/gitignore housekeeping

Location:
editorial-workflow-manager/trunk
Files:
8 edited

Legend:

Unmodified
Added
Removed
  • editorial-workflow-manager/trunk/editorial-workflow-manager.php

    r3457373 r3472097  
    11<?php
    2 
    32/**
    43 * Plugin Name: Editorial Workflow Manager
    54 * Description: Add editorial checklists and approvals to the WordPress editor.
    6  * Version:     0.3.4
     5 * Version:     0.3.5
    76 * Author:      Vasileios Zisis
    87 * Author URI:  https://profiles.wordpress.org/vzisis/
     
    1211 * License:           GPL-2.0-or-later
    1312 * License URI:       https://www.gnu.org/licenses/gpl-2.0.html
     13 *
     14 * @package EditorialWorkflowManager
    1415 */
    1516
    16 if (! defined('ABSPATH')) {
    17     exit; // Exit if accessed directly.
    18 }
    19 final class EDIWORMAN_Plugin
    20 {
    21 
    22     const VERSION = '0.3.3';
    23 
    24     private static $instance = null;
    25 
    26     /** @var EDIWORMAN_Templates_CPT */
    27     private $templates_cpt;
    28 
    29     /** @var EDIWORMAN_Settings */
    30     private $settings;
    31 
    32     /** @var EDIWORMAN_Meta */
    33     private $meta;
    34 
    35     /** @var EDIWORMAN_Editor_Assets */
    36     private $editor_assets;
    37 
    38     public static function instance()
    39     {
    40         if (null === self::$instance) {
    41             self::$instance = new self();
    42         }
    43 
    44         return self::$instance;
    45     }
    46 
    47     private function __construct()
    48     {
    49         $this->define_constants();
    50 
    51         // Load classes.
    52         require_once EDIWORMAN_PATH . 'includes/class-ediworman-templates-cpt.php';
    53         require_once EDIWORMAN_PATH . 'includes/class-ediworman-settings.php';
    54         require_once EDIWORMAN_PATH . 'includes/class-ediworman-meta.php';
    55         require_once EDIWORMAN_PATH . 'includes/class-ediworman-editor-assets.php';
    56         require_once EDIWORMAN_PATH . 'includes/class-ediworman-default-templates.php';
    57 
    58         // Instantiate.
    59         $this->templates_cpt = new EDIWORMAN_Templates_CPT();
    60         $this->settings      = new EDIWORMAN_Settings();
    61         $this->meta          = new EDIWORMAN_Meta();
    62         $this->editor_assets = new EDIWORMAN_Editor_Assets();
    63 
    64         // Hooks.
    65         add_action('init', [$this, 'on_init']);
    66     }
    67 
    68     private function define_constants()
    69     {
    70         define('EDIWORMAN_VERSION', self::VERSION);
    71         define('EDIWORMAN_FILE', __FILE__);
    72         define('EDIWORMAN_PATH', plugin_dir_path(__FILE__));
    73         define('EDIWORMAN_URL', plugin_dir_url(__FILE__));
    74     }
    75 
    76     public function on_init()
    77     {
    78         // other init stuff later (if needed)
    79     }
    80 
    81     public static function activate()
    82     {
    83         EDIWORMAN_Default_Templates::create_on_activation();
    84     }
     17if ( ! defined( 'ABSPATH' ) ) {
     18    exit; // Exit if accessed directly.
    8519}
    8620
    87 register_activation_hook(__FILE__, ['EDIWORMAN_Plugin', 'activate']);
     21/**
     22 * Plugin bootstrap and service wiring.
     23 */
     24final class EDIWORMAN_Plugin {
     25
     26    const VERSION = '0.3.5';
     27
     28    /**
     29     * Singleton plugin instance.
     30     *
     31     * @var EDIWORMAN_Plugin|null
     32     */
     33    private static $instance = null;
     34
     35    /**
     36     * Checklist template CPT handler.
     37     *
     38     * @var EDIWORMAN_Templates_CPT
     39     */
     40    private $templates_cpt;
     41
     42    /**
     43     * Plugin settings handler.
     44     *
     45     * @var EDIWORMAN_Settings
     46     */
     47    private $settings;
     48
     49    /**
     50     * Post meta registration and save hooks.
     51     *
     52     * @var EDIWORMAN_Meta
     53     */
     54    private $meta;
     55
     56    /**
     57     * Block editor asset loader.
     58     *
     59     * @var EDIWORMAN_Editor_Assets
     60     */
     61    private $editor_assets;
     62
     63    /**
     64     * Get plugin singleton instance.
     65     *
     66     * @return EDIWORMAN_Plugin
     67     */
     68    public static function instance() {
     69        if ( null === self::$instance ) {
     70            self::$instance = new self();
     71        }
     72
     73        return self::$instance;
     74    }
     75
     76    /**
     77     * Construct plugin services and hooks.
     78     */
     79    private function __construct() {
     80        $this->define_constants();
     81
     82        // Load classes.
     83        require_once EDIWORMAN_PATH . 'includes/class-ediworman-templates-cpt.php';
     84        require_once EDIWORMAN_PATH . 'includes/class-ediworman-settings.php';
     85        require_once EDIWORMAN_PATH . 'includes/class-ediworman-meta.php';
     86        require_once EDIWORMAN_PATH . 'includes/class-ediworman-editor-assets.php';
     87        require_once EDIWORMAN_PATH . 'includes/class-ediworman-default-templates.php';
     88
     89        // Instantiate.
     90        $this->templates_cpt = new EDIWORMAN_Templates_CPT();
     91        $this->settings      = new EDIWORMAN_Settings();
     92        $this->meta          = new EDIWORMAN_Meta();
     93        $this->editor_assets = new EDIWORMAN_Editor_Assets();
     94
     95        // Hooks.
     96        add_action( 'init', array( $this, 'on_init' ) );
     97    }
     98
     99    /**
     100     * Define plugin constants used across the codebase.
     101     *
     102     * @return void
     103     */
     104    private function define_constants() {
     105        define( 'EDIWORMAN_VERSION', self::VERSION );
     106        define( 'EDIWORMAN_FILE', __FILE__ );
     107        define( 'EDIWORMAN_PATH', plugin_dir_path( __FILE__ ) );
     108        define( 'EDIWORMAN_URL', plugin_dir_url( __FILE__ ) );
     109    }
     110
     111    /**
     112     * Run init-time plugin behavior.
     113     *
     114     * @return void
     115     */
     116    public function on_init() {
     117        // Reserved for future init-time hooks.
     118    }
     119
     120    /**
     121     * Activation callback.
     122     *
     123     * @return void
     124     */
     125    public static function activate() {
     126        EDIWORMAN_Default_Templates::create_on_activation();
     127    }
     128}
     129
     130register_activation_hook( __FILE__, array( 'EDIWORMAN_Plugin', 'activate' ) );
    88131
    89132// Bootstrap the plugin.
  • editorial-workflow-manager/trunk/includes/class-ediworman-default-templates.php

    r3433414 r3472097  
    11<?php
     2/**
     3 * Create default checklist templates on plugin activation.
     4 *
     5 * @package EditorialWorkflowManager
     6 */
     7
     8if ( ! defined( 'ABSPATH' ) ) {
     9    exit;
     10}
    211
    312/**
    4  * Create default checklist templates on plugin activation.
     13 * Creates default template posts and default post type mappings.
    514 */
     15class EDIWORMAN_Default_Templates {
    616
    7 if (! defined('ABSPATH')) {
    8     exit;
     17    /**
     18     * Create default templates and map Blog Post SOP to "post".
     19     */
     20    public static function create_on_activation() {
     21        // Ensure the CPT is registered so the admin will see these nicely.
     22        $cpt = new EDIWORMAN_Templates_CPT();
     23        $cpt->register_cpt();
     24
     25        // 1) Define the templates and their items.
     26        $definitions = array(
     27            'Blog Post SOP'            => array(
     28                'Set featured image',
     29                'Write excerpt / meta description',
     30                'Add at least 2 internal links',
     31                'Check external links (open in new tab if needed)',
     32                'Spellcheck and grammar check',
     33                'Confirm category and tags',
     34            ),
     35            'Landing Page QA'          => array(
     36                'Check layout on mobile',
     37                'Test primary CTA button/link',
     38                'Test form submission (if any)',
     39                'Confirm thank-you page or message',
     40                'Confirm analytics / pixel tracking',
     41                'Check page speed (basic)',
     42            ),
     43            'Announcement / News Post' => array(
     44                'Verify dates, names, and key facts',
     45                'Add internal link to relevant product/service page',
     46                'Add featured image or banner',
     47                'Check tone and brand voice',
     48                'Confirm any required disclaimer',
     49                'Prepare or schedule social share copy',
     50            ),
     51        );
     52
     53        $created_ids = array();
     54
     55        foreach ( $definitions as $title => $items ) {
     56            // Try to find an existing template with this title.
     57            $existing = get_posts(
     58                array(
     59                    'post_type'      => 'ediworman_template',
     60                    'title'          => $title,
     61                    'post_status'    => 'any',
     62                    'posts_per_page' => 1,
     63                    'orderby'        => 'ID',
     64                    'order'          => 'ASC',
     65                    'fields'         => 'ids',
     66                    'no_found_rows'  => true,
     67                )
     68            );
     69
     70            if ( ! empty( $existing ) ) {
     71                $template_id = (int) $existing[0];
     72            } else {
     73                // Create a new template.
     74                $template_id = wp_insert_post(
     75                    array(
     76                        'post_type'   => 'ediworman_template',
     77                        'post_status' => 'publish',
     78                        'post_title'  => $title,
     79                    )
     80                );
     81            }
     82
     83            if ( $template_id && ! is_wp_error( $template_id ) ) {
     84                // Store / update the checklist items meta.
     85                update_post_meta( $template_id, '_ediworman_items', $items );
     86                $created_ids[ $title ] = (int) $template_id;
     87            }
     88        }
     89
     90        // 2) Map "Blog Post SOP" to the "post" post type, if nothing set yet.
     91        if ( isset( $created_ids['Blog Post SOP'] ) ) {
     92            $settings = get_option( EDIWORMAN_Settings::OPTION_NAME, array() );
     93
     94            if ( ! isset( $settings['post_type_templates'] ) || ! is_array( $settings['post_type_templates'] ) ) {
     95                $settings['post_type_templates'] = array();
     96            }
     97
     98            // Only set if there is no mapping yet (don't overwrite user choices).
     99            if ( empty( $settings['post_type_templates']['post'] ) ) {
     100                $settings['post_type_templates']['post'] = $created_ids['Blog Post SOP'];
     101                update_option( EDIWORMAN_Settings::OPTION_NAME, $settings );
     102            }
     103        }
     104    }
    9105}
    10 
    11 class EDIWORMAN_Default_Templates
    12 {
    13 
    14     /**
    15      * Create default templates and map Blog Post SOP to "post".
    16      */
    17     public static function create_on_activation()
    18     {
    19         // Ensure the CPT is registered so the admin will see these nicely.
    20         if (class_exists('EDIWORMAN_Templates_CPT')) {
    21             $cpt = new EDIWORMAN_Templates_CPT();
    22             $cpt->register_cpt();
    23         }
    24 
    25         // 1) Define the templates and their items.
    26         $definitions = [
    27             'Blog Post SOP' => [
    28                 'Set featured image',
    29                 'Write excerpt / meta description',
    30                 'Add at least 2 internal links',
    31                 'Check external links (open in new tab if needed)',
    32                 'Spellcheck and grammar check',
    33                 'Confirm category and tags',
    34             ],
    35             'Landing Page QA' => [
    36                 'Check layout on mobile',
    37                 'Test primary CTA button/link',
    38                 'Test form submission (if any)',
    39                 'Confirm thank-you page or message',
    40                 'Confirm analytics / pixel tracking',
    41                 'Check page speed (basic)',
    42             ],
    43             'Announcement / News Post' => [
    44                 'Verify dates, names, and key facts',
    45                 'Add internal link to relevant product/service page',
    46                 'Add featured image or banner',
    47                 'Check tone and brand voice',
    48                 'Confirm any required disclaimer',
    49                 'Prepare or schedule social share copy',
    50             ],
    51         ];
    52 
    53         $created_ids = [];
    54 
    55         foreach ($definitions as $title => $items) {
    56             // Try to find an existing template with this title.
    57             $existing = get_posts(
    58                 [
    59                     'post_type'      => 'ediworman_template',
    60                     'title'          => $title,
    61                     'post_status'    => 'any',
    62                     'posts_per_page' => 1,
    63                     'orderby'        => 'ID',
    64                     'order'          => 'ASC',
    65                     'fields'         => 'ids',
    66                     'no_found_rows'  => true,
    67                 ]
    68             );
    69 
    70             if (! empty($existing)) {
    71                 $template_id = (int) $existing[0];
    72             } else {
    73                 // Create a new template.
    74                 $template_id = wp_insert_post(
    75                     [
    76                         'post_type'   => 'ediworman_template',
    77                         'post_status' => 'publish',
    78                         'post_title'  => $title,
    79                     ]
    80                 );
    81             }
    82 
    83             if ($template_id && ! is_wp_error($template_id)) {
    84                 // Store / update the checklist items meta.
    85                 update_post_meta($template_id, '_ediworman_items', $items);
    86                 $created_ids[$title] = (int) $template_id;
    87             }
    88         }
    89 
    90         // 2) Map "Blog Post SOP" to the "post" post type, if nothing set yet.
    91         if (class_exists('EDIWORMAN_Settings') && isset($created_ids['Blog Post SOP'])) {
    92             $settings = get_option(EDIWORMAN_Settings::OPTION_NAME, []);
    93 
    94             if (! isset($settings['post_type_templates']) || ! is_array($settings['post_type_templates'])) {
    95                 $settings['post_type_templates'] = [];
    96             }
    97 
    98             // Only set if there is no mapping yet (don't overwrite user choices).
    99             if (empty($settings['post_type_templates']['post'])) {
    100                 $settings['post_type_templates']['post'] = $created_ids['Blog Post SOP'];
    101                 update_option(EDIWORMAN_Settings::OPTION_NAME, $settings);
    102             }
    103         }
    104     }
    105 }
  • editorial-workflow-manager/trunk/includes/class-ediworman-editor-assets.php

    r3457373 r3472097  
    11<?php
     2/**
     3 * Enqueue block editor assets (Gutenberg sidebar).
     4 *
     5 * @package EditorialWorkflowManager
     6 */
     7
     8if ( ! defined( 'ABSPATH' ) ) {
     9    exit;
     10}
    211
    312/**
    4  * Enqueue block editor assets (Gutenberg sidebar).
     13 * Enqueues editor scripts and localizes checklist data.
    514 */
     15class EDIWORMAN_Editor_Assets {
    616
    7 if (! defined('ABSPATH')) {
    8     exit;
     17    /**
     18     * Register editor enqueue hook.
     19     *
     20     * @return void
     21     */
     22    public function __construct() {
     23        // This hook only runs in the block editor.
     24        add_action( 'enqueue_block_editor_assets', array( $this, 'enqueue' ) );
     25    }
     26
     27    /**
     28     * Enqueue the sidebar JavaScript and pass checklist data to it.
     29     */
     30    public function enqueue() {
     31        // Always enqueue the JS in the block editor.
     32        wp_enqueue_script(
     33            'ediworman-sidebar',
     34            EDIWORMAN_URL . 'assets/js/sidebar.js',
     35            array(
     36                'wp-plugins',
     37                'wp-edit-post',
     38                'wp-element',
     39                'wp-components',
     40                'wp-data',
     41                'wp-core-data',
     42            ),
     43            EDIWORMAN_VERSION,
     44            true
     45        );
     46
     47        // Try to detect the current post type.
     48        if ( ! function_exists( 'get_current_screen' ) ) {
     49            return;
     50        }
     51
     52        $screen = get_current_screen();
     53
     54        // We only care about actual post editing screens.
     55        if ( ! $screen || empty( $screen->post_type ) || 'post' !== $screen->base ) {
     56            return;
     57        }
     58
     59        $post_type = sanitize_key( $screen->post_type );
     60        if ( ! $post_type || ! post_type_exists( $post_type ) ) {
     61            return;
     62        }
     63
     64        $template_id = null;
     65        $items       = array();
     66
     67        $template_id = EDIWORMAN_Settings::get_template_for_post_type( $post_type );
     68
     69        if ( $template_id ) {
     70            $template_id  = absint( $template_id );
     71            $stored_items = get_post_meta( $template_id, '_ediworman_items', true );
     72            if ( is_array( $stored_items ) ) {
     73                $items = array_values(
     74                    array_filter(
     75                        array_map( 'sanitize_text_field', $stored_items ),
     76                        'strlen'
     77                    )
     78                );
     79            }
     80        }
     81
     82        // Pass data to JS (even if items is empty).
     83        wp_localize_script(
     84            'ediworman-sidebar',
     85            'EDIWORMAN_CHECKLIST_DATA',
     86            array(
     87                'templateId' => $template_id,
     88                'postType'   => $post_type,
     89                'items'      => $items,
     90            )
     91        );
     92    }
    993}
    10 
    11 class EDIWORMAN_Editor_Assets
    12 {
    13 
    14     public function __construct()
    15     {
    16         // This hook only runs in the block editor.
    17         add_action('enqueue_block_editor_assets', [$this, 'enqueue']);
    18     }
    19 
    20     /**
    21      * Enqueue the sidebar JavaScript and pass checklist data to it.
    22      */
    23     public function enqueue()
    24     {
    25         // Always enqueue the JS in the block editor.
    26         wp_enqueue_script(
    27             'ediworman-sidebar',
    28             EDIWORMAN_URL . 'assets/js/sidebar.js',
    29             [
    30                 'wp-plugins',
    31                 'wp-edit-post',
    32                 'wp-element',
    33                 'wp-components',
    34                 'wp-data',
    35                 'wp-core-data',
    36             ],
    37             EDIWORMAN_VERSION,
    38             true
    39         );
    40 
    41         // Try to detect the current post type.
    42         if (! function_exists('get_current_screen')) {
    43             return;
    44         }
    45 
    46         $screen = get_current_screen();
    47 
    48         // We only care about actual post editing screens.
    49         if (! $screen || empty($screen->post_type) || $screen->base !== 'post') {
    50             return;
    51         }
    52 
    53         $post_type = sanitize_key($screen->post_type);
    54         if (! $post_type || ! post_type_exists($post_type)) {
    55             return;
    56         }
    57 
    58         $template_id = null;
    59         $items       = [];
    60 
    61         if (class_exists('EDIWORMAN_Settings')) {
    62             $template_id = EDIWORMAN_Settings::get_template_for_post_type($post_type);
    63         }
    64 
    65         if ($template_id) {
    66             $template_id = absint($template_id);
    67             $stored_items = get_post_meta($template_id, '_ediworman_items', true);
    68             if (is_array($stored_items)) {
    69                 $items = array_values(
    70                     array_filter(
    71                         array_map('sanitize_text_field', $stored_items),
    72                         'strlen'
    73                     )
    74                 );
    75             }
    76         }
    77 
    78         // Pass data to JS (even if items is empty).
    79         wp_localize_script(
    80             'ediworman-sidebar',
    81             'EDIWORMAN_CHECKLIST_DATA',
    82             [
    83                 'templateId' => $template_id,
    84                 'postType'   => $post_type,
    85                 'items'      => $items,
    86             ]
    87         );
    88     }
    89 }
  • editorial-workflow-manager/trunk/includes/class-ediworman-meta.php

    r3457373 r3472097  
    11<?php
     2/**
     3 * Post meta for storing checklist state per post.
     4 *
     5 * @package EditorialWorkflowManager
     6 */
     7
     8if ( ! defined( 'ABSPATH' ) ) {
     9    exit;
     10}
    211
    312/**
    4  * Post meta for storing checklist state per post.
     13 * Registers and maintains editorial checklist post meta.
    514 */
     15class EDIWORMAN_Meta {
    616
    7 if (! defined('ABSPATH')) {
    8     exit;
     17    /**
     18     * Register meta and save handlers.
     19     *
     20     * @return void
     21     */
     22    public function __construct() {
     23        add_action( 'init', array( $this, 'register_meta' ) );
     24        add_action( 'save_post', array( $this, 'capture_last_editor' ), 10, 1 );
     25    }
     26
     27    /**
     28     * Sanitize the checked items meta.
     29     *
     30     * @param mixed $value Raw meta value.
     31     * @return array
     32     */
     33    public function sanitize_checked_items( $value ) {
     34        if ( ! is_array( $value ) ) {
     35            if ( is_string( $value ) && '' !== $value ) {
     36                $value = array( $value );
     37            } else {
     38                return array();
     39            }
     40        }
     41
     42        $sanitized = array();
     43
     44        foreach ( $value as $item ) {
     45            if ( ! is_scalar( $item ) ) {
     46                continue;
     47            }
     48
     49            $label = sanitize_text_field( wp_unslash( (string) $item ) );
     50            if ( '' === $label ) {
     51                continue;
     52            }
     53
     54            $sanitized[] = $label;
     55        }
     56
     57        return array_values( array_unique( $sanitized ) );
     58    }
     59
     60    /**
     61     * Register post meta used by the plugin.
     62     *
     63     * - _ediworman_checked_items: array of checklist item labels that are checked.
     64     * - _ediworman_last_editor:   user ID of the last editor (for display only).
     65     *
     66     * @return void
     67     */
     68    public function register_meta() {
     69
     70        // Checklist state.
     71        register_post_meta(
     72            '',
     73            '_ediworman_checked_items',
     74            array(
     75                'single'            => true,
     76                'type'              => 'array',
     77                'default'           => array(),
     78                'show_in_rest'      => array(
     79                    'schema' => array(
     80                        'type'  => 'array',
     81                        'items' => array(
     82                            'type' => 'string',
     83                        ),
     84                    ),
     85                ),
     86                'sanitize_callback' => array( $this, 'sanitize_checked_items' ),
     87                'auth_callback'     => static function ( $allowed, $meta_key, $post_id ) {
     88                    if ( false === $allowed || '_ediworman_checked_items' !== $meta_key ) {
     89                        return false;
     90                    }
     91
     92                    $post_id = (int) $post_id;
     93                    if ( $post_id <= 0 ) {
     94                        return false;
     95                    }
     96
     97                    return current_user_can( 'edit_post', $post_id );
     98                },
     99            )
     100        );
     101
     102        // Last editor (user ID).
     103        register_post_meta(
     104            '',
     105            '_ediworman_last_editor',
     106            array(
     107                'single'            => true,
     108                'type'              => 'integer',
     109                'default'           => 0,
     110                'show_in_rest'      => true,
     111                'sanitize_callback' => 'absint',
     112                'auth_callback'     => static function ( $allowed, $meta_key, $post_id ) {
     113                    if ( false === $allowed || '_ediworman_last_editor' !== $meta_key ) {
     114                        return false;
     115                    }
     116
     117                    $post_id = (int) $post_id;
     118                    if ( $post_id <= 0 ) {
     119                        return false;
     120                    }
     121
     122                    return current_user_can( 'edit_post', $post_id );
     123                },
     124            )
     125        );
     126    }
     127
     128    /**
     129     * Store the last editor's user ID when a post is saved.
     130     *
     131     * @param int $post_id Post ID being saved.
     132     * @return void
     133     */
     134    public function capture_last_editor( $post_id ) {
     135        // Don't run on autosave / revisions.
     136        if ( wp_is_post_autosave( $post_id ) || wp_is_post_revision( $post_id ) ) {
     137            return;
     138        }
     139
     140        // Only for real posts/pages/etc.
     141
     142        // Permission check.
     143        if ( ! current_user_can( 'edit_post', $post_id ) ) {
     144            return;
     145        }
     146
     147        $user_id = get_current_user_id();
     148        if ( $user_id ) {
     149            update_post_meta( $post_id, '_ediworman_last_editor', (int) $user_id );
     150        }
     151    }
    9152}
    10 
    11 if (! class_exists('EDIWORMAN')) {
    12 
    13     class EDIWORMAN_Meta
    14     {
    15 
    16         public function __construct()
    17         {
    18             add_action('init', [$this, 'register_meta']);
    19             add_action('save_post', [$this, 'capture_last_editor'], 10, 3);
    20         }
    21 
    22         /**
    23          * Sanitize the checked items meta.
    24          *
    25          * @param mixed  $value
    26          * @param string $meta_key
    27          * @param string $object_type
    28          * @return array
    29          */
    30         public function sanitize_checked_items($value, $meta_key, $object_type)
    31         {
    32             if (! is_array($value)) {
    33                 if (is_string($value) && $value !== '') {
    34                     $value = [$value];
    35                 } else {
    36                     return [];
    37                 }
    38             }
    39 
    40             $sanitized = [];
    41 
    42             foreach ($value as $item) {
    43                 if (! is_scalar($item)) {
    44                     continue;
    45                 }
    46 
    47                 $label = sanitize_text_field(wp_unslash((string) $item));
    48                 if ($label === '') {
    49                     continue;
    50                 }
    51 
    52                 $sanitized[] = $label;
    53             }
    54 
    55             return array_values(array_unique($sanitized));
    56         }
    57 
    58         /**
    59          * Register post meta used by the plugin.
    60          *
    61          * - _ediworman_checked_items: array of checklist item labels that are checked.
    62          * - _ediworman_last_editor:   user ID of the last editor (for display only).
    63          */
    64         public function register_meta()
    65         {
    66 
    67             // Checklist state.
    68             register_post_meta(
    69                 '',
    70                 '_ediworman_checked_items',
    71                 [
    72                     'single'       => true,
    73                     'type'         => 'array',
    74                     'default'      => [],
    75                     'show_in_rest' => [
    76                         'schema' => [
    77                             'type'  => 'array',
    78                             'items' => [
    79                                 'type' => 'string',
    80                             ],
    81                         ],
    82                     ],
    83                     'sanitize_callback' => [$this, 'sanitize_checked_items'],
    84                     'auth_callback' => static function ($allowed, $meta_key, $post_id, $user_id = 0, $cap = '', $caps = []) {
    85                         $post_id = (int) $post_id;
    86                         if ($post_id <= 0) {
    87                             return false;
    88                         }
    89 
    90                         return current_user_can('edit_post', $post_id);
    91                     },
    92                 ]
    93             );
    94 
    95             // Last editor (user ID).
    96             register_post_meta(
    97                 '',
    98                 '_ediworman_last_editor',
    99                 [
    100                     'single'       => true,
    101                     'type'         => 'integer',
    102                     'default'      => 0,
    103                     'show_in_rest' => true,
    104                     'sanitize_callback' => 'absint',
    105                     'auth_callback' => static function ($allowed, $meta_key, $post_id, $user_id = 0, $cap = '', $caps = []) {
    106                         $post_id = (int) $post_id;
    107                         if ($post_id <= 0) {
    108                             return false;
    109                         }
    110 
    111                         return current_user_can('edit_post', $post_id);
    112                     },
    113                 ]
    114             );
    115         }
    116 
    117         /**
    118          * Store the last editor's user ID when a post is saved.
    119          *
    120          * @param int     $post_id
    121          * @param WP_Post $post
    122          * @param bool    $update
    123          */
    124         public function capture_last_editor($post_id, $post, $update)
    125         {
    126             // Don't run on autosave / revisions.
    127             if (wp_is_post_autosave($post_id) || wp_is_post_revision($post_id)) {
    128                 return;
    129             }
    130 
    131             // Only for real posts/pages/etc (not our template CPT if you prefer to exclude).
    132             // If you want to skip templates, uncomment:
    133             // if ( $post->post_type === 'ediworman_template' ) {
    134             //     return;
    135             // }
    136 
    137             // Permission check.
    138             if (! current_user_can('edit_post', $post_id)) {
    139                 return;
    140             }
    141 
    142             $user_id = get_current_user_id();
    143             if ($user_id) {
    144                 update_post_meta($post_id, '_ediworman_last_editor', (int) $user_id);
    145             }
    146         }
    147     }
    148 }
  • editorial-workflow-manager/trunk/includes/class-ediworman-settings.php

    r3457373 r3472097  
    11<?php
    2 
    32/**
    43 * Plugin settings: map post types to checklist templates.
     4 *
     5 * @package EditorialWorkflowManager
    56 */
    67
    7 if (! defined('ABSPATH')) {
    8     exit;
     8if ( ! defined( 'ABSPATH' ) ) {
     9    exit;
    910}
    1011
    11 class EDIWORMAN_Settings
    12 {
    13     /**
    14      * Option name.
    15      */
    16     const OPTION_NAME = 'ediworman_settings';
    17 
    18     public function __construct()
    19     {
    20         add_action('admin_menu', [$this, 'register_menu']);
    21         add_action('admin_init', [$this, 'register_settings']);
    22     }
    23 
    24     /**
    25      * Add a settings page under "Settings".
    26      */
    27     public function register_menu()
    28     {
    29         add_options_page(
    30             __('Editorial Workflow', 'editorial-workflow-manager'),
    31             __('Editorial Workflow', 'editorial-workflow-manager'),
    32             'manage_options',
    33             'ediworman-settings',
    34             [$this, 'render_page']
    35         );
    36     }
    37 
    38     /**
    39      * Register the settings.
    40      */
    41     public function register_settings()
    42     {
    43         register_setting(
    44             'ediworman_settings_group',
    45             self::OPTION_NAME,
    46             [
    47                 'type'              => 'array',
    48                 'sanitize_callback' => [$this, 'sanitize_settings'],
    49                 'default'           => [],
    50             ]
    51         );
    52     }
    53 
    54     /**
    55      * Sanitize settings on save.
    56      *
    57      * @param array $input
    58      * @return array
    59      */
    60     public function sanitize_settings($input)
    61     {
    62         $output = [];
    63 
    64         $output['post_type_templates'] = [];
    65 
    66         if (! is_array($input)) {
    67             return $output;
    68         }
    69 
    70         if (! empty($input['post_type_templates']) && is_array($input['post_type_templates'])) {
    71             foreach ($input['post_type_templates'] as $post_type => $template_id) {
    72                 $post_type = sanitize_key($post_type);
    73                 $template_id = absint($template_id);
    74 
    75                 if (! $post_type || ! post_type_exists($post_type)) {
    76                     continue;
    77                 }
    78 
    79                 // Prevent assigning our own CPT or attachments.
    80                 if ($post_type === 'ediworman_template' || $post_type === 'attachment') {
    81                     continue;
    82                 }
    83 
    84                 if ($template_id > 0) {
    85                     $template = get_post($template_id);
    86 
    87                     if (! $template || $template->post_type !== 'ediworman_template') {
    88                         continue;
    89                     }
    90 
    91                     // If it's in trash, ignore the mapping.
    92                     if ($template->post_status === 'trash') {
    93                         continue;
    94                     }
    95 
    96                     $output['post_type_templates'][$post_type] = (int) $template_id;
    97                 }
    98             }
    99         }
    100 
    101         return $output;
    102     }
    103 
    104     /**
    105      * Render the settings page.
    106      */
    107     public function render_page()
    108     {
    109         if (! current_user_can('manage_options')) {
    110             return;
    111         }
    112 
    113         $settings = get_option(self::OPTION_NAME, []);
    114         if (! is_array($settings)) {
    115             $settings = [];
    116         }
    117 
    118         $mappings   = isset($settings['post_type_templates']) ? $settings['post_type_templates'] : [];
    119 
    120         // Get public post types that have UI.
    121         $post_types = get_post_types(
    122             [
    123                 'show_ui' => true,
    124                 'public'  => true,
    125             ],
    126             'objects'
    127         );
    128 
    129         // Exclude our own CPT.
    130         unset($post_types['ediworman_template']);
    131 
    132         // Exclude Media (attachments) – checklists for media items don't make sense.
    133         unset($post_types['attachment']);
    134 
    135         // Get all checklist templates.
    136         $templates = get_posts(
    137             [
    138                 'post_type'      => 'ediworman_template',
    139                 'post_status'    => 'any',
    140                 'posts_per_page' => -1,
    141                 'orderby'        => 'title',
    142                 'order'          => 'ASC',
    143             ]
    144         );
    145 ?>
    146         <div class="wrap">
    147             <h1><?php esc_html_e('Editorial Workflow Settings', 'editorial-workflow-manager'); ?></h1>
    148 
    149             <p>
    150                 <?php esc_html_e('Use checklist templates to enforce a consistent review process before publishing content.', 'editorial-workflow-manager'); ?>
    151             </p>
    152 
    153             <div class="notice notice-info">
    154                 <p><strong><?php esc_html_e('Getting started', 'editorial-workflow-manager'); ?></strong></p>
    155                 <ol>
    156                     <li>
    157                         <?php
    158                         printf(
    159                             /* translators: %s: menu label "Checklist Templates" */
    160                             esc_html__('Go to %s and create or edit checklist templates (or use the defaults).', 'editorial-workflow-manager'),
    161                             '<em>' . esc_html__('Checklist Templates → Add New', 'editorial-workflow-manager') . '</em>'
    162                         );
    163                         ?>
    164                     </li>
    165                     <li>
    166                         <?php esc_html_e('Return to this page and map each post type to a checklist template.', 'editorial-workflow-manager'); ?>
    167                     </li>
    168                     <li>
    169                         <?php esc_html_e('Edit a post or page and open the “Editorial Checklist” sidebar in the block editor to see and tick off the checklist items.', 'editorial-workflow-manager'); ?>
    170                     </li>
    171                     <li>
    172                         <?php esc_html_e('If you delete a checklist template, come back here to assign a new template to any post types that were using it.', 'editorial-workflow-manager'); ?>
    173                     </li>
    174                 </ol>
    175             </div>
    176 
    177             <form method="post" action="options.php">
    178                 <?php
    179                 settings_fields('ediworman_settings_group');
    180                 ?>
    181 
    182                 <table class="form-table" role="presentation">
    183                     <tbody>
    184                         <tr>
    185                             <th scope="row">
    186                                 <label><?php esc_html_e('Template per post type', 'editorial-workflow-manager'); ?></label>
    187                             </th>
    188                             <td>
    189                                 <p class="description">
    190                                     <?php esc_html_e('Choose which checklist template should be used by default for each post type.', 'editorial-workflow-manager'); ?>
    191                                 </p>
    192 
    193                                 <table>
    194                                     <thead>
    195                                         <tr>
    196                                             <th style="text-align:left;"><?php esc_html_e('Post type', 'editorial-workflow-manager'); ?></th>
    197                                             <th style="text-align:left;"><?php esc_html_e('Checklist template', 'editorial-workflow-manager'); ?></th>
    198                                         </tr>
    199                                     </thead>
    200                                     <tbody>
    201                                         <?php foreach ($post_types as $post_type => $obj) : ?>
    202                                             <tr>
    203                                                 <td>
    204                                                     <?php echo esc_html($obj->labels->singular_name); ?>
    205                                                     <br>
    206                                                     <code><?php echo esc_html($post_type); ?></code>
    207                                                 </td>
    208                                                 <td>
    209                                                     <select name="ediworman_settings[post_type_templates][<?php echo esc_attr($post_type); ?>]">
    210                                                         <option value="0">
    211                                                             <?php esc_html_e('None', 'editorial-workflow-manager'); ?>
    212                                                         </option>
    213                                                         <?php foreach ($templates as $template) : ?>
    214                                                             <?php
    215                                                             $selected = isset($mappings[$post_type]) && (int) $mappings[$post_type] === (int) $template->ID
    216                                                                 ? 'selected'
    217                                                                 : '';
    218                                                             ?>
    219                                                             <option value="<?php echo esc_attr($template->ID); ?>" <?php selected((int) ($mappings[$post_type] ?? 0), (int) $template->ID); ?>>
    220                                                                 <?php echo esc_html($template->post_title); ?>
    221                                                             </option>
    222                                                         <?php endforeach; ?>
    223                                                     </select>
    224                                                 </td>
    225                                             </tr>
    226                                         <?php endforeach; ?>
    227                                     </tbody>
    228                                 </table>
    229 
    230                             </td>
    231                         </tr>
    232                     </tbody>
    233                 </table>
    234 
    235                 <?php submit_button(); ?>
    236             </form>
    237         </div>
    238 <?php
    239     }
    240 
    241     /**
    242      * Helper: get template ID for a given post type.
    243      *
    244      * Returns null if no valid template exists anymore (e.g. deleted or trashed).
    245      *
    246      * @param string $post_type
    247      * @return int|null
    248      */
    249     public static function get_template_for_post_type($post_type)
    250     {
    251         $post_type = sanitize_key($post_type);
    252         if (! $post_type || ! post_type_exists($post_type)) {
    253             return null;
    254         }
    255 
    256         $settings = get_option(self::OPTION_NAME, []);
    257         if (! is_array($settings)) {
    258             $settings = [];
    259         }
    260 
    261         if (empty($settings['post_type_templates'][$post_type])) {
    262             return null;
    263         }
    264 
    265         $template_id = (int) $settings['post_type_templates'][$post_type];
    266         if ($template_id <= 0) {
    267             return null;
    268         }
    269 
    270         $template = get_post($template_id);
    271 
    272         // If the template is gone or not our CPT, treat as no template.
    273         if (! $template || $template->post_type !== 'ediworman_template') {
    274             return null;
    275         }
    276 
    277         // If it's in trash, also treat as no template.
    278         if ($template->post_status === 'trash') {
    279             return null;
    280         }
    281 
    282         return $template_id;
    283     }
     12/**
     13 * Registers and renders plugin settings.
     14 */
     15class EDIWORMAN_Settings {
     16
     17    /**
     18     * Option name.
     19     */
     20    const OPTION_NAME = 'ediworman_settings';
     21
     22    /**
     23     * Register admin menu and settings hooks.
     24     *
     25     * @return void
     26     */
     27    public function __construct() {
     28        add_action( 'admin_menu', array( $this, 'register_menu' ) );
     29        add_action( 'admin_init', array( $this, 'register_settings' ) );
     30    }
     31
     32    /**
     33     * Add a settings page under "Settings".
     34     */
     35    public function register_menu() {
     36        add_options_page(
     37            __( 'Editorial Workflow', 'editorial-workflow-manager' ),
     38            __( 'Editorial Workflow', 'editorial-workflow-manager' ),
     39            'manage_options',
     40            'ediworman-settings',
     41            array( $this, 'render_page' )
     42        );
     43    }
     44
     45    /**
     46     * Register the settings.
     47     */
     48    public function register_settings() {
     49        register_setting(
     50            'ediworman_settings_group',
     51            self::OPTION_NAME,
     52            array(
     53                'type'              => 'array',
     54                'sanitize_callback' => array( $this, 'sanitize_settings' ),
     55                'default'           => array(),
     56            )
     57        );
     58    }
     59
     60    /**
     61     * Sanitize settings on save.
     62     *
     63     * @param array $input Raw settings input.
     64     * @return array
     65     */
     66    public function sanitize_settings( $input ) {
     67        $output = array();
     68
     69        $output['post_type_templates'] = array();
     70
     71        if ( ! is_array( $input ) ) {
     72            return $output;
     73        }
     74
     75        if ( ! empty( $input['post_type_templates'] ) && is_array( $input['post_type_templates'] ) ) {
     76            foreach ( $input['post_type_templates'] as $post_type => $template_id ) {
     77                $post_type   = sanitize_key( $post_type );
     78                $template_id = absint( $template_id );
     79
     80                if ( ! $post_type || ! post_type_exists( $post_type ) ) {
     81                    continue;
     82                }
     83
     84                // Prevent assigning our own CPT or attachments.
     85                if ( 'ediworman_template' === $post_type || 'attachment' === $post_type ) {
     86                    continue;
     87                }
     88
     89                if ( $template_id > 0 ) {
     90                    $template = get_post( $template_id );
     91
     92                    if ( ! $template || 'ediworman_template' !== $template->post_type ) {
     93                        continue;
     94                    }
     95
     96                    // If it's in trash, ignore the mapping.
     97                    if ( 'trash' === $template->post_status ) {
     98                        continue;
     99                    }
     100
     101                    $output['post_type_templates'][ $post_type ] = (int) $template_id;
     102                }
     103            }
     104        }
     105
     106        return $output;
     107    }
     108
     109    /**
     110     * Render the settings page.
     111     */
     112    public function render_page() {
     113        if ( ! current_user_can( 'manage_options' ) ) {
     114            return;
     115        }
     116
     117        $settings = get_option( self::OPTION_NAME, array() );
     118        if ( ! is_array( $settings ) ) {
     119            $settings = array();
     120        }
     121
     122        $mappings = isset( $settings['post_type_templates'] ) ? $settings['post_type_templates'] : array();
     123
     124        // Get public post types that have UI.
     125        $post_types = get_post_types(
     126            array(
     127                'show_ui' => true,
     128                'public'  => true,
     129            ),
     130            'objects'
     131        );
     132
     133        // Exclude our own CPT.
     134        unset( $post_types['ediworman_template'] );
     135
     136        // Exclude Media (attachments) – checklists for media items don't make sense.
     137        unset( $post_types['attachment'] );
     138
     139        // Get all checklist templates.
     140        $templates = get_posts(
     141            array(
     142                'post_type'      => 'ediworman_template',
     143                'post_status'    => 'any',
     144                'posts_per_page' => -1,
     145                'orderby'        => 'title',
     146                'order'          => 'ASC',
     147            )
     148        );
     149        ?>
     150        <div class="wrap">
     151            <h1><?php esc_html_e( 'Editorial Workflow Settings', 'editorial-workflow-manager' ); ?></h1>
     152
     153            <p>
     154                <?php esc_html_e( 'Use checklist templates to enforce a consistent review process before publishing content.', 'editorial-workflow-manager' ); ?>
     155            </p>
     156
     157            <div class="notice notice-info">
     158                <p><strong><?php esc_html_e( 'Getting started', 'editorial-workflow-manager' ); ?></strong></p>
     159                <ol>
     160                    <li>
     161                        <?php
     162                        printf(
     163                            /* translators: %s: menu label "Checklist Templates" */
     164                            esc_html__( 'Go to %s and create or edit checklist templates (or use the defaults).', 'editorial-workflow-manager' ),
     165                            '<em>' . esc_html__( 'Checklist Templates → Add New', 'editorial-workflow-manager' ) . '</em>'
     166                        );
     167                        ?>
     168                    </li>
     169                    <li>
     170                        <?php esc_html_e( 'Return to this page and map each post type to a checklist template.', 'editorial-workflow-manager' ); ?>
     171                    </li>
     172                    <li>
     173                        <?php esc_html_e( 'Edit a post or page and open the “Editorial Checklist” sidebar in the block editor to see and tick off the checklist items.', 'editorial-workflow-manager' ); ?>
     174                    </li>
     175                    <li>
     176                        <?php esc_html_e( 'If you delete a checklist template, come back here to assign a new template to any post types that were using it.', 'editorial-workflow-manager' ); ?>
     177                    </li>
     178                </ol>
     179            </div>
     180
     181            <form method="post" action="options.php">
     182                <?php
     183                settings_fields( 'ediworman_settings_group' );
     184                ?>
     185
     186                <table class="form-table" role="presentation">
     187                    <tbody>
     188                        <tr>
     189                            <th scope="row">
     190                                <label><?php esc_html_e( 'Template per post type', 'editorial-workflow-manager' ); ?></label>
     191                            </th>
     192                            <td>
     193                                <p class="description">
     194                                    <?php esc_html_e( 'Choose which checklist template should be used by default for each post type.', 'editorial-workflow-manager' ); ?>
     195                                </p>
     196
     197                                <table>
     198                                    <thead>
     199                                        <tr>
     200                                            <th style="text-align:left;"><?php esc_html_e( 'Post type', 'editorial-workflow-manager' ); ?></th>
     201                                            <th style="text-align:left;"><?php esc_html_e( 'Checklist template', 'editorial-workflow-manager' ); ?></th>
     202                                        </tr>
     203                                    </thead>
     204                                    <tbody>
     205                                        <?php foreach ( $post_types as $post_type => $obj ) : ?>
     206                                            <tr>
     207                                                <td>
     208                                                    <?php echo esc_html( $obj->labels->singular_name ); ?>
     209                                                    <br>
     210                                                    <code><?php echo esc_html( $post_type ); ?></code>
     211                                                </td>
     212                                                <td>
     213                                                    <select name="ediworman_settings[post_type_templates][<?php echo esc_attr( $post_type ); ?>]">
     214                                                        <option value="0">
     215                                                            <?php esc_html_e( 'None', 'editorial-workflow-manager' ); ?>
     216                                                        </option>
     217                                                        <?php foreach ( $templates as $template ) : ?>
     218                                                            <?php
     219                                                            $selected = isset( $mappings[ $post_type ] ) && (int) $mappings[ $post_type ] === (int) $template->ID
     220                                                                ? 'selected'
     221                                                                : '';
     222                                                            ?>
     223                                                            <option value="<?php echo esc_attr( $template->ID ); ?>" <?php selected( (int) ( $mappings[ $post_type ] ?? 0 ), (int) $template->ID ); ?>>
     224                                                                <?php echo esc_html( $template->post_title ); ?>
     225                                                            </option>
     226                                                        <?php endforeach; ?>
     227                                                    </select>
     228                                                </td>
     229                                            </tr>
     230                                        <?php endforeach; ?>
     231                                    </tbody>
     232                                </table>
     233
     234                            </td>
     235                        </tr>
     236                    </tbody>
     237                </table>
     238
     239                <?php submit_button(); ?>
     240            </form>
     241        </div>
     242        <?php
     243    }
     244
     245    /**
     246     * Helper: get template ID for a given post type.
     247     *
     248     * Returns null if no valid template exists anymore (e.g. deleted or trashed).
     249     *
     250     * @param string $post_type Post type slug.
     251     * @return int|null
     252     */
     253    public static function get_template_for_post_type( $post_type ) {
     254        $post_type = sanitize_key( $post_type );
     255        if ( ! $post_type || ! post_type_exists( $post_type ) ) {
     256            return null;
     257        }
     258
     259        $settings = get_option( self::OPTION_NAME, array() );
     260        if ( ! is_array( $settings ) ) {
     261            $settings = array();
     262        }
     263
     264        if ( empty( $settings['post_type_templates'][ $post_type ] ) ) {
     265            return null;
     266        }
     267
     268        $template_id = (int) $settings['post_type_templates'][ $post_type ];
     269        if ( $template_id <= 0 ) {
     270            return null;
     271        }
     272
     273        $template = get_post( $template_id );
     274
     275        // If the template is gone or not our CPT, treat as no template.
     276        if ( ! $template || 'ediworman_template' !== $template->post_type ) {
     277            return null;
     278        }
     279
     280        // If it's in trash, also treat as no template.
     281        if ( 'trash' === $template->post_status ) {
     282            return null;
     283        }
     284
     285        return $template_id;
     286    }
    284287}
  • editorial-workflow-manager/trunk/includes/class-ediworman-templates-cpt.php

    r3433414 r3472097  
    11<?php
     2/**
     3 * Checklist Templates CPT + meta box for checklist items.
     4 *
     5 * @package EditorialWorkflowManager
     6 */
     7
     8if ( ! defined( 'ABSPATH' ) ) {
     9    exit;
     10}
    211
    312/**
    4  * Checklist Templates CPT + meta box for checklist items.
     13 * Registers the checklist template post type and meta box persistence.
    514 */
     15class EDIWORMAN_Templates_CPT {
    616
    7 if (! defined('ABSPATH')) {
    8     exit;
     17    /**
     18     * Register CPT and meta box hooks.
     19     *
     20     * @return void
     21     */
     22    public function __construct() {
     23        // Register CPT on init.
     24        add_action( 'init', array( $this, 'register_cpt' ) );
     25
     26        // Meta box for checklist items.
     27        add_action( 'add_meta_boxes', array( $this, 'register_metaboxes' ) );
     28
     29        // Save checklist items.
     30        add_action( 'save_post_ediworman_template', array( $this, 'save_template_meta' ), 10, 2 );
     31    }
     32
     33    /**
     34     * Register the "Checklist Template" custom post type.
     35     */
     36    public function register_cpt() {
     37        $labels = array(
     38            'name'               => __( 'Checklist Templates', 'editorial-workflow-manager' ),
     39            'singular_name'      => __( 'Checklist Template', 'editorial-workflow-manager' ),
     40            'add_new'            => __( 'Add New', 'editorial-workflow-manager' ),
     41            'add_new_item'       => __( 'Add New Checklist Template', 'editorial-workflow-manager' ),
     42            'edit_item'          => __( 'Edit Checklist Template', 'editorial-workflow-manager' ),
     43            'new_item'           => __( 'New Checklist Template', 'editorial-workflow-manager' ),
     44            'view_item'          => __( 'View Checklist Template', 'editorial-workflow-manager' ),
     45            'search_items'       => __( 'Search Checklist Templates', 'editorial-workflow-manager' ),
     46            'not_found'          => __( 'No checklist templates found.', 'editorial-workflow-manager' ),
     47            'not_found_in_trash' => __( 'No checklist templates found in Trash.', 'editorial-workflow-manager' ),
     48            'menu_name'          => __( 'Checklist Templates', 'editorial-workflow-manager' ),
     49        );
     50
     51        $args = array(
     52            'labels'              => $labels,
     53            'public'              => false,
     54            'show_ui'             => true,
     55            'show_in_menu'        => true,
     56            'show_in_nav_menus'   => false,
     57            'show_in_admin_bar'   => false,
     58            'exclude_from_search' => true,
     59            'publicly_queryable'  => false,
     60            'has_archive'         => false,
     61            'rewrite'             => false,
     62            'supports'            => array( 'title' ),
     63            'capability_type'     => 'post',
     64        );
     65
     66        register_post_type( 'ediworman_template', $args );
     67    }
     68
     69    /**
     70     * Register meta boxes for checklist templates.
     71     */
     72    public function register_metaboxes() {
     73        add_meta_box(
     74            'ediworman_template_items',
     75            __( 'Checklist Items', 'editorial-workflow-manager' ),
     76            array( $this, 'render_items_metabox' ),
     77            'ediworman_template',
     78            'normal',
     79            'high'
     80        );
     81    }
     82
     83    /**
     84     * Render the checklist items meta box.
     85     *
     86     * @param WP_Post $post Current checklist template post.
     87     */
     88    public function render_items_metabox( $post ) {
     89        // Security nonce.
     90        wp_nonce_field( 'ediworman_save_template_items', 'ediworman_template_items_nonce' );
     91
     92        $items = get_post_meta( $post->ID, '_ediworman_items', true );
     93        if ( ! is_array( $items ) ) {
     94            $items = array();
     95        }
     96
     97        $value = implode( "\n", $items );
     98        ?>
     99        <p>
     100            <?php esc_html_e( 'Enter one checklist item per line. These items will appear in the editor sidebar for posts using this template.', 'editorial-workflow-manager' ); ?>
     101        </p>
     102        <textarea
     103            name="ediworman_template_items"
     104            id="ediworman_template_items"
     105            style="width: 100%; min-height: 200px;"><?php echo esc_textarea( $value ); ?></textarea>
     106        <?php
     107    }
     108
     109    /**
     110     * Save the checklist items when the template is saved.
     111     *
     112     * @param int     $post_id Post ID being saved.
     113     * @param WP_Post $post    Post object being saved.
     114     * @return void
     115     */
     116    public function save_template_meta( $post_id, $post ) {
     117        // Autosave? Bail.
     118        if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
     119            return;
     120        }
     121
     122        // Right post type?
     123        if ( 'ediworman_template' !== $post->post_type ) {
     124            return;
     125        }
     126
     127        // Nonce check.
     128        $nonce = isset( $_POST['ediworman_template_items_nonce'] )
     129            ? sanitize_text_field( wp_unslash( $_POST['ediworman_template_items_nonce'] ) )
     130            : '';
     131
     132        if ( ! $nonce || ! wp_verify_nonce( $nonce, 'ediworman_save_template_items' ) ) {
     133            return;
     134        }
     135
     136        // Capability check.
     137        if ( ! current_user_can( 'edit_post', $post_id ) ) {
     138            return;
     139        }
     140
     141        // If field missing, delete meta (or keep existing - your choice).
     142        if ( ! isset( $_POST['ediworman_template_items'] ) ) {
     143            delete_post_meta( $post_id, '_ediworman_items' );
     144            return;
     145        }
     146
     147        $raw = sanitize_textarea_field( wp_unslash( $_POST['ediworman_template_items'] ) );
     148
     149        $lines = preg_split( '/\R/', $raw ); // Split on any newline type.
     150        $items = array_filter(
     151            array_map(
     152                static function ( $line ) {
     153                    return sanitize_text_field( trim( $line ) );
     154                },
     155                $lines
     156            )
     157        );
     158
     159        update_post_meta( $post_id, '_ediworman_items', $items );
     160    }
    9161}
    10 
    11 class EDIWORMAN_Templates_CPT
    12 {
    13 
    14     public function __construct()
    15     {
    16         // Register CPT on init.
    17         add_action('init', [$this, 'register_cpt']);
    18 
    19         // Meta box for checklist items.
    20         add_action('add_meta_boxes', [$this, 'register_metaboxes']);
    21 
    22         // Save checklist items.
    23         add_action('save_post_ediworman_template', [$this, 'save_template_meta'], 10, 2);
    24     }
    25 
    26     /**
    27      * Register the "Checklist Template" custom post type.
    28      */
    29     public function register_cpt()
    30     {
    31         $labels = [
    32             'name'               => __('Checklist Templates', 'editorial-workflow-manager'),
    33             'singular_name'      => __('Checklist Template', 'editorial-workflow-manager'),
    34             'add_new'            => __('Add New', 'editorial-workflow-manager'),
    35             'add_new_item'       => __('Add New Checklist Template', 'editorial-workflow-manager'),
    36             'edit_item'          => __('Edit Checklist Template', 'editorial-workflow-manager'),
    37             'new_item'           => __('New Checklist Template', 'editorial-workflow-manager'),
    38             'view_item'          => __('View Checklist Template', 'editorial-workflow-manager'),
    39             'search_items'       => __('Search Checklist Templates', 'editorial-workflow-manager'),
    40             'not_found'          => __('No checklist templates found.', 'editorial-workflow-manager'),
    41             'not_found_in_trash' => __('No checklist templates found in Trash.', 'editorial-workflow-manager'),
    42             'menu_name'          => __('Checklist Templates', 'editorial-workflow-manager'),
    43         ];
    44 
    45         $args = [
    46             'labels'             => $labels,
    47             'public'             => false,
    48             'show_ui'            => true,
    49             'show_in_menu'       => true,
    50             'show_in_nav_menus'  => false,
    51             'show_in_admin_bar'  => false,
    52             'exclude_from_search' => true,
    53             'publicly_queryable' => false,
    54             'has_archive'        => false,
    55             'rewrite'            => false,
    56             'supports'           => ['title'],
    57             'capability_type'    => 'post',
    58         ];
    59 
    60         register_post_type('ediworman_template', $args);
    61     }
    62 
    63     /**
    64      * Register meta boxes for checklist templates.
    65      */
    66     public function register_metaboxes()
    67     {
    68         add_meta_box(
    69             'ediworman_template_items',
    70             __('Checklist Items', 'editorial-workflow-manager'),
    71             [$this, 'render_items_metabox'],
    72             'ediworman_template',
    73             'normal',
    74             'high'
    75         );
    76     }
    77 
    78     /**
    79      * Render the checklist items meta box.
    80      *
    81      * @param WP_Post $post
    82      */
    83     public function render_items_metabox($post)
    84     {
    85         // Security nonce.
    86         wp_nonce_field('ediworman_save_template_items', 'ediworman_template_items_nonce');
    87 
    88         $items = get_post_meta($post->ID, '_ediworman_items', true);
    89         if (! is_array($items)) {
    90             $items = [];
    91         }
    92 
    93         $value = implode("\n", $items);
    94 ?>
    95         <p>
    96             <?php esc_html_e('Enter one checklist item per line. These items will appear in the editor sidebar for posts using this template.', 'editorial-workflow-manager'); ?>
    97         </p>
    98         <textarea
    99             name="ediworman_template_items"
    100             id="ediworman_template_items"
    101             style="width: 100%; min-height: 200px;"><?php echo esc_textarea($value); ?></textarea>
    102 <?php
    103     }
    104 
    105     /**
    106      * Save the checklist items when the template is saved.
    107      *
    108      * @param int     $post_id
    109      * @param WP_Post $post
    110      */
    111     public function save_template_meta($post_id, $post)
    112     {
    113         // Autosave? Bail.
    114         if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) {
    115             return;
    116         }
    117 
    118         // Right post type?
    119         if ($post->post_type !== 'ediworman_template') {
    120             return;
    121         }
    122 
    123         // Nonce check.
    124         $nonce = isset($_POST['ediworman_template_items_nonce'])
    125             ? sanitize_text_field(wp_unslash($_POST['ediworman_template_items_nonce']))
    126             : '';
    127 
    128         if (! $nonce || ! wp_verify_nonce($nonce, 'ediworman_save_template_items')) {
    129             return;
    130         }
    131 
    132         // Capability check.
    133         if (! current_user_can('edit_post', $post_id)) {
    134             return;
    135         }
    136 
    137         // If field missing, delete meta (or keep existing - your choice).
    138         if (! isset($_POST['ediworman_template_items'])) {
    139             delete_post_meta($post_id, '_ediworman_items');
    140             return;
    141         }
    142 
    143         $raw = sanitize_textarea_field(wp_unslash($_POST['ediworman_template_items']));
    144 
    145         $lines = preg_split('/\R/', $raw); // split on any newline type
    146         $items = array_filter(
    147             array_map(
    148                 static function ($line) {
    149                     return sanitize_text_field(trim($line));
    150                 },
    151                 $lines
    152             )
    153         );
    154 
    155         update_post_meta($post_id, '_ediworman_items', $items);
    156     }
    157 }
  • editorial-workflow-manager/trunk/readme.txt

    r3457373 r3472097  
    55Tested up to: 6.9
    66Requires PHP: 7.4
    7 Stable tag: 0.3.4
     7Stable tag: 0.3.5
    88License: GPLv2 or later
    99License URI: https://www.gnu.org/licenses/gpl-2.0.html
     
    101101== Changelog ==
    102102
     103= 0.3.5 =
     104* Maintenance release: improved PHPCS compliance (PHPDoc comments, Yoda conditions, line endings, and callback parameter cleanup).
     105
    103106= 0.3.4 =
    104107* Various security improvements (Better data validation and sanitization).
  • editorial-workflow-manager/trunk/uninstall.php

    r3442109 r3472097  
    44 *
    55 * Runs when the plugin is deleted from WordPress.
     6 *
     7 * @package EditorialWorkflowManager
    68 */
    79
    8 if (! defined('WP_UNINSTALL_PLUGIN')) {
    9     exit;
     10if ( ! defined( 'WP_UNINSTALL_PLUGIN' ) ) {
     11    exit;
    1012}
    1113
     
    1820 * @return void
    1921 */
    20 function ediworman_uninstall_cleanup_site()
    21 {
    22     // Delete settings.
    23     delete_option('ediworman_settings');
     22function ediworman_uninstall_cleanup_site() {
     23    // Delete settings.
     24    delete_option( 'ediworman_settings' );
    2425
    25     // Remove post meta stored on edited content.
    26     delete_post_meta_by_key('_ediworman_checked_items');
    27     delete_post_meta_by_key('_ediworman_last_editor');
     26    // Remove post meta stored on edited content.
     27    delete_post_meta_by_key( '_ediworman_checked_items' );
     28    delete_post_meta_by_key( '_ediworman_last_editor' );
    2829
    29     // Remove checklist template item meta (in case templates are left behind).
    30     delete_post_meta_by_key('_ediworman_items');
     30    // Remove checklist template item meta (in case templates are left behind).
     31    delete_post_meta_by_key( '_ediworman_items' );
    3132
    32     // Delete checklist templates CPT posts.
    33     $limit = 100;
     33    // Delete checklist templates CPT posts.
     34    $limit = 100;
    3435
    35     do {
    36         $template_ids = get_posts(
    37             [
    38                 'post_type'      => 'ediworman_template',
    39                 'post_status'    => 'any',
    40                 'posts_per_page' => $limit,
    41                 'fields'         => 'ids',
    42                 'no_found_rows'  => true,
    43                 'orderby'        => 'ID',
    44                 'order'          => 'ASC',
    45             ]
    46         );
     36    do {
     37        $template_ids = get_posts(
     38            array(
     39                'post_type'      => 'ediworman_template',
     40                'post_status'    => 'any',
     41                'posts_per_page' => $limit,
     42                'fields'         => 'ids',
     43                'no_found_rows'  => true,
     44                'orderby'        => 'ID',
     45                'order'          => 'ASC',
     46            )
     47        );
    4748
    48         foreach ($template_ids as $template_id) {
    49             wp_delete_post((int) $template_id, true);
    50         }
    51     } while (! empty($template_ids));
     49        foreach ( $template_ids as $template_id ) {
     50            wp_delete_post( (int) $template_id, true );
     51        }
     52    } while ( ! empty( $template_ids ) );
    5253}
    5354
     
    5758 * @return void
    5859 */
    59 function ediworman_uninstall()
    60 {
    61     if (is_multisite()) {
    62         $ediworman_site_ids = get_sites(
    63             [
    64                 'fields' => 'ids',
    65             ]
    66         );
     60function ediworman_uninstall() {
     61    if ( is_multisite() ) {
     62        $ediworman_site_ids = get_sites(
     63            array(
     64                'fields' => 'ids',
     65            )
     66        );
    6767
    68         foreach ($ediworman_site_ids as $ediworman_site_id) {
    69             switch_to_blog((int) $ediworman_site_id);
    70             ediworman_uninstall_cleanup_site();
    71             restore_current_blog();
    72         }
     68        foreach ( $ediworman_site_ids as $ediworman_site_id ) {
     69            switch_to_blog( (int) $ediworman_site_id );
     70            ediworman_uninstall_cleanup_site();
     71            restore_current_blog();
     72        }
    7373
    74         // In case anything was stored at network level in the future.
    75         delete_site_option('ediworman_settings');
    76         return;
    77     }
     74        // In case anything was stored at network level in the future.
     75        delete_site_option( 'ediworman_settings' );
     76        return;
     77    }
    7878
    79     ediworman_uninstall_cleanup_site();
     79    ediworman_uninstall_cleanup_site();
    8080}
    8181
Note: See TracChangeset for help on using the changeset viewer.