Beyond the basic

In a previous post, I showed you how to install a simple WordPress plugin. But the plugin used a non-standard way of changing the font, and the color and style were hard-coded (You have to edit the plugin and upgrade it every time you want to change the text and font style.) Let’s look at a better way of doing it. Here is the prompt I gave Gemini.

Please write me a WordPress plugin called “My Customizable Shortcode Plugin” that uses a short code called [my_customisable_shortcode] that uses a settings tab in the dashboard to change the text and font style. Use a color picker and bold and italic checkboxes. The code should uses standard WordPress function calls to change the font instead of updating the header directly.

Example output: Hello, World!

The first version worked fine. But I tinkered with it. So here are two versions. The last version allows multiple lines of text and to select from 3 fonts.

Version

<?php
/**
 * Plugin Name: My Customizable Shortcode Plugin
 * Description: Creates a customizable shortcode [my_customisable_shortcode] managed via a dashboard settings page.
 * Version: 1.0
 * Author: Les Ey
 * Author URI: https://les-ey.online
 * License: GPL-2.0-or-later
 * License URI: http://www.gnu.org/licenses/gpl-2.0.html
 * Text Domain: mscp
 */

// Exit if accessed directly
if ( ! defined( 'ABSPATH' ) ) {
    exit;
}

// --- CONSTANTS ---
define( 'MSCP_SLUG', 'mscp-settings' );
define( 'MSCP_OPTION_GROUP', 'mscp_options' );
define( 'MSCP_OPTION_NAME', 'mscp_shortcode_options' );

/**
 * Load plugin textdomain for translation support.
 */
function mscp_load_textdomain() {
    load_plugin_textdomain( 'mscp', false, dirname( plugin_basename( __FILE__ ) ) . '/languages' );
}
add_action( 'plugins_loaded', 'mscp_load_textdomain' );


/**
 * 1. ADMIN MENU AND SETTINGS SETUP
 * Registers the settings page and settings fields.
 */
function mscp_add_admin_menu() {
    add_menu_page(
        esc_html__( 'Shortcode Customizer', 'mscp' ),
        esc_html__( 'Custom Shortcode', 'mscp' ),
        'manage_options',
        MSCP_SLUG,
        'mscp_settings_page',
        'dashicons-edit-page',
        80
    );
}
add_action( 'admin_menu', 'mscp_add_admin_menu' );

function mscp_settings_init() {
    register_setting( MSCP_OPTION_GROUP, MSCP_OPTION_NAME, 'mscp_options_validate' );

    add_settings_section(
        'mscp_main_section',
        esc_html__( 'Customize Shortcode Appearance', 'mscp' ),
        'mscp_settings_section_callback',
        MSCP_SLUG
    );

    // Text Field
    add_settings_field(
        'mscp_text_content',
        esc_html__( 'Shortcode Text', 'mscp' ),
        'mscp_text_content_render',
        MSCP_SLUG,
        'mscp_main_section'
    );

    // Color Picker Field
    add_settings_field(
        'mscp_text_color',
        esc_html__( 'Text Color', 'mscp' ),
        'mscp_text_color_render',
        MSCP_SLUG,
        'mscp_main_section'
    );

    // Font Size Field
    add_settings_field(
        'mscp_font_size',
        esc_html__( 'Font Size (px)', 'mscp' ),
        'mscp_font_size_render',
        MSCP_SLUG,
        'mscp_main_section'
    );

    // Bold Checkbox
    add_settings_field(
        'mscp_is_bold',
        esc_html__( 'Bold Text', 'mscp' ),
        'mscp_is_bold_render',
        MSCP_SLUG,
        'mscp_main_section'
    );

    // Italics Checkbox
    add_settings_field(
        'mscp_is_italic',
        esc_html__( 'Italic Text', 'mscp' ),
        'mscp_is_italic_render',
        MSCP_SLUG,
        'mscp_main_section'
    );
}
add_action( 'admin_init', 'mscp_settings_init' );

/**
 * Settings Field Render Functions
 */
function mscp_get_options() {
    // Note: Default text content must be wrapped in __() to be translatable
    $default_text = esc_html__( 'Hello, World!', 'mscp' );
    return get_option( MSCP_OPTION_NAME, array(
        'text_content' => $default_text,
        'text_color'   => '#0073AA',
        'font_size'    => 16,
        'is_bold'      => 0,
        'is_italic'    => 0,
    ) );
}

