'', 'mail' => '', 'bell' => '', 'send' => '', 'shield' => '', 'database' => '', 'filter' => '', 'search' => '', 'chevron' => '', 'chart' => '', 'bolt' => '', 'target' => '', 'grid' => '', 'wand' => '', 'clock' => '', 'sliders' => '', 'refresh' => '', 'plus' => '', 'code' => '', 'sparkle' => '', 'desktop' => '', 'tablet' => '', 'mobile' => '', ); $path = isset( $paths[ $name ] ) ? $paths[ $name ] : $paths['layers']; return ''; } /** * A pastel icon tile. * * @param string $name Icon name. * @param string $tone Colour tone. * @param bool $large Large variant. * @return string */ public static function tile( $name, $tone = 'amber', $large = false ) { return sprintf( '%s', $large ? ' srpp-tile-lg' : '', esc_attr( $tone ), self::icon( $name ) ); } /** * Initial-based avatar, coloured deterministically from the label. * * @param string $text Label. * @return string */ public static function avatar( $text ) { $words = preg_split( '/\s+/', trim( wp_strip_all_tags( $text ) ) ); $initials = ''; foreach ( (array) $words as $word ) { if ( '' === $word ) { continue; } $initials .= function_exists( 'mb_substr' ) ? mb_substr( $word, 0, 1 ) : substr( $word, 0, 1 ); if ( strlen( $initials ) >= 2 ) { break; } } $initials = $initials ? strtoupper( $initials ) : 'RC'; $tones = array( 'teal', 'coral', 'amber', 'violet', 'blue', 'pink' ); $tone = $tones[ abs( crc32( $text ) ) % count( $tones ) ]; return sprintf( '', esc_attr( $tone ), esc_html( $initials ) ); } /** * The three-up explainer strip. * * @param array $items Each: icon, tone, title, text. */ public static function features( $items ) { echo '
'; foreach ( $items as $item ) { printf( '
%s

%s

%s

', self::tile( $item['icon'], $item['tone'], true ), // phpcs:ignore WordPress.Security.EscapeOutput esc_html( $item['title'] ), esc_html( $item['text'] ) ); } echo '
'; } /** * The cream footer band. * * @param string $title Title. * @param string $text Copy. * @param string $action Button markup. * @param string $icon Icon name. * @param string $tone Tone. */ public static function footnote( $title, $text, $action = '', $icon = 'mail', $tone = 'amber' ) { printf( '
%s
%s

%s

%s
', self::tile( $icon, $tone, true ), // phpcs:ignore WordPress.Security.EscapeOutput esc_html( $title ), esc_html( $text ), $action ? '
' . wp_kses_post( $action ) . '
' : '' ); } /** * Capability check helper. * * @return bool */ public static function can() { return current_user_can( self::CAP ); } /** * Register menus. */ public function menu() { $this->screens['main'] = add_menu_page( __( 'Related Content', 'advanced-css3-related-posts-widget' ), __( 'Related Content', 'advanced-css3-related-posts-widget' ), self::CAP, 'srpp-displays', array( $this, 'page_displays' ), 'dashicons-screenoptions', 58 ); add_submenu_page( 'srpp-displays', __( 'Displays', 'advanced-css3-related-posts-widget' ), __( 'Displays', 'advanced-css3-related-posts-widget' ), self::CAP, 'srpp-displays', array( $this, 'page_displays' ) ); $this->screens['edit'] = add_submenu_page( 'srpp-displays', __( 'Add Display', 'advanced-css3-related-posts-widget' ), __( 'Add Display', 'advanced-css3-related-posts-widget' ), self::CAP, 'srpp-edit', array( $this, 'page_edit' ) ); $this->screens['templates'] = add_submenu_page( 'srpp-displays', __( 'Templates', 'advanced-css3-related-posts-widget' ), __( 'Templates', 'advanced-css3-related-posts-widget' ), self::CAP, 'srpp-templates', array( $this, 'page_templates' ) ); $this->screens['analytics'] = add_submenu_page( 'srpp-displays', __( 'Analytics', 'advanced-css3-related-posts-widget' ), __( 'Analytics', 'advanced-css3-related-posts-widget' ), self::CAP, 'srpp-analytics', array( $this, 'page_analytics' ) ); $this->screens['settings'] = add_submenu_page( 'srpp-displays', __( 'Settings', 'advanced-css3-related-posts-widget' ), __( 'Settings', 'advanced-css3-related-posts-widget' ), self::CAP, 'srpp-settings', array( 'SRPP_Settings', 'render' ) ); $this->screens['tools'] = add_submenu_page( 'srpp-displays', __( 'Tools', 'advanced-css3-related-posts-widget' ), __( 'Tools', 'advanced-css3-related-posts-widget' ), self::CAP, 'srpp-tools', array( 'SRPP_Tools', 'render' ) ); } /** * Is the current screen one of ours? * * @return bool */ public static function is_plugin_screen() { if ( ! isset( $_GET['page'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification return false; } return 0 === strpos( sanitize_key( wp_unslash( $_GET['page'] ) ), 'srpp-' ); // phpcs:ignore WordPress.Security.NonceVerification } /** * Admin assets. * * @param string $hook Hook suffix. */ public function assets( $hook ) { $is_editor_screen = in_array( $hook, array( 'post.php', 'post-new.php' ), true ); if ( ! self::is_plugin_screen() && ! $is_editor_screen ) { return; } wp_enqueue_style( 'srpp-admin', SRPP_URL . 'admin/css/admin.css', array( 'wp-color-picker' ), SRPP_VERSION ); wp_enqueue_style( 'srpp', SRPP_URL . 'public/css/srpp.css', array(), SRPP_VERSION ); wp_enqueue_script( 'srpp-admin', SRPP_URL . 'admin/js/admin.js', array( 'jquery', 'wp-color-picker', 'jquery-ui-sortable' ), SRPP_VERSION, true ); wp_localize_script( 'srpp-admin', 'SRPP_ADMIN', array( 'ajax' => admin_url( 'admin-ajax.php' ), 'nonce' => wp_create_nonce( 'srpp_admin' ), 'i18n' => array( 'search' => __( 'Type to search…', 'advanced-css3-related-posts-widget' ), 'noResults' => __( 'Nothing found.', 'advanced-css3-related-posts-widget' ), 'remove' => __( 'Remove', 'advanced-css3-related-posts-widget' ), 'confirm' => __( 'Delete this display? This cannot be undone.', 'advanced-css3-related-posts-widget' ), 'loading' => __( 'Rendering preview…', 'advanced-css3-related-posts-widget' ), ), 'templates' => SRPP_Templates::all(), ) ); // The live preview can render the carousel template. wp_enqueue_script( 'srpp-carousel', SRPP_URL . 'public/js/carousel.js', array(), SRPP_VERSION, true ); } /** * Handle row actions (delete / duplicate / toggle). */ public function handle_actions() { if ( ! self::can() || ! isset( $_GET['srpp_action'], $_GET['display'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification return; } $action = sanitize_key( wp_unslash( $_GET['srpp_action'] ) ); // phpcs:ignore WordPress.Security.NonceVerification $id = absint( wp_unslash( $_GET['display'] ) ); // phpcs:ignore WordPress.Security.NonceVerification $nonce = isset( $_GET['_wpnonce'] ) ? sanitize_text_field( wp_unslash( $_GET['_wpnonce'] ) ) : ''; if ( ! wp_verify_nonce( $nonce, 'srpp_row_' . $id ) ) { return; } $message = ''; switch ( $action ) { case 'delete': SRPP_Display::delete( $id ); $message = 'deleted'; break; case 'duplicate': SRPP_Display::duplicate( $id ); $message = 'duplicated'; break; case 'toggle': $display = SRPP_Display::get( $id ); if ( $display ) { SRPP_Display::save( $id, array( 'title' => $display->title, 'status' => $display->is_active() ? 'draft' : 'publish', ) ); } $message = 'toggled'; break; } wp_safe_redirect( add_query_arg( array( 'page' => 'srpp-displays', 'srpp_msg' => $message ), admin_url( 'admin.php' ) ) ); exit; } /** * Admin notices. */ public function notices() { if ( ! self::is_plugin_screen() || ! isset( $_GET['srpp_msg'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification return; } $messages = array( 'saved' => __( 'Display saved.', 'advanced-css3-related-posts-widget' ), 'deleted' => __( 'Display deleted.', 'advanced-css3-related-posts-widget' ), 'duplicated' => __( 'Display duplicated.', 'advanced-css3-related-posts-widget' ), 'toggled' => __( 'Status updated.', 'advanced-css3-related-posts-widget' ), 'settings' => __( 'Settings saved.', 'advanced-css3-related-posts-widget' ), 'cache' => __( 'Recommendation cache cleared.', 'advanced-css3-related-posts-widget' ), 'stats' => __( 'Statistics reset.', 'advanced-css3-related-posts-widget' ), 'imported' => __( 'Displays imported.', 'advanced-css3-related-posts-widget' ), ); $key = sanitize_key( wp_unslash( $_GET['srpp_msg'] ) ); // phpcs:ignore WordPress.Security.NonceVerification if ( isset( $messages[ $key ] ) ) { printf( '

%s

', esc_html( $messages[ $key ] ) ); } } /** * Shared page header — the warm hero band. * * @param string $title Title. * @param string $subtitle Subtitle. * @param string $action Optional button HTML. * @param array $args icon, highlights. */ public static function header( $title, $subtitle = '', $action = '', $args = array() ) { $args = wp_parse_args( $args, array( 'icon' => 'layers', 'highlights' => array(), ) ); ?>

render(); } /** * Display editor screen. */ public function page_edit() { if ( ! self::can() ) { return; } $editor = new SRPP_Editor(); $editor->render(); } /** * Template gallery screen. */ public function page_templates() { if ( ! self::can() ) { return; } echo '
'; self::header( __( 'Template library', 'advanced-css3-related-posts-widget' ), __( 'Layout controls the structure, the template controls the visual DNA. Mix them freely.', 'advanced-css3-related-posts-widget' ), '', array( 'icon' => 'grid' ) ); echo '
'; } /** * Analytics screen. */ public function page_analytics() { if ( ! self::can() ) { return; } $days = isset( $_GET['days'] ) ? absint( wp_unslash( $_GET['days'] ) ) : 30; // phpcs:ignore WordPress.Security.NonceVerification $days = in_array( $days, array( 7, 30, 90, 365 ), true ) ? $days : 30; $totals = SRPP_Analytics::totals( $days ); $by_tpl = SRPP_Analytics::by( 'template', $days ); $by_plc = SRPP_Analytics::by( 'placement', $days ); $by_display = SRPP_Analytics::by( 'display_id', $days ); $by_target = SRPP_Analytics::by( 'target_id', $days, 10 ); echo '
'; self::header( __( 'Related content performance', 'advanced-css3-related-posts-widget' ), __( 'Which recommendation strategy actually earns clicks.', 'advanced-css3-related-posts-widget' ), '', array( 'icon' => 'chart', 'highlights' => array( array( 'icon' => 'target', 'tone' => 'amber', 'title' => __( 'Per template', 'advanced-css3-related-posts-widget' ), 'text' => __( 'See which visual style pulls readers through.', 'advanced-css3-related-posts-widget' ), ), array( 'icon' => 'bolt', 'tone' => 'teal', 'title' => __( 'Per placement', 'advanced-css3-related-posts-widget' ), 'text' => __( 'Mid-article usually beats the footer. Confirm it.', 'advanced-css3-related-posts-widget' ), ), array( 'icon' => 'shield', 'tone' => 'violet', 'title' => __( 'Privacy-safe', 'advanced-css3-related-posts-widget' ), 'text' => __( 'Daily aggregates only. No cookies, no personal data.', 'advanced-css3-related-posts-widget' ), ), ), ) ); if ( ! SRPP_Analytics::enabled() ) { printf( '

%s

', esc_html__( 'Tracking is currently disabled in Settings, so no new data is being collected.', 'advanced-css3-related-posts-widget' ) ); } echo '

'; foreach ( array( 7, 30, 90, 365 ) as $range ) { printf( '%s', $range === $days ? 'is-active' : '', esc_url( add_query_arg( array( 'page' => 'srpp-analytics', 'days' => $range ), admin_url( 'admin.php' ) ) ), /* translators: %d: number of days */ esc_html( sprintf( _n( 'Last %d day', 'Last %d days', $range, 'advanced-css3-related-posts-widget' ), $range ) ) ); } echo '

'; printf( '
%1$s
%2$s%3$s
%4$s
%5$s%6$s
%7$s
%8$s%9$s%%
', self::tile( 'target', 'blue', true ), // phpcs:ignore WordPress.Security.EscapeOutput esc_html__( 'Impressions', 'advanced-css3-related-posts-widget' ), esc_html( number_format_i18n( $totals['impressions'] ) ), self::tile( 'bolt', 'teal', true ), // phpcs:ignore WordPress.Security.EscapeOutput esc_html__( 'Clicks', 'advanced-css3-related-posts-widget' ), esc_html( number_format_i18n( $totals['clicks'] ) ), self::tile( 'chart', 'amber', true ), // phpcs:ignore WordPress.Security.EscapeOutput esc_html__( 'Click-through rate', 'advanced-css3-related-posts-widget' ), esc_html( number_format_i18n( $totals['ctr'], 2 ) ) ); $this->stats_table( __( 'Per template', 'advanced-css3-related-posts-widget' ), $by_tpl, 'template' ); $this->stats_table( __( 'Per placement', 'advanced-css3-related-posts-widget' ), $by_plc, 'placement' ); $this->stats_table( __( 'Per display', 'advanced-css3-related-posts-widget' ), $by_display, 'display' ); $this->stats_table( __( 'Most clicked recommendations', 'advanced-css3-related-posts-widget' ), $by_target, 'post' ); echo '
'; } /** * Render one analytics table. * * @param string $title Title. * @param array $rows Rows. * @param string $type Label type. */ protected function stats_table( $title, $rows, $type ) { $tones = array( 'template' => array( 'grid', 'violet' ), 'placement' => array( 'target', 'teal' ), 'display' => array( 'layers', 'amber' ), 'post' => array( 'sparkle', 'coral' ), ); $tone = isset( $tones[ $type ] ) ? $tones[ $type ] : array( 'chart', 'amber' ); echo '
'; printf( '
%s

%s

', self::tile( $tone[0], $tone[1] ), // phpcs:ignore WordPress.Security.EscapeOutput esc_html( $title ) ); if ( empty( $rows ) ) { echo '

' . esc_html__( 'No data for this period yet.', 'advanced-css3-related-posts-widget' ) . '

'; return; } echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; foreach ( $rows as $row ) { $label = $row->label; if ( 'template' === $type ) { $template = SRPP_Templates::get( $row->label ); $label = $template['name']; } elseif ( 'display' === $type ) { $display = SRPP_Display::get( $row->label ); $label = $display ? $display->title : sprintf( '#%d', (int) $row->label ); } elseif ( 'post' === $type ) { $label = get_the_title( (int) $row->label ); $label = $label ? $label : sprintf( '#%d', (int) $row->label ); } elseif ( 'placement' === $type ) { $all = array_merge( SRPP_Placement::content_placements(), srpp_is_woo_active() ? SRPP_Woo::placements() : array() ); $label = isset( $all[ $row->label ] ) ? $all[ $row->label ] : $row->label; } printf( '', esc_html( $label ), esc_html( number_format_i18n( $row->impressions ) ), esc_html( number_format_i18n( $row->clicks ) ), esc_html( number_format_i18n( $row->ctr, 2 ) ) ); } echo '
' . esc_html__( 'Item', 'advanced-css3-related-posts-widget' ) . '' . esc_html__( 'Impressions', 'advanced-css3-related-posts-widget' ) . '' . esc_html__( 'Clicks', 'advanced-css3-related-posts-widget' ) . '' . esc_html__( 'CTR', 'advanced-css3-related-posts-widget' ) . '
%s%s%s%s%%
'; } }