Legal Site.

Developed and maintained a responsive website showcase legal services and information.

Custom Plugin

This is a plugin based on the client's requirements.

The following code snippet is an implementation of a cronjob management system and custom configurations for a WordPress plugin. This code includes key functionalities such as:

  • Registering custom routes in the WordPress REST API.
  • Setting up and managing scheduled tasks (cronjobs) to automate specific processes, such as publishing or unpublishing content.
  • Managing permissions to ensure that only users with specific roles can perform certain actions.
  • Interacting with WordPress options and custom fields to store and retrieve dynamic settings.

The full source code of the plugin's main class is provided below, including comments to help understand each functionality.

                            
                                namespace xxUnpu;
                                
                                use WP_REST_Request;
                                use WP_REST_Response;
                                
                                class Main
                                {
                                    /**
                                     * Constructor to initialize actions and filters.
                                     */
                                    public function __construct()
                                    {
                                        add_action('admin_menu', [$this, 'add_admin_menu']);
                                        add_action('admin_enqueue_scripts', [$this, 'enqueue_admin_scripts']);
                                        add_action('rest_api_init', [$this, 'register_api_routes']);
                                    }
                                
                                    /**
                                     * Adds the main admin menu and submenus for the plugin.
                                     */
                                    public function add_admin_menu()
                                    {
                                        $unpu_page = add_menu_page(
                                            'Unpublish Manager',
                                            'Unpublish Manager',
                                            'manage_options',
                                            xx_UNPU_ADMIN_PAGE,
                                            [$this, 'render_people_table']
                                        );
                                
                                        add_submenu_page(
                                            xx_UNPU_ADMIN_PAGE,
                                            'Unpublish Media Posts',
                                            'Unpublish Media Posts',
                                            'manage_options',
                                            'xx-unpu-media',
                                            [$this, 'render_media_table']
                                        );
                                    }
                                
                                    /**
                                     * Renders the media table template.
                                     */
                                    public function render_media_table()
                                    {
                                        include xx_UNPU_ROOT . '/templates/media-table.php';
                                    }
                                
                                    /**
                                     * Enqueues admin styles and scripts for the plugin.
                                     */
                                    public function enqueue_admin_scripts()
                                    {
                                        wp_enqueue_style(
                                            'xx-unpu',
                                            xx_UNPU_PLUGIN_URI . 'assets/style.css',
                                            null,
                                            filemtime(plugin_dir_path(__DIR__) . 'assets/style.css'),
                                            false
                                        );
                                
                                        wp_enqueue_script(
                                            'xx-unpu',
                                            xx_UNPU_PLUGIN_URI . 'assets/script.js',
                                            [],
                                            filemtime(plugin_dir_path(__DIR__) . 'assets/script.js')
                                        );
                                    }
                                
                                    /**
                                     * Handles the REST API endpoint for updating the media cronjob settings.
                                     *
                                     * @param WP_REST_Request $request The REST API request object.
                                     * @return WP_REST_Response The response object containing the status message.
                                     */
                                    public function media_auto_update_cronjob_rest(WP_REST_Request $request)
                                    {
                                        $payload = $request->get_params();
                                        $cron_type = isset($payload['cron-type']) ? $payload['cron-type'] : null;
                                
                                        if (!$cron_type || !in_array($cron_type, ['five-years', 'two-years'])) {
                                            return new WP_REST_Response([
                                                'message' => 'Invalid or missing cron-type parameter.'
                                            ], 400);
                                        }
                                
                                        // Configuration for cron jobs
                                        $cron_jobs = [
                                            'five-years' => [
                                                'enable_option' => 'unpublish_media_auto_status',
                                                'date_option' => 'unpublish_media_auto_date',
                                                'event_name' => 'update_auto_media_posts_event',
                                                'schedule' => 'yearly',
                                                'log_prefix' => 'five_years'
                                            ],
                                            'two-years' => [
                                                'enable_option' => 'unpublish_media_auto_status_two',
                                                'date_option' => 'unpublish_media_auto_date_two',
                                                'event_name' => 'update_auto_media_posts_event_two',
                                                'schedule' => 'daily',
                                                'log_prefix' => 'two_years'
                                            ]
                                        ];
                                
                                        $settings = $cron_jobs[$cron_type];
                                        $enableCronjob = isset($payload["cronjob-checkbox"]) ? '1' : '0';
                                        $selected_date = isset($payload["cronjob-date"]) ? sanitize_text_field($payload["cronjob-date"]) : date('Y-m-d');
                                
                                        update_option($settings['date_option'], $selected_date);
                                        update_option($settings['enable_option'], $enableCronjob);
                                
                                        $background_process_five_years = new \MediaBackgroundProcess('media_auto_update_five_years');
                                        $background_process_two_years = new \MediaBackgroundProcess('media_auto_update_two_years');
                                
                                        if ($enableCronjob === '1') {
                                            $timestamp = strtotime($selected_date . ' 00:00:00');
                                
                                            if (!wp_next_scheduled($settings['event_name'])) {
                                                wp_schedule_event($timestamp, $settings['schedule'], $settings['event_name']);
                                                $next_run_date = wp_next_scheduled($settings['event_name']);
                                                update_option("next_run_{$settings['event_name']}", date('Y-m-d H:i:s', $next_run_date));
                                                error_log("Cronjob scheduled for {$settings['schedule']} execution.");
                                            }
                                        } else {
                                            $timestamp = wp_next_scheduled($settings['event_name']);
                                            if ($timestamp) {
                                                wp_unschedule_event($timestamp, $settings['event_name']);
                                                delete_option("next_run_{$settings['event_name']}");
                                                error_log("Cronjob unscheduled.");
                                
                                                if ($cron_type == 'five-years') {
                                                    $background_process_five_years->delete_all();
                                                } else {
                                                    $background_process_two_years->delete_all();
                                                }
                                            }
                                        }
                                
                                        return new WP_REST_Response([
                                            'message' => "Cronjob for {$cron_type} updated."
                                        ], 200);
                                    }
                                
                                    /**
                                     * Retrieves the status of the 5-year cronjob.
                                     *
                                     * @param WP_REST_Request $request The REST API request object.
                                     * @return WP_REST_Response The response object with cronjob status information.
                                     */
                                    public function get_cronjob_status_five_years(WP_REST_Request $request)
                                    {
                                        $last_run_date_five = get_option('last_run_update_auto_media_posts_event', 'Never');
                                        $cronjob_status_five = get_option('unpublish_media_auto_status');
                                        $logs_five = get_option('cronjob_log_five_years', 'No logs yet.');
                                
                                        return new WP_REST_Response([
                                            'last_run' => $last_run_date_five,
                                            'status' => $cronjob_status_five ? 'Enabled' : 'Disabled',
                                            'logs' => $logs_five,
                                        ], 200);
                                    }
                                
                                    /**
                                     * Retrieves the status of the 2-year cronjob.
                                     *
                                     * @param WP_REST_Request $request The REST API request object.
                                     * @return WP_REST_Response The response object with cronjob status information.
                                     */
                                    public function get_cronjob_status_two_years(WP_REST_Request $request)
                                    {
                                        $last_run_date_two = get_option('last_run_update_auto_media_posts_event_two', 'Never');
                                        $cronjob_status_two = get_option('unpublish_media_auto_status_two');
                                        $logs_two = get_option('cronjob_log_two_years', 'No logs yet.');
                                
                                        return new WP_REST_Response([
                                            'last_run' => $last_run_date_two,
                                            'status' => $cronjob_status_two ? 'Enabled' : 'Disabled',
                                            'logs' => $logs_two,
                                        ], 200);
                                    }
                                
                                    /**
                                     * Clears the cronjob logs for the specified type.
                                     *
                                     * @param WP_REST_Request $request The REST API request object.
                                     * @return WP_REST_Response The response indicating if the log was cleared successfully.
                                     */
                                    public function clear_cronjob_log(WP_REST_Request $request)
                                    {
                                        $type = $request->get_param('type');
                                        error_log(print_r("clear cron: " . $type, true));
                                
                                        if ($type === 'five-years') {
                                            update_option('cronjob_log_five_years', 'No logs yet.');
                                            wp_cache_delete('cronjob_log_five_years', 'options');
                                            error_log('deleted five');
                                        } elseif ($type === 'two-years') {
                                            update_option('cronjob_log_two_years', 'No logs yet.');
                                            wp_cache_delete('cronjob_log_two_years', 'options');
                                        }
                                
                                        return new WP_REST_Response(['message' => 'Log cleared successfully'], 200);
                                    }
                                
                                    /**
                                     * Excludes media from automatic processes via REST API.
                                     *
                                     * @param WP_REST_Request $request The REST API request object.
                                     * @return WP_REST_Response The response indicating if the field was updated successfully.
                                     */
                                    public function exclude_media_auto_exclude(WP_REST_Request $request)
                                    {
                                        $payload = $request->get_params();
                                        $exclude_media_ids = $payload;
                                
                                        $updated = update_field('related_media_posts', $exclude_media_ids, 'options');
                                
                                        if (is_wp_error($updated)) {
                                            return new WP_REST_Response([
                                                'error' => 'There was an error updating the field.',
                                            ], 500);
                                        }
                                
                                        return new WP_REST_Response([
                                            'field-updated' => 'Field successfully updated',
                                        ], 200);
                                    }
                                
                                    /**
                                     * Checks if the current user has the required permissions for REST API requests.
                                     *
                                     * @return bool True if the user has the required role, false otherwise.
                                     */
                                    public function api_permissions()
                                    {
                                        $auth_cookie = wp_parse_auth_cookie('', 'logged_in');
                                        $user = get_user_by('login', $auth_cookie['username']);
                                
                                        if ($user !== false && $user->exists()) {
                                            return in_array('xx_user', (array) $user->roles) ||
                                                   in_array('xx_admin_user', (array) $user->roles) ||
                                                   in_array('administrator', (array) $user->roles);
                                        }
                                
                                        return false;
                                    }
                                
                                    /**
                                     * Registers custom REST API routes for the plugin.
                                     */
                                    public function register_api_routes()
                                    {
                                        register_rest_route('unpu', '/exclude-media-auto-exclude', array(
                                            'methods' => array('GET', 'POST'),
                                            'callback' => [$this, 'exclude_media_auto_exclude'],
                                            'permission_callback' => [$this, 'api_permissions'],
                                        ));
                                
                                        register_rest_route('unpu', '/media-auto-update-cronjob-rest', array(
                                            'methods' => 'POST',
                                            'callback' => [$this, 'media_auto_update_cronjob_rest'],
                                            'permission_callback' => [$this, 'api_permissions'],
                                        ));
                                
                                        register_rest_route('unpu', '/get-cronjob-status-five-years', array(
                                            'methods' => 'GET',
                                            'callback' => [$this, 'get_cronjob_status_five_years'],
                                            'permission_callback' => [$this, 'api_permissions'],
                                        ));
                                
                                        register_rest_route('unpu', '/get-cronjob-status-two-years', array(
                                            'methods' => 'GET',
                                            'callback' => [$this, 'get_cronjob_status_two_years'],
                                            'permission_callback' => [$this, 'api_permissions'],
                                        ));
                                
                                        register_rest_route('unpu', '/clear-cronjob-log', array(
                                            'methods' => 'POST',
                                            'callback' => [$this, 'clear_cronjob_log'],
                                            'permission_callback' => [$this, 'api_permissions'],
                                        ));
                                    }
                                }
                            
                        