function mscp_settings_section_callback() {
    echo '<p>' . esc_html__( 'Configure the text and style for the [my_customisable_shortcode].', 'mscp' ) . '</p>';
}

function mscp_text_content_render() {
    $options = mscp_get_options();
    ?>
    <input type="text" name="<?php echo MSCP_OPTION_NAME; ?>[text_content]" value="<?php echo esc_attr( $options['text_content'] ); ?>" style="width: 400px;" />
    <?php
}

function mscp_text_color_render() {
    $options = mscp_get_options();
    ?>
    <input type="text" name="<?php echo MSCP_OPTION_NAME; ?>[text_color]" id="mscp_text_color" value="<?php echo esc_attr( $options['text_color'] ); ?>" class="mscp-color-field" data-default-color="#0073AA" />
    <?php
}

function mscp_font_size_render() {
    $options = mscp_get_options();
    ?>
    <input type="number" name="<?php echo MSCP_OPTION_NAME; ?>[font_size]" value="<?php echo esc_attr( $options['font_size'] ); ?>" min="8" max="72" /> px
    <p class="description"><?php esc_html_e( 'Enter font size in pixels.', 'mscp' ); ?></p>
    <?php
}

function mscp_is_bold_render() {
    $options = mscp_get_options();
    $checked = ( isset( $options['is_bold'] ) && $options['is_bold'] == 1 ) ? 'checked="checked"' : '';
    ?>
    <input type="checkbox" name="<?php echo MSCP_OPTION_NAME; ?>[is_bold]" value="1" <?php echo $checked; ?> />
    <?php
}

function mscp_is_italic_render() {
    $options = mscp_get_options();
    $checked = ( isset( $options['is_italic'] ) && $options['is_italic'] == 1 ) ? 'checked="checked"' : '';
    ?>
    <input type="checkbox" name="<?php echo MSCP_OPTION_NAME; ?>[is_italic]" value="1" <?php echo $checked; ?> />
    <?php
}

function mscp_options_validate( $input ) {
    $output = mscp_get_options(); // Start with current options

    // Sanitize text content
    if ( isset( $input['text_content'] ) ) {
        $output['text_content'] = sanitize_text_field( $input['text_content'] );
    }

    // Sanitize color (must be a valid hex color)
    if ( isset( $input['text_color'] ) ) {
        $output['text_color'] = sanitize_hex_color( $input['text_color'] );
    }

    // Sanitize font size (integer check)
    if ( isset( $input['font_size'] ) ) {
        $output['font_size'] = absint( $input['font_size'] );
        if ( $output['font_size'] < 8 || $output['font_size'] > 72 ) {
            $output['font_size'] = 16; // Default safe value
        }
    }

    // Sanitize checkboxes (0 or 1)
    $output['is_bold'] = ( isset( $input['is_bold'] ) && $input['is_bold'] == 1 ) ? 1 : 0;
    $output['is_italic'] = ( isset( $input['is_italic'] ) && $input['is_italic'] == 1 ) ? 1 : 0;

    return $output;
}

/**
 * Settings Page HTML
 */
function mscp_settings_page() {
    // Check user capabilities
    if ( ! current_user_can( 'manage_options' ) ) {
        return;
    }

    ?>
    <div class="wrap">
        <h1><?php echo esc_html( get_admin_page_title() ); ?></h1>
        <form action="options.php" method="post">
            <?php
            // Output security fields for the registered setting group
            settings_fields( MSCP_OPTION_GROUP );
            // Output settings sections and fields
            do_settings_sections( MSCP_SLUG );
            // Output save settings button, translated
            submit_button( esc_attr__( 'Save Customizations', 'mscp' ) );
            ?>
        </form>
    </div>
    <?php
}

/**
 * 2. ADMIN SCRIPTS AND STYLES (for Color Picker)
 * Enqueues the color picker and initializes it with inline JavaScript.
 */
