'sahcs', 'title' => __('Case Study Sections', 'siteaudithub-case-studies'), 'icon' => null, ]); return $categories; }); /** Style options every section gets in the sidebar (colours, font, spacing). */ function sahcs_block_supports($extra = []) { return array_merge([ 'anchor' => true, 'className' => true, 'html' => false, 'color' => ['background' => true, 'text' => true, 'link' => true], 'typography' => ['fontSize' => true, 'lineHeight' => true, '__experimentalFontFamily' => true, '__experimentalFontWeight' => true], 'spacing' => ['margin' => ['top', 'bottom'], 'padding' => true], ], $extra); } function sahcs_blocks_definitions() { $str = function ($default = '') { return ['type' => 'string', 'default' => $default]; }; $bool = function ($default) { return ['type' => 'boolean', 'default' => $default]; }; $num = function ($default) { return ['type' => 'number', 'default' => $default]; }; $arr = function ($default = []) { return ['type' => 'array', 'default' => $default]; }; return [ 'hero' => [ 'title' => __('Case Study Hero', 'siteaudithub-case-studies'), 'description' => __('Title, short introduction, category and the main image.', 'siteaudithub-case-studies'), 'keywords' => ['header', 'banner', 'intro'], 'attributes' => [ 'intro' => $str(), 'imageId' => $num(0), 'showCategory' => $bool(true), 'showAuthor' => $bool(true), 'layout' => $str('split'), 'buttonText' => $str(), 'buttonUrl' => $str(), 'newTab' => $bool(false), 'nofollow' => $bool(false), ], ], 'glance' => [ 'title' => __('At a Glance', 'siteaudithub-case-studies'), 'description' => __('A quick summary: client, challenge, solution and result. Search engines and AI answers love this.', 'siteaudithub-case-studies'), 'keywords' => ['summary', 'tldr', 'overview'], 'attributes' => [ 'title' => $str(__('At a glance', 'siteaudithub-case-studies')), // small label above the heading 'heading' => $str(), 'intro' => $str(), 'showIcons' => $bool(true), 'iconColors' => $str(''), // '' = as in Settings, or mixed | brand 'items' => $arr([ ['label' => __('Client', 'siteaudithub-case-studies'), 'text' => ''], ['label' => __('Challenge', 'siteaudithub-case-studies'), 'text' => ''], ['label' => __('Solution', 'siteaudithub-case-studies'), 'text' => ''], ['label' => __('Result', 'siteaudithub-case-studies'), 'text' => ''], ]), ], ], 'facts' => [ 'title' => __('Key Facts', 'siteaudithub-case-studies'), 'description' => __('Project details card: industry, services, location, website or any label you like.', 'siteaudithub-case-studies'), 'keywords' => ['details', 'project', 'info'], 'attributes' => [ 'title' => $str(__('Project details', 'siteaudithub-case-studies')), 'items' => $arr([ ['label' => __('Client', 'siteaudithub-case-studies'), 'value' => '', 'url' => ''], ['label' => __('Industry', 'siteaudithub-case-studies'), 'value' => '', 'url' => ''], ['label' => __('Services', 'siteaudithub-case-studies'), 'value' => '', 'url' => ''], ['label' => __('Website', 'siteaudithub-case-studies'), 'value' => '', 'url' => ''], ]), ], ], 'section' => [ 'title' => __('Story Section', 'siteaudithub-case-studies'), 'description' => __('A titled section for your story. Add paragraphs, lists, images or buttons inside.', 'siteaudithub-case-studies'), 'keywords' => ['overview', 'challenges', 'solutions', 'conclusion', 'brief'], 'attributes' => [ 'kind' => $str('custom'), 'heading' => $str(), 'tone' => $str('plain'), ], ], 'results' => [ 'title' => __('Results', 'siteaudithub-case-studies'), 'description' => __('Big result numbers and/or a before-and-after table. Improvement % is calculated for you.', 'siteaudithub-case-studies'), 'keywords' => ['stats', 'metrics', 'numbers', 'before after', 'kpi'], 'attributes' => [ 'title' => $str(__('Results', 'siteaudithub-case-studies')), 'intro' => $str(), 'display' => $str('cards'), 'items' => $arr([ ['label' => '', 'before' => '', 'after' => '', 'prefix' => '', 'suffix' => '', 'better' => 'higher'], ['label' => '', 'before' => '', 'after' => '', 'prefix' => '', 'suffix' => '', 'better' => 'higher'], ['label' => '', 'before' => '', 'after' => '', 'prefix' => '', 'suffix' => '', 'better' => 'higher'], ]), 'note' => $str(), ], ], 'gallery' => [ 'title' => __('Case Study Gallery', 'siteaudithub-case-studies'), 'description' => __('Screenshots or photos in a grid or masonry layout, with a lightbox.', 'siteaudithub-case-studies'), 'keywords' => ['images', 'photos', 'screenshots'], 'attributes' => [ 'title' => $str(), 'images' => $arr(), 'layout' => $str('grid'), 'columns' => $num(3), 'lightbox' => $bool(true), 'captions' => $bool(true), ], ], 'testimonial' => [ 'title' => __('Testimonial', 'siteaudithub-case-studies'), 'description' => __('A client quote with photo, name and role.', 'siteaudithub-case-studies'), 'keywords' => ['quote', 'review', 'client'], 'attributes' => [ 'quote' => $str(), 'name' => $str(), 'role' => $str(), 'imageId' => $num(0), ], ], 'team' => [ 'title' => __('Project Team', 'siteaudithub-case-studies'), 'description' => __('The people who worked on this project, with photo, name, role and profile link.', 'siteaudithub-case-studies'), 'keywords' => ['people', 'members', 'credits', 'staff'], 'attributes' => [ 'eyebrow' => $str(), 'title' => $str(__('Project team', 'siteaudithub-case-studies')), 'highlight' => $bool(true), 'intro' => $str(), 'quote' => $str(), 'layout' => $str('cards'), 'items' => $arr([ ['name' => '', 'role' => '', 'imageId' => 0, 'email' => '', 'link' => ''], ]), ], ], 'cta' => [ 'title' => __('Call to Action', 'siteaudithub-case-studies'), 'description' => __('A closing box with a heading, text and a button. Empty fields use the defaults from Settings.', 'siteaudithub-case-studies'), 'keywords' => ['button', 'contact', 'cta'], 'attributes' => [ 'heading' => $str(), 'text' => $str(), 'buttonText' => $str(), 'buttonUrl' => $str(), 'newTab' => $bool(false), 'nofollow' => $bool(false), 'style' => $str('gradient'), ], ], 'faq' => [ 'title' => __('Case Study FAQ', 'siteaudithub-case-studies'), 'description' => __('Questions and answers. Adds FAQ structured data for search engines.', 'siteaudithub-case-studies'), 'keywords' => ['questions', 'answers', 'accordion'], 'attributes' => [ 'title' => $str(__('Frequently asked questions', 'siteaudithub-case-studies')), 'items' => $arr([ ['q' => '', 'a' => ''], ['q' => '', 'a' => ''], ]), ], ], 'related' => [ 'title' => __('Related Case Studies', 'siteaudithub-case-studies'), 'description' => __('More case studies: picked automatically from the same category, or chosen by you.', 'siteaudithub-case-studies'), 'keywords' => ['more', 'similar'], 'attributes' => [ 'title' => $str(__('More case studies', 'siteaudithub-case-studies')), 'count' => $num(3), 'mode' => $str('auto'), // auto | manual 'ids' => ['type' => 'array', 'default' => [], 'items' => ['type' => 'integer']], 'layout' => $str(''), // '' = as in Settings, or grid | list ], ], 'grid' => [ 'title' => __('Case Studies', 'siteaudithub-case-studies'), 'description' => __('Show a list of case studies, one case study as a card, or one section of a case study, on any page.', 'siteaudithub-case-studies'), 'keywords' => ['grid', 'list', 'portfolio', 'showcase', 'card', 'section'], 'attributes' => [ 'show' => $str('list'), // list | card | section 'pick' => $str('auto'), // auto (category / order) | manual (chosen ids) 'ids' => ['type' => 'array', 'default' => [], 'items' => ['type' => 'integer']], 'caseId' => $num(0), 'section' => $str('results'), 'sectionLink' => $bool(true), 'cardImage' => $bool(true), 'title' => $str(), 'category' => $str(), 'limit' => $num(6), 'columns' => $num(3), 'layout' => $str('grid'), 'filters' => $bool(false), 'orderby' => $str('latest'), 'excerpt' => $bool(true), 'button' => $bool(false), ], ], 'archive' => [ 'title' => __('Case Studies Listing', 'siteaudithub-case-studies'), 'description' => __('The main case studies listing (used by the listing page template).', 'siteaudithub-case-studies'), 'attributes' => [], 'inserter' => false, ], ]; } add_action('init', function () { wp_register_script( 'sahcs-blocks', SAHCS_URL . 'assets/js/blocks.js', ['wp-blocks', 'wp-element', 'wp-block-editor', 'wp-components', 'wp-i18n', 'wp-server-side-render', 'wp-data', 'wp-plugins', 'wp-editor', 'wp-core-data', 'wp-compose', 'wp-api-fetch', 'wp-dom-ready', 'wp-notices', 'wp-html-entities'], sahcs_asset_ver('assets/js/blocks.js'), true ); wp_set_script_translations('sahcs-blocks', 'siteaudithub-case-studies', SAHCS_PATH . 'languages'); wp_register_style('sahcs-editor', SAHCS_URL . 'assets/css/editor.css', ['sahcs-front'], sahcs_asset_ver('assets/css/editor.css')); foreach (sahcs_blocks_definitions() as $slug => $def) { $supports = sahcs_block_supports(isset($def['inserter']) && $def['inserter'] === false ? ['inserter' => false, 'color' => false, 'typography' => false, 'spacing' => false] : []); if (in_array($slug, ['grid', 'related', 'archive'], true)) { $supports['align'] = ['wide', 'full']; } register_block_type('sahcs/' . $slug, [ 'api_version' => 3, 'title' => $def['title'], 'description' => $def['description'], 'category' => 'sahcs', 'keywords' => isset($def['keywords']) ? $def['keywords'] : [], 'attributes' => $def['attributes'], 'supports' => $supports, 'editor_script' => 'sahcs-blocks', 'editor_style' => 'sahcs-editor', 'style' => 'sahcs-front', 'render_callback' => 'sahcs_render_block_' . $slug, 'uses_context' => ['postId', 'postType'], ]); } }, 7); /** Data the editor script needs. */ add_action('enqueue_block_editor_assets', function () { $presets = function_exists('sahcs_industry_presets') ? sahcs_industry_presets() : []; $list = []; foreach ($presets as $key => $p) { $list[] = ['key' => $key, 'label' => $p['label'], 'content' => $p['content']]; } $current = get_post(); $dup_url = ''; if ($current && function_exists('sahcs_can_duplicate') && $current->post_status !== 'auto-draft' && sahcs_can_duplicate($current)) { $dup_url = sahcs_duplicate_url($current->ID); } wp_localize_script('sahcs-blocks', 'sahcsEditor', [ 'cpt' => SAHCS_CPT, 'duplicateUrl' => $dup_url, 'justCopied' => !empty($_GET['sahcs_duplicated']), // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- display-only flag 'presets' => $list, 'settings' => [ 'ctaHeading' => sahcs_get_settings('cta_heading'), 'ctaText' => sahcs_get_settings('cta_text'), 'ctaButton' => sahcs_get_settings('cta_button'), 'related_layout' => sahcs_get_settings('related_layout'), 'icon_colors' => sahcs_get_settings('icon_colors'), ], ]); }); /* ------------------------------------------------------------------ * Helpers * ---------------------------------------------------------------- */ /** The post a block is showing: from block context, else the current post. */ function sahcs_block_post_id($block) { if ($block instanceof WP_Block && !empty($block->context['postId'])) { return (int) $block->context['postId']; } return (int) get_the_ID(); } /** Is this the editor's live preview request (REST block renderer)? */ function sahcs_is_editor_preview() { if (!defined('REST_REQUEST') || !REST_REQUEST) { return false; } $route = isset($GLOBALS['wp']->query_vars['rest_route']) ? (string) $GLOBALS['wp']->query_vars['rest_route'] : ''; return strpos($route, '/wp/v2/block-renderer') === 0; } /** * Wrapper attributes (includes the colours/fonts chosen in the sidebar). * In the editor preview the editor's own block wrapper already carries * those styles, so only the plain classes are returned. */ function sahcs_wrapper($classes) { if (sahcs_is_editor_preview()) { return 'class="' . esc_attr($classes) . '"'; } return get_block_wrapper_attributes(['class' => 'sahcs-scope ' . $classes]); } /** Section titles are H2 on the case study page itself. */ function sahcs_block_heading_id($text, $anchor_attr) { if (!empty($anchor_attr)) { return ''; } return sahcs_unique_anchor($text); } /** Unique, readable id for a heading ("results", "results-2", ...). */ function sahcs_unique_anchor($text) { static $used = []; $base = sanitize_title(wp_strip_all_tags((string) $text)); if ($base === '') { $base = 'section'; } $id = $base; $n = 2; while (isset($used[$id])) { $id = $base . '-' . $n; $n++; } $used[$id] = true; return $id; } /** Whether the case study page is being shown by SiteAuditHub Case Studies' own page layout. */ function sahcs_is_embedded() { return !empty($GLOBALS['sahcs_embed']); } /* ------------------------------------------------------------------ * Render callbacks * ---------------------------------------------------------------- */ function sahcs_render_block_hero($a, $content, $block) { $post_id = sahcs_block_post_id($block); $post = get_post($post_id); if (!$post) { return ''; } $layout = $a['layout'] === 'stacked' ? 'stacked' : 'split'; $image_id = !empty($a['imageId']) ? (int) $a['imageId'] : (int) get_post_thumbnail_id($post); $title_tag = sahcs_is_embedded() ? 'h2' : 'h1'; $out = '
'; $out .= '
'; if (!empty($a['showCategory'])) { $terms = sahcs_post_terms($post->ID); if ($terms) { $out .= '

'; foreach (array_slice($terms, 0, 3) as $t) { $out .= '' . esc_html($t->name) . ''; } $out .= '

'; } } $title = wp_strip_all_tags(get_the_title($post)); if (sahcs_is_editor_preview()) { // Show the title as it is being typed, before the post is saved. $typed = isset($_GET['sahcs_preview_title']) ? sanitize_text_field(wp_unslash($_GET['sahcs_preview_title'])) : null; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only preview inside the REST request if ($typed !== null) { $title = $typed; } if ($title === '') { $title = __('Your case study title', 'siteaudithub-case-studies'); } } $out .= '<' . $title_tag . ' class="sahcs-hero__title">' . esc_html($title) . ''; if (!empty($a['intro'])) { $out .= '

' . esc_html($a['intro']) . '

'; } $out .= sahcs_byline($post, !isset($a['showAuthor']) || !empty($a['showAuthor'])); if (!empty($a['buttonText']) && !empty($a['buttonUrl'])) { $out .= '

' . esc_html($a['buttonText']) . '

'; } $out .= '
'; if ($image_id) { $img = wp_get_attachment_image($image_id, 'large', false, [ 'class' => 'sahcs-hero__img', 'loading' => 'eager', 'fetchpriority' => 'high', 'decoding' => 'async', 'sizes' => $layout === 'split' ? '(max-width: 900px) 100vw, 50vw' : '(max-width: 1200px) 100vw, 1200px', 'style' => 'width:100%;height:auto;max-width:100%', ]); if ($img) { $out .= '
' . $img . '
'; } } $out .= '
'; return $out; } /** * Turns a website or email typed as plain text into a link, e.g. * "www.example.com", "example.com", "https://…" or "hello@site.com". * URLs inside a longer sentence become links too. Returns safe HTML. */ /** Whether a URL points to another website. */ function sahcs_is_external($url) { $home = wp_parse_url(home_url(), PHP_URL_HOST); $host = wp_parse_url($url, PHP_URL_HOST); return $host && $home && strtolower($host) !== strtolower($home); } /** * href + target + rel for a link. * $new_tab: true / false, or null = automatic (other websites open in a new tab). * $nofollow: adds rel="nofollow" so search engines don't pass ranking to it. */ function sahcs_link_attrs($url, $new_tab = null, $nofollow = false) { if ($new_tab === null) { $new_tab = sahcs_is_external($url); } $rel = []; if ($new_tab) { $rel[] = 'noopener'; } if ($nofollow) { $rel[] = 'nofollow'; } return 'href="' . esc_url($url) . '"' . ($new_tab ? ' target="_blank"' : '') . ($rel ? ' rel="' . esc_attr(implode(' ', $rel)) . '"' : ''); } /** Reads the "open in new tab" choice of a list item: true, false or null (automatic). */ function sahcs_item_new_tab($item) { return (is_array($item) && isset($item['newTab']) && is_bool($item['newTab'])) ? $item['newTab'] : null; } /** Makes "www.site.com" / "site.com" a full https:// address. */ function sahcs_full_url($url) { $url = sahcs_trim($url); return preg_match('#^(https?://|mailto:|tel:|/|\#)#i', $url) ? $url : 'https://' . $url; } function sahcs_linkify($text) { $text = sahcs_trim($text); if ($text === '') { return ''; } // The whole value is one email address. if (is_email($text)) { return '' . esc_html($text) . ''; } // The whole value is one website address (with or without https://). if (preg_match('#^(https?://)?(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+([a-z]{2,24})(:\d+)?(/\S*)?$#i', $text, $m)) { $clear = preg_match('#^(https?://|www\.)#i', $text); // Without https:// or www. only real-looking endings count, so // "Next.js" or "Node.js" stay plain text. $tlds = ['com', 'net', 'org', 'in', 'io', 'co', 'ai', 'app', 'dev', 'store', 'shop', 'tech', 'info', 'biz', 'me', 'us', 'uk', 'ca', 'au', 'nz', 'de', 'fr', 'es', 'it', 'nl', 'eu', 'ae', 'sg', 'online', 'site', 'website', 'agency', 'studio', 'design', 'digital', 'xyz', 'health', 'edu', 'gov']; if ($clear || in_array(strtolower($m[2]), $tlds, true)) { $url = preg_match('#^https?://#i', $text) ? $text : 'https://' . $text; return '' . esc_html($text) . ''; } } // Links inside a sentence: only clear ones (https://… or www.…). $html = esc_html($text); return preg_replace_callback('#\b(?:https?://|www\.)[^\s<>"\']+#i', function ($m) { $shown = rtrim($m[0], '.,;:!?)'); $tail = substr($m[0], strlen($shown)); $url = html_entity_decode($shown, ENT_QUOTES, 'UTF-8'); $url = preg_match('#^https?://#i', $url) ? $url : 'https://' . $url; return '' . $shown . '' . $tail; }, $html); } /** Icon names offered for "At a glance" lines. */ function sahcs_glance_icons() { return ['bag', 'box', 'gear', 'link', 'clock', 'layers', 'pin', 'target', 'users', 'calendar', 'briefcase', 'globe', 'trending', 'alert', 'zap', 'dollar', 'star', 'tag', 'mail']; } /** Picks an icon from the label ("Industry" → bag, "Website" → link …). */ function sahcs_guess_icon($label) { $l = function_exists('mb_strtolower') ? mb_strtolower((string) $label) : strtolower((string) $label); $map = [ 'link' => ['website', 'site', 'url', 'link', 'web', 'store', 'domain'], 'bag' => ['industry', 'sector', 'niche', 'category', 'market segment', 'ecommerce', 'e-commerce'], 'gear' => ['tech', 'platform', 'stack', 'tool', 'theme', 'cms', 'software', 'app'], 'clock' => ['time', 'duration', 'timeline', 'weeks', 'months', 'period', 'deadline'], 'layers' => ['service', 'scope', 'deliverable', 'work done', 'what we did'], 'pin' => ['location', 'market', 'region', 'country', 'city', 'where'], 'trending' => ['result', 'outcome', 'impact', 'growth', 'conversion', 'revenue', 'roi', 'sales'], 'target' => ['goal', 'objective', 'target', 'aim', 'kpi'], 'users' => ['team', 'people', 'staff', 'audience', 'customers', 'users'], 'calendar' => ['date', 'year', 'launch', 'started', 'completed'], 'dollar' => ['budget', 'cost', 'price', 'investment', 'spend'], 'alert' => ['challenge', 'problem', 'issue', 'pain'], 'zap' => ['solution', 'approach', 'strategy', 'how'], 'briefcase' => ['role', 'engagement', 'contract', 'partner'], 'box' => ['project', 'product', 'brand', 'client', 'company', 'business', 'name'], 'mail' => ['email', 'contact'], ]; foreach ($map as $icon => $words) { foreach ($words as $w) { if ($l !== '' && strpos($l, $w) !== false) { return $icon; } } } return 'star'; } function sahcs_render_block_glance($a) { $items = array_values(array_filter((array) $a['items'], function ($i) { return is_array($i) && !empty($i['text']); })); if (!$items) { return ''; } $eyebrow = sahcs_trim($a['title']); $heading = sahcs_trim(isset($a['heading']) ? $a['heading'] : ''); $intro = sahcs_trim(isset($a['intro']) ? $a['intro'] : ''); $icons = !isset($a['showIcons']) || !empty($a['showIcons']); $choice = isset($a['iconColors']) ? $a['iconColors'] : ''; $mixed = $choice === 'mixed' || ($choice === '' && sahcs_get_settings('icon_colors') === 'mixed'); $label = $heading !== '' ? $heading : ($eyebrow !== '' ? $eyebrow : __('At a glance', 'siteaudithub-case-studies')); $classes = 'sahcs-glance' . ($icons ? ' sahcs-glance--icons' : '') . ($heading !== '' || $intro !== '' ? ' sahcs-glance--headed' : ''); $out = ''; return $out; } function sahcs_render_block_facts($a) { $items = array_filter((array) $a['items'], function ($i) { return !empty($i['value']) || !empty($i['url']); }); if (!$items) { return ''; } $out = ''; return $out; } function sahcs_section_default_heading($kind) { $map = [ 'overview' => __('Overview', 'siteaudithub-case-studies'), 'challenges' => __('Challenges', 'siteaudithub-case-studies'), 'solutions' => __('Solutions', 'siteaudithub-case-studies'), 'conclusion' => __('Conclusion', 'siteaudithub-case-studies'), 'approach' => __('Our approach', 'siteaudithub-case-studies'), ]; return isset($map[$kind]) ? $map[$kind] : ''; } function sahcs_render_block_section($a, $content) { $heading = $a['heading'] !== '' ? $a['heading'] : sahcs_section_default_heading($a['kind']); if (sahcs_trim(wp_strip_all_tags($content, true)) === '' && strpos($content, ''; if ($heading !== '') { $out .= '

' . esc_html($heading) . '

'; } $out .= '
' . $content . '
'; $out .= ''; return $out; } function sahcs_render_block_results($a) { $items = array_values(array_filter((array) $a['items'], function ($i) { return !empty($i['label']) && (isset($i['after']) && $i['after'] !== ''); })); if (!$items) { return ''; } $display = in_array($a['display'], ['cards', 'table', 'both'], true) ? $a['display'] : 'cards'; $id = sahcs_block_heading_id($a['title'], isset($a['anchor']) ? $a['anchor'] : ''); $out = '
'; if ($a['title'] !== '') { $out .= '

' . esc_html($a['title']) . '

'; } if ($a['intro'] !== '') { $out .= '

' . esc_html($a['intro']) . '

'; } if ($display === 'cards' || $display === 'both') { $n = count($items); $mod = ($n % 4 === 0) ? ' sahcs-stats--quad' : (($n === 2) ? ' sahcs-stats--pair' : ''); $out .= '
'; foreach ($items as $i) { $change = sahcs_metric_change($i); $out .= '
'; $out .= '

' . esc_html(sahcs_format_value($i['after'], $i)) . '

'; $out .= '

' . esc_html($i['label']) . '

'; if ($change || (isset($i['before']) && $i['before'] !== '')) { $out .= '

'; if ($change) { $out .= '' . esc_html($change['label']) . ' '; } if (isset($i['before']) && $i['before'] !== '') { /* translators: %s: value before the project, e.g. 1.40% */ $out .= '' . esc_html(sprintf(__('from %s', 'siteaudithub-case-studies'), sahcs_format_value($i['before'], $i))) . ''; } $out .= '

'; } $out .= '
'; } $out .= '
'; } if ($display === 'table' || $display === 'both') { $has_change = false; foreach ($items as $i) { if (sahcs_metric_change($i)) { $has_change = true; break; } } $out .= '
'; $out .= ''; if ($has_change) { $out .= ''; } $out .= ''; foreach ($items as $i) { $change = sahcs_metric_change($i); $out .= ''; $out .= ''; $out .= ''; if ($has_change) { $out .= ''; } $out .= ''; } $out .= '
' . esc_html__('Metric', 'siteaudithub-case-studies') . '' . esc_html__('Before', 'siteaudithub-case-studies') . '' . esc_html__('After', 'siteaudithub-case-studies') . '' . esc_html__('Change', 'siteaudithub-case-studies') . '
' . esc_html($i['label']) . '' . esc_html(sahcs_format_value(isset($i['before']) ? $i['before'] : '', $i)) . '' . esc_html(sahcs_format_value($i['after'], $i)) . '' . ($change ? '' . esc_html($change['label']) . '' : '') . '
'; } if ($a['note'] !== '') { $out .= '

' . esc_html($a['note']) . '

'; } $out .= '
'; return $out; } function sahcs_render_block_gallery($a) { $images = array_values(array_filter((array) $a['images'], function ($i) { return !empty($i['id']) && wp_attachment_is_image((int) $i['id']); })); if (!$images) { return ''; } static $gallery_n = 0; $gallery_n++; $cols = max(1, min(4, (int) $a['columns'])); $layout = $a['layout'] === 'masonry' ? 'masonry' : 'grid'; $group = 'sahcs-gallery-' . $gallery_n; $out = '
'; if ($a['title'] !== '') { $id = sahcs_block_heading_id($a['title'], isset($a['anchor']) ? $a['anchor'] : ''); $out .= ''; } $out .= '
'; return $out; } /** * Quote text → safe HTML: an empty line starts a new paragraph, a single line * break stays a line break, and **words** between double stars become bold. */ function sahcs_quote_html($text) { $out = ''; foreach (preg_split('/\R\s*\R/u', sahcs_trim($text)) as $para) { $para = sahcs_trim($para); if ($para === '') { continue; } $html = esc_html($para); $html = preg_replace('/\*\*(.+?)\*\*/su', '$1', $html); $out .= '

' . nl2br($html) . '

'; } return $out; } function sahcs_render_block_testimonial($a) { if (sahcs_trim($a['quote']) === '') { return ''; } $out = '
'; $out .= ''; $out .= '
' . sahcs_quote_html($a['quote']) . '
'; if ($a['name'] !== '' || !empty($a['imageId'])) { $out .= '
'; $photo = ''; if (!empty($a['imageId'])) { $photo = wp_get_attachment_image((int) $a['imageId'], 'thumbnail', false, [ 'class' => 'sahcs-testimonial__photo', 'loading' => 'lazy', 'alt' => '', 'style' => 'width:96px;height:96px;max-width:96px', ]); } if ($photo === '' && $a['name'] !== '') { $photo = ''; } $out .= $photo . ''; if ($a['name'] !== '') { $out .= '' . esc_html($a['name']) . ''; } if ($a['role'] !== '') { $out .= '' . esc_html($a['role']) . ''; } $out .= ''; $out .= '
'; } $out .= ''; $out .= '
'; return $out; } /** * "Written by " line under the hero introduction. * Shown when enabled in Settings → General and in the Hero section. */ function sahcs_byline($post, $enabled_here = true) { if (!$enabled_here || !$post || !sahcs_get_settings('show_author')) { return ''; } $author_id = (int) $post->post_author; $name = $author_id ? get_the_author_meta('display_name', $author_id) : ''; if ($name === '') { return ''; } $label = sahcs_get_settings('author_label'); $out = ''; return $out; } /** Up to two initials, used when a team member has no photo. */ function sahcs_initials($name) { $words = preg_split('/\s+/u', sahcs_trim($name), -1, PREG_SPLIT_NO_EMPTY); $out = ''; foreach (array_slice($words, 0, 2) as $w) { $out .= function_exists('mb_substr') ? mb_substr($w, 0, 1) : substr($w, 0, 1); } return function_exists('mb_strtoupper') ? mb_strtoupper($out) : strtoupper($out); } /** Team members with a name, from the saved Team section attributes. */ function sahcs_team_items($items) { $out = []; foreach ((array) $items as $i) { if (!is_array($i) || sahcs_trim(isset($i['name']) ? $i['name'] : '') === '') { continue; } $out[] = [ 'name' => sahcs_trim($i['name']), 'role' => sahcs_trim(isset($i['role']) ? $i['role'] : ''), 'imageId' => isset($i['imageId']) ? (int) $i['imageId'] : 0, 'link' => isset($i['link']) && sahcs_trim($i['link']) !== '' ? esc_url_raw(sahcs_full_url($i['link'])) : '', 'newTab' => sahcs_item_new_tab($i), 'nofollow' => !empty($i['nofollow']), 'email' => isset($i['email']) && is_email(sahcs_trim($i['email'])) ? sahcs_trim($i['email']) : '', ]; } return $out; } /** Small inline line icons, drawn for SiteAuditHub Case Studies. */ function sahcs_icon($name) { $paths = [ 'mail' => '', 'linkedin' => '', 'link' => '', 'bag' => '', 'box' => '', 'gear' => '', 'clock' => '', 'layers' => '', 'pin' => '', 'target' => '', 'users' => '', 'calendar' => '', 'briefcase' => '', 'globe' => '', 'trending' => '', 'alert' => '', 'zap' => '', 'dollar' => '', 'star' => '', 'tag' => '', 'quote' => '', ]; if (!isset($paths[$name])) { return ''; } return ''; } /** Title with its last word in the gradient colour (optional). */ function sahcs_highlight_title($title, $on) { $title = sahcs_trim($title); if (!$on || strpos($title, ' ') === false) { return $on && $title !== '' ? '' . esc_html($title) . '' : esc_html($title); } $pos = strrpos($title, ' '); return esc_html(substr($title, 0, $pos)) . ' ' . esc_html(substr($title, $pos + 1)) . ''; } /** Email / profile icons for a team member. */ function sahcs_member_links($m) { $links = []; if ($m['email'] !== '') { /* translators: %s: person's name */ $links[] = ''; } if ($m['link'] !== '') { $is_li = (bool) preg_match('#linkedin\.com#i', $m['link']); $opens_new = $m['newTab'] === null ? sahcs_is_external($m['link']) : $m['newTab']; /* translators: %s: person's name */ $label = $is_li ? sprintf(__('%s on LinkedIn', 'siteaudithub-case-studies'), $m['name']) : sprintf(__('%s, profile', 'siteaudithub-case-studies'), $m['name']); if ($opens_new) { $label .= ' ' . __('(opens in a new tab)', 'siteaudithub-case-studies'); } $links[] = ''; } if (!$links) { return ''; } return '' . implode('', $links) . ''; } function sahcs_render_block_team($a) { $items = sahcs_team_items($a['items']); if (!$items) { return ''; } $layout = $a['layout'] === 'compact' ? 'compact' : 'cards'; $eyebrow = sahcs_trim(isset($a['eyebrow']) ? $a['eyebrow'] : ''); $intro = sahcs_trim($a['intro']); $quote = sahcs_trim(isset($a['quote']) ? $a['quote'] : ''); $has_text = ($a['title'] !== '' || $intro !== '' || $quote !== ''); // With several people in the cards layout, the cards go under the text. $wide = $layout === 'cards' && (count($items) > 2 || ($intro === '' && $quote === '')); $classes = 'sahcs-team sahcs-team--' . $layout . ($wide ? ' sahcs-team--wide' : '') . (count($items) > 1 ? ' sahcs-team--many' : ''); $id = sahcs_block_heading_id($a['title'], isset($a['anchor']) ? $a['anchor'] : ''); $out = '
'; if ($has_text) { $out .= '
'; if ($eyebrow !== '') { $out .= '

' . esc_html($eyebrow) . '

'; } if ($a['title'] !== '') { $out .= '

' . sahcs_highlight_title($a['title'], !isset($a['highlight']) || !empty($a['highlight'])) . '

'; if ($layout === 'cards' && $eyebrow === '') { $out .= ''; } } if ($intro !== '') { $out .= '
' . wpautop(esc_html($intro)) . '
'; } if ($quote !== '') { $out .= '

' . esc_html($quote) . '

'; } $out .= '
'; } $photo_size = $layout === 'compact' ? 120 : 176; $out .= '
    '; foreach ($items as $m) { $photo = ''; if ($m['imageId']) { $photo = wp_get_attachment_image($m['imageId'], $layout === 'compact' ? 'thumbnail' : 'medium', false, [ 'class' => 'sahcs-team__photo', 'loading' => 'lazy', 'alt' => '', 'sizes' => $photo_size . 'px', 'style' => 'width:' . $photo_size . 'px;height:' . $photo_size . 'px', ]); } if ($photo === '') { $photo = ''; } $out .= '
  • '; $out .= '' . $photo . ''; $out .= ''; $out .= '' . esc_html($m['name']) . ''; if ($m['role'] !== '') { $out .= '' . esc_html($m['role']) . ''; } $out .= ''; $out .= sahcs_member_links($m); $out .= '
  • '; } $out .= '
'; return $out; } function sahcs_render_block_cta($a) { $s = sahcs_get_settings(); $heading = $a['heading'] !== '' ? $a['heading'] : $s['cta_heading']; $text = $a['text'] !== '' ? $a['text'] : $s['cta_text']; $btn = $a['buttonText'] !== '' ? $a['buttonText'] : $s['cta_button']; $url = $a['buttonUrl'] !== '' ? $a['buttonUrl'] : $s['cta_url']; if ($heading === '' && $text === '' && ($btn === '' || $url === '')) { return ''; } $style = $a['style'] === 'soft' ? 'soft' : 'gradient'; $out = '
'; $out .= '
'; if ($heading !== '') { $out .= '

' . esc_html($heading) . '

'; } if ($text !== '') { $out .= '

' . esc_html($text) . '

'; } $out .= '
'; if ($btn !== '' && $url !== '') { $own = $a['buttonUrl'] !== ''; $new_tab = $own ? !empty($a['newTab']) : !empty($s['cta_new_tab']); $nofollow = $own ? !empty($a['nofollow']) : !empty($s['cta_nofollow']); $out .= '

' . esc_html($btn) . '

'; } $out .= '
'; return $out; } function sahcs_render_block_faq($a) { static $faq_count = 0; $faq_count++; $group = 'sahcs-faq-' . $faq_count; $items = array_values(array_filter((array) $a['items'], function ($i) { return !empty($i['q']) && !empty($i['a']); })); if (!$items) { return ''; } $GLOBALS['sahcs_faq_items'] = array_merge(isset($GLOBALS['sahcs_faq_items']) ? $GLOBALS['sahcs_faq_items'] : [], $items); $id = sahcs_block_heading_id($a['title'], isset($a['anchor']) ? $a['anchor'] : ''); $out = '
'; if ($a['title'] !== '') { $out .= '

' . esc_html($a['title']) . '

'; } $out .= '
'; foreach ($items as $n => $i) { // Same "name" = only one answer open at a time (native accordion). $out .= '
'; $out .= '

' . esc_html($i['q']) . '

'; $out .= '
' . wpautop(esc_html($i['a'])) . '
'; $out .= '
'; } $out .= '
'; return $out; } function sahcs_render_block_related($a, $content, $block) { $post_id = sahcs_block_post_id($block); if (!$post_id) { return ''; } $count = (int) $a['count'] > 0 ? (int) $a['count'] : (int) sahcs_get_settings('related_count'); $html = sahcs_related($post_id, $count, $a['title'] !== '' ? $a['title'] : __('More case studies', 'siteaudithub-case-studies'), [ 'ids' => (isset($a['mode']) && $a['mode'] === 'manual' && !empty($a['ids'])) ? array_map('intval', (array) $a['ids']) : [], 'layout' => isset($a['layout']) ? $a['layout'] : '', ]); if ($html === '') { return ''; } return '
' . $html . '
'; } function sahcs_render_block_grid($a) { $show = isset($a['show']) ? $a['show'] : 'list'; if ($show === 'card' || $show === 'section') { if (empty($a['caseId'])) { return function_exists('sahcs_editor_hint') ? sahcs_editor_hint(__('Choose a case study in the block settings on the right.', 'siteaudithub-case-studies')) : ''; } $one = $show === 'card' ? sahcs_shortcode_card(['id' => (int) $a['caseId'], 'image' => !empty($a['cardImage']) ? 'yes' : 'no']) : sahcs_shortcode_section([ 'id' => (int) $a['caseId'], 'section' => sanitize_key($a['section']), 'link' => !empty($a['sectionLink']) ? 'yes' : 'no', ]); if ($one === '' || sahcs_is_editor_preview() || strpos($one, 'sahcs-hint') !== false) { return $one; } return '
'sahcs-grid-block sahcs-grid-block--' . $show]) . '>' . $one . '
'; } $picked = (isset($a['pick']) && $a['pick'] === 'manual' && !empty($a['ids'])) ? implode(',', array_map('intval', (array) $a['ids'])) : ''; $html = sahcs_grid([ 'ids' => $picked, 'title' => $a['title'], 'category' => $a['category'], 'limit' => $a['limit'], 'columns' => $a['columns'], 'layout' => $a['layout'], 'filters' => $a['filters'], 'orderby' => $a['orderby'], 'excerpt' => $a['excerpt'], 'button' => $a['button'], ]); if ($html === '') { if (sahcs_is_editor_preview()) { return '

' . esc_html__('No published case studies match these settings yet.', 'siteaudithub-case-studies') . '

'; } return ''; } if (sahcs_is_editor_preview()) { return $html; } return '
'sahcs-grid-block']) . '>' . $html . '
'; } function sahcs_render_block_archive() { return sahcs_render_archive(); }