This code manages scheduled tasks (cronjobs) in WordPress to update "media" post types. It includes:

  • Registering cron events for two periods: "annual" and "daily".
  • Bulk updating posts by changing their status to "draft" based on criteria such as dates and exclusions.
  • Logging detailed records to monitor execution.
  • Custom queries to handle dynamic content efficiently.

It provides an efficient solution to automate maintenance processes and control content in WordPress.

                            
   
                                /**
                                * Registers a WordPress action for the "five_years" cron event.
                                */
                                add_action('update_auto_media_posts_event', function () {
                                    media_auto_update_cronjob('five_years');
                                });

                                /**
                                * Registers a WordPress action for the "two_years" cron event.
                                */
                                add_action('update_auto_media_posts_event_two', function () {
                                    media_auto_update_cronjob('two_years');
                                });

                                /**
                                * Logs messages for cronjob activities.
                                *
                                * @param string $message The message to log.
                                * @param string $type The type of log, either 'five_years' or 'two_years'.
                                */
                                function add_to_cron_log($message, $type = 'five_years') {
                                    $log_option = $type === 'five_years' ? 'cronjob_log_five_years' : 'cronjob_log_two_years';
                                    $existing_logs = get_option($log_option, '');
                                    $timestamp = current_time('timestamp');
                                    $formatted_date = gmdate('Y-m-d H:i:s T O', $timestamp);
                                    $new_log_entry = "[$formatted_date] $message\n";

                                    // Remove default message if present
                                    if (trim($existing_logs) === 'No logs yet.') {
                                        $existing_logs = '';
                                    }

                                    // Append the new log entry and update the option.
                                    update_option($log_option, $existing_logs . $new_log_entry);
                                }

                                /**
                                * Updates the status of multiple posts to 'draft' in bulk.
                                *
                                * @param array $post_ids The array of post IDs to update.
                                * @param string $log_type The type of log, either 'five_years' or 'two_years'.
                                * @return int|false The number of rows affected, or false on error.
                                */
                                function bulk_update_posts_to_draft($post_ids, $log_type) {
                                    global $wpdb;

                                    // Ensure all post IDs are integers.
                                    $post_ids = array_map('absint', $post_ids);
                                    $ids_placeholder = implode(',', array_fill(0, count($post_ids), '%d'));

                                    // Prepare and execute the SQL update query.
                                    $query = "UPDATE {$wpdb->posts} 
                                            SET post_status = 'draft' 
                                            WHERE ID IN ($ids_placeholder)";
                                    $result = $wpdb->query($wpdb->prepare($query, $post_ids));

                                    if ($result === false) {
                                        error_log('Error updating posts: ' . $wpdb->last_error);
                                        add_to_cron_log('Error updating posts: ' . $wpdb->last_error, $log_type);
                                        return false;
                                    }

                                    // Clear the cache and log the updates.
                                    wp_cache_flush();
                                    $total_posts = count($post_ids);
                                    $detailed_count = 100;

                                    foreach (array_slice($post_ids, 0, $detailed_count) as $post_id) {
                                        add_to_cron_log("Post ID {$post_id} changed to draft successfully.", $log_type);
                                    }

                                    if ($total_posts > $detailed_count) {
                                        $remaining_posts = $total_posts - $detailed_count;
                                        add_to_cron_log("And {$remaining_posts} more posts changed to draft successfully.", $log_type);
                                    }
                                    
                                    return $result;
                                }

                                /**
                                * Handles the automatic update of media posts for a specified cron type.
                                *
                                * @param string $type The type of cron job ('five_years' or 'two_years').
                                */
                                function media_auto_update_cronjob($type) {
                                    $log_type = $type === 'two_years' ? 'two_years' : 'five_years';

                                    // Log the start of the cron job.
                                    add_to_cron_log('Running...', $log_type);
                                    error_log("Cronjob running for {$log_type}...");

                                    // Determine the cutoff date for the query.
                                    $since_option = $type === 'two_years' ? 'unpublish_media_auto_date_two' : 'unpublish_media_auto_date';
                                    $since = get_option($since_option, '2026-01-01');
                                    $since_timestamp = strtotime($since);
                                    $time_period = $type === 'two_years' ? '-2 years' : '-4 years';
                                    $cutoff_date = strtotime($time_period, $since_timestamp);
                                    $exclude_media_ids = get_field('related_media_posts', 'options') ?: [];
                                    $formatted_date = date('Y-m-d', $cutoff_date);
                                    $local_time = current_time('timestamp');
                                    $formatted_last_date = gmdate('Y-m-d H:i:s T O', $local_time);

                                    // Log relevant information.
                                    add_to_cron_log("Since: {$formatted_date}", $log_type);
                                    add_to_cron_log('Exclude IDs: ' . (is_array($exclude_media_ids) ? implode(', ', $exclude_media_ids) : $exclude_media_ids), $log_type);

                                    // Build the query arguments to fetch the posts.
                                    $args = array(
                                        'post_type'      => 'media',
                                        'post_status'    => 'publish',
                                        'posts_per_page' => -1,
                                        'post__not_in'   => $exclude_media_ids,
                                        'date_query'     => array(
                                            array(
                                                'before'    => date('Y-m-d', $cutoff_date),
                                                'inclusive' => true,
                                            )
                                        ),
                                        'fields'         => 'ids',
                                    );

                                    // Add a meta query for 'two_years'.
                                    if ($type === 'two_years') {
                                        $args['meta_query'] = array(
                                            array(
                                                'key'     => 'gated_content_disclaimer',
                                                'value'   => '1',
                                                'compare' => '='
                                            )
                                        );
                                    }

                                    $posts_to_process = get_posts($args);

                                    // Exit if no posts are found.
                                    if (empty($posts_to_process)) {
                                        $last_run_option = $type === 'two_years' ? 'last_run_update_auto_media_posts_event_two' : 'last_run_update_auto_media_posts_event';
                                        update_option($last_run_option, $formatted_last_date);
                                        add_to_cron_log('No posts found to process.', $log_type);
                                        error_log('No posts, stopping process.');
                                        return;
                                    }

                                    // Log the total number of posts found and process them in bulk.
                                    add_to_cron_log('Total Posts Found: ' . count($posts_to_process), $log_type);
                                    bulk_update_posts_to_draft($posts_to_process, $log_type);

                                    // Finalize the cron job.
                                    add_to_cron_log('All posts processed.', $log_type);
                                    $last_run_option = $type === 'two_years' ? 'last_run_update_auto_media_posts_event_two' : 'last_run_update_auto_media_posts_event';
                                    update_option($last_run_option, $formatted_last_date);
                                    error_log('Cronjob completed successfully.');
                                }

                            
                        