function mscp_admin_scripts( $hook ) {
    // Check if we are on the correct admin page before loading assets
    if ( strpos( $hook, MSCP_SLUG ) === false ) {
        return;
    }

    // Enqueue the native WordPress color picker scripts and styles
    wp_enqueue_style( 'wp-color-picker' );
    wp_enqueue_script( 'wp-color-picker' );

    // Inline script to initialize the color picker on the color input field
    $inline_script = 'jQuery(document).ready(function($){ $("#mscp_text_color").wpColorPicker(); });';
    wp_add_inline_script( 'wp-color-picker', $inline_script );
}
add_action( 'admin_enqueue_scripts', 'mscp_admin_scripts' );


/**
 * 3. SHORTCODE IMPLEMENTATION
 * The main function to output the customized text.
 */
function mscp_shortcode_output( $atts ) {
    // Get the current options
    $options = mscp_get_options();

    // Define the output
    $content = esc_html( $options['text_content'] );
    $output  = '<span class="mscp-shortcode-text">' . $content . '</span>';

    // The shortcode should not be rendered if the text is empty
    if ( empty( $content ) ) {
        return '';
    }

    // Enqueue styles when the shortcode is used (this is important)
    mscp_enqueue_styles();

    return $output;
}
add_shortcode( 'my_customisable_shortcode', 'mscp_shortcode_output' );


/**
 * 4. FRONT-END STYLING (The solution for inline style injection)
 * Enqueues a base style handle and uses wp_add_inline_style to inject custom CSS.
 */
function mscp_enqueue_styles() {
    $options = mscp_get_options();

    // 1. Register a style handle (we use false for the source as it's purely for inline styles)
    wp_register_style( 'mscp-custom-style', false );

    // 2. Enqueue the registered handle
    wp_enqueue_style( 'mscp-custom-style' );

    // 3. Build the dynamic CSS based on options
    $css = '.mscp-shortcode-text { ';
    $css .= 'color: ' . esc_attr( $options['text_color'] ) . ';';
    $css .= 'font-size: ' . absint( $options['font_size'] ) . 'px;';

    if ( isset( $options['is_bold'] ) && $options['is_bold'] == 1 ) {
        $css .= 'font-weight: bold;';
    } else {
        $css .= 'font-weight: normal;';
    }

    if ( isset( $options['is_italic'] ) && $options['is_italic'] == 1 ) {
        $css .= 'font-style: italic;';
    } else {
        $css .= 'font-style: normal;';
    }

    // Add a default font-family for aesthetic consistency, if the user requested "font style"
    $css .= 'font-family: sans-serif;';

    $css .= 'display: inline-block;'; // Ensures all properties apply correctly
    $css .= '}';

    // 4. Inject the custom CSS using the standard WordPress function
    wp_add_inline_style( 'mscp-custom-style', $css );
}

// NOTE: We don't hook mscp_enqueue_styles to 'wp_enqueue_scripts' directly.
// Instead, we call mscp_enqueue_styles() *inside* the shortcode function (mscp_shortcode_output)
// to ensure the styles are only loaded when the shortcode is actually present on the page.

?>
<?php
/**
 * Plugin Name: My Customizable Shortcode Plugin
 * Description: Creates a customizable shortcode [my_customisable_shortcode] managed via a dashboard settings page.
 * Version: 1.1
 * Author: Les Ey
 * Author URI: https://les-ey.online
 * License: GPL-2.0-or-later
 * License URI: http://www.gnu.org/licenses/gpl-2.0.html
 * Text Domain: mscp
 */

// Exit if accessed directly
if ( ! defined( 'ABSPATH' ) ) {
    exit;
}

// --- CONSTANTS ---
define( 'MSCP_SLUG', 'mscp-settings' );
define( 'MSCP_OPTION_GROUP', 'mscp_options' );
define( 'MSCP_OPTION_NAME', 'mscp_shortcode_options' );

/**
 * Defines the available fonts and their CSS stacks, mapping them to reliable Google Fonts
 * for consistent front-end display.
 */
function mscp_get_available_fonts() {
    return array(
        'roboto' => array(
            'label' => 'Arial-like (Roboto Sans-Serif)',
            'stack' => 'Roboto, Arial, sans-serif',
        ),
        'lora' => array(
            'label' => 'Times-like (Lora Serif)',
            'stack' => 'Lora, "Times New Roman", Times, serif',
        ),
        'inconsolata' => array(
            'label' => 'Consolas-like (Inconsolata Monospace)',
            'stack' => 'Inconsolata, Consolas, monospace',
        ),
    );
}

