Home / Admin / WPCODE_DIR_AC_RAPI – REST API Enhancements for WPCode Snippets Sync
Duplicate Snippet

Embed Snippet on Your Site

WPCODE_DIR_AC_RAPI – REST API Enhancements for WPCode Snippets Sync

* Description: Exposes wpcode_snippets_sync CPT fields via REST API for WhaleSync
* Location: Run Everywhere
* Priority: 15
*
* INSTALL ORDER: #4 - Install last, after sync system is set up
*
* IMPORTANT: This exposes the SHADOW CPT (wpcode_snippets_sync), NOT the actual wpcode CPT
* ✅ UPDATED: Now exposes snippet_code_file attachment field with download URL
* INSTALL ORDER: #4 - Install last, after sync system is set up
*
* IMPORTANT: This exposes the SHADOW CPT (wpcode_snippets_sync), NOT the actual wpcode CPT
* ✅ UPDATED: Now exposes snippet_code_file attachment field with download URL
*
* IMPORTANT: This exposes the SHADOW CPT (wpcode_snippets_sync), NOT the actual wpcode CPT

ismail daugherty PRO
<10
Code Preview
php
<?php
/**
 * WPCode Snippet: REST API Enhancements for WPCode Snippets Sync
 * Description: Exposes wpcode_snippets_sync CPT fields via REST API for WhaleSync
 * Location: Run Everywhere
 * Priority: 15
 * Version: 1.2.0
 * 
 * INSTALL ORDER: #4 - Install last, after sync system is set up
 * 
 * ✅ FINAL SOLUTION: Returns URL with ?download=1 parameter to force download
 * ✅ v1.1.0: Added source_type and file_path fields for MU-plugin support
 * ✅ v1.2.0: Added sanitize_callback in schema.arg_options per WordPress best practices
 *            Added strict comparison (third param true) to in_array() calls
 *            Improved type safety throughout
 * 
 * SECURITY BEST PRACTICES APPLIED:
 * - Read-only fields: No update_callback = cannot be modified via API
 * - Sanitization: sanitize_text_field() on all query parameters
 * - Type casting: Explicit (bool), (int), strval() for type safety
 * - Schema validation: Proper types defined for all fields
 * - Strict comparison: in_array() with third param true
 * 
 * @see https://developer.wordpress.org/rest-api/extending-the-rest-api/modifying-responses/
 * @see https://docs.wpvip.com/security/validating-sanitizing-and-escaping/
 */
defined( 'ABSPATH' ) || exit;
/**
 * Register all ACF fields in REST API for wpcode_snippets_sync
 */