This code defines a MediaTable class based on WP_List_Table to create a custom table in the WordPress admin area.

                            
                                namespace xxUnpu\Table;

                                use WP_Query;
                                use WP_List_Table;
                                use WP_Post;

                                class MediaTable extends WP_List_Table
                                {
                                    /**
                                    * Constructor for the MediaTable class.
                                    */
                                    public function __construct()
                                    {
                                        // Set up the list table with singular and plural labels
                                        parent::__construct([
                                            'singular' => 'media',
                                            'plural' => 'media'
                                        ]);
                                    }

                                    /**
                                    * Check if the current user has permission to make AJAX requests.
                                    *
                                    * @return bool True if the user can edit posts, false otherwise.
                                    */
                                    public function ajax_user_can()
                                    {
                                        return current_user_can('editor');
                                    }

                                    /**
                                    * Display a message when there are no items found.
                                    */
                                    public function no_items()
                                    {
                                        echo 'No people found';
                                    }

                                    /**
                                    * Get a list of columns for the list table.
                                    *
                                    * @return array Array of column names.
                                    */
                                    function get_columns()
                                    {
                                        return array(
                                            'post_title' => 'Title',
                                            'post_id' => 'ID',
                                            'status_post' => 'Status Post',
                                            'status_acf' => 'Gated Content Status',
                                            'date' => 'Date',
                                            'lang' => 'Language'
                                        );
                                    }

                                    /**
                                    * Get a list of hidden columns for the list table.
                                    *
                                    * @return array Array of column names that are hidden.
                                    */
                                    public function get_hidden_columns()
                                    {
                                        return [];
                                    }

                                    /**
                                    * Get a list of sortable columns for the list table.
                                    *
                                    * @return array Array of sortable columns.
                                    */
                                    protected function get_sortable_columns()
                                    {
                                        return [];
                                    }

                                    /**
                                    * Prepare the list table items.
                                    */
                                    public function prepare_items()
                                    {
                                        // Set up column headers
                                        $this->_column_headers = array(
                                            $this->get_columns(),
                                            $this->get_hidden_columns(),
                                            $this->get_sortable_columns(),
                                        );

                                        // Handle search input
                                        $search = isset($_REQUEST['s']) ? wp_unslash(trim($_REQUEST['s'])) : '';

                                        $unpu_per_page = 20;
                                        $paged = $this->get_pagenum();

                                        // Query arguments to fetch media posts
                                        $args = [
                                            'post_type' => 'media',
                                            'posts_per_page' => $unpu_per_page,
                                            'offset' => ($paged - 1) * $unpu_per_page,
                                        ];

                                        // Add search term if present
                                        if (!empty($search)) {
                                            $args['s'] = $search;
                                        }

                                        // Add orderby and order parameters if set
                                        if (isset($_REQUEST['orderby'])) {
                                            $args['orderby'] = $_REQUEST['orderby'];
                                        }

                                        if (isset($_REQUEST['order'])) {
                                            $args['order'] = $_REQUEST['order'];
                                        }

                                        // Execute the query
                                        $query = new WP_Query($args);
                                        $this->items = $query->posts;

                                        // Set up pagination arguments
                                        $this->set_pagination_args(
                                            array(
                                                'total_items' => $query->found_posts,
                                                'per_page'    => $unpu_per_page,
                                            )
                                        );
                                    }

                                    /**
                                    * Generate the list table rows.
                                    */
                                    public function display_rows()
                                    {
                                        foreach ($this->items as $media) {
                                            echo "\n\t" . $this->single_row($media);
                                        }
                                    }

                                    /**
                                    * Generate HTML for a single row.
                                    *
                                    * @param WP_Post $media The current media item.
                                    * @return string HTML output for a single row.
                                    */
                                    public function single_row($media)
                                    {
                                        $r = "";

                                        // Set up actions for the 'edit' and 'view' links
                                        $actions = [];
                                        $edit_page = 'post.php?' . http_build_query([
                                            'post' => $media->ID,
                                            'action' => 'edit'
                                        ]);
                                        $view_link = get_permalink($media->ID);
                                        $actions['edit'] = "Edit";
                                        $actions['view'] = "View";

                                        // Get column information
                                        list($columns, $hidden, $sortable, $primary) = $this->get_column_info();

                                        // Loop through each column and generate cell content
                                        foreach ($columns as $column_name => $column_display_name) {
                                            $css_classes = "$column_name column-$column_name";
                                            if ($primary === $column_name) {
                                                $css_classes .= ' has-row-actions column-primary';
                                            }

                                            if (in_array($column_name, $hidden, true)) {
                                                $css_classes .= ' hidden';
                                            }

                                            $data = 'data-colname="' . wp_strip_all_tags($column_display_name) . '"';
                                            $attributes = "class='$css_classes' $data";
                                            $r .= "";

                                            // Render cell content based on the column name
                                            switch ($column_name) {
                                                case 'post_id':
                                                    $post_id = $media->ID;
                                                    $r .= ucfirst($post_id);
                                                    break;
                                                case 'post_title':
                                                    $name = $media->post_title ?: '(no name)';
                                                    $r .= $name;
                                                    break;
                                                case 'status_post':
                                                    $post_status = get_post_status($media->ID);
                                                    $r .= ucfirst($post_status);
                                                    break;
                                                case 'status_acf':
                                                    $status_acf = get_post_meta($media->ID, 'gated_content_disclaimer', true);
                                                    $r .= !empty($status_acf) ? 'Active' : 'Inactive';
                                                    break;
                                                case 'date':
                                                    $status = get_the_date('d M Y', $media->ID);
                                                    $r .= ucfirst($status);
                                                    break;
                                                case 'lang':
                                                    $translations = pll_the_languages(array('raw' => 1));
                                                    $lang = pll_get_post_language($media->ID);
                                                    $r .= $translations[$lang]['flag'];
                                                    break;
                                            }

                                            // Add row actions if this is the primary column
                                            if ($primary === $column_name) {
                                                $r .= $this->row_actions($actions);
                                            }

                                            $r .= '';
                                        }

                                        $r .= '';

                                        return $r;
                                    }
                                }
                            
                        

Preview

Custom Plugin preview
Custom Plugin preview