/**
 * Load plugin textdomain for translation support.
 */
function mscp_load_textdomain() {
    load_plugin_textdomain( 'mscp', false, dirname( plugin_basename( __FILE__ ) ) . '/languages' );
}
add_action( 'plugins_loaded', 'mscp_load_textdomain' );


/**
 * 1. ADMIN MENU AND SETTINGS SETUP
 * Registers the settings page and settings fields.
 */
function mscp_add_admin_menu() {
    add_menu_page(
        esc_html__( 'Shortcode Customizer', 'mscp' ),
        esc_html__( 'Custom Shortcode', 'mscp' ),
        'manage_options',
        MSCP_SLUG,
        'mscp_settings_page',
        'dashicons-edit-page',
        80
    );
}
add_action( 'admin_menu', 'mscp_add_admin_menu' );

function mscp_settings_init() {
    register_setting( MSCP_OPTION_GROUP, MSCP_OPTION_NAME, 'mscp_options_validate' );

    add_settings_section(
        'mscp_main_section',
        esc_html__( 'Customize Shortcode Appearance', 'mscp' ),
        'mscp_settings_section_callback',
        MSCP_SLUG
    );

    // Text Field (Memo Field)
    add_settings_field(
        'mscp_text_content',
        esc_html__( 'Shortcode Text/Memo', 'mscp' ),
        'mscp_text_content_render',
        MSCP_SLUG,
        'mscp_main_section'
    );

    // Color Picker Field
    add_settings_field(
        'mscp_text_color',
        esc_html__( 'Text Color', 'mscp' ),
        'mscp_text_color_render',
        MSCP_SLUG,
        'mscp_main_section'
    );

    // Font Name Dropdown (New Field)
    add_settings_field(
        'mscp_font_name',
        esc_html__( 'Font Name', 'mscp' ),
        'mscp_font_name_render',
        MSCP_SLUG,
        'mscp_main_section'
    );

    // Font Size Field
    add_settings_field(
        'mscp_font_size',
        esc_html__( 'Font Size (px)', 'mscp' ),
        'mscp_font_size_render',
        MSCP_SLUG,
        'mscp_main_section'
    );

    // Bold Checkbox
    add_settings_field(
        'mscp_is_bold',
        esc_html__( 'Bold Text', 'mscp' ),
        'mscp_is_bold_render',
        MSCP_SLUG,
        'mscp_main_section'
    );

    // Italics Checkbox
    add_settings_field(
        'mscp_is_italic',
        esc_html__( 'Italic Text', 'mscp' ),
        'mscp_is_italic_render',
        MSCP_SLUG,
        'mscp_main_section'
    );
}
add_action( 'admin_init', 'mscp_settings_init' );

/**
 * Settings Field Render Functions
 */
function mscp_get_options() {
    // Note: Default text content must be wrapped in __() to be translatable
    $default_text = esc_html__( 'Hello, World!', 'mscp' );
    return get_option( MSCP_OPTION_NAME, array(
        'text_content' => $default_text,
        'text_color'   => '#0073AA',
        'font_name'    => 'roboto', // Default font name updated
        'font_size'    => 16,
        'is_bold'      => 0,
        'is_italic'    => 0,
    ) );
}

function mscp_settings_section_callback() {
    echo '<p>' . esc_html__( 'Configure the text and style for the [my_customisable_shortcode].', 'mscp' ) . '</p>';
}

function mscp_text_content_render() {
    $options = mscp_get_options();
    ?>
    <textarea name="<?php echo MSCP_OPTION_NAME; ?>[text_content]" rows="5" cols="50" style="width: 400px;"><?php echo esc_textarea( $options['text_content'] ); ?></textarea>
    <?php
}

function mscp_text_color_render() {
    $options = mscp_get_options();
    ?>
    <input type="text" name="<?php echo MSCP_OPTION_NAME; ?>[text_color]" id="mscp_text_color" value="<?php echo esc_attr( $options['text_color'] ); ?>" class="mscp-color-field" data-default-color="#0073AA" />
    <?php
}

/**
 * Renders the font name dropdown selection.
 */