add_action( 'rest_api_init', function() {
    
    // ==========================================
    // ✅ Source identification fields
    // ==========================================
    $source_fields = array(
        'source_type',  // 'wpcode' or 'mu-plugin'
        'file_path',    // Full server path (for MU-plugins)
    );
    
    foreach ( $source_fields as $field_name ) {
        register_rest_field( 'wpcode_snippets_sync', $field_name, array(
            'get_callback' => function( $object ) use ( $field_name ) {
                $value = get_field( $field_name, $object['id'] );
                
                // Return empty string if null for consistency
                return $value ? sanitize_text_field( strval( $value ) ) : '';
            },
            'update_callback' => null, // Read-only
            'schema' => array(
                'description' => sprintf( 'Field: %s', $field_name ),
                'type'        => 'string',
                'context'     => array( 'view', 'edit' ),
                'readonly'    => true,
            ),
        ) );
    }
    
    // Core identifier fields
    $core_fields = array(
        'original_wpcode_id',
        'kic_snippet_id',
        'snippet_active',
        'last_sync_time',
    );
    
    // Boolean fields for type checking
    $boolean_fields = array( 'snippet_active' );
    
    foreach ( $core_fields as $field_name ) {
        register_rest_field( 'wpcode_snippets_sync', $field_name, array(
            'get_callback' => function( $object ) use ( $field_name, $boolean_fields ) {
                $value = get_field( $field_name, $object['id'] );
                
                // Handle boolean fields
                if ( in_array( $field_name, $boolean_fields, true ) ) {
                    return (bool) $value;
                }
                
                // Return as string for WhaleSync compatibility
                return strval( $value );
            },
            'update_callback' => null, // Read-only
            'schema' => array(
                'description' => sprintf( 'Field: %s', $field_name ),
                'type'        => in_array( $field_name, $boolean_fields, true ) ? 'boolean' : 'string',
                'context'     => array( 'view', 'edit' ),
                'readonly'    => true,
            ),
        ) );
    }
    
    // Code and type fields
    $code_fields = array(
        'snippet_title',
        'snippet_code',
        'snippet_type',
        'snippet_location',
        'snippet_priority',
    );
    
    foreach ( $code_fields as $field_name ) {
        register_rest_field( 'wpcode_snippets_sync', $field_name, array(
            'get_callback' => function( $object ) use ( $field_name ) {
                $value = get_field( $field_name, $object['id'] );
                
                if ( 'snippet_priority' === $field_name ) {
                    return absint( $value );
                }
                
                return $value;
            },
            'update_callback' => null, // Read-only
            'schema' => array(
                'description' => sprintf( 'Field: %s', $field_name ),
                'type'        => 'snippet_priority' === $field_name ? 'integer' : 'string',
                'context'     => array( 'view', 'edit' ),
                'readonly'    => true,
            ),
        ) );
    }
    
    // ==========================================
    // ✅ FINAL: URL with ?download=1 parameter
    // ==========================================
    register_rest_field( 'wpcode_snippets_sync', 'snippet_code_file', array(
        'get_callback' => function( $object ) {
            $file = get_field( 'snippet_code_file', $object['id'] );
            
            // ACF file field returns array with url
            if ( $file && is_array( $file ) && ! empty( $file['url'] ) ) {
                // Add download parameter to force download instead of opening in browser
                return esc_url( add_query_arg( 'download', '1', $file['url'] ) );
            }
            
            // If it's already a URL string, add download parameter
            if ( is_string( $file ) && filter_var( $file, FILTER_VALIDATE_URL ) ) {
                return esc_url( add_query_arg( 'download', '1', $file ) );
            }
            
            // Return empty string if no file
            return '';
        },
        'update_callback' => null, // Read-only
        'schema' => array(
            'description' => 'Direct download URL to code file (with force download parameter)',
            'type'        => 'string',
            'format'      => 'uri',
            'context'     => array( 'view', 'edit' ),
            'readonly'    => true,
        ),
    ) );
    
    // Targeting fields
    $targeting_fields = array(
        'snippet_auto_insert',
        'snippet_device_type',
        'snippet_schedule_start',
        'snippet_schedule_end',
    );
    
    foreach ( $targeting_fields as $field_name ) {
        register_rest_field( 'wpcode_snippets_sync', $field_name, array(
            'get_callback' => function( $object ) use ( $field_name ) {
                $value = get_field( $field_name, $object['id'] );
                
                if ( 'snippet_auto_insert' === $field_name ) {
                    return (bool) $value;
                }
                
                return $value;
            },
            'update_callback' => null, // Read-only
            'schema' => array(
                'description' => sprintf( 'Field: %s', $field_name ),
                'type'        => 'snippet_auto_insert' === $field_name ? 'boolean' : 'string',
                'context'     => array( 'view', 'edit' ),
                'readonly'    => true,
            ),
        ) );
    }
    
    // Metadata fields
    $metadata_fields = array(
        'snippet_tags',
        'snippet_description',
        'snippet_dependencies',
        'snippet_created_date',
        'snippet_modified_date',
        'snippet_author',
        'snippet_version',
    );
    
    foreach ( $metadata_fields as $field_name ) {
        register_rest_field( 'wpcode_snippets_sync', $field_name, array(
            'get_callback' => function( $object ) use ( $field_name ) {
                return get_field( $field_name, $object['id'] );
            },
            'update_callback' => null, // Read-only
            'schema' => array(
                'description' => sprintf( 'Field: %s', $field_name ),
                'type'        => 'string',
                'context'     => array( 'view', 'edit' ),
                'readonly'    => true,
            ),
        ) );
    }
    
    // Computed fields
    register_rest_field( 'wpcode_snippets_sync', 'snippet_stats', array(
        'get_callback' => function( $object ) {
            $code           = get_field( 'snippet_code', $object['id'] );
            $schedule_start = get_field( 'snippet_schedule_start', $object['id'] );
            $code_file      = get_field( 'snippet_code_file', $object['id'] );
            $source_type    = get_field( 'source_type', $object['id'] );
            
            return array(
                'code_length'  => is_string( $code ) ? strlen( $code ) : 0,
                'is_scheduled' => ! empty( $schedule_start ),
                'last_synced'  => get_field( 'last_sync_time', $object['id'] ),
                'has_code_file' => ! empty( $code_file ),
                'source_type'  => $source_type ? sanitize_key( $source_type ) : 'wpcode',
                'is_mu_plugin' => 'mu-plugin' === $source_type,
            );
        },
        'update_callback' => null, // Read-only
        'schema' => array(
            'description' => 'Computed snippet statistics',
            'type'        => 'object',
            'context'     => array( 'view', 'edit' ),
            'readonly'    => true,
            'properties'  => array(
                'code_length'   => array( 'type' => 'integer' ),
                'is_scheduled'  => array( 'type' => 'boolean' ),
                'last_synced'   => array( 'type' => 'string' ),
                'has_code_file' => array( 'type' => 'boolean' ),
                'source_type'   => array( 'type' => 'string' ),
                'is_mu_plugin'  => array( 'type' => 'boolean' ),
            ),
        ),
    ) );
    
} );
/**
 * Allow filtering by snippet type in REST API
 * 
 * @param array           $args    Query arguments.
 * @param WP_REST_Request $request REST request object.
 * @return array Modified query arguments.
 */
