$value ) { if ( is_string( $key ) && false !== stripos( $key, 'tenant' ) && is_scalar( $value ) && '' !== trim( (string) $value ) ) { return 'connected'; } } return 'no_tenant'; } /** * Pre-filled mailto link to request Commerce7 integration help from 7Labs. * * @return string */ public static function integration_help_mailto() { $subject = __( 'Commerce7 integration help', '7labs-debug-branding-for-commerce7' ); $body = sprintf( /* translators: %s: site URL */ __( "Hi 7Labs,\n\nI'd like help connecting Commerce7 on my WordPress site:\n%s\n\nWhat I need help with:\n- ", '7labs-debug-branding-for-commerce7' ), home_url( '/' ) ); return 'mailto:7labs@dmitru.com?subject=' . rawurlencode( $subject ) . '&body=' . rawurlencode( $body ); } /** * Sanitize a URL path slug (no leading/trailing slashes). * * @param string $slug Raw slug. * @return string */ public static function sanitize_slug( $slug ) { $slug = trim( (string) $slug ); $slug = trim( $slug, '/' ); $slug = preg_replace( '#/+#', '/', $slug ); return sanitize_text_field( $slug ); } /** * Normalize a Commerce7 collection slug (not a full URL or admin page). * * @param string $slug Raw slug or path like collection/wines. * @return string Empty when invalid. */ public static function normalize_collection_slug( $slug ) { $slug = self::sanitize_slug( $slug ); if ( '' === $slug ) { return ''; } if ( false !== strpos( $slug, '/' ) ) { $parts = explode( '/', $slug ); $slug = self::sanitize_slug( end( $parts ) ); } // Reject plugin admin slugs accidentally saved as collection slugs. if ( '' === $slug || preg_match( '/^c7dbt(-|$)/', $slug ) ) { return ''; } return $slug; } /** * Best collection slug for shop routes (saved value, C7WP, then wines). * * @return string */ public static function collection_slug() { $stored = self::normalize_collection_slug( C7DBT_Settings::get()['default_collection_slug'] ); if ( $stored ) { return $stored; } $c7 = self::get_c7wp_settings(); foreach ( array( 'c7wp_continue_shopping', 'continue_shopping' ) as $key ) { if ( empty( $c7[ $key ] ) ) { continue; } $from_c7 = self::normalize_collection_slug( $c7[ $key ] ); if ( $from_c7 ) { return $from_c7; } } return 'wines'; } /** * Build absolute front-end URL from path. * * @param string $path Path like collection/wines. * @return string */ public static function home_path_url( $path ) { $path = ltrim( self::sanitize_slug( $path ), '/' ); return trailingslashit( home_url( '/' . $path ) ); } /** * Relative luminance for contrast checks (0–1). * * @param string $hex Hex color. * @return float|null */ public static function relative_luminance( $hex ) { $rgb = self::hex_to_rgb( $hex ); if ( ! $rgb ) { return null; } $channels = array_map( static function ( $c ) { $c = $c / 255; return ( $c <= 0.03928 ) ? ( $c / 12.92 ) : pow( ( $c + 0.055 ) / 1.055, 2.4 ); }, $rgb ); return ( 0.2126 * $channels[0] ) + ( 0.7152 * $channels[1] ) + ( 0.0722 * $channels[2] ); } /** * WCAG contrast ratio between two hex colors. * * @param string $hex1 First color. * @param string $hex2 Second color. * @return float|null */ public static function contrast_ratio( $hex1, $hex2 ) { $l1 = self::relative_luminance( $hex1 ); $l2 = self::relative_luminance( $hex2 ); if ( null === $l1 || null === $l2 ) { return null; } $lighter = max( $l1, $l2 ); $darker = min( $l1, $l2 ); return ( $lighter + 0.05 ) / ( $darker + 0.05 ); } /** * Hex to RGB. * * @param string $hex Hex color. * @return array{0:int,1:int,2:int}|null */ public static function hex_to_rgb( $hex ) { $hex = ltrim( (string) $hex, '#' ); if ( 3 === strlen( $hex ) ) { $hex = $hex[0] . $hex[0] . $hex[1] . $hex[1] . $hex[2] . $hex[2]; } if ( ! preg_match( '/^[a-fA-F0-9]{6}$/', $hex ) ) { return null; } return array( hexdec( substr( $hex, 0, 2 ) ), hexdec( substr( $hex, 2, 2 ) ), hexdec( substr( $hex, 4, 2 ) ), ); } /** * Normalize hex color to #rrggbb or empty. * * @param string $color Color. * @return string */ public static function sanitize_hex_color( $color ) { $color = trim( (string) $color ); if ( '' === $color ) { return ''; } $sanitized = sanitize_hex_color( $color ); return $sanitized ? $sanitized : ''; } /** * Colors are considered matching within tolerance. * * @param string $a Hex a. * @param string $b Hex b. * @return bool */ public static function colors_match( $a, $b ) { $a = strtolower( ltrim( (string) $a, '#' ) ); $b = strtolower( ltrim( (string) $b, '#' ) ); if ( 3 === strlen( $a ) ) { $a = $a[0] . $a[0] . $a[1] . $a[1] . $a[2] . $a[2]; } if ( 3 === strlen( $b ) ) { $b = $b[0] . $b[0] . $b[1] . $b[1] . $b[2] . $b[2]; } return $a && $b && $a === $b; } /** * Display name for service CTAs (always 7Labs-owned). * * @return string */ public static function company_label() { return C7DBT_Marketing::COMPANY; } /** * Primary booking / contact URL — falls back to 7Labs. * * @param string $campaign UTM campaign. * @return string */ public static function primary_cta_url( $campaign = 'sidebar' ) { return C7DBT_Marketing::booking_url( $campaign ); } /** * Whether monetization CTAs are available (always true for 7Labs-owned plugin). * * @return bool */ public static function services_configured() { return true; } /** * Build package CTA URL (booking link or mailto fallback). * * @param string $package_slug Package slug. * @param string $title Package title for email subject. * @return string */ public static function package_cta_url( $package_slug, $title ) { unset( $package_slug, $title ); return C7DBT_Marketing::booking_url( 'package' ); } /** * Human label for dashboard / wizard status pills. * * @param string $status Raw status code. * @return string */ public static function status_label( $status ) { switch ( $status ) { case 'error': return __( 'Needs work', '7labs-debug-branding-for-commerce7' ); case 'warn': return __( 'Needs attention', '7labs-debug-branding-for-commerce7' ); default: return __( 'Looks good', '7labs-debug-branding-for-commerce7' ); } } /** * Service packages for soft CTAs. * * @return array */ public static function service_packages() { $packages = array( array( 'id' => 'debug_fix', 'type' => 'project', 'slug' => 'debug-fix', 'title' => __( 'Commerce7 Debug & Bug Fix', '7labs-debug-branding-for-commerce7' ), 'best_for' => __( 'Broken shop, checkout, or widgets after Quick Fix', '7labs-debug-branding-for-commerce7' ), 'price' => __( 'Contact for pricing', '7labs-debug-branding-for-commerce7' ), 'description' => __( 'Hands-on troubleshooting when the shop, checkout, or widgets still misbehave after Quick Fix.', '7labs-debug-branding-for-commerce7' ), 'includes' => array( __( 'Shop URL, redirect & menu link audit', '7labs-debug-branding-for-commerce7' ), __( 'Checkout, cart & widget conflict fixes', '7labs-debug-branding-for-commerce7' ), __( 'Security audit & malware cleanup if your site was compromised', '7labs-debug-branding-for-commerce7' ), __( 'Written summary of what broke and what we changed', '7labs-debug-branding-for-commerce7' ), __( 'One revision round after delivery', '7labs-debug-branding-for-commerce7' ), ), ), array( 'id' => 'native_storefront', 'type' => 'project', 'slug' => 'native-storefront', 'title' => __( 'Native Storefront Build', '7labs-debug-branding-for-commerce7' ), 'best_for' => __( 'A lean, theme-native shop you do not want to DIY', '7labs-debug-branding-for-commerce7' ), 'price' => __( 'Contact for pricing', '7labs-debug-branding-for-commerce7' ), 'description' => __( 'A theme-native Commerce7 shop — lean markup, your brand, fewer plugins to babysit.', '7labs-debug-branding-for-commerce7' ), 'includes' => array( __( 'Routes, redirects & continue-shopping setup', '7labs-debug-branding-for-commerce7' ), __( 'Brand Kit polish matched to your theme', '7labs-debug-branding-for-commerce7' ), __( 'Shop layout & PDP spacing tuned for your builder', '7labs-debug-branding-for-commerce7' ), __( 'Pre-launch QA before you go live', '7labs-debug-branding-for-commerce7' ), ), ), array( 'id' => 'monthly_care', 'type' => 'care', 'slug' => 'monthly-care', 'title' => __( 'C7 Shop Care', '7labs-debug-branding-for-commerce7' ), 'price' => __( 'Contact for pricing', '7labs-debug-branding-for-commerce7' ), 'description' => __( 'Ongoing Commerce7 shop care on your existing host — before club drops, harvest campaigns, and seasonal pushes.', '7labs-debug-branding-for-commerce7' ), 'best_for' => __( 'Monthly peace of mind for routes, QA, security, and campaign readiness', '7labs-debug-branding-for-commerce7' ), 'includes' => array( __( 'Monthly redirect & broken-link checks', '7labs-debug-branding-for-commerce7' ), __( 'Security monitoring, malware scan & hardening review', '7labs-debug-branding-for-commerce7' ), __( 'Seasonal brand & layout tweaks', '7labs-debug-branding-for-commerce7' ), __( 'Pre-campaign Site QA pass', '7labs-debug-branding-for-commerce7' ), __( 'Priority email when something looks off', '7labs-debug-branding-for-commerce7' ), ), ), ); foreach ( $packages as &$package ) { $package['url'] = self::package_cta_url( $package['slug'], $package['title'] ); } unset( $package ); return $packages; } /** * Pick one recommended package id from a health report. * * @param array $health Health report from C7DBT_Health::report(). * @return string Package id. */ public static function recommend_package_id( array $health ) { $cards = isset( $health['cards'] ) && is_array( $health['cards'] ) ? $health['cards'] : array(); $status = static function( $key ) use ( $cards ) { return isset( $cards[ $key ]['status'] ) ? $cards[ $key ]['status'] : 'ok'; }; $is_bad = static function( $s ) { return 'error' === $s || 'warn' === $s; }; $debug_keys = array( 'shop', 'site' ); foreach ( $debug_keys as $k ) { if ( $is_bad( $status( $k ) ) ) { return 'debug_fix'; } } if ( $is_bad( $status( 'launch' ) ) ) { return 'monthly_care'; } if ( isset( $health['overall'] ) && 'ok' === $health['overall'] ) { return 'native_storefront'; } if ( $is_bad( $status( 'design' ) ) ) { return 'native_storefront'; } return 'debug_fix'; } /** * Contextual dashboard nudge when health score is below target. * * @param array $health Health report from C7DBT_Health::report(). * @return array|null Keys: title, text, package, card_label. */ public static function dashboard_health_nudge( array $health ) { if ( ! isset( $health['score'] ) || (int) $health['score'] >= 85 ) { return null; } $cards = isset( $health['cards'] ) && is_array( $health['cards'] ) ? $health['cards'] : array(); $priority = array( 'error' => 3, 'warn' => 2, 'ok' => 1, ); $focus_key = null; $focus_status = 'ok'; $focus_label = ''; foreach ( $cards as $key => $card ) { $status = isset( $card['status'] ) ? $card['status'] : 'ok'; if ( ! isset( $priority[ $status ] ) || $priority[ $status ] < 2 ) { continue; } if ( $priority[ $status ] > $priority[ $focus_status ] ) { $focus_status = $status; $focus_key = $key; $focus_label = isset( $card['label'] ) ? (string) $card['label'] : ''; } } if ( null === $focus_key ) { return null; } $package_id = self::recommend_package_id( $health ); $package = self::package_by_id( $package_id ); if ( ! $package ) { return null; } $copy = array( 'shop' => array( 'title' => __( 'Shop & Redirects still need attention', '7labs-debug-branding-for-commerce7' ), 'text' => __( 'Quick Fix covers slug, redirect, and menus. If this card stays yellow or red, Debug & Bug Fix is a hands-on pass for routes and live shop URLs.', '7labs-debug-branding-for-commerce7' ), ), 'site' => array( 'title' => __( 'Menus & Conflicts flagged', '7labs-debug-branding-for-commerce7' ), 'text' => __( 'Elementor on checkout, cache plugins, and multilingual setups often need a developer. Debug & Bug Fix targets integration conflicts the free scanner found.', '7labs-debug-branding-for-commerce7' ), ), 'design' => array( 'title' => __( 'Design still mismatched', '7labs-debug-branding-for-commerce7' ), 'text' => __( 'Brand Kit is free to DIY. Native Storefront Build is for a theme-native shop with fewer moving parts if you would rather not tune Elementor yourself.', '7labs-debug-branding-for-commerce7' ), ), 'launch' => array( 'title' => __( 'Not launch-ready yet', '7labs-debug-branding-for-commerce7' ), 'text' => __( 'Launch & QA or security checks are still open. C7 Shop Care includes pre-campaign Site QA, monitoring, and seasonal fixes on your existing host.', '7labs-debug-branding-for-commerce7' ), ), ); $blob = isset( $copy[ $focus_key ] ) ? $copy[ $focus_key ] : array( 'title' => __( 'Optional hands-on help', '7labs-debug-branding-for-commerce7' ), 'text' => __( 'Every tool here stays free. Packages below are optional if you would rather not DIY the remaining gaps.', '7labs-debug-branding-for-commerce7' ), ); return array( 'title' => $blob['title'], 'text' => $blob['text'], 'package' => $package, 'card_label' => $focus_label, 'focus_key' => $focus_key, ); } /** * Find one package by id. * * @param string $id Package id. * @return array|null */ public static function package_by_id( $id ) { foreach ( self::service_packages() as $package ) { if ( isset( $package['id'] ) && $package['id'] === $id ) { return $package; } } return null; } /** * Contextual soft-sell nudge for a plugin admin screen. * * @param string $page Current page slug (e.g. c7dbt-brand). * @return array|null Keys: title, text, package_id, secondary_label, secondary_url. */ public static function screen_nudge( $page ) { $page = sanitize_key( $page ); $map = array( 'c7dbt-routes' => 'debug_fix', 'c7dbt-menus' => 'debug_fix', 'c7dbt-design' => 'native_storefront', 'c7dbt-prelaunch' => 'monthly_care', ); if ( empty( $map[ $page ] ) ) { return null; } $package = self::package_by_id( $map[ $page ] ); if ( ! $package ) { return null; } $copy = array( 'debug_fix' => array( 'title' => __( 'Something still broken in the shop?', '7labs-debug-branding-for-commerce7' ), 'text' => __( 'Commerce7 Debug & Bug Fix is a hands-on pass for routes, checkout, menus, and widgets — optional while these free tools stay available.', '7labs-debug-branding-for-commerce7' ), ), 'native_storefront' => array( 'title' => __( 'Want a leaner long-term build?', '7labs-debug-branding-for-commerce7' ), 'text' => __( 'Native Storefront Build puts Commerce7 in the theme with fewer moving parts. Brand Kit here stays free to DIY.', '7labs-debug-branding-for-commerce7' ), ), 'monthly_care' => array( 'title' => __( 'Shipping a club drop or campaign soon?', '7labs-debug-branding-for-commerce7' ), 'text' => __( 'C7 Shop Care adds pre-launch QA, security monitoring, and seasonal brand tweaks before campaigns.', '7labs-debug-branding-for-commerce7' ), ), ); $blob = isset( $copy[ $package['id'] ] ) ? $copy[ $package['id'] ] : array( 'title' => $package['title'], 'text' => $package['description'], ); return array( 'title' => $blob['title'], 'text' => $blob['text'], 'package' => $package, 'secondary_label' => __( 'See all packages', '7labs-debug-branding-for-commerce7' ), 'secondary_url' => admin_url( 'admin.php?page=c7dbt-help' ), ); } /** * Guide URL from settings (falls back to 7Labs). * * @return string */ public static function guide_url() { return C7DBT_Marketing::guide_url(); } /** * Partner URL — unused for white-label; kept for optional 7Labs landing. * * @return string */ public static function partner_url() { return ''; } /** * Plain-text report for Menus & Conflicts screen. * * @param array $conflicts Conflict issues. * @param array $menu_issues Menu scanner issues. * @param array $content_issues Content scanner issues. * @return string */ public static function site_issues_report_text( array $conflicts, array $menu_issues, array $content_issues ) { $lines = array(); $lines[] = 'Commerce7 site issues report'; $lines[] = 'Site: ' . home_url( '/' ); $lines[] = 'Plugin: Commerce7 Debug & Branding Tools v' . C7DBT_VERSION; $lines[] = ''; $lines[] = '=== Integration conflicts (' . count( $conflicts ) . ') ==='; if ( empty( $conflicts ) ) { $lines[] = 'None detected.'; } else { foreach ( $conflicts as $issue ) { $dev = ! empty( $issue['needs_dev'] ) ? ' [usually needs developer]' : ''; $lines[] = '- ' . ( isset( $issue['message'] ) ? $issue['message'] : '' ) . $dev; if ( ! empty( $issue['fix'] ) ) { $lines[] = ' Fix: ' . $issue['fix']; } } } $lines[] = ''; $lines[] = '=== Menu links (' . count( $menu_issues ) . ') ==='; if ( empty( $menu_issues ) ) { $lines[] = 'No broken shop menu links.'; } else { foreach ( $menu_issues as $issue ) { $lines[] = sprintf( '- %s → %s in menu "%s" (suggested: %s)', isset( $issue['title'] ) ? $issue['title'] : '', isset( $issue['url'] ) ? $issue['url'] : '', isset( $issue['menu_name'] ) ? $issue['menu_name'] : '', isset( $issue['suggested'] ) ? $issue['suggested'] : '' ); } } $lines[] = ''; $lines[] = '=== Content pages (' . count( $content_issues ) . ') ==='; if ( empty( $content_issues ) ) { $lines[] = 'No empty shop links in scanned content.'; } else { foreach ( $content_issues as $row ) { $urls = isset( $row['urls'] ) && is_array( $row['urls'] ) ? implode( ', ', $row['urls'] ) : ''; $lines[] = '- ' . ( isset( $row['title'] ) ? $row['title'] : '' ) . ' (' . ( isset( $row['post_type'] ) ? $row['post_type'] : '' ) . '): ' . $urls; } } $lines[] = ''; $lines[] = '---'; $lines[] = 'Need help fixing this? 7Labs offers Commerce7 Debug & Bug Fix — contact for pricing.'; $lines[] = home_url( '/' ); return implode( "\n", $lines ); } /** * Plain-text report for Launch & QA screen. * * @param array $prelaunch Pre-launch checklist report. * @param array|null $qa_report Site QA last report. * @param array|null $security Security checklist report. * @return string */ public static function launch_qa_report_text( array $prelaunch, $qa_report, $security = null ) { $lines = array(); $lines[] = 'Commerce7 launch & QA report'; $lines[] = 'Site: ' . home_url( '/' ); $lines[] = 'Plugin: Commerce7 Debug & Branding Tools v' . C7DBT_VERSION; $lines[] = ''; $lines[] = '=== Pre-launch checklist ==='; $lines[] = sprintf( 'Passed: %d · Failed: %d · Ready: %s', isset( $prelaunch['passed'] ) ? (int) $prelaunch['passed'] : 0, isset( $prelaunch['failed'] ) ? (int) $prelaunch['failed'] : 0, ! empty( $prelaunch['ready'] ) ? 'yes' : 'no' ); if ( ! empty( $prelaunch['items'] ) && is_array( $prelaunch['items'] ) ) { foreach ( $prelaunch['items'] as $item ) { $mark = ! empty( $item['pass'] ) ? '[OK]' : '[FAIL]'; $opt = ! empty( $item['optional'] ) ? ' (optional)' : ''; $lines[] = $mark . ' ' . ( isset( $item['label'] ) ? $item['label'] : '' ) . $opt; if ( empty( $item['pass'] ) && ! empty( $item['fix'] ) ) { $lines[] = ' ' . $item['fix']; } } } $lines[] = ''; $lines[] = '=== Live Site QA ==='; if ( empty( $qa_report ) || ! is_array( $qa_report ) ) { $lines[] = 'Not run yet.'; } else { $lines[] = sprintf( 'Passed: %d · Failed: %d · Last run: %s', isset( $qa_report['passed'] ) ? (int) $qa_report['passed'] : 0, isset( $qa_report['failed'] ) ? (int) $qa_report['failed'] : 0, isset( $qa_report['ran_at'] ) ? $qa_report['ran_at'] : '' ); if ( ! empty( $qa_report['items'] ) && is_array( $qa_report['items'] ) ) { foreach ( $qa_report['items'] as $item ) { $status = isset( $item['status'] ) ? $item['status'] : ''; if ( 'skipped' === $status ) { $mark = '[SKIP]'; } else { $mark = ! empty( $item['pass'] ) ? '[OK]' : '[FAIL]'; } $lines[] = $mark . ' ' . ( isset( $item['label'] ) ? $item['label'] : '' ); if ( ! empty( $item['detail'] ) ) { $lines[] = ' ' . $item['detail']; } } } } $lines[] = ''; $lines[] = '=== Security basics ==='; if ( empty( $security ) || ! is_array( $security ) ) { $lines[] = 'Not run yet.'; } else { $lines[] = sprintf( 'Passed: %d · Failed: %d · Ready: %s', isset( $security['passed'] ) ? (int) $security['passed'] : 0, isset( $security['failed'] ) ? (int) $security['failed'] : 0, ! empty( $security['ready'] ) ? 'yes' : 'no' ); if ( ! empty( $security['items'] ) && is_array( $security['items'] ) ) { foreach ( $security['items'] as $item ) { $mark = ! empty( $item['pass'] ) ? '[OK]' : '[FAIL]'; $opt = ! empty( $item['optional'] ) ? ' (optional)' : ''; $dev = ! empty( $item['needs_dev'] ) ? ' (usually needs developer)' : ''; $lines[] = $mark . ' ' . ( isset( $item['label'] ) ? $item['label'] : '' ) . $opt . $dev; if ( empty( $item['pass'] ) && ! empty( $item['fix'] ) ) { $lines[] = ' ' . $item['fix']; } } } } $lines[] = ''; $lines[] = '---'; $lines[] = 'Forwarding to an agency? 7Labs can run a pre-launch pass or C7 Shop Care (security monitoring included) — contact for pricing.'; $lines[] = home_url( '/' ); return implode( "\n", $lines ); } /** * mailto: link for sharing a report with 7Labs or an agency. * * @param string $subject Email subject. * @param string $body Plain-text body. * @return string */ public static function agency_report_mailto( $subject, $body ) { $settings = C7DBT_Settings::get(); $email = '7labs@dmitru.com'; if ( ! $email ) { $pkg = self::package_by_id( 'debug_fix' ); if ( $pkg && ! empty( $pkg['url'] ) && 0 === strpos( $pkg['url'], 'mailto:' ) ) { return $pkg['url']; } return ''; } return 'mailto:' . $email . '?subject=' . rawurlencode( $subject ) . '&body=' . rawurlencode( $body ); } }