function mscp_font_name_render() {
    $options = mscp_get_options();
    $fonts = mscp_get_available_fonts();
    ?>
    <select name="<?php echo MSCP_OPTION_NAME; ?>[font_name]">
        <?php foreach ( $fonts as $key => $font ) : ?>
            <option value="<?php echo esc_attr( $key ); ?>" <?php selected( $options['font_name'], $key ); ?>>
                <?php echo esc_html( $font['label'] ); ?>
            </option>
        <?php endforeach; ?>
    </select>
    <p class="description"><?php esc_html_e( 'These are Google Fonts equivalents for reliable cross-browser display.', 'mscp' ); ?></p>
    <?php
}

function mscp_font_size_render() {
    $options = mscp_get_options();
    ?>
    <input type="number" name="<?php echo MSCP_OPTION_NAME; ?>[font_size]" value="<?php echo esc_attr( $options['font_size'] ); ?>" min="8" max="72" /> px
    <p class="description"><?php esc_html_e( 'Enter font size in pixels.', 'mscp' ); ?></p>
    <?php
}

function mscp_is_bold_render() {
    $options = mscp_get_options();
    $checked = ( isset( $options['is_bold'] ) && $options['is_bold'] == 1 ) ? 'checked="checked"' : '';
    ?>
    <input type="checkbox" name="<?php echo MSCP_OPTION_NAME; ?>[is_bold]" value="1" <?php echo $checked; ?> />
    <?php
}

function mscp_is_italic_render() {
    $options = mscp_get_options();
    $checked = ( isset( $options['is_italic'] ) && $options['is_italic'] == 1 ) ? 'checked="checked"' : '';
    ?>
    <input type="checkbox" name="<?php echo MSCP_OPTION_NAME; ?>[is_italic]" value="1" <?php echo $checked; ?> />
    <?php
}

function mscp_options_validate( $input ) {
    $output = mscp_get_options(); // Start with current options
    $fonts = mscp_get_available_fonts();

    // Sanitize text content - allows line breaks
    if ( isset( $input['text_content'] ) ) {
        $output['text_content'] = sanitize_textarea_field( $input['text_content'] );
    }

    // Sanitize color (must be a valid hex color)
    if ( isset( $input['text_color'] ) ) {
        $output['text_color'] = sanitize_hex_color( $input['text_color'] );
    }

    // Sanitize font name - ensures it matches an available key
    if ( isset( $input['font_name'] ) ) {
        $font_keys = array_keys( $fonts );
        $selected_font = sanitize_key( $input['font_name'] );

        if ( in_array( $selected_font, $font_keys, true ) ) {
            $output['font_name'] = $selected_font;
        } else {
            // Fallback to new default if somehow an invalid key is submitted
            $output['font_name'] = 'roboto';
        }
    }

    // Sanitize font size (integer check)
    if ( isset( $input['font_size'] ) ) {
        $output['font_size'] = absint( $input['font_size'] );
        if ( $output['font_size'] < 8 || $output['font_size'] > 72 ) {
            $output['font_size'] = 16; // Default safe value
        }
    }

    // Sanitize checkboxes (0 or 1)
    $output['is_bold'] = ( isset( $input['is_bold'] ) && $input['is_bold'] == 1 ) ? 1 : 0;
    $output['is_italic'] = ( isset( $input['is_italic'] ) && $input['is_italic'] == 1 ) ? 1 : 0;

    return $output;
}

/**
 * Settings Page HTML
 */
function mscp_settings_page() {
    // Check user capabilities
    if ( ! current_user_can( 'manage_options' ) ) {
        return;
    }

    ?>
    <div class="wrap">
        <h1><?php echo esc_html( get_admin_page_title() ); ?></h1>
        <form action="options.php" method="post">
            <?php
            // Output security fields for the registered setting group
            settings_fields( MSCP_OPTION_GROUP );
            // Output settings sections and fields
            do_settings_sections( MSCP_SLUG );
            // Output save settings button, translated
            submit_button( esc_attr__( 'Save Customizations', 'mscp' ) );
            ?>
        </form>
    </div>
    <?php
}

/**
 * 2. ADMIN SCRIPTS AND STYLES (for Color Picker)
 * Enqueues the color picker and initializes it with inline JavaScript.
 */
function mscp_admin_scripts( $hook ) {
    // Check if we are on the correct admin page before loading assets
    if ( strpos( $hook, MSCP_SLUG ) === false ) {
        return;
    }

    // Enqueue the native WordPress color picker scripts and styles
    wp_enqueue_style( 'wp-color-picker' );
    wp_enqueue_script( 'wp-color-picker' );

    // Inline script to initialize the color picker on the color input field
    $inline_script = 'jQuery(document).ready(function($){ $("#mscp_text_color").wpColorPicker(); });';
    wp_add_inline_script( 'wp-color-picker', $inline_script );
}
add_action( 'admin_enqueue_scripts', 'mscp_admin_scripts' );


/**
 * 3. SHORTCODE IMPLEMENTATION
 * The main function to output the customized text.
 */
function mscp_shortcode_output( $atts ) {
    // Get the current options
    $options = mscp_get_options();

    $content_raw = $options['text_content'];

    // The shortcode should not be rendered if the text is empty
    if ( empty( $content_raw ) ) {
        return '';
    }

    // Convert newlines to HTML <br> tags
    $content_with_breaks = nl2br( $content_raw );

    // Use wp_kses to strip any malicious HTML tags, but explicitly allow the safe <br> tag
    $display_content = wp_kses( $content_with_breaks, array( 'br' => array() ) );

    // Define the output
    $output  = '<span class="mscp-shortcode-text">' . $display_content . '</span>';

    // Enqueue styles when the shortcode is used (this is important)
    mscp_enqueue_styles();

    return $output;
}
add_shortcode( 'my_customisable_shortcode', 'mscp_shortcode_output' );


/**
 * 4. FRONT-END STYLING (The solution for inline style injection)
 * Enqueues a base style handle and uses wp_add_inline_style to inject custom CSS.
 */
function mscp_enqueue_styles() {
    $options = mscp_get_options();
    $fonts = mscp_get_available_fonts();

    // Determine the font data to use
    $selected_font_key = isset( $options['font_name'] ) && array_key_exists( $options['font_name'], $fonts )
        ? $options['font_name']
        : 'roboto'; // Fallback to 'roboto' key

    $font_data = $fonts[$selected_font_key];

    // --- ENQUEUE GOOGLE FONTS ---
    // Combined URL to load all three font families (regular and bold weights) in one request.
    $font_stack_url = 'https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&family=Lora:wght@400;700&family=Inconsolata:wght@400;700&display=swap';

    // Enqueue the Google Fonts URL using a standard WordPress function.
    wp_enqueue_style( 'mscp-google-fonts', $font_stack_url, array(), null );

    // 1. Register a style handle (we use false for the source as it's purely for inline styles)
    // Dependencies array ensures inline styles load *after* the Google Font faces.
    wp_register_style( 'mscp-custom-style', false, array('mscp-google-fonts') );

    // 2. Enqueue the registered handle
    wp_enqueue_style( 'mscp-custom-style' );

    // 3. Build the dynamic CSS based on options
    $css = '.mscp-shortcode-text { ';
    
    // Using !important on all custom properties to ensure theme styles do not override
    $css .= 'color: ' . esc_attr( $options['text_color'] ) . ' !important;';
    $css .= 'font-size: ' . absint( $options['font_size'] ) . 'px !important;';
    $css .= 'font-family: ' . esc_attr( $font_data['stack'] ) . ' !important;'; // Re-adding !important here too

    if ( isset( $options['is_bold'] ) && $options['is_bold'] == 1 ) {
        $css .= 'font-weight: bold !important;';
    } else {
        // Explicitly set normal weight to ensure consistency with Google Fonts
        $css .= 'font-weight: 400 !important;';
    }

    if ( isset( $options['is_italic'] ) && $options['is_italic'] == 1 ) {
        $css .= 'font-style: italic !important;';
    } else {
        $css .= 'font-style: normal !important;';
    }

    $css .= 'display: inline-block;'; // Ensures all properties apply correctly
    $css .= 'white-space: pre-wrap;'; // Ensures wrapping works correctly with <br> and spaces
    $css .= '}';

    // 4. Inject the custom CSS using the standard WordPress function
    wp_add_inline_style( 'mscp-custom-style', $css );
}

// NOTE: We don't hook mscp_enqueue_styles to 'wp_enqueue_scripts' directly.
// Instead, we call mscp_enqueue_styles() *inside* the shortcode function (mscp_shortcode_output)
// to ensure the styles are only loaded when the shortcode is actually present on the page.

?>

Got Questions?

Views: 73