array( label, file ). */ function code_live_snow_styles() { return array( 'dot' => array( 'label' => __( 'Soft dot', 'code-live-snow' ), 'file' => '', ), 'classic' => array( 'label' => __( 'Classic flake', 'code-live-snow' ), 'file' => 'flake-classic.svg', ), 'crystal' => array( 'label' => __( 'Crystal flake', 'code-live-snow' ), 'file' => 'flake-crystal.svg', ), 'simple' => array( 'label' => __( 'Simple flake', 'code-live-snow' ), 'file' => 'flake-simple.svg', ), 'sparkle' => array( 'label' => __( 'Sparkle', 'code-live-snow' ), 'file' => 'flake-sparkle.svg', ), ); } /** * URL of a bundled style image. * * @param string $slug Style slug. * @return string Empty for canvas-drawn styles. */ function code_live_snow_style_url( $slug ) { $styles = code_live_snow_styles(); if ( empty( $styles[ $slug ]['file'] ) ) { return ''; } return CODE_LIVE_SNOW_URL . 'assets/images/' . $styles[ $slug ]['file']; } /** * Default option values. * * @return array */ function code_live_snow_defaults() { return array( 'enabled' => '1', 'pages' => '', 'desktop_count' => 80, 'mobile_count' => 35, 'speed' => 1.2, 'direction' => 'down', 'styles' => array( 'dot', 'classic', 'crystal' ), 'images' => array(), 'min_size' => 8, 'max_size' => 22, 'zindex' => 9999, 'start_date' => '', 'end_date' => '', ); } /** * Allowed snow directions. * * @return array */ function code_live_snow_directions() { return array( 'down' => __( 'Top to Bottom', 'code-live-snow' ), 'left' => __( 'Right to Left', 'code-live-snow' ), 'right' => __( 'Left to Right', 'code-live-snow' ), 'diagonal' => __( 'Diagonal', 'code-live-snow' ), ); } /** * Get saved options merged with defaults. * * @return array */ function code_live_snow_get_options() { $saved = get_option( 'code_live_snow_options', array() ); if ( ! is_array( $saved ) ) { $saved = array(); } // Upgrade from 1.3.x, which stored a single image URL. if ( ! isset( $saved['images'] ) && ! empty( $saved['image'] ) ) { $id = attachment_url_to_postid( $saved['image'] ); $saved['images'] = $id ? array( $id ) : array(); } unset( $saved['image'] ); $options = wp_parse_args( $saved, code_live_snow_defaults() ); if ( ! is_array( $options['styles'] ) ) { $options['styles'] = array(); } if ( ! is_array( $options['images'] ) ) { $options['images'] = array(); } return $options; } /** * Validate a Y-m-d date string. * * @param mixed $value Raw value. * @return string Valid date or empty string. */ function code_live_snow_sanitize_date( $value ) { $value = sanitize_text_field( (string) $value ); if ( '' === $value ) { return ''; } $date = DateTime::createFromFormat( 'Y-m-d', $value ); if ( $date && $date->format( 'Y-m-d' ) === $value ) { return $value; } return ''; } /** * Clamp an integer to a range. * * @param mixed $value Raw value. * @param int $min Minimum. * @param int $max Maximum. * @return int */ function code_live_snow_clamp( $value, $min, $max ) { return min( max( absint( $value ), $min ), $max ); } /** * Sanitize settings. * * @param mixed $input Raw input from the settings form. * @return array */ function code_live_snow_sanitize( $input ) { $defaults = code_live_snow_defaults(); if ( ! is_array( $input ) ) { return $defaults; } // Page IDs: keep only positive integers, comma separated. $pages = ''; if ( isset( $input['pages'] ) ) { $parts = array_map( 'trim', explode( ',', sanitize_text_field( wp_unslash( $input['pages'] ) ) ) ); $ids = array_map( 'absint', array_filter( $parts, 'ctype_digit' ) ); $ids = array_filter( $ids ); $pages = implode( ',', array_unique( $ids ) ); } $direction = isset( $input['direction'] ) ? sanitize_key( $input['direction'] ) : $defaults['direction']; if ( ! array_key_exists( $direction, code_live_snow_directions() ) ) { $direction = $defaults['direction']; } // Built-in styles: whitelist only. $styles = array(); if ( isset( $input['styles'] ) && is_array( $input['styles'] ) ) { $allowed = array_keys( code_live_snow_styles() ); foreach ( $input['styles'] as $slug ) { $slug = sanitize_key( $slug ); if ( in_array( $slug, $allowed, true ) ) { $styles[] = $slug; } } } $styles = array_values( array_unique( $styles ) ); // Custom images: existing image attachments only, max 20. $images = array(); if ( isset( $input['images'] ) && is_array( $input['images'] ) ) { foreach ( $input['images'] as $id ) { $id = absint( $id ); if ( $id && wp_attachment_is_image( $id ) ) { $images[] = $id; } } } $images = array_slice( array_values( array_unique( $images ) ), 0, 20 ); // Never leave the snow without anything to draw. if ( empty( $styles ) && empty( $images ) ) { $styles = array( 'dot' ); } $min_size = isset( $input['min_size'] ) ? code_live_snow_clamp( $input['min_size'], 2, 100 ) : $defaults['min_size']; $max_size = isset( $input['max_size'] ) ? code_live_snow_clamp( $input['max_size'], 2, 150 ) : $defaults['max_size']; if ( $max_size < $min_size ) { $max_size = $min_size; } $speed = isset( $input['speed'] ) ? (float) $input['speed'] : $defaults['speed']; return array( 'enabled' => ! empty( $input['enabled'] ) ? '1' : '0', 'pages' => $pages, 'desktop_count' => isset( $input['desktop_count'] ) ? code_live_snow_clamp( $input['desktop_count'], 1, 500 ) : $defaults['desktop_count'], 'mobile_count' => isset( $input['mobile_count'] ) ? code_live_snow_clamp( $input['mobile_count'], 1, 500 ) : $defaults['mobile_count'], 'speed' => min( max( round( $speed, 1 ), 0.1 ), 20 ), 'direction' => $direction, 'styles' => $styles, 'images' => $images, 'min_size' => $min_size, 'max_size' => $max_size, 'zindex' => isset( $input['zindex'] ) ? min( absint( $input['zindex'] ), 2147483647 ) : $defaults['zindex'], 'start_date' => isset( $input['start_date'] ) ? code_live_snow_sanitize_date( $input['start_date'] ) : '', 'end_date' => isset( $input['end_date'] ) ? code_live_snow_sanitize_date( $input['end_date'] ) : '', ); } /* ---------------- ADMIN ---------------- */ /** * Register the settings page. */ function code_live_snow_admin_menu() { add_options_page( __( 'Code Live Snow', 'code-live-snow' ), __( 'Code Live Snow', 'code-live-snow' ), 'manage_options', 'code-live-snow', 'code_live_snow_settings_page' ); } add_action( 'admin_menu', 'code_live_snow_admin_menu' ); /** * Register the option. */ function code_live_snow_register_settings() { register_setting( 'code_live_snow_group', 'code_live_snow_options', array( 'type' => 'array', 'sanitize_callback' => 'code_live_snow_sanitize', 'default' => code_live_snow_defaults(), ) ); } add_action( 'admin_init', 'code_live_snow_register_settings' ); /** * Enqueue admin assets only on our settings page. * * @param string $hook Current admin page hook. */ function code_live_snow_admin_assets( $hook ) { if ( 'settings_page_code-live-snow' !== $hook ) { return; } wp_enqueue_media(); wp_enqueue_style( 'code-live-snow-admin', CODE_LIVE_SNOW_URL . 'assets/css/admin.css', array(), CODE_LIVE_SNOW_VERSION ); // Previews of the bundled styles, shown as CSS backgrounds. $css = ''; foreach ( array_keys( code_live_snow_styles() ) as $slug ) { $url = code_live_snow_style_url( $slug ); if ( $url ) { $css .= sprintf( '.cls-style-preview--%s{background-image:url("%s")}', sanitize_html_class( $slug ), esc_url( $url ) ); } } wp_add_inline_style( 'code-live-snow-admin', $css ); wp_enqueue_script( 'code-live-snow-admin', CODE_LIVE_SNOW_URL . 'assets/js/admin.js', array( 'jquery' ), CODE_LIVE_SNOW_VERSION, true ); wp_localize_script( 'code-live-snow-admin', 'codeLiveSnowAdmin', array( 'title' => __( 'Select Snow Images', 'code-live-snow' ), 'button' => __( 'Use these images', 'code-live-snow' ), 'remove' => __( 'Remove image', 'code-live-snow' ), 'name' => 'code_live_snow_options[images][]', 'max' => 20, ) ); } add_action( 'admin_enqueue_scripts', 'code_live_snow_admin_assets' ); /** * Add a "Settings" link on the Plugins screen. * * @param array $links Existing links. * @return array */ function code_live_snow_action_links( $links ) { $url = admin_url( 'options-general.php?page=code-live-snow' ); array_unshift( $links, '' . esc_html__( 'Settings', 'code-live-snow' ) . '' ); return $links; } add_filter( 'plugin_action_links_' . plugin_basename( __FILE__ ), 'code_live_snow_action_links' ); /** * Render the settings page. */ function code_live_snow_settings_page() { if ( ! current_user_can( 'manage_options' ) ) { return; } $o = code_live_snow_get_options(); ?>

$o['end_date'] ) { return false; } if ( '' !== $o['pages'] ) { $allowed = array_map( 'absint', explode( ',', $o['pages'] ) ); if ( ! is_singular() || ! in_array( (int) get_queried_object_id(), $allowed, true ) ) { return false; } } return true; } /** * Build the list of flake sources for the front end. * An empty string means "draw a soft dot on the canvas". * * @param array $o Options. * @return array */ function code_live_snow_sources( $o ) { $sources = array(); foreach ( $o['styles'] as $slug ) { $sources[] = esc_url_raw( code_live_snow_style_url( $slug ) ); } foreach ( $o['images'] as $image_id ) { $url = wp_get_attachment_image_url( $image_id, 'thumbnail' ); if ( $url ) { $sources[] = esc_url_raw( $url ); } } if ( empty( $sources ) ) { $sources[] = ''; } return array_values( array_unique( $sources ) ); } /** * Enqueue front-end script and styles. */ function code_live_snow_frontend_assets() { if ( ! code_live_snow_should_display() ) { return; } $o = code_live_snow_get_options(); wp_register_style( 'code-live-snow', false, array(), CODE_LIVE_SNOW_VERSION ); wp_enqueue_style( 'code-live-snow' ); wp_add_inline_style( 'code-live-snow', sprintf( '#code-live-snow{position:fixed;top:0;left:0;width:100%%;height:100%%;pointer-events:none;z-index:%d}', absint( $o['zindex'] ) ) ); wp_enqueue_script( 'code-live-snow', CODE_LIVE_SNOW_URL . 'assets/js/snow.js', array(), CODE_LIVE_SNOW_VERSION, true ); wp_localize_script( 'code-live-snow', 'codeLiveSnow', array( 'desktopCount' => absint( $o['desktop_count'] ), 'mobileCount' => absint( $o['mobile_count'] ), 'speed' => (float) $o['speed'], 'direction' => $o['direction'], 'minSize' => absint( $o['min_size'] ), 'maxSize' => absint( $o['max_size'] ), 'sources' => code_live_snow_sources( $o ), ) ); } add_action( 'wp_enqueue_scripts', 'code_live_snow_frontend_assets' );