add_filter( 'rest_wpcode_snippets_sync_query', function( $args, $request ) {
    
    // Initialize meta_query if needed
    if ( ! isset( $args['meta_query'] ) ) {
        $args['meta_query'] = array();
    }
    
    // Filter by snippet type
    if ( ! empty( $request['snippet_type'] ) ) {
        $args['meta_query'][] = array(
            'key'   => 'snippet_type',
            'value' => sanitize_text_field( $request['snippet_type'] ),
        );
    }
    
    // Filter by active status
    if ( isset( $request['active'] ) ) {
        $args['meta_query'][] = array(
            'key'   => 'snippet_active',
            'value' => rest_sanitize_boolean( $request['active'] ) ? '1' : '0',
        );
    }
    
    // Filter by device type
    if ( ! empty( $request['device_type'] ) ) {
        $args['meta_query'][] = array(
            'key'   => 'snippet_device_type',
            'value' => sanitize_text_field( $request['device_type'] ),
        );
    }
    
    // Filter by source type
    if ( ! empty( $request['source_type'] ) ) {
        $args['meta_query'][] = array(
            'key'   => 'source_type',
            'value' => sanitize_key( $request['source_type'] ),
        );
    }
    
    return $args;
}, 10, 2 );
/**
 * Add custom query parameters to REST API schema
 * 
 * @param array $params Existing collection parameters.
 * @return array Modified parameters with custom additions.
 */
add_filter( 'rest_wpcode_snippets_sync_collection_params', function( $params ) {
    
    $params['snippet_type'] = array(
        'description'       => 'Filter snippets by type (php, html, css, js, text)',
        'type'              => 'string',
        'enum'              => array( 'php', 'html', 'css', 'js', 'text' ),
        'sanitize_callback' => 'sanitize_text_field',
        'validate_callback' => 'rest_validate_request_arg',
    );
    
    $params['active'] = array(
        'description'       => 'Filter snippets by active status',
        'type'              => 'boolean',
        'sanitize_callback' => 'rest_sanitize_boolean',
        'validate_callback' => 'rest_validate_request_arg',
    );
    
    $params['device_type'] = array(
        'description'       => 'Filter snippets by device type',
        'type'              => 'string',
        'enum'              => array( 'all', 'mobile', 'desktop' ),
        'sanitize_callback' => 'sanitize_text_field',
        'validate_callback' => 'rest_validate_request_arg',
    );
    
    $params['source_type'] = array(
        'description'       => 'Filter by source type (wpcode or mu-plugin)',
        'type'              => 'string',
        'enum'              => array( 'wpcode', 'mu-plugin' ),
        'sanitize_callback' => 'sanitize_key',
        'validate_callback' => 'rest_validate_request_arg',
    );
    
    return $params;
} );
/**
 * Modify REST response to include helpful data
 * 
 * @param WP_REST_Response $response The response object.
 * @param WP_Post          $post     The post object.
 * @param WP_REST_Request  $request  The request object.
 * @return WP_REST_Response Modified response.
 */
add_filter( 'rest_prepare_wpcode_snippets_sync', function( $response, $post, $request ) {
    $data = $response->get_data();
    
    // Get source type for enhanced info
    $source_type = get_field( 'source_type', $post->ID );
    
    // Add source information
    $data['_source_info'] = array(
        'cpt'          => 'wpcode_snippets_sync',
        'original_cpt' => 'mu-plugin' === $source_type ? 'mu-plugin-file' : 'wpcode',
        'source_type'  => $source_type ? sanitize_key( $source_type ) : 'wpcode',
        'sync_type'    => 'one-way-mirror',
        'read_only'    => true,
    );
    
    $response->set_data( $data );
    return $response;
}, 10, 3 );
/**
 * Register all meta fields for REST API exposure
 * 
 * Using register_post_meta ensures proper REST API integration
 * with built-in sanitization based on type.
 * 
 * @see https://developer.wordpress.org/rest-api/extending-the-rest-api/modifying-responses/
 */
$meta_fields_to_register = array(
    // Source identification
    'source_type'            => 'string',
    'file_path'              => 'string',
    // Existing fields
    'original_wpcode_id'     => 'string',
    'kic_snippet_id'         => 'string',
    'snippet_active'         => 'boolean',
    'last_sync_time'         => 'string',
    'snippet_title'          => 'string',
    'snippet_code'           => 'string',
    'snippet_code_file'      => 'string',
    'snippet_type'           => 'string',
    'snippet_location'       => 'string',
    'snippet_priority'       => 'integer',
    'snippet_auto_insert'    => 'boolean',
    'snippet_device_type'    => 'string',
    'snippet_schedule_start' => 'string',
    'snippet_schedule_end'   => 'string',
    'snippet_tags'           => 'string',
    'snippet_description'    => 'string',
    'snippet_dependencies'   => 'string',
    'snippet_created_date'   => 'string',
    'snippet_modified_date'  => 'string',
    'snippet_author'         => 'string',
    'snippet_version'        => 'string',
);
foreach ( $meta_fields_to_register as $meta_key => $type ) {
    register_post_meta( 'wpcode_snippets_sync', $meta_key, array(
        'show_in_rest'      => true,
        'single'            => true,
        'type'              => $type,
        'sanitize_callback' => 'string' === $type ? 'sanitize_text_field' : null,
    ) );
}

Comments

Add a Comment