'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) . '' . $title_tag . '>';
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 = '';
$out .= ' ';
if ($eyebrow !== '' || $heading !== '' || $intro !== '') {
$out .= '';
if ($eyebrow !== '') {
$out .= '
' . esc_html($eyebrow) . '
';
}
if ($heading !== '') {
$out .= '
' . esc_html($heading) . ' ';
}
if ($intro !== '') {
$out .= '
' . esc_html($intro) . '
';
}
$out .= '
';
}
$out .= '';
foreach ($items as $n => $i) {
$item_label = isset($i['label']) ? $i['label'] : '';
$url = isset($i['url']) ? sahcs_trim($i['url']) : '';
$text_html = $url !== ''
? '' . esc_html($i['text']) . ' '
: sahcs_linkify($i['text']);
$icon_html = '';
if ($icons) {
$choice = isset($i['icon']) ? (string) $i['icon'] : '';
if ($choice === 'custom' && !empty($i['iconId'])) {
// An icon the user uploaded (SVG, PNG, WebP…), shown at 28 × 28 px.
$src = wp_get_attachment_image_url((int) $i['iconId'], 'thumbnail');
if (!$src) {
$src = wp_get_attachment_url((int) $i['iconId']);
}
if ($src) {
$icon_html = ' ';
}
}
if ($icon_html === '' && $choice !== 'none') {
$name = in_array($choice, sahcs_glance_icons(), true) ? $choice : sahcs_guess_icon($item_label);
$icon_html = '' . sahcs_icon($name) . ' ';
}
}
$item_class = 'sahcs-glance__item' . ($mixed ? ' sahcs-glance__item--c' . (($n % 6) + 1) : '') . ($icon_html === '' ? ' sahcs-glance__item--no-icon' : '');
$out .= '
' . $icon_html . '' . esc_html($item_label) . ' ' . $text_html . ' ';
}
$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 = '';
if ($a['title'] !== '') {
$out .= '' . esc_html($a['title']) . '
';
}
$out .= '';
foreach ($items as $i) {
$label = isset($i['label']) ? $i['label'] : '';
$value = !empty($i['value']) ? $i['value'] : (isset($i['url']) ? preg_replace('#^https?://(www\.)?#', '', untrailingslashit($i['url'])) : '');
if (!empty($i['url'])) {
$value_html = '' . esc_html($value) . ' ';
} else {
$value_html = sahcs_linkify($value);
}
$out .= '
' . esc_html($label) . ' ' . $value_html . ' ';
}
$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 .= '' . esc_html__('Metric', 'siteaudithub-case-studies') . ' ' . esc_html__('Before', 'siteaudithub-case-studies') . ' ' . esc_html__('After', 'siteaudithub-case-studies') . ' ';
if ($has_change) {
$out .= '' . esc_html__('Change', 'siteaudithub-case-studies') . ' ';
}
$out .= '';
foreach ($items as $i) {
$change = sahcs_metric_change($i);
$out .= '' . esc_html($i['label']) . ' ';
$out .= '' . esc_html(sahcs_format_value(isset($i['before']) ? $i['before'] : '', $i)) . ' ';
$out .= '' . esc_html(sahcs_format_value($i['after'], $i)) . ' ';
if ($has_change) {
$out .= '' . ($change ? '' . esc_html($change['label']) . ' ' : '') . ' ';
}
$out .= ' ';
}
$out .= '
';
}
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 .= '' . esc_html($a['title']) . ' ';
}
$out .= '';
foreach ($images as $img) {
$id = (int) $img['id'];
$caption = !empty($a['captions']) ? wp_get_attachment_caption($id) : '';
$alt = sahcs_trim((string) get_post_meta($id, '_wp_attachment_image_alt', true));
$size = $layout === 'masonry' ? 'large' : 'medium_large';
$tag = wp_get_attachment_image($id, $size, false, [
'loading' => 'lazy',
'sizes' => '(max-width: 640px) 100vw, (max-width: 1024px) 50vw, ' . round(100 / $cols) . 'vw',
'alt' => $alt,
'style' => $layout === 'masonry' ? 'width:100%;height:auto;max-width:100%' : 'width:100%;height:100%;max-width:100%',
]);
$out .= '
';
if (!empty($a['lightbox'])) {
$full = wp_get_attachment_image_url($id, 'full');
$out .= '' . $tag . '' . esc_html__('Open larger image', 'siteaudithub-case-studies') . ' ';
} else {
$out .= $tag;
}
if ($caption) {
$out .= '' . esc_html($caption) . ' ';
}
$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 .= '' . sahcs_icon('quote') . ' ';
$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 = '' . esc_html(sahcs_initials($a['name'])) . ' ';
}
$out .= $photo . '';
if ($a['name'] !== '') {
$out .= '' . esc_html($a['name']) . ' ';
}
if ($a['role'] !== '') {
$out .= '' . esc_html($a['role']) . ' ';
}
$out .= ' ';
$out .= ' ';
}
$out .= '' . sahcs_icon('quote') . ' ';
$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 = '';
$avatar = get_avatar($author_id, 32, '', '', ['class' => 'sahcs-hero__avatar', 'loading' => 'lazy']);
if ($avatar) {
$out .= $avatar;
}
$out .= '';
if ($label !== '') {
$out .= esc_html($label) . ' ';
}
$out .= '' . esc_html($name) . ' ';
$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 '' . $paths[$name] . ' ';
}
/** 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[] = '' . sahcs_icon('mail') . ' ';
}
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[] = '' . sahcs_icon($is_li ? 'linkedin' : 'link') . ' ';
}
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 .= '
' . sahcs_icon('quote') . ' ' . 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 = '' . esc_html(sahcs_initials($m['name'])) . ' ';
}
$